Add multiple targets
Allow the user to define multiple targets to execute a consistent group of tasks.
This commit is contained in:
parent
38c0f65801
commit
d4f744952e
3 changed files with 159 additions and 90 deletions
86
README.md
86
README.md
|
@ -33,7 +33,7 @@ without the annoyance of having to manually copy or link files.
|
||||||
Dotbot itself is entirely self contained and requires no installation (it's
|
Dotbot itself is entirely self contained and requires no installation (it's
|
||||||
self-bootstrapping), so it's not necessary to install any software before you
|
self-bootstrapping), so it's not necessary to install any software before you
|
||||||
provision a new machine! All you have to do is download your dotfiles and then
|
provision a new machine! All you have to do is download your dotfiles and then
|
||||||
run `./install`.
|
run `./install -t <list of targets>`.
|
||||||
|
|
||||||
Template
|
Template
|
||||||
--------
|
--------
|
||||||
|
@ -94,10 +94,10 @@ installer should be able to be run multiple times without causing any
|
||||||
problems.** This makes a lot of things easier to do (in particular, syncing
|
problems.** This makes a lot of things easier to do (in particular, syncing
|
||||||
updates between machines becomes really easy).
|
updates between machines becomes really easy).
|
||||||
|
|
||||||
Dotbot configuration files are YAML (or JSON) arrays of tasks, where each task
|
Dotbot configuration files are YAML (or JSON) dictionaries of targets with
|
||||||
is a dictionary that contains a command name mapping to data for that command.
|
arrays of tasks, where each task is a dictionary that contains a command name
|
||||||
Tasks are run in the order in which they are specified. Commands within a task
|
mapping to data for that command. Tasks are run in the order in which they are
|
||||||
do not have a defined ordering.
|
specified. Commands within a task do not have a defined ordering.
|
||||||
|
|
||||||
### Link
|
### Link
|
||||||
|
|
||||||
|
@ -122,7 +122,8 @@ should be forcibly linked.
|
||||||
##### Example (YAML)
|
##### Example (YAML)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- link:
|
work:
|
||||||
|
- link:
|
||||||
~/.config/terminator:
|
~/.config/terminator:
|
||||||
create: true
|
create: true
|
||||||
path: config/terminator/
|
path: config/terminator/
|
||||||
|
@ -136,7 +137,8 @@ should be forcibly linked.
|
||||||
##### Example (JSON)
|
##### Example (JSON)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[{
|
{
|
||||||
|
"work": [{
|
||||||
"link": {
|
"link": {
|
||||||
"~/.config/terminator": {
|
"~/.config/terminator": {
|
||||||
"create": true,
|
"create": true,
|
||||||
|
@ -149,7 +151,8 @@ should be forcibly linked.
|
||||||
"path": "zshrc"
|
"path": "zshrc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shell
|
### Shell
|
||||||
|
@ -171,7 +174,8 @@ this syntax, all keys are optional except for the command itself.
|
||||||
##### Example (YAML)
|
##### Example (YAML)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- shell:
|
work:
|
||||||
|
- shell:
|
||||||
- mkdir -p ~/src
|
- mkdir -p ~/src
|
||||||
- [mkdir -p ~/downloads, Creating downloads directory]
|
- [mkdir -p ~/downloads, Creating downloads directory]
|
||||||
-
|
-
|
||||||
|
@ -186,7 +190,8 @@ this syntax, all keys are optional except for the command itself.
|
||||||
##### Example (JSON)
|
##### Example (JSON)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[{
|
{
|
||||||
|
"work": [{
|
||||||
"shell": [
|
"shell": [
|
||||||
"mkdir -p ~/src",
|
"mkdir -p ~/src",
|
||||||
["mkdir -p ~/downloads", "Creating downloads directory"],
|
["mkdir -p ~/downloads", "Creating downloads directory"],
|
||||||
|
@ -200,7 +205,8 @@ this syntax, all keys are optional except for the command itself.
|
||||||
"stderr": true
|
"stderr": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clean
|
### Clean
|
||||||
|
@ -216,15 +222,18 @@ Clean commands are specified as an array of directories to be cleaned.
|
||||||
##### Example (YAML)
|
##### Example (YAML)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- clean: ['~']
|
work:
|
||||||
|
- clean: ['~']
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Example (JSON)
|
##### Example (JSON)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[{
|
{
|
||||||
|
"work": [{
|
||||||
"clean": ["~"]
|
"clean": ["~"]
|
||||||
}]
|
}]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Example
|
### Full Example
|
||||||
|
@ -234,16 +243,26 @@ configuration. The conventional name for the configuration file is
|
||||||
`install.conf.yaml`.
|
`install.conf.yaml`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- clean: ['~']
|
common:
|
||||||
|
- clean: ['~']
|
||||||
|
|
||||||
- link:
|
- link:
|
||||||
~/.dotfiles: ''
|
~/.dotfiles: ''
|
||||||
~/.tmux.conf: tmux.conf
|
~/.tmux.conf: tmux.conf
|
||||||
~/.vim: vim/
|
~/.vim: vim/
|
||||||
~/.vimrc: vimrc
|
~/.vimrc: vimrc
|
||||||
|
|
||||||
- shell:
|
- shell:
|
||||||
- [git update-submodules, Installing/updating submodules]
|
- [git update-submodules, Installing/updating submodules]
|
||||||
|
|
||||||
|
laptop:
|
||||||
|
- shell:
|
||||||
|
- [sudo apt-get install vim, Installing vim]
|
||||||
|
|
||||||
|
server:
|
||||||
|
- shell:
|
||||||
|
- [sudo apt-get install tmux, Installing tmux]
|
||||||
|
- [echo 'Europe/Paris' | sudo tee /etc/timezone > /dev/null && sudo dpkg-reconfigure -f noninteractive tzdata, Configuring timezone]
|
||||||
```
|
```
|
||||||
|
|
||||||
The configuration file can also be written in JSON. Here is the JSON equivalent
|
The configuration file can also be written in JSON. Here is the JSON equivalent
|
||||||
|
@ -251,7 +270,8 @@ of the YAML configuration given above. The conventional name for this file is
|
||||||
`install.conf.json`.
|
`install.conf.json`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"common": [
|
||||||
{
|
{
|
||||||
"clean": ["~"]
|
"clean": ["~"]
|
||||||
},
|
},
|
||||||
|
@ -268,7 +288,35 @@ of the YAML configuration given above. The conventional name for this file is
|
||||||
["git submodule update --init --recursive", "Installing submodules"]
|
["git submodule update --init --recursive", "Installing submodules"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"laptop": [
|
||||||
|
{
|
||||||
|
"clean": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shell": [
|
||||||
|
["sudo apt-get install vim", "Installing vim"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"server": [
|
||||||
|
{
|
||||||
|
"clean": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shell": [
|
||||||
|
["sudo apt-get install tmux", "Installing tmux"],
|
||||||
|
["echo 'Europe/Paris' | sudo tee /etc/timezone > /dev/null && sudo dpkg-reconfigure -f noninteractive tzdata", "Configuring timezone"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from .config import ConfigReader, ReadingError
|
from .config import ConfigReader, ReadingError, ConfigurationError
|
||||||
from .dispatcher import Dispatcher, DispatchError
|
from .dispatcher import Dispatcher, DispatchError
|
||||||
from .messenger import Messenger
|
from .messenger import Messenger
|
||||||
from .messenger import Level
|
from .messenger import Level
|
||||||
|
@ -17,9 +17,12 @@ def add_options(parser):
|
||||||
parser.add_argument('-c', '--config-file', nargs = 1, dest = 'config_file',
|
parser.add_argument('-c', '--config-file', nargs = 1, dest = 'config_file',
|
||||||
help = 'run commands given in CONFIGFILE', metavar = 'CONFIGFILE',
|
help = 'run commands given in CONFIGFILE', metavar = 'CONFIGFILE',
|
||||||
required = True)
|
required = True)
|
||||||
|
parser.add_argument('-t', '--targets', nargs = '*', dest = 'targets',
|
||||||
|
help = 'set the target environments defined in the configuration file', metavar = 'TARGET',
|
||||||
|
required = True)
|
||||||
|
|
||||||
def read_config(config_file):
|
def read_config(config_file, target):
|
||||||
reader = ConfigReader(config_file)
|
reader = ConfigReader(config_file, target)
|
||||||
return reader.get_config()
|
return reader.get_config()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -34,14 +37,20 @@ def main():
|
||||||
log.set_level(Level.INFO)
|
log.set_level(Level.INFO)
|
||||||
if (options.verbose):
|
if (options.verbose):
|
||||||
log.set_level(Level.DEBUG)
|
log.set_level(Level.DEBUG)
|
||||||
tasks = read_config(options.config_file[0])
|
targets = options.targets
|
||||||
|
target_tasks = read_config(options.config_file[0], targets)
|
||||||
|
|
||||||
|
success = True
|
||||||
|
for target, tasks in target_tasks.iteritems():
|
||||||
|
log.info('\nExecuting tasks for target %s' % target)
|
||||||
dispatcher = Dispatcher(options.base_directory[0])
|
dispatcher = Dispatcher(options.base_directory[0])
|
||||||
success = dispatcher.dispatch(tasks)
|
success &= dispatcher.dispatch(tasks)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
log.info('\n==> All tasks executed successfully')
|
log.info('\n==> All tasks executed successfully')
|
||||||
else:
|
else:
|
||||||
raise DispatchError('\n==> Some tasks were not executed successfully')
|
raise DispatchError('\n==> Some tasks were not executed successfully')
|
||||||
except (ReadingError, DispatchError) as e:
|
except (ReadingError, DispatchError, ConfigurationError) as e:
|
||||||
log.error('%s' % e)
|
log.error('%s' % e)
|
||||||
exit(1)
|
exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import yaml
|
import yaml
|
||||||
|
from .messenger import Messenger
|
||||||
|
|
||||||
class ConfigReader(object):
|
class ConfigReader(object):
|
||||||
def __init__(self, config_file_path):
|
def __init__(self, config_file_path, targets):
|
||||||
self._config = self._read(config_file_path)
|
complete_config = self._read(config_file_path)
|
||||||
|
|
||||||
|
target_configs = {}
|
||||||
|
for target in targets:
|
||||||
|
if not complete_config.has_key(target):
|
||||||
|
raise ConfigurationError('The target %s is not defined in the configuration file' % target)
|
||||||
|
target_configs[target] = complete_config.get(target)
|
||||||
|
|
||||||
|
self._config = target_configs
|
||||||
|
|
||||||
def _read(self, config_file_path):
|
def _read(self, config_file_path):
|
||||||
try:
|
try:
|
||||||
|
@ -17,3 +26,6 @@ class ConfigReader(object):
|
||||||
|
|
||||||
class ReadingError(Exception):
|
class ReadingError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ConfigurationError(Exception):
|
||||||
|
pass
|
||||||
|
|
Loading…
Reference in a new issue