vagrant-config_builder 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,26 @@
1
+ CHANGELOG
2
+ =========
3
+
4
+ 0.2.0
5
+ -----
6
+
7
+ 2013-08-16
8
+
9
+ This is a backwards compatible bugfix and feature release.
10
+
11
+ ### User notes
12
+
13
+ #### Enhancements
14
+
15
+ * (GH-10) Add support to vm model for hostname.
16
+
17
+ #### Bugfixes
18
+
19
+ * (GH-9) Correctly merge VM attributes by concatentating arrays.
20
+
21
+ 0.1.0
22
+ -----
23
+
24
+ 2013-08-08
25
+
26
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ # We depend on Vagrant for development, but we don't add it as a
7
+ # gem dependency because we expect to be installed within the
8
+ # Vagrant environment itself using `vagrant plugin`.
9
+ gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git"
10
+ end
data/README.markdown CHANGED
@@ -51,6 +51,7 @@ for clarity.
51
51
  name: db
52
52
  private_networks: [ {ip: '10.20.1.2'} ]
53
53
  box: centos-5-i386
54
+ hostname: db.puppetlabs.vm
54
55
  roles: bigvm
55
56
  -
56
57
  name: web
@@ -60,6 +61,16 @@ for clarity.
60
61
  Installation
61
62
  ------------
62
63
 
63
- Installation into the Vagrant internal gems:
64
+ ### Installation into the Vagrant internal gems:
64
65
 
65
66
  * `vagrant plugin install vagrant-config_builder`
67
+
68
+ ### Installation from source
69
+
70
+ Build the gem:
71
+
72
+ * `gem build vagrant-config_builder.gemspec`
73
+
74
+ Install the gem:
75
+
76
+ * `gem install vagrant-config_builder-<version>.gem`
@@ -7,7 +7,13 @@ module ConfigBuilder
7
7
  # @api private
8
8
  class ClassRegistry
9
9
 
10
- class UnknownEntryError < Vagrant::Errors::VagrantError; end
10
+ class UnknownEntry < Vagrant::Errors::VagrantError
11
+ error_key('unknown_entry', 'config_builder.class_registry')
12
+ end
13
+
14
+ class DuplicateEntry < Vagrant::Errors::VagrantError
15
+ error_key('duplicate_entry', 'config_builder.class_registry')
16
+ end
11
17
 
12
18
  def initialize(name)
13
19
  @name = name
@@ -21,7 +27,12 @@ module ConfigBuilder
21
27
  #
22
28
  # @return [void]
23
29
  def register(identifier, klass)
24
- @klasses[identifier] = klass
30
+ if @klasses[identifier]
31
+ raise DuplicateEntry, :registry => @name,
32
+ :identifiers => @klasses.keys
33
+ else
34
+ @klasses[identifier] = klass
35
+ end
25
36
  end
26
37
 
27
38
  # Retrieve a class associated with the specified identifier
@@ -33,7 +44,9 @@ module ConfigBuilder
33
44
  if (klass = @klasses[identifier])
34
45
  klass
35
46
  else
36
- raise UnknownEntryError, "#{self.inspect} doesn't have an entry registered with key #{identifier.inspect}"
47
+ raise UnknownEntry, :registry => @name,
48
+ :identifier => identifier.inspect,
49
+ :identifiers => @klasses.keys
37
50
  end
38
51
  end
39
52
 
@@ -44,7 +57,8 @@ module ConfigBuilder
44
57
  #
45
58
  # @return [Object] The generated object
46
59
  def generate(hash)
47
- identifier = hash.delete('type')
60
+ config = hash.dup
61
+ identifier = config.delete('type')
48
62
 
49
63
  klass = retrieve(identifier)
50
64
 
@@ -52,7 +66,7 @@ module ConfigBuilder
52
66
  end
53
67
 
54
68
  def inspect
55
- "<#{self.class}: (#{@name})>"
69
+ "<#{self.class}: (#{@name}) keys: #{@klasses.keys.inspect}>"
56
70
  end
