confer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/bin/confer +7 -0
- data/lib/confer.rb +7 -0
- data/lib/confer/builtins.rb +1 -0
- data/lib/confer/cli.rb +72 -0
- data/lib/confer/configurator.rb +64 -0
- data/lib/confer/configurators/locale.rb +25 -0
- data/lib/confer/configurators/tzdata.rb +49 -0
- data/lib/confer/connection.rb +75 -0
- data/lib/confer/extensions/array.rb +22 -0
- data/lib/confer/extensions/hash.rb +15 -0
- data/lib/confer/inventory.rb +85 -0
- data/lib/confer/recipe.rb +87 -0
- data/lib/confer/version.rb +3 -0
- metadata +174 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e9bed58740690419da224ac3da8449b5ad24ec5e
|
4
|
+
data.tar.gz: 2a6c54c6a28b37259418a140383ec34817f37618
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11a984f70c62beeda250d6a037147057507219d0625ad18935b068e411ba2fe8a57e3ecc593e62c9b8bab5319f16c95913bb55c1ece375175fbe6ca31f333898
|
7
|
+
data.tar.gz: 568e89efcdd151db5c1d21c602b61c18147b259d673505df0e4c65c420f8bea25bfe34e06a6b41c0562049691bcc9f2a7d75c97a47a793a564378409fda83974
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Cliff Rowley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Confer - lightweight configuration management
|
2
|
+
|
3
|
+
Inspired by tools such as [Ansible](http://www.ansibleworks.com), [Puppet](http://puppetlabs.com) and [Chef](http://opscode.com/chef), but at a much smaller scale.
|
4
|
+
|
5
|
+
## Why smaller scale?
|
6
|
+
|
7
|
+
Because sometimes these otherwise awesome tools take an inordinate amount of time and effort to configure and maintain if you only have a server or two to manage. For example if (like me) you have your own dedicated server that you would like to configure by recipe but you don't need webscale™, then Confer is possibly for you.
|
8
|
+
|
9
|
+
## Status
|
10
|
+
|
11
|
+
Confer is very young and very much in heavy development at this stage so I can't vouch for its stability nor integrity, however I am experienced enough that my code won't blow up in your face. I've intentionally kept the source simple with little in the way of magic so you should find it easy to read should you encounter any problems. Failing that, please feel free to submit issues.
|
12
|
+
|
13
|
+
**NOTE: Confer is not yet feature complete, let alone production ready.**
|
14
|
+
|
15
|
+
## Upcoming features
|
16
|
+
|
17
|
+
* **Flexible recipes**: A YAML recipe simply describes a list of configurators and their parameters, so if your requirements are a little more complex or dynamic you can construct recipes programmatically with a high level DSL. Or if you need something a little closer to the metal (to integrate within your own code for example) you can simply import Confer as a library and interact directly with its classes.
|
18
|
+
* **Easy to extend**: At the heart of Confer are its "configurators" which do all the heavy lifting to apply and verify the configuration defined in recipes. Configurators are built around a simple lifecycle and can be written using a high level DSL or with plain old Ruby objects that implement the appropriate methods.
|
19
|
+
* **Idempotency**: The configurator lifecycle includes querying and verification of existing configuration so changes are only applied if actually necessary. Verification can be implicit or explicit, allowing for complex use cases.
|
20
|
+
* **No client/server**: Confer performs all of its operations over SSH, and no server component is required. In many cases a client-server model may be desirable, however this may be an indication that you need to consider a more comprehensive tool.
|
21
|
+
* **Generators**: In order to speed up the Confer workflow, boilerplate recipes and configurators can be generated.
|
22
|
+
|
23
|
+
## Caveats
|
24
|
+
|
25
|
+
As with any software, there are always going to be some drawbacks and limitations.
|
26
|
+
|
27
|
+
* **Weak cross platform support**: One of Confer's strengths is its simplicity, particularly in regards to writing custom configurators that are appropriate for _your_ setup. However this simplicity means there is no platform abstraction layer, and a configurator that must support multiple platforms will need to contain a fair number of ifs and buts. This _may_ be addressed in a future update.
|
28
|
+
* **Ruby 1.9 untested**: At this stage no effort has been made to test Confer with anything less than Ruby 2.0. Apologies for that, but at this point I'm more interested in actually getting a working version. I almost certainly will put some effort into supporting Ruby 1.9 in the very near future. Pull requests are also welcome.
|
29
|
+
* **No tests**: I love TDD, but I've had to make an exception on this project because I need it yesterday. Therefore please consider this version of Confer as a prototype until such a time as I can guarantee its behavior with comprehensive tests.
|
30
|
+
|
31
|
+
## Requirements
|
32
|
+
|
33
|
+
Currently the only requirement is that you must be able to connect to the host being configured via SSH, and that Ruby 2.0 is installed on your workstation.
|
34
|
+
|
35
|
+
## Installing
|
36
|
+
|
37
|
+
If you're using a Ruby version manager such as RVM or rbenv (which you should!), you can simply `gem install confer`, otherwise you'll probably need to `sudo gem install confer`. This should be done client side. There is no need to install Confer on the server.
|
38
|
+
|
39
|
+
Alternatively you can install from the source by fetching it and running `rake install`.
|
40
|
+
|
41
|
+
## License
|
42
|
+
|
43
|
+
In the spirit of openness and freedom to use and abuse Confer is released under the MIT license, which basically means you are free do whatever you like with it. But it'd be really nice if awesome changes and bug fixes made their way back to the source as pull requests ;-)
|
data/bin/confer
ADDED
data/lib/confer.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'confer/configurators/tzdata'
|
data/lib/confer/cli.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#
|
2
|
+
# The command line interface, obviously ;-)
|
3
|
+
#
|
4
|
+
# Fair warning that the CLI interface will almost certainly change drastically
|
5
|
+
# at some point in the not too distant future. I've used GLI because frankly
|
6
|
+
# it's the quickest thing to prototype with, but I don't intend to keep it
|
7
|
+
# because I find the interface it generates to be unintuitive and often a tad
|
8
|
+
# frustrating.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'gli'
|
12
|
+
require 'confer'
|
13
|
+
|
14
|
+
include GLI::App
|
15
|
+
|
16
|
+
program_desc 'Lightweight configuration management'
|
17
|
+
program_long_desc 'Inspired by tools such as Ansible, Puppet and Chef, but at a much smaller scale if you only have a server or two to manage.'
|
18
|
+
version Confer::VERSION
|
19
|
+
|
20
|
+
flag 'host', 'h', default_value: 'default', desc: 'Specify the host to configure'
|
21
|
+
flag 'inventory', 'i', default_value: 'inventory.yml', desc: 'Specify the inventory to use'
|
22
|
+
flag 'recipe', 'r', default_value: 'recipe.yml', desc: 'Specify the recipe to use'
|
23
|
+
|
24
|
+
inventory = nil
|
25
|
+
recipe = nil
|
26
|
+
host = nil
|
27
|
+
|
28
|
+
pre do |global, command, opts, args|
|
29
|
+
inventory = Confer::Inventory.from_file global[:inventory]
|
30
|
+
recipe = Confer::Recipe.from_file global[:recipe]
|
31
|
+
host = inventory.hosts[global[:host]]
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
on_error do |e|
|
37
|
+
$stdout.puts "#{e.class.name}: #{e.message}"
|
38
|
+
$stdout.puts e.backtrace.join "\n"
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Queries configurator values'
|
43
|
+
command :query do |c|
|
44
|
+
c.flag 'configurator', '-c', desc: 'The configurator to query'
|
45
|
+
|
46
|
+
c.action do |global, opts, args|
|
47
|
+
Confer::Connection.start(host.options['connection']) do |connection|
|
48
|
+
recipe.steps.each do |step|
|
49
|
+
$stdout.puts "===== #{step.configurator} => #{step.options}"
|
50
|
+
configurator = Confer::Configurator.get(step.configurator, step.options)
|
51
|
+
puts configurator.query(connection)
|
52
|
+
$stdout.puts
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Verifies the recipe against configurator values'
|
59
|
+
command :verify do |c|
|
60
|
+
c.action do |global, opts, args|
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Applies the recipe to the remote host'
|
66
|
+
command :apply do |c|
|
67
|
+
c.action do |global, opts, args|
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
exit run ARGV
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module Confer
|
4
|
+
|
5
|
+
class ConfiguratorError < StandardError; end
|
6
|
+
class ConfiguratorNotFoundError < ConfiguratorError; end
|
7
|
+
|
8
|
+
#
|
9
|
+
# Public: Encapsulates the actual interaction with the remote server.
|
10
|
+
#
|
11
|
+
class Configurator
|
12
|
+
|
13
|
+
#
|
14
|
+
# Public: Find and instantiate a Configurator with the given name.
|
15
|
+
#
|
16
|
+
# name - A String containing the name of the configurator to get.
|
17
|
+
# options - A Hash of options to pass to the configurator.
|
18
|
+
#
|
19
|
+
# Raises
|
20
|
+
#
|
21
|
+
# ConfiguratorNotFoundError - If a configurator with the specified name can
|
22
|
+
# not be located.
|
23
|
+
# ConfiguratorError - If the configurator was found byt cannot be
|
24
|
+
# instantiated for some reason.
|
25
|
+
#
|
26
|
+
# Returns a Configurator instance if found.
|
27
|
+
#
|
28
|
+
def self.get(name, options = {})
|
29
|
+
require "confer/configurators/#{name}"
|
30
|
+
Confer::Configurators.const_get(name.camelize).new(options)
|
31
|
+
rescue LoadError => e
|
32
|
+
raise ConfiguratorNotFoundError.new("Configurator #{name} not found")
|
33
|
+
rescue StandardError => e
|
34
|
+
raise ConfiguratorError.new(e)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Public: Instantiates a new Configurator. By itself this method doesn't
|
39
|
+
# really do very much for the base class, however for subclasses it will
|
40
|
+
# attempt to apply the options passed to attributes if they exist.
|
41
|
+
#
|
42
|
+
# options - A Hash of configurator specific options.
|
43
|
+
#
|
44
|
+
def initialize(options = {})
|
45
|
+
options.apply_to self
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Public: Query and return the remote configuration.
|
50
|
+
#
|
51
|
+
def query(connection); end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Public: Verify the remote configuration against the Configurator attributes.
|
55
|
+
#
|
56
|
+
def verify(connection); end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Public: Modify the remote configuration to match the Configurator attributes.
|
60
|
+
#
|
61
|
+
def apply(connection); end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Confer
|
2
|
+
module Configurators
|
3
|
+
|
4
|
+
class Locale < Configurator
|
5
|
+
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :encoding
|
8
|
+
|
9
|
+
def query(connection)
|
10
|
+
connection.exec! 'locale -a'
|
11
|
+
end
|
12
|
+
|
13
|
+
# verify do
|
14
|
+
# params[:locales].all? { |locale| sh "locale -a | grep '/^#{locale}$/'" == locale }
|
15
|
+
# end
|
16
|
+
|
17
|
+
# configure do
|
18
|
+
# sh "echo '#{params[:locales].join('\\\n')}' > /etc/locale.gen"
|
19
|
+
# sh "locale-gen"
|
20
|
+
# end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
|
3
|
+
module Confer
|
4
|
+
module Configurators
|
5
|
+
|
6
|
+
#
|
7
|
+
# Public: Configures the timezone on the remote host.
|
8
|
+
#
|
9
|
+
# Attributes
|
10
|
+
#
|
11
|
+
# timezone - The desired time zone in the format expected by the file
|
12
|
+
# `/etc/timezone` on a Linux machine. E.g. London, UK would
|
13
|
+
# be `Europe/London`.
|
14
|
+
#
|
15
|
+
class Tzdata < Configurator
|
16
|
+
|
17
|
+
#
|
18
|
+
# Public: The desired time zone.
|
19
|
+
#
|
20
|
+
attr_accessor :timezone
|
21
|
+
|
22
|
+
#
|
23
|
+
# See `Confer::Configurator#query`
|
24
|
+
#
|
25
|
+
def query(connection)
|
26
|
+
connection.exec! 'cat /etc/timezone'
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# See `Confer::Configurator#verify`
|
31
|
+
#
|
32
|
+
def verify(connection)
|
33
|
+
expected = query.strip
|
34
|
+
return true if expected == timezone
|
35
|
+
Diffy::Diff.new(expected, timezone).to_s(:color)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# See `Confer::Configurator#apply`
|
40
|
+
#
|
41
|
+
def apply(connection)
|
42
|
+
# sh "echo '#{params[:timezone]}' > /etc/timezone"
|
43
|
+
# sh "dpkg-reconfigure -f noninteractive tzdata"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Confer
|
4
|
+
|
5
|
+
#
|
6
|
+
# Public: Encapsulates a connection to a remote host.
|
7
|
+
#
|
8
|
+
class Connection
|
9
|
+
|
10
|
+
#
|
11
|
+
# Public: Creates a new instance and immediately connects to the remote
|
12
|
+
# host, yielding the instance to the given block.
|
13
|
+
#
|
14
|
+
# opts - The options to pass to the new Connection instance.
|
15
|
+
# block - The block to receive the new instance.
|
16
|
+
#
|
17
|
+
def self.start(opts = {}, &block)
|
18
|
+
Connection.new(opts).start(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Public: A String containing the remote hostname.
|
23
|
+
#
|
24
|
+
attr_accessor :host
|
25
|
+
|
26
|
+
#
|
27
|
+
# Public: A String containing the remote username.
|
28
|
+
#
|
29
|
+
attr_accessor :user
|
30
|
+
|
31
|
+
#
|
32
|
+
# Public: A Hash containing the SSH configuration options.
|
33
|
+
#
|
34
|
+
attr_accessor :config
|
35
|
+
|
36
|
+
#
|
37
|
+
# Public: Initialies a Connection instance.
|
38
|
+
#
|
39
|
+
# host - The host to connect to.
|
40
|
+
#
|
41
|
+
def initialize(opts = {})
|
42
|
+
@config ||= {}
|
43
|
+
opts.apply_to self
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Public: Opens a connection to the remote host and yields self to the
|
48
|
+
# given block for further interaction. The connection will be closed once
|
49
|
+
# the block has executed.
|
50
|
+
#
|
51
|
+
# block - The block to yield to.
|
52
|
+
#
|
53
|
+
def start(&block)
|
54
|
+
Net::SSH.start(self.host, self.user, self.config) do |ssh|
|
55
|
+
@ssh = ssh
|
56
|
+
yield(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Public: Execute a command on the remote host.
|
62
|
+
#
|
63
|
+
# command - The command to execute.
|
64
|
+
# opts - A hash of options:
|
65
|
+
# :shell - True if the command should be run via the remote shell.
|
66
|
+
#
|
67
|
+
# Returns the raw response from the command.
|
68
|
+
#
|
69
|
+
def exec!(command, opts = {})
|
70
|
+
@ssh.exec! command
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
#
|
4
|
+
# Public: Removes and returns the last element of the Array if it is a Hash.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
#
|
8
|
+
# array = ["one", "two", {three: 'four'}]
|
9
|
+
# opts = array.extract_options!
|
10
|
+
# # => {three: 'four'}
|
11
|
+
# array
|
12
|
+
# # => ["one", "two"]
|
13
|
+
#
|
14
|
+
# Returns the last element of the Array if it is a Hash, otherwise an empty
|
15
|
+
# hash is returned.
|
16
|
+
#
|
17
|
+
def extract_options!
|
18
|
+
return pop if last.is_a?(Hash)
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
#
|
4
|
+
# Public: Iterates through the keys of this hash and for each key sets the
|
5
|
+
# attribute of the same name on `object`. If a property of the same does
|
6
|
+
# not exist, it is silently ignored.
|
7
|
+
#
|
8
|
+
# object - The object on which to apply the values of this Hash.
|
9
|
+
#
|
10
|
+
#
|
11
|
+
def apply_to(object)
|
12
|
+
self.each { |k, v| object.send("#{k}=", v) if object.respond_to? "#{k}=" }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Confer
|
4
|
+
|
5
|
+
class InventoryError < StandardError; end
|
6
|
+
class InventoryNotFoundError < InventoryError; end
|
7
|
+
class InventorySyntaxError < InventoryError; end
|
8
|
+
|
9
|
+
#
|
10
|
+
# Public: Encapsulates an inventory, which is simply a list of hosts and the
|
11
|
+
# credentials and transport mechanisms required to interact with them.
|
12
|
+
#
|
13
|
+
class Inventory
|
14
|
+
|
15
|
+
#
|
16
|
+
# Public: Loads an inventory from a YAML file.
|
17
|
+
#
|
18
|
+
# path - A String containing the path to the YAML file to load.
|
19
|
+
#
|
20
|
+
# Returns an Inventory instance.
|
21
|
+
#
|
22
|
+
def self.from_file(path)
|
23
|
+
self.from_hash YAML.load File.open(path, 'r').read
|
24
|
+
rescue Errno::ENOENT => e
|
25
|
+
raise InventoryNotFoundError.new(e)
|
26
|
+
rescue Psych::SyntaxError => e
|
27
|
+
raise InventorySyntaxError.new(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Public: Loads an inventory from a Hash of hosts.
|
32
|
+
#
|
33
|
+
# array - A Hash of hosts and their options, indexed by name.
|
34
|
+
#
|
35
|
+
# Returns an Inventory instance.
|
36
|
+
#
|
37
|
+
def self.from_hash(hash)
|
38
|
+
Inventory.new Hash[hash.map { |k, v| [k, Host.new(k, v)] }]
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Public: A Hash of the hosts and their options in this Inventory, indexed
|
43
|
+
# by name.
|
44
|
+
#
|
45
|
+
attr_accessor :hosts
|
46
|
+
|
47
|
+
#
|
48
|
+
# Public: Creates a new Inventory instance.
|
49
|
+
#
|
50
|
+
# hosts - A Hash of hosts their options in the inventory, indexed by name.
|
51
|
+
#
|
52
|
+
def initialize(hosts = {})
|
53
|
+
@hosts = hosts
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Internal: Encapsulates a single host in an Inventory.
|
58
|
+
#
|
59
|
+
class Host
|
60
|
+
|
61
|
+
#
|
62
|
+
# Public: A String containing the name of the host.
|
63
|
+
#
|
64
|
+
attr_reader :name
|
65
|
+
|
66
|
+
#
|
67
|
+
# Public: A Hash containing the host options.
|
68
|
+
#
|
69
|
+
attr_reader :options
|
70
|
+
|
71
|
+
#
|
72
|
+
# Internal: Iniitalize a new Host with configuration.
|
73
|
+
#
|
74
|
+
# args - An array containing the name of the host and a Hash containing
|
75
|
+
# the host options.
|
76
|
+
#
|
77
|
+
def initialize(*args)
|
78
|
+
@options = args.extract_options!
|
79
|
+
@name = args.first
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Confer
|
4
|
+
|
5
|
+
class RecipeError < StandardError; end
|
6
|
+
class RecipeNotFoundError < RecipeError; end
|
7
|
+
class RecipeSyntaxError < RecipeError; end
|
8
|
+
|
9
|
+
#
|
10
|
+
# Public: Encapsulates a recipe. Usually instantiated by calling a method
|
11
|
+
# from a mixed in module such as `Confer::Recipe::Yaml`.
|
12
|
+
#
|
13
|
+
class Recipe
|
14
|
+
|
15
|
+
#
|
16
|
+
# Public: Loads a recipe from a YAML file.
|
17
|
+
#
|
18
|
+
# path - A String containing the path to the YAML file to load.
|
19
|
+
#
|
20
|
+
# Returns a Recipe instance.
|
21
|
+
#
|
22
|
+
def self.from_file(path)
|
23
|
+
self.from_array YAML.load File.open(path, 'r').read
|
24
|
+
rescue Errno::ENOENT => e
|
25
|
+
raise RecipeNotFoundError.new(e)
|
26
|
+
rescue Psych::SyntaxError => e
|
27
|
+
raise RecipeSyntaxError.new(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Public: Loads a recipe from an array of steps.
|
32
|
+
#
|
33
|
+
# array - An Array containing a Hash of options indexed by name for each
|
34
|
+
# step in the recipe.
|
35
|
+
#
|
36
|
+
# Returns a Recipe instance.
|
37
|
+
#
|
38
|
+
def self.from_array(array)
|
39
|
+
Recipe.new array.map { |e| Step.new(*e.first) }
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Public: An Array of steps this recipe contains.
|
44
|
+
#
|
45
|
+
attr_reader :steps
|
46
|
+
|
47
|
+
#
|
48
|
+
# Public: Instantiate a new instance with the given steps.
|
49
|
+
#
|
50
|
+
# array - An Array of Hash objects defining the steps for this recipe.
|
51
|
+
# opts - A Hash of options.
|
52
|
+
#
|
53
|
+
def initialize(steps = [])
|
54
|
+
@steps = steps
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Internal: Encapsulates a single step in a recipe.
|
59
|
+
#
|
60
|
+
class Step
|
61
|
+
|
62
|
+
#
|
63
|
+
# Public: A String containing the name of the configurator.
|
64
|
+
#
|
65
|
+
attr_reader :configurator
|
66
|
+
|
67
|
+
#
|
68
|
+
# Public: A Hash containing the configurator options.
|
69
|
+
#
|
70
|
+
attr_reader :options
|
71
|
+
|
72
|
+
#
|
73
|
+
# Internal: Initialize a new Step with configuration.
|
74
|
+
#
|
75
|
+
# args - An array contaning the configurator name and a Hash containing
|
76
|
+
# the configurator options.
|
77
|
+
#
|
78
|
+
def initialize(*args)
|
79
|
+
@options = args.extract_options!
|
80
|
+
@configurator = args.first
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: confer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cliff Rowley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: syntax
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: active_support
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: i18n
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: diffy
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: gli
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: net-ssh
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Inspired by tools such as Ansible, Puppet and Chef, but at a much smaller
|
126
|
+
scale if you only have a server or two to manage.
|
127
|
+
email:
|
128
|
+
- cliffrowley@gmail.com
|
129
|
+
executables:
|
130
|
+
- confer
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- bin/confer
|
135
|
+
- lib/confer/builtins.rb
|
136
|
+
- lib/confer/cli.rb
|
137
|
+
- lib/confer/configurator.rb
|
138
|
+
- lib/confer/configurators/locale.rb
|
139
|
+
- lib/confer/configurators/tzdata.rb
|
140
|
+
- lib/confer/connection.rb
|
141
|
+
- lib/confer/extensions/array.rb
|
142
|
+
- lib/confer/extensions/hash.rb
|
143
|
+
- lib/confer/inventory.rb
|
144
|
+
- lib/confer/recipe.rb
|
145
|
+
- lib/confer/version.rb
|
146
|
+
- lib/confer.rb
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- CHANGELOG.md
|
150
|
+
homepage: http://github.com/cliffrowley/confer
|
151
|
+
licenses:
|
152
|
+
- MIT
|
153
|
+
metadata: {}
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - '>='
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
requirements: []
|
169
|
+
rubyforge_project:
|
170
|
+
rubygems_version: 2.1.11
|
171
|
+
signing_key:
|
172
|
+
specification_version: 4
|
173
|
+
summary: Lightweight configuration management
|
174
|
+
test_files: []
|