builderator 1.0.0.pre.rc.9 → 1.0.0.pre.rc.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff5accfec82561d29eafb07b3477a9f739684fd2
4
- data.tar.gz: ac6c8c3d414ddff108046a84fc5c32e0ac4a10a3
3
+ metadata.gz: c7e1986d4b7bb1afd09020158460d0ab35220850
4
+ data.tar.gz: 1545ff99c44cbb336fd0315d6f43d3121568a720
5
5
  SHA512:
6
- metadata.gz: 8e50ade752fcfa4ae0d80bbb90eb614d9cb52301f614d76efe02239ce3cece1e1413724039e4bdb4c0d11fdef7aec1bdddc8898fb7232c8f88d6a5ca63ec4153
7
- data.tar.gz: 161932a36e9ac67e57fcfc7ccb682bb44cbebd279be4c467ed5002e7f09ab20f7f706a69ed3c25eb80f342e3b8b5623f48bf6675270cb9e7c7d77cf0c3df5ae1
6
+ metadata.gz: 419b869cec9a0f1eca44d02e4aa125a7bb4a5e959f5caea5c093ef7477066d64f6a62c9aa1bc7423636d962920a5c9496a520260ce55fe575eb53762d37a6b5d
7
+ data.tar.gz: 2639dbc8a87bbd6a788a8960fbdda57a64a15dc6c498f6ccdc8a06789bad1b569c8966887f6f4b8c496e7213d26c2cfc963fed1e7bf4b650797e9e01e5f78e2f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- builderator (1.0.0.pre.rc.8)
4
+ builderator (1.0.0.pre.rc.9)
5
5
  aws-sdk (~> 2.0)
6
6
  berkshelf (~> 3.2)