57
71
  end
58
72
  end
@@ -0,0 +1,22 @@
1
+ module ConfigBuilder
2
+ # Welcome to hell.
3
+ # @api private
4
+ class ExtensionHandler
5
+ def initialize
6
+ @logger = Log4r::Logger.new('vagrant::config_builder::extension_handler')
7
+ end
8
+
9
+ def load_from_plugins
10
+ Vagrant.plugin('2').manager.registered.each do |plugin|
11
+ load_plugin(plugin)
12
+ end
13
+ end
14
+
15
+ def load_plugin(plugin)
16
+ if plugin.respond_to? :config_builder_hook
17
+ @logger.info "Loading config_builder extension #{plugin.inspect}"
18
+ plugin.config_builder_hook
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,64 +1,75 @@
1
+ # The 'roles' filter adds a mechanism for defining generic VM roles and
2
+ # applying them to VMs.
3
+ #
4
+ # Defining roles
5
+ # --------------
6
+ #
7
+ # This filter adds support for a top level `roles` key. It contains a hash of
8
+ # role names that define a hash containing the role behavior.
9
+ #
10
+ # @note: The 'vms' field is of type Array, while the 'roles' field is of type
11
+ # Hash. This is because order of declaration matters for the actual VMs, while
12
+ # the order of declaration of roles does not matter.
13
+ #
14
+ # @example
15
+ # >> hash_config
16
+ # => {
17
+ # 'roles' => {
18
+ # 'webserver' => {
19
+ # 'synced_folders' => [
20
+ # {'host_path' => './www', 'guest_path' => '/var/www'},
21
+ # {'host_path' => './webserver-binaries', 'guest_path' => '/opt/webserver-binaries'},
22
+ # ]
23
+ # },
24
+ # 'database' => {
25
+ # 'provisioners' => [
26
+ # {'type' => 'puppet', 'manifest' => 'dbserver.pp'},
27
+ # {'type' => 'shell', 'path' => 'scripts/initialize-db.sh'},
28
+ # ],
29
+ # }
30
+ # },
31
+ # 'vms' => [
32
+ # {'name' => 'web', 'roles' => 'webserver'},
33
+ # {'name' => 'db', 'roles' => 'database'},
34
+ # {'name' => 'standalone', 'roles' => ['webserver', 'database']},
35
+ # ],
36
+ # }
37
+ #
1
38
  class ConfigBuilder::Filter::Roles
2
39
 
3
- def run(input)
4
- strip_filter_data(input)
40
+ def run(hash_config)
41
+ @roles = hash_config.delete('roles')
5
42
 
6
- vms = @vms.map { |vm_hash| filter_vm(vm_hash) }
43
+ hash_config['vms'].map! { |vm_hash| filter_vm(vm_hash) }
7
44
 
8
- add_filtered_data(input, vms)
9
-
10
- input
45
+ hash_config
11
46
  end
12
47
 
13
48
  private
14
49
 
15
- def strip_filter_data(input)
16
- @roles = input.delete('roles')
17
- @vms = input.delete('vms')
18
- end
19
-
20
- def add_filtered_data(input, vms)
21
- input['vms'] = vms
22
- end
23
-
24
- def filter_vm(vm_hash)
25
- role_names = roles_for_vm(vm_hash.delete('roles'))
26
-
27
- new_vm_hash = merge_role_definitions(role_names)
28
- new_vm_hash.merge!(vm_hash)
50
+ def filter_vm(old_vm)
51
+ role_list = old_vm.delete('roles')
52
+ node_stack = roles_for_vm(role_list)
29
53
 
30
- new_vm_hash
31
- end
32
-
33
- # Generate a hash of all the merged roles. Roles specified later in the hash
34
- # overwrite earlier roles.
35
- #
36
- # @param names [Hash]
37
- #
38
- # @return [Hash]
39
- def merge_role_definitions(role_names)
40
- vm_template = {}
54
+ node_stack << old_vm
41
55
 
