confer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.0.1
4
+
5
+ * Initial version
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.
@@ -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 ;-)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $:.unshift lib unless $:.include?(lib)
5
+
6
+ require 'bundler/setup'
7
+ require 'confer/cli'
@@ -0,0 +1,7 @@
1
+ require 'confer/version'
2
+ require 'confer/extensions/hash'
3
+ require 'confer/extensions/array'
4
+ require 'confer/inventory'
5
+ require 'confer/recipe'
6
+ require 'confer/connection'
7
+ require 'confer/configurator'
@@ -0,0 +1 @@
1
+ require 'confer/configurators/tzdata'
@@ -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
@@ -0,0 +1,3 @@
1
+ module Confer
2
+ VERSION = '0.0.1' # :nodoc:
3
+ 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: []