7
7
  chef (~> 12.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0-rc.9
1
+ 1.0.0-rc.10
@@ -1,9 +1,9 @@
1
1
  Configuration DSL
2
2
  =================
3
3
 
4
- The configuration DSL is made up of key-value pairs, called `attributes`, which are grouped into `namespaces` and `collections`.
4
+ Builderator's configuration language is a Ruby DSL, composed of `attributes`, which are grouped into `namespaces`. A `namespace` may be singular, or it may be part of a `collection`, in which case more than one named entry may be defined with the same `namespace`.
5
5
 
6
- Namespaces can accessed with blocks, or with a fluent interface:
6
+ Namespaces and collections can accessed with blocks, or with a fluent interface:
7
7
 
8
8
  ```ruby
9
9
  aws do |a|
@@ -14,26 +14,119 @@ end
14
14
  aws.region = 'us-west-1'
15
15
  ```
16
16
 
17
- Collections are named sets. Like namespaces, they can be accessed with blocks, or a fluent interface:
17
+ Collections are sets of named items. Like namespaces, they can be accessed with blocks, or a fluent interface:
18
18
 
19
19
  ```ruby
20
20
  profile :default do |default_profile|
21
- default_profile.chef.run_list 'apt:default', 'redis:server'
21
+ default_profile.chef.run_list 'apt:default', 'redis:server', 'app::server'
22
22
  end
23
23
 
24
24
  profile(:default).chef.environment 'development'
25
+ profile(:prod).chef.environment 'production'
25
26
  ```
26
27
 
27
- In the example above, the same collection is accessed twice. The final result looks like:
28
+ In the example above, the same collection item is accessed twice. A second item is also defined in the same collection. The final result looks like:
28
29
 
29
30
  ```json
30
31
  {
31
32
  "profile": {
32
33
  "default": {
33
34
  "chef": {
34
- "run_list": ["apt:default", "redis:server"],
35
+ "run_list": ["apt:default", "redis:server", "app::server"],
35
36
  "environment": "development"
36
37
  }
38
+ },
39
+ "prod": {
40
+ "chef": {
41
+ "environment": "production"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ Collections and namespaces may be nested indefinitely.
49
+
50
+ ## Extending Collection Items
51
+
52
+ A collection item can extend another item using a hash-notation:
53
+
54
+ ```ruby
55
+ profile :prod => Config.profile(:default) do |prod|
56
+ prod.chef.environment = 'production'
57
+ end
58
+ ```
59
+
60
+ Following the example, above, the `prod` profile will now be pre-populated with all of the values in the `default` profile, which can be overridden:
61
+
62
+ ```json
63
+ {
64
+ "profile": {
65
+ "default": {
66
+ "chef": {
67
+ "run_list": ["apt:default", "redis:server", "app::server"],
68
+ "environment": "development"
69
+ }
70
+ },
71
+ "prod": {
72
+ "chef": {
73
+ "run_list": ["apt:default", "redis:server", "app::server"],
74
+ "environment": "production"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## List-type Attributes
82
+
83
+ Some configuration attributes are actually ordered sets of values. These are referred to as `list-type` attributes, and have some additional options:
84
+
85
+ ```ruby
86
+ chef.run_list 'apt:default', :mode => :override
87
+ ```
88
+
89
+ The `:mode` parameter tells the configuration manager how how to compile a list-type attribute that is defined in two or more layers, or is modified in an extended collection item. Currently, two modes are implemented:
90
+
91
+ * `:override` - Instructs the compiler to discard any elements that have been loaded from lower layers. This does not have any effect upon the behavior of the same attribute in higher layers, meaning that the current layer's override may be appended to or overridden by future layers, according to their `mode` parameter.
92
+ * `:union` - Default behavior. Instructs the compiler to perform a set-union between the current layer's elements and the current set of elements compiled from lower layers.
93
+
94
+ List-type attributes may also have an `appender method`, which allows elements to be appended to the current set _in that layer_. **List-type attributes do not have an `=` setter.**
95
+
96
+ Because `chef.run_list` is a list-type attribute, we can tell Builderator to override the `default` profile's `run_list`:
97
+
98
+ ```ruby
99
+ profile :prod => Config.profile(:default) do |prod|
100
+ prod.chef.environment = 'production'
101
+ prod.chef.run_list 'apt:default', 'redis:server', 'app::server', 'app::tls', :mode => :override
102
+ end
103
+ ```
104
+
105
+ We could also append to `default`'s `run_list` without modifying `default`:
106
+
107
+ ```ruby
108
+ profile :prod => Config.profile(:default) do |prod|
109
+ prod.chef.environment = 'production'
110
+ prod.chef.run_list_item 'app::tls'
111
+ end
112
+ ```
113
+
114
+ Both of the above will result in the same compiled configuration:
115
+
116
+ ```json
117
+ {
118
+ "profile": {
119
+ "default": {
120
+ "chef": {
121
+ "run_list": ["apt:default", "redis:server", "app::server"],
122
+ "environment": "development"
123
+ }
124
+ },
125
+ "prod": {
126
+ "chef": {
127
+ "run_list": ["apt:default", "redis:server", "app::server", "app::tls"],
128
+ "environment": "production"
129
+ }
37
130
  }
38
131
  }
39
132
  }
@@ -46,9 +139,9 @@ In the example above, the same collection is accessed twice. The final result lo
46
139
 
47
140
  * `vendored(name, path)` - Return the absolute path to `path` in the named vendor resource. _ Hint: Use this helper to reference Builderator policy files and Chef data_bag and environment sources in an external repository._
48
141
 
49
- ## Configuration File DSL
142
+ * `relative(path)` - Return the absolute path to `path` relative to the calling Buildfile _Hint: Use this helper to reference templates included with a vendored policy._
50
143
 
51
- Collections and namespaces may be nested indefinitely.
144
+ ## Configuration Parameters
52
145
 
53
146
  * [Namespace `cookbook`](configuration/cookbook.md)
54
147
  * [Collection `profile`](configuration/profile.md)
@@ -60,14 +153,12 @@ Collections and namespaces may be nested indefinitely.
60
153
  * `version` The version of this release of the build. Auto-populated by `autoversion` by default
61
154
  * `cleanup` Enable post-build cleanup tasks. Default `true`
62
155
 
63
- * `relative(path)` - Return the absolute path to `path` relative to the calling Buildfile _Hint: Use this helper to reference templates included with a vendored policy._
64
-
65
- ## Namespace `autoversion`
156
+ ### Namespace `autoversion`
66
157
 
67
158
  * `create_tags` During a release, automatically create and push new SCM tags
68
159
  * `search_tags` Use SCM tags to determine the current version of the build
69
160
 
70
- ## Namespace `chef`
161
+ ### Namespace `chef`
71
162
 
72
163
  Global configurations for chef provisioners in Vagrant and Packer
73
164
 
@@ -75,28 +166,28 @@ Global configurations for chef provisioners in Vagrant and Packer
75
166
  * `staging_directory` the path in VMs and images that Chef artifacts should be mounted/copied to. Defaults to `/var/chef`
76
167
  * `version` The version of chef to install with Omnibus
77
168
 
78
- ## Namespace `local`
169
+ ### Namespace `local`
79
170
 
80
171
  Local paths used for build tasks
81
172
 
82
173
  * `cookbook_path` Path at which to vendor cookbooks. Default `.builderator/cookbooks`
83
174
  * `data_bag_path` and `environment_path` Paths that Chef providers should load data-bag and environment documents from.
84
175
 
85
- ## Collection `policy`
176
+ ### Collection `policy`
86
177
 
87
178
  Load additional attributes into the parent file from a relative path
88
179
 
89
180
  * `path` Load a DSL file, relative => true
90
181
  * `json` Load a JSON file relative => true
91
182
 
92
- ## Namespace `aws`
183
+ ### Namespace `aws`
93
184
 
94
185
  AWS API configurations. _Hint: Configure these in `$HOME/.builderator/Buildfile`, or use a built-in credential source, e.g. ~/.aws/config!_
95
186
 
96
187
  * `region` The default AWS region to use
97
188
  * `access_key` and `secret_key` A valid IAM key-pair
98
189
 
99
- ## Collection `vendor`
190
+ ### Collection `vendor`
100
191
 
101
192
  Fetch remote artifacts for builds
102
193
 
@@ -105,16 +196,16 @@ Fetch remote artifacts for builds
105
196
  * `git` Fetch a git repository
106
197
  * `github` Fetch a git repository from a GitHub URI (e.g. `OWNER/REPO`) using the SSH protocol. You must have a valid SSH key configuration for public GitHub.
107
198
  * Git-specific parameters:
108
- * `branch`
109
- * `tag`
110
- * `ref`
199
+ * `remote` - The name of the remote repository at `git` or `github`. Defaults to `origin`
200
+ * `branch` - The SCM branch to check out. Defaults to `master`
201
+ * `tag` or `ref` - A SHA-ish or SCM tag to check out. Overrides `branch`.
111
202
  * `rel` Checkout a sub-directory of a git repository
112
203
 
113
- ## Namespace `cleaner`
204
+ ### Namespace `cleaner`
114
205
 
115
206
  Configuration parameters for `build-clean` tasks
116
207
 
117
- ### Namespace `limits`
208
+ #### Namespace `limits`
118
209
 
119
210
  Maximum number of resources to remove without manual override
120
211
 
@@ -123,25 +214,25 @@ Maximum number of resources to remove without manual override
123
214
  * `snapshots`
124
215
  * `volumes`
125
216
 
126
- ## Namespace `generator`
217
+ ### Namespace `generator`
127
218
 
128
219
  Configurations for the `generator` task
129
220
 
130
- ### Collection `project`
221
+ #### Collection `project`
131
222
 
132
223
  * `builderator.version` The version of Builderator to install with Bundler
133
224
  * `ruby.version` The version of ruby to require for Bundler and `rbenv`/`rvm`
134
225
 
135
- #### Namespace `vagrant`
226
+ ##### Namespace `vagrant`
136
227
 
137
228
  * `install` Boolean, include the vagrant gem from GitHub `mitchellh/vagrant`
138
229
  * `version` The version of Vagrant to use from GitHub, if `install` is true
139
230
 
140
- ##### Collection `plugin`
231
+ ###### Collection `plugin`
141
232
 
142
233
  Vagrant plugins to install, either with the `build vagrant plugin` command, for a system-wide installation of Vagrant, or in the generated Gemfile if `install` is true
143
234
 
144
- #### Collection `resource`
235
+ ##### Collection `resource`
145
236
 
146
237
  Add a managed file to the project definition
147
238
 
@@ -14,7 +14,7 @@ module Builderator
14
14
  end
15
15
 
16
16
  def all_layers
17
- ([defaults] + layers + [overrides, argv])
17
+ ([GLOBAL_DEFAULTS, defaults] + layers + [overrides, argv])
18
18
  end
19
19
 
20
20
  def defaults
@@ -51,22 +51,23 @@ module Builderator
51
51
  compiled.unseal
52
52
  compile_iterations = 0
53
53
 
54
- ## Initialize with global defaults
54
+ ## Inject GLOBAL_DEFAULTS before starting compile
55
55
  compiled.merge(GLOBAL_DEFAULTS.compile)
56
56
 
57
57
  ## Automatically recompile while layers are dirty
58
58
  loop do
59
59
  fail "Re-compile iteration limit of #{max_iterations} has been exceeded" if compile_iterations >= max_iterations
60
60
 
61
- ## Reset flags before next iteration
62
- compiled.clean
63
-
64
61
  ## Merge layers from lowest to highest. Compile, then merge.
65
- all_layers.each(&:compile)
66
62
  all_layers.each do |layer|
67
- compiled.merge(layer)
63
+ layer.compile
64
+ end
68
65
 
66
+ all_layers.each do |layer|
69
67
  layer.policies.each { |_, policy| compiled.merge(policy) }
68
+
69
+ ## Merge layer after its policy documents to allow overides
70
+ compiled.merge(layer)
70
71
  end
71
72
 
72
73
  break unless dirty?
@@ -79,7 +80,7 @@ module Builderator
79
80
  alias_method :recompile, :compile
80
81
 
81
82
  def dirty?
82
- all_layers.any?(&:dirty) || compiled.dirty
83
+ all_layers.any?(&:dirty)
83
84
  end
84
85
 
85
86
  def compiled
@@ -2,6 +2,7 @@ require 'forwardable'
2
2
  require 'json'
3
3
 
4
4
  require_relative './rash'
5
+ require_relative './list'
5
6
 
6
7
  module Builderator
7
8
  module Config
@@ -18,31 +19,30 @@ module Builderator
18
19
  # Helpers for Array-type attributes
19
20
  ##
20
21
  if options[:type] == :list
21
- default = Array
22
+ define_method(attribute_name) do |*arg, **run_options|
23
+ ## Instantiate List if it doesn't exist yet. `||=` will always return a new Rash.
24
+ @attributes[attribute_name] = Config::List.new(run_options) unless @attributes.has?(attribute_name, Config::List)
22
25
 
23
- ## Add an appender DSL method
24
- define_method(options[:singular]) do |*args|
25
- append_if_valid(attribute_name, args.flatten, default, options)
26
+ @attributes[attribute_name].set(*arg.flatten) unless arg.empty?
27
+ @attributes[attribute_name]
28
+ end
29
+
30
+ define_method(options[:singular]) do |*arg, **run_options|
31
+ send(attribute_name, run_options).push(*arg.flatten)
26
32
  end if options.include?(:singular)
33
+
34
+ return
27
35
  end
28
36
 
29
37
  ## Getter/Setter
30
38
  define_method(attribute_name) do |*arg|
31
- arg.flatten!
32
- arg = arg.first unless options[:type] == :list
33
-
34
- set_or_return(attribute_name, arg, default, options)
39
+ set_or_return(attribute_name, arg.first, default, options)
35
40
  end
36
41
 
37
42
  ## Setter
38
43
  define_method("#{attribute_name}=") do |arg|
39
44
  set_if_valid(attribute_name, arg, options)
40
- end unless options[:type] == :list
41
- end
42
-
43
- ## Add a method the DSL
44
- def define(method_name, &block)
45
- define_method(method_name, &block)
45
+ end
46
46
  end
47
47
 
48
48
  ##
@@ -58,7 +58,7 @@ module Builderator
58
58
  # end
59
59
  # ```
60
60
  #
61
- # Multiple calls to the DSL method will are safe and will
61
+ # Multiple calls to the DSL method are safe and will
62
62
  # update the same sub-node.
63
63
  ##
64
64
  def namespace(namespace_name, &definition)
@@ -68,7 +68,7 @@ module Builderator
68
68
  nodes[namespace_name] ||= namespace_class.new(
69
69
  @attributes[namespace_name],
70
70
  :name => namespace_name,
71
- :parent => self, &block).compile
71
+ :parent => self, &block)
72
72
  end
73
73
  end
74
74
 
@@ -85,16 +85,29 @@ module Builderator
85
85
  # Multiple entities can be added to the collection by calling the DSL method
86
86
  # with unique `name` arguments. Multiple calls to the DSL method with the
87
87
  # same name argument will update the existing entity in place
88
+ #
89
+ # An entry can be defined as an extension of another node by passing a hash
90
+ # as the instance name: `name => Config.node(:name)`. This will use the values
91
+ # defined in `Config.node(:name)` as defaults for the new entry
88
92
  ##
89
93
  def collection(collection_name, &definition)
90
94
  collection_class = Collection.create(collection_name, &definition)
91
95
 
92
96
  define_method(collection_name) do |instance_name = nil, &block|
97
+ extension_base = nil
98
+
99
+ ## Allow extension to be defined as a key-value
100
+ if instance_name.is_a?(Hash)
101
+ extension_base = instance_name.first.last
102
+ instance_name = instance_name.first.first
103
+ end
104
+
93
105
  nodes[collection_name] ||= collection_class.new(
94
- @attributes[collection_name])
106
+ @attributes[collection_name],
107
+ :parent => self)
95
108
 
96
109
  return nodes[collection_name] if instance_name.nil?
97
- nodes[collection_name].fetch(instance_name, &block).compile
110
+ nodes[collection_name].fetch(instance_name, :extends => extension_base, &block)
98
111
  end
99
112
  end
100
113
  end
@@ -118,14 +131,10 @@ module Builderator
118
131
  end
119
132
 
120
133
  ## All dirty state should aggregate at the root node
121
- def dirty
122
- return @dirty if parent == self
123
- parent.dirty
124
- end
134
+ def dirty(update = false)
135
+ return @dirty ||= update if parent == self
125
136
 
126
- def dirty=(set)
127
- return @dirty = set if parent == self
128
- parent.dirty = set
137
+ parent.dirty(update)
129
138
  end
130
139
 
131
140
  def ==(other)
@@ -135,6 +144,7 @@ module Builderator
135
144
  attr_reader :attributes
136
145
  attr_reader :nodes
137
146
  attr_reader :parent
147
+ attr_reader :extends
138
148
 
139
149
  def initialize(attributes = {}, options = {}, &block)
140
150
  @attributes = Rash.coerce(attributes)
@@ -143,24 +153,35 @@ module Builderator
143
153
 
144
154
  ## Track change status for consumers
145
155
  @parent = options.fetch(:parent, self)
146
- self.dirty = false if parent == self
156
+ @extends = options[:extends]
157
+ @dirty = false
147
158
  end
148
159
 
149
160
  ## Clear dirty state flag
150
161
  def clean
151
- self.dirty = false
162
+ @dirty = false
152
163
  end
153
164
 
154
- def compile
155
- instance_eval(&@block) if @block
165
+ def compile(evaluate = true)
166
+ ## Compile this node and its children
167
+ @block.call(self) if @block && evaluate
168
+ nodes.each { |_, node| node.compile }
169
+
170
+ ## Underlay base values if present
171
+ if extends.is_a?(Attributes)
172
+ merged_atributes = extends.attributes.clone
173
+ merged_atributes.merge!(attributes)
174
+
175
+ attributes.merge!(merged_atributes)
176
+ end
177
+
156
178
  self
157
179
  end
158
180
 
159
181
  def merge(other)
160
- self.dirty |= attributes.merge!(other.attributes)
182
+ dirty(attributes.merge!(other.attributes))
161
183
  self
162
184
  end
163
- alias_method :includes, :merge
164
185
 
165
186
  def to_json(*_)
166
187
  JSON.pretty_generate(to_hash)
@@ -182,24 +203,12 @@ module Builderator
182
203
  ## Unchanged
183
204
  return if @attributes[key] == arg
184
205
 
185
- self.dirty |= true ## A mutation has occured
206
+ dirty(true) ## A mutation has occured
186
207
  @attributes[key] = arg
187
208
  end
188
209
 
189
- def append_if_valid(key, arg, default = Array, **options)
190
- ## TODO: define validation interface
191
-
192
- attribute = set_or_return(key, nil, default, options)
193
- arg.reject! { |item| attribute.include?(item) }
194
-
195
- return if arg.empty?
196
-
197
- @dirty |= true ## A mutation has occured
198
- attribute.push(*arg)
199
- end
200
-
201
210
  def set_or_return(key, arg = nil, default = nil, **options)
202
- if arg.nil? || (arg.is_a?(Array) && arg.empty?)
211
+ if arg.nil?
203
212
  return @attributes[key] if @attributes.has?(key)
204
213
 
205
214
  ## Default
@@ -249,11 +258,6 @@ module Builderator
249
258
  @name = options.fetch(:name, self.class.name)
250
259
  @collection = options[:collection]
251
260
  end
252
-
253
- def compile
254
- @block.call(self) if @block
255
- self
256
- end
257
261
  end
258
262
 
259
263
  ##
@@ -291,12 +295,13 @@ module Builderator
291
295
  end
292
296
 
293
297
  ## Get namespace instances
294
- def fetch(instance_name, &block)
295
- self.class.namespace_class.new(
298
+ def fetch(instance_name, **options, &block)
299
+ nodes[instance_name] ||= self.class.namespace_class.new(
296
300
  attributes[instance_name],
297
301
  :collection => self,
298
302
  :name => instance_name,
299
- :parent => self, &block)
303
+ :parent => self,
304
+ :extends => options[:extends], &block)
300
305
  end
301
306
  alias_method :[], :fetch
302
307
  end
@@ -39,7 +39,7 @@ module Builderator
39
39
  @type = options.fetch(:type, :code)
40
40
  @source = options.fetch(:source, nil)
41
41
 
42
- super(attributes, &block)
42
+ super(attributes, options, &block)
43
43
  end
44
44
 
45
45
  def compile
@@ -48,15 +48,19 @@ module Builderator
48
48
  case @type
49
49
  when :file
50
50
  instance_eval(IO.read(source), source, 0)
51
+ super(false)
52
+
51
53
  when :json
52
54
  update = Rash.coerce(JSON.parse(IO.read(source)))
53
55
 
54
56
  unless @attributes == update
55
- @dirty |= true
57
+ dirty(true)
56
58
  @attributes = update
57
59
  end
58
60
  else
59
61
  instance_eval(&@block) if @block
62
+ super(false)
63
+
60
64
  end
61
65
 
62
66
  ## Overlay policies
@@ -71,7 +75,7 @@ module Builderator
71
75
  end
72
76
 
73
77
  policies[name].compile
74
- self.dirty |= policies[name].dirty
78
+ dirty(policies[name].dirty)
75
79
  end
76
80
 
77
81
  self
@@ -273,6 +277,7 @@ module Builderator
273
277
 
274
278
  attribute :git
275
279
  attribute :github
280
+ attribute :remote
276
281
  attribute :branch
277
282
  attribute :tag
278
283
  attribute :ref
@@ -0,0 +1,63 @@
1
+ module Builderator
2
+ module Config
3
+ ##
4
+ # Extend Array with context about how its values should be merged with other
5
+ # configuration layers. Possible modes are:
6
+ #
7
+ # * 'override' - Do not merge. Replace the other node's elements
8
+ # * 'union' - Perform a set-union on the elements of this and the other node
9
+ ##
10
+ class List < Array
11
+ class << self
12
+ def coerce(somehting, options = {})
13
+ return somehting if somehting.is_a?(self)
14
+ return new(options).push(*somehting) if somehting.is_a?(Array)
15
+
16
+ ## `somehting` is not a valid input. Just give back an instance.
17
+ new([], options)
18
+ end
19
+ end
20
+
21
+ attr_reader :mode
22
+
23
+ def initialize(from = nil, **options)
24
+ @mode = options.fetch(:mode, :union)
25
+
26
+ merge!(from) unless from.nil?
27
+ end
28
+
29
+ def clone
30
+ self.class.new(self, :mode => mode)
31
+ end
32
+
33
+ def set(*elements)
34
+ clear
35
+ push(*elements)
36
+ end
37
+
38
+ ##
39
+ # Combine elements with `other` according to `other`'s `mode`
40
+ ##
41
+ def merge!(other)
42
+ other = self.class.coerce(other)
43
+
44
+ case other.mode
45
+ when :override
46
+ return false if self == other
47
+ set(*other)
48
+
49
+ when :union
50
+ merged = self | other
51
+ return false if merged == self
52
+
53
+ set(*merged)
54
+
55
+ else
56
+ fail "Invalid List mode #{other.mode}!"
57
+ end
58
+
59
+ true
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,5 @@
1
+ require_relative './list'
2
+
1
3
  module Builderator
2
4
  module Config
3
5
  ##
@@ -26,6 +28,10 @@ module Builderator
26
28
  merge!(from) ## Clone a Rash or coerce a Hash to a new Rash
27
29
  end
28
30
 
31
+ def clone
32
+ self.class.new(self, sealed)
33
+ end
34
+
29
35
  def seal(action = true)
30
36
  @sealed = action
31
37
  each_value { |v| v.seal(action) if v.is_a?(self.class) }
@@ -35,7 +41,9 @@ module Builderator
35
41
  seal(false)
36
42
  end
37
43
 
38
- alias_method :has?, :include?
44
+ def has?(key, klass = BasicObject)
45
+ include?(key) && fetch(key).is_a?(klass)
46
+ end
39
47
 
40
48
  ## Symbolize keys
41
49
  [:include?, :[], :fetch, :[]=, :store].each do |m|
@@ -56,24 +64,21 @@ module Builderator
56
64
  next if self[k] == v
57
65
 
58
66
  ## Merge Arrays
59
- if fetch(k, nil).is_a?(Array) && v.is_a?(Array)
60
- next if (self[k] | v) == self[k]
61
-
62
- dirty |= true
63
- next self[k] |= v
67
+ if v.is_a?(Array)
68
+ self[k] = has?(k) ? Config::List.coerce(self[k]) : Config::List.new
69
+ dirty = self[k].merge!(v) || dirty
70
+ next
64
71
  end
65
72
 
66
73
  ## Overwrite non-Hash values
67
74
  unless v.is_a?(Hash)
68
- dirty |= true
75
+ dirty = true
69
76
  next self[k] = v
70
77
  end
71
78
 
72
- ## Replace `self[k]` with a new Rash unless it already is one
73
- self[k] = self.class.new unless fetch(k, nil).is_a?(self.class)
74
-
75
79
  ## Merge recursivly coerces `v` to a Rash
76
- dirty |= self[k].merge!(v)
80
+ self[k] = self.class.coerce(self[k])
81
+ dirty = self[k].merge!(v) || dirty
77
82
  end
78
83
 
79
84
  dirty
@@ -53,16 +53,20 @@ module Builderator
53
53
 
54
54
  no_commands do
55
55
  def _fetch_git(path, params)
56
- empty_directory path
56
+ ## Ensure that there isn't already something there
57
+ unless path.join('.git').exist?
58
+ remove_dir path
59
+ empty_directory path
60
+ end
57
61
 
58
62
  inside path do
59
63
  ## Initialize new repository
60
64
  unless path.join('.git').exist?
61
65
  run 'git init'
62
- run "git remote add origin #{ params.git }"
66
+ run "git remote add #{ params.fetch(:remote, 'origin') } #{ params.git }"
63
67
  end
64
68
 
65
- run 'git fetch origin --tags --prune'
69
+ run "git fetch #{ params.fetch(:remote, 'origin') } --tags --prune"
66
70
 
67
71
  ## Checkout reference
68
72
  if params.has?(:tag) then run "git checkout #{ params.tag }"
@@ -71,7 +75,7 @@ module Builderator
71
75
  run "git checkout #{ params.fetch(:branch, 'master') }"
72
76
 
73
77
  ## Only pull if a tracking branch is checked out
74
- run 'git pull'
78
+ run "git pull #{ params.fetch(:remote, 'origin') } #{ params.fetch(:branch, 'master') }"
75
79
  end
76
80
 
77
81
  ## Apply relative subdirectory
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: builderator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.rc.9
4
+ version: 1.0.0.pre.rc.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Manero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-25 00:00:00.000000000 Z
11
+ date: 2016-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -183,6 +183,7 @@ files:
183
183
  - lib/builderator/config/attributes.rb
184
184
  - lib/builderator/config/defaults.rb
185
185
  - lib/builderator/config/file.rb
186
+ - lib/builderator/config/list.rb
186
187
  - lib/builderator/config/rash.rb
187
188
  - lib/builderator/control/cleaner.rb
188
189
  - lib/builderator/control/cookbook.rb
@@ -256,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
256
257
  version: 1.3.1
257
258
  requirements: []
258
259
  rubyforge_project:
259
- rubygems_version: 2.4.5
260
+ rubygems_version: 2.4.5.1
260
261
  signing_key:
261
262
  specification_version: 4
262
263
  summary: Tools to make CI Packer builds awesome