42
- role_names.each do |name|
43
- role_definition = @roles[name]
44
- vm_template.merge!(role_definition)
56
+ new_vm = node_stack.inject({}) do |accumulator, role|
57
+ merge_nodes(accumulator, role)
45
58
  end
46
59
 
47
- vm_template
60
+ new_vm
48
61
  end
49
62
 
50
63
  # @return [Array]
51
64
  def roles_for_vm(role_field)
65
+
52
66
  case role_field
53
- when Array
54
- rval = role_field
55
- when String
56
- rval = [role_field]
57
- when NilClass
58
- rval = []
67
+ when Array then names = role_field
68
+ when String then names = [role_field]
69
+ when NilClass then names = []
59
70
  end
60
71
 
61
- rval
72
+ names.map { |name| role_definition(name) }
62
73
  end
63
74
 
64
75
  def role_definition(name)
@@ -68,4 +79,39 @@ class ConfigBuilder::Filter::Roles
68
79
  raise "Couldn't find role named #{name.inspect} in roles #{@roles.keys.inspect}"
69
80
  end
70
81
  end
82
+
83
+ # Merge two node hash structures, with values in `right` overwriting values
84
+ # in `left`.
85
+ #
86
+ # @param left [Hash]
87
+ # @param right [Hash]
88
+ #
89
+ # @return [Hash]
90
+ def merge_nodes(left, right)
91
+ retval = right.clone
92
+
93
+ array_keys = %w[
94
+ provisioners
95
+ synced_folders
96
+ forwarded_ports
97
+ private_networks
98
+ public_networks
99
+ ]
100
+
101
+ array_keys.each do |key|
102
+ if (left[key] and right[key])
103
+ retval[key] += left[key]
104
+ elsif left[key]
105
+ retval[key] = left[key]
106
+ end
107
+ end
108
+
109
+ single_keys = %w[provider box name]
110
+
111
+ single_keys.each do |key|
112
+ retval[key] = left[key] if left[key]
113
+ end
114
+
115
+ retval
116
+ end
71
117
  end
@@ -9,7 +9,7 @@ class ConfigBuilder::Model::Network::PrivateNetwork < ConfigBuilder::Model::Base
9
9
 
10
10
  def to_proc
11
11
  Proc.new do |vm_config|
12
- vm_config.network(:private_network, :ip => attr(:ip))
12
+ vm_config.network(:private_network, @attrs)
13
13
  end
14
14
  end
15
15
  end
@@ -70,6 +70,10 @@ class ConfigBuilder::Model::VM < ConfigBuilder::Model::Base
70
70
  # @return [String] The name of the instantiated box in this environment
71
71
  def_model_attribute :name
72
72
 
73
+ # @!attribute [rw] hostname
74
+ # @return [String] The hostname the machine should have.
75
+ def_model_attribute :hostname
76
+
73
77
  def initialize
