1
0
Fork 0
mirror of synced 2024-11-22 08:15:34 -05:00

Add multiple targets

Allow the user to define multiple targets to execute a consistent group of tasks.
This commit is contained in:
Morgan Courbet 2015-02-03 18:08:57 +01:00
parent 38c0f65801
commit d4f744952e
3 changed files with 159 additions and 90 deletions

210
README.md
View file

@ -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,34 +122,37 @@ should be forcibly linked.
##### Example (YAML) ##### Example (YAML)
```yaml ```yaml
- link: work:
~/.config/terminator: - link:
create: true ~/.config/terminator:
path: config/terminator/ create: true
~/.vim: vim/ path: config/terminator/
~/.vimrc: vimrc ~/.vim: vim/
~/.zshrc: ~/.vimrc: vimrc
force: true ~/.zshrc:
path: zshrc force: true
path: zshrc
``` ```
##### Example (JSON) ##### Example (JSON)
```json ```json
[{ {
"link": { "work": [{
"~/.config/terminator": { "link": {
"create": true, "~/.config/terminator": {
"path": "config/terminator/" "create": true,
}, "path": "config/terminator/"
"~/.vim": "vim/", },
"~/.vimrc": "vimrc", "~/.vim": "vim/",
"~/.zshrc": { "~/.vimrc": "vimrc",
"force": true, "~/.zshrc": {
"path": "zshrc" "force": true,
"path": "zshrc"
}
} }
} }]
}] }
``` ```
### Shell ### Shell
@ -171,36 +174,39 @@ this syntax, all keys are optional except for the command itself.
##### Example (YAML) ##### Example (YAML)
```yaml ```yaml
- shell: work:
- mkdir -p ~/src - shell:
- [mkdir -p ~/downloads, Creating downloads directory] - mkdir -p ~/src
- - [mkdir -p ~/downloads, Creating downloads directory]
command: read var && echo Your variable is $var -
stdin: true command: read var && echo Your variable is $var
stdout: true stdin: true
- stdout: true
command: read fail -
stderr: true command: read fail
stderr: true
``` ```
##### Example (JSON) ##### Example (JSON)
```json ```json
[{ {
"shell": [ "work": [{
"mkdir -p ~/src", "shell": [
["mkdir -p ~/downloads", "Creating downloads directory"], "mkdir -p ~/src",
{ ["mkdir -p ~/downloads", "Creating downloads directory"],
"command": "read var && echo Your variable is $var", {
"stdin": true, "command": "read var && echo Your variable is $var",
"stdout": true "stdin": true,
}, "stdout": true
{ },
"command": "read fail", {
"stderr": true "command": "read fail",
} "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
[{ {
"clean": ["~"] "work": [{
}] "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,24 +270,53 @@ of the YAML configuration given above. The conventional name for this file is
`install.conf.json`. `install.conf.json`.
```json ```json
[ {
{ "common": [
"clean": ["~"] {
}, "clean": ["~"]
{ },
"link": { {
"~/.dotfiles": "", "link": {
"~/.tmux.conf": "tmux.conf", "~/.dotfiles": "",
"~/.vim": "vim/", "~/.tmux.conf": "tmux.conf",
"~/.vimrc": "vimrc" "~/.vim": "vim/",
"~/.vimrc": "vimrc"
}
},
{
"shell": [
["git submodule update --init --recursive", "Installing submodules"]
]
} }
}, ],
{ "laptop": [
"shell": [ {
["git submodule update --init --recursive", "Installing submodules"] "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

View file

@ -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
dispatcher = Dispatcher(options.base_directory[0]) target_tasks = read_config(options.config_file[0], targets)
success = dispatcher.dispatch(tasks)
success = True
for target, tasks in target_tasks.iteritems():
log.info('\nExecuting tasks for target %s' % target)
dispatcher = Dispatcher(options.base_directory[0])
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:

View file

@ -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