74
78
  @defaults = {
75
79
  :provisioners => [],
@@ -85,6 +89,7 @@ class ConfigBuilder::Model::VM < ConfigBuilder::Model::Base
85
89
  vm_config = config.vm
86
90
 
87
91
  vm_config.box = attr(:box) if defined? attr(:box)
92
+ vm_config.hostname = attr(:hostname) if defined? attr(:hostname)
88
93
 
89
94
  eval_models(vm_config)
90
95
  end
@@ -6,8 +6,6 @@ module ConfigBuilder
6
6
 
7
7
  module Model
8
8
 
9
- class UnknownAttributeError < Vagrant::Errors::VagrantError; end
10
-
11
9
  require 'config_builder/model/base'
12
10
 
13
11
  require 'config_builder/model/root'
@@ -1,11 +1,14 @@
1
1
  require 'config_builder/loader'
2
2
  require 'config_builder/filter_stack'
3
3
  require 'config_builder/model'
4
+ require 'config_builder/extension_handler'
4
5
 
5
6
  module ConfigBuilder
6
7
  class Runner
7
8
 
8
9
  def run(identifier, method, value)
10
+ load_extensions
11
+
9
12
  data = ConfigBuilder::Loader.generate(identifier, method, value)
10
13
  filtered_data = run_filters(data)
11
14
  model = generate_model(filtered_data)
@@ -13,6 +16,11 @@ module ConfigBuilder
13
16
 
14
17
  private
15
18
 
19
+ def load_extensions
20
+ ext = ConfigBuilder::ExtensionHandler.new
21
+ ext.load_from_plugins
22
+ end
23
+
16
24
  def run_filters(data)
17
25
  stack = ConfigBuilder::FilterStack.new
18
26
  stack.filter(data)
@@ -1,3 +1,3 @@
1
1
  module ConfigBuilder
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -11,4 +11,14 @@ module ConfigBuilder
11
11
  runner = ConfigBuilder::Runner.new
12
12
  runner.run(identifier, method, value)
13
13
  end
14
+
15
+ def self.source_root
16
+ @source_root ||= File.expand_path('..', __FILE__)
17
+ end
18
+
19
+ def self.template_root
20
+ @template_root ||= File.expand_path('../templates', source_root)
21
+ end
14
22
  end
23
+
24
+ I18n.load_path << File.join(ConfigBuilder.template_root, 'locales/en.yml')
@@ -0,0 +1,245 @@
1
+ require 'spec_helper'
2
+
3
+ describe ConfigBuilder::Filter::Roles do
4
+
5
+ def dup(o)
6
+ Marshal.load(Marshal.dump(o))
7
+ end
8
+
9
+ let(:roles) do
10
+ {
11
+ 'shell-provisioner' => {
12
+ 'provisioners' => [
13
+ {'type' => 'shell', 'inline' => '/usr/bin/sl'},
14
+ ],
15
+ },
16
+ 'puppet-provisioner' => {
17
+ 'provisioners' => [
18
+ {'type' => 'puppet', 'manifest' => 'sl.pp'},
19
+ {'type' => 'puppet', 'manifest' => 'starwars.pp'},
20
+ ],
21
+ },
22
+ 'potato-provisioner' => {
23
+ 'provisioners' => [
24
+ {'type' => 'potato', 'potato' => 'POHTAHTO.pp'},
25
+ ],
26
+ },
27
+ 'folders-12' => {
28
+ 'synced_folders' => [
29
+ {'guest_path' => '/guest-1', 'host_path' => './host-1'},
30
+ {'guest_path' => '/guest-2', 'host_path' => './host-2'},
31
+ ],
32
+ },
33
+ 'folders-34' => {
34
+ 'synced_folders' => [
35
+ {'guest_path' => '/guest-3', 'host_path' => './host-3'},
36
+ {'guest_path' => '/guest-4', 'host_path' => './host-4'},
37
+ ],
38
+ }
39
+ }
40
+ end
41
+
42
+ let(:vms) do
43
+ [
44
+ {'name' => 'master'},
45
+ {'name' => 'debian-6-agent'},
46
+ ]
47
+ end
48
+
49
+
50
+ describe "without a top level roles key" do
51
+
52
+ let(:config) do
53
+ {'vms' => vms}
54
+ end
55
+
56
+ it "doesn't alter the structure" do
57
+ input = dup(config)
58
+ output = subject.run(input)
59
+
60
+ expect(output).to eq config
61
+ end
62
+ end
63
+
64
+ describe 'removing the role' do
65
+ let(:config) do
66
+ {
67
+ 'vms' => [{'name' => 'master'}],
68
+ 'roles' => roles,
69
+ }
70
+ end
71
+
72
+ it 'strips out the roles key' do
73
+ output = subject.run(config)
74
+ expect(output).to_not have_key 'roles'
75
+ end
76
+ end
77
+
78
+ describe 'and a vm with no roles' do
79
+ let(:vms) { [{'name' => 'master'}] }
80
+
81
+ let(:config) do
82
+ {
83
+ 'vms' => vms,
84
+ 'roles' => roles,
85
+ }
86
+ end
87
+
88
+ it "doesn't alter the vm" do
89
+ input = dup(config)
90
+ output = subject.run(input)
91
+ expect(output['vms']).to eq vms
92
+ end
93
+ end
94
+
95
+ describe 'and one vm' do
96
+ describe 'with one role' do
97
+ let(:vms) { [{'name' => 'master', 'roles' => 'shell-provisioner'}] }
98
+
99
+ let(:config) do
100
+ {
101
+ 'vms' => vms,
102
+ 'roles' => roles,
103
+ }
104
+ end
105
+
106
+ let(:filtered_vm) do
107
+ output = subject.run(config)
108
+ output['vms'][0]
109
+ end
110
+
111
+ it "removes the 'roles' key" do
112
+ expect(filtered_vm).to_not have_key 'roles'
113
+ end
114
+
115
+ it 'applies the role' do
116
+ expected = [{
117
+ 'type' => 'shell',
118
+ 'inline' => '/usr/bin/sl',
119
+ }]
120
+
121
+ expect(filtered_vm['provisioners']).to eq expected
122
+ end
123
+ end
124
+
125
+ describe 'with two roles' do
126
+ let(:vms) do
127
+ [{
128
+ 'name' => 'master',
129
+ 'roles' => ['shell-provisioner', 'folders-12'],
130
+ }]
131
+ end
132
+
133
+ let(:config) do
134
+ {
135
+ 'vms' => vms,
136
+ 'roles' => roles,
137
+ }
138
+ end
139
+
140
+ let(:filtered_vm) do
141
+ output = subject.run(config)
142
+ output['vms'][0]
143
+ end
144
+
145
+ it 'applies all of the roles' do
146
+ expected_prov = [{
147
+ 'type' => 'shell',
148
+ 'inline' => '/usr/bin/sl',
149
+ }]
150
+
151
+ expected_folder = [
152
+ {'guest_path' => '/guest-1', 'host_path' => './host-1'},
153
+ {'guest_path' => '/guest-2', 'host_path' => './host-2'},
154
+ ]
155
+
156
+ expect(filtered_vm['provisioners']).to eq expected_prov
157
+ expect(filtered_vm['synced_folders']).to eq expected_folder
158
+ end
159
+ end
160
+ end
161
+
162
+ describe "with multiple VMs and shared roles" do
163
+ let(:vms) do
164
+ [
165
+ {
166
+ 'name' => 'master',
167
+ 'roles' => ['shell-provisioner', 'potato-provisioner', 'folders-12', 'folders-34'],
168
+ },
169
+ {
170
+ 'name' => 'agent',
171
+ 'roles' => ['shell-provisioner', 'puppet-provisioner', 'folders-34'],
172
+ }
173
+ ]
174
+ end
175
+
176
+ let(:config) do
177
+ {
178
+ 'vms' => vms,
179
+ 'roles' => roles,
180
+ }
181
+ end
182
+
183
+ let(:filtered_vms) do
184
+ output = subject.run(config)
185
+ output['vms']
186
+ end
187
+
188
+ describe 'the first node' do
189
+ let(:vm) { filtered_vms[0] }
190
+ it 'has the provisioners set in the right order' do
191
+ expected = [
192
+ {'type' => 'potato', 'potato' => 'POHTAHTO.pp'},
193
+ {'type' => 'shell', 'inline' => '/usr/bin/sl'},
194
+ ]
195
+ expect(vm['provisioners']).to eq expected
196
+ end
197
+
198
+ it 'has the synced folders set in the right order' do
199
+ expected = [
200
+ {'guest_path' => '/guest-3', 'host_path' => './host-3'},
201
+ {'guest_path' => '/guest-4', 'host_path' => './host-4'},
202
+ {'guest_path' => '/guest-1', 'host_path' => './host-1'},
203
+ {'guest_path' => '/guest-2', 'host_path' => './host-2'},
204
+ ]
205
+
206
+ expect(vm['synced_folders']).to eq expected
207
+ end
208
+ end
209
+
210
+ describe 'the second node' do
211
+ let(:vm) { filtered_vms[1] }
212
+
213
+ it 'has the provisioners set in the right order' do
214
+ expected = [
215
+ {'type' => 'puppet', 'manifest' => 'sl.pp'},
216
+ {'type' => 'puppet', 'manifest' => 'starwars.pp'},
217
+ {'type' => 'shell', 'inline' => '/usr/bin/sl'},
218
+ ]
219
+ expect(vm['provisioners']).to eq expected
220
+ end
221
+
222
+ it 'has the synced folders set in the right order' do
223
+ expected = [
224
+ {'guest_path' => '/guest-3', 'host_path' => './host-3'},
225
+ {'guest_path' => '/guest-4', 'host_path' => './host-4'},
226
+ ]
227
+
228
+ expect(vm['synced_folders']).to eq expected
229
+ end
230
+ end
231
+ end
232
+
233
+ describe 'when a class references a non-existent role' do
234
+ let(:config) do
235
+ {
236
+ 'vms' => [{'name' => 'master', 'roles' => 'nope'}],
237
+ 'roles' => {'yep' => {'box' => 'moxxi'}}
238
+ }
239
+
240
+ it 'raises an error' do
241
+ expect { subject.run(config) }.to raise_error, /Couldn't find role named .*nope.*/
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'config_builder'
3
+
4
+ require 'rspec'
@@ -0,0 +1,11 @@
1
+ ---
2
+ en:
3
+ config_builder:
4
+ class_registry:
5
+ unknown_entry: |-
6
+ Unable to locate a class associated with the identifier %{identifier} in the registry
7
+ %{registry}. The relevant plugin may have not loaded the config_builder extension.
8
+ The identifiers registered with %{registry} are %{identifiers}.
9
+ duplicate_entry: |-
10
+ The class registry %{registry} already has a class registered with identifier %{identifer}
11
+ and cannot replace the existing entry.
@@ -16,4 +16,6 @@ Gem::Specification.new do |gem|
16
16
 
17
17
  gem.files = %x{git ls-files -z}.split("\0")
18
18
  gem.require_path = 'lib'
19
+
20
+ gem.add_development_dependency 'rspec', '~> 2.14.0'
19
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-config_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,14 +9,32 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-08 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2013-08-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.14.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.14.0
14
30
  description:
15
31
  email: adrien@somethingsinistral.net
16
32
  executables: []
17
33
  extensions: []
18
34
  extra_rdoc_files: []
19
35
  files:
36
+ - CHANGELOG
37
+ - Gemfile
20
38
  - LICENSE
21
39
  - README.markdown
22
40
  - examples/roles.yaml
@@ -24,6 +42,7 @@ files:
24
42
  - lib/config_builder.rb
25
43
  - lib/config_builder/action/load_extensions.rb
26
44
  - lib/config_builder/class_registry.rb
45
+ - lib/config_builder/extension_handler.rb
27
46
  - lib/config_builder/filter.rb
28
47
  - lib/config_builder/filter/roles.rb
29
48
  - lib/config_builder/filter_stack.rb
@@ -46,6 +65,9 @@ files:
46
65
  - lib/config_builder/runner.rb
47
66
  - lib/config_builder/version.rb
48
67
  - lib/vagrant-config_builder.rb
68
+ - spec/config_builder/filter/roles_spec.rb
69
+ - spec/spec_helper.rb
70
+ - templates/locales/en.yml
49
71
  - vagrant-config_builder.gemspec
50
72
  homepage: https://github.com/adrienthebo/vagrant-config_builder
51
73
  licenses: