builderator 0.3.15 → 1.0.0.pre.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +440 -0
  4. data/README.md +72 -18
  5. data/Rakefile +1 -2
  6. data/VERSION +1 -1
  7. data/bin/build-clean +102 -0
  8. data/bin/build-data +45 -0
  9. data/builderator.gemspec +7 -4
  10. data/docs/configuration.md +154 -0
  11. data/docs/configuration/cookbook.md +19 -0
  12. data/docs/configuration/profile.md +71 -0
  13. data/docs/versioning.md +65 -0
  14. data/lib/builderator.rb +3 -0
  15. data/lib/builderator/config.rb +93 -0
  16. data/lib/builderator/config/attributes.rb +287 -0
  17. data/lib/builderator/config/defaults.rb +163 -0
  18. data/lib/builderator/config/file.rb +336 -0
  19. data/lib/builderator/config/rash.rb +80 -0
  20. data/lib/builderator/control/cleaner.rb +138 -0
  21. data/lib/builderator/control/cookbook.rb +16 -0
  22. data/lib/builderator/control/data.rb +16 -0
  23. data/lib/builderator/control/data/image.rb +98 -0
  24. data/lib/builderator/control/version.rb +128 -0
  25. data/lib/builderator/control/version/auto.rb +48 -0
  26. data/lib/builderator/control/version/bump.rb +82 -0
  27. data/lib/builderator/control/version/comparable.rb +77 -0
  28. data/lib/builderator/control/version/git.rb +45 -0
  29. data/lib/builderator/control/version/scm.rb +92 -0
  30. data/lib/builderator/interface.rb +67 -0
  31. data/lib/builderator/interface/berkshelf.rb +38 -0
  32. data/lib/builderator/interface/packer.rb +75 -0
  33. data/lib/builderator/interface/vagrant.rb +31 -0
  34. data/lib/builderator/metadata.rb +5 -3
  35. data/lib/builderator/model/cleaner.rb +49 -0
  36. data/lib/builderator/model/cleaner/images.rb +93 -0
  37. data/lib/builderator/model/cleaner/instances.rb +58 -0
  38. data/lib/builderator/model/cleaner/launch_configs.rb +47 -0
  39. data/lib/builderator/model/cleaner/scaling_groups.rb +45 -0
  40. data/lib/builderator/model/cleaner/snapshots.rb +50 -0
  41. data/lib/builderator/model/cleaner/volumes.rb +48 -0
  42. data/lib/builderator/patch/berkshelf.rb +18 -0
  43. data/lib/builderator/patch/thor-actions.rb +47 -0
  44. data/lib/builderator/tasks.rb +127 -17
  45. data/lib/builderator/tasks/berkshelf.rb +63 -0
  46. data/lib/builderator/tasks/packer.rb +17 -56
  47. data/lib/builderator/tasks/vagrant.rb +111 -42
  48. data/lib/builderator/tasks/vendor.rb +94 -0
  49. data/lib/builderator/tasks/version.rb +58 -0
  50. data/lib/builderator/util.rb +37 -11
  51. data/lib/builderator/util/aws_exception.rb +1 -1
  52. data/lib/builderator/util/limit_exception.rb +12 -11
  53. data/lib/builderator/util/task_exception.rb +0 -2
  54. data/mkmf.log +4 -0
  55. data/spec/config_spec.rb +30 -0
  56. data/spec/data/Berksfile +6 -0
  57. data/spec/data/Buildfile +0 -0
  58. data/spec/data/Vagrantfile +0 -0
  59. data/spec/data/history.json +483 -0
  60. data/spec/data/packer.json +0 -0
  61. data/spec/interface_spec.rb +36 -0
  62. data/spec/resource/Buildfile +27 -0
  63. data/spec/spec_helper.rb +90 -0
  64. data/spec/version_spec.rb +282 -0
  65. data/template/Berksfile.erb +10 -0
  66. data/template/Buildfile.erb +28 -0
  67. data/template/Gemfile.erb +16 -0
  68. data/template/README.md.erb +61 -0
  69. data/template/Vagrantfile.erb +75 -0
  70. data/template/gitignore.erb +104 -0
  71. data/{.rubocop.yml → template/rubocop.erb} +0 -0
  72. metadata +203 -56
  73. data/.gitignore +0 -14
  74. data/lib/builderator/control/ami.rb +0 -65
  75. data/lib/builderator/control/clean.rb +0 -130
  76. data/lib/builderator/model.rb +0 -46
  77. data/lib/builderator/model/images.rb +0 -89
  78. data/lib/builderator/model/instances.rb +0 -55
  79. data/lib/builderator/model/launch_configs.rb +0 -46
  80. data/lib/builderator/model/scaling_groups.rb +0 -43
  81. data/lib/builderator/model/snapshots.rb +0 -49
  82. data/lib/builderator/model/volumes.rb +0 -48
  83. data/lib/builderator/tasks/ami.rb +0 -47
  84. data/lib/builderator/tasks/berks.rb +0 -68
  85. data/lib/builderator/tasks/clean.rb +0 -97
  86. data/lib/builderator/util/berkshim.rb +0 -34
  87. data/lib/builderator/util/cookbook.rb +0 -87
  88. data/lib/builderator/util/packer.rb +0 -39
  89. data/lib/builderator/util/shell.rb +0 -44
@@ -0,0 +1,71 @@
1
+ Collection `profile`
2
+ ====================
3
+
4
+ A profile is a combination of Chef parameters, and Vagrant and Packer configurations. Profiles should provide
5
+
6
+ * `tags, type: hash` EC2 tags to apply to instances and AMIs
7
+ * `log_level` Chef log-level. Default `:info`
8
+
9
+ ## Collection `artifact`
10
+
11
+ An externally managed resource to push to VMs and image builds, e.g. `bundle.tar.gz` from a Maven build.
12
+
13
+ * `path` The workspace-rooted path to the artifact
14
+ * `destination` The absolute path on the VM or image at which the artifact should be placed
15
+
16
+ ## Namespace `chef`
17
+ * `run_list, type: list, singular: run_list_item, unique: true` The Chef runlist for this profile
18
+ * `environment` The Chef environment to load for this
19
+ * `node_attrs, type: hash` A hash of node attributes for this profile
20
+
21
+
22
+ ## Namespace `packer`
23
+
24
+ Packer configurations for this profile
25
+
26
+ ### Collection `build`
27
+
28
+ Add a packer build
29
+
30
+ * `type` the build provider (e.g. amazon-ebs, virtualbox)
31
+ * `instance_type` the EC2 instance type to use
32
+ * `source_ami` The source AMI ID for an `amazon-ebs`
33
+ * `ssh_username` Default `ubuntu`
34
+ * `ami_virtualization_type` Default `hvm`
35
+
36
+ ## TODO: Share accounts
37
+
38
+ * `ami_name` Name for new AMI
39
+ * `ami_description` Description for the new AMI
40
+
41
+
42
+ ## Namespace `vagrant`
43
+
44
+ Vagrant VM configurations
45
+
46
+ ### Namespace `local`
47
+
48
+ Parameters for a local VM build
49
+
50
+ * `provider` Default `virtualbox`
51
+ * `box` Default `ubuntu-14.04-x86_64`
52
+ * `box_url` Default `https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box`
53
+
54
+ * `cpus` Default 2
55
+ * `memory` Default 1024 (MB)
56
+
57
+ ## Namespace `ec2`
58
+
59
+ Parameters for the provisioning EC2 nodes with Vagrant
60
+
61
+ * `provider` Default `aws`
62
+ * `box` Default `dummy`
63
+ * `box_url` Default `https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box`
64
+ * `instance_type`
65
+ * `source_ami`
66
+ * `ssh_username`
67
+ * `virtualization_type`
68
+ * `instance_profile`
69
+ * `subnet_id`
70
+ * `security_groups, type: list, singular: security_group, unique: true`
71
+ * `public_ip`
@@ -0,0 +1,65 @@
1
+ Versioning
2
+ ==========
3
+
4
+ This functionality is currently supported for Git.
5
+
6
+ ## Version Bump Steps
7
+
8
+ * `major` - Increment major version.
9
+ * `major-prerelease` - Start a pre-release train from the next major version (increments major version).
10
+ * `minor` - Increment minor version.
11
+ * `minor-prerelease` - Start a pre-release train from the next minor version (increments minor version).
12
+ * `patch` - Increment the patch version.
13
+ * `patch-prerelease` - Force a new pre-release train from the next patch version, even if the current version is a pre-release.
14
+ * `release` - Release a pre-release train a the current `major`.`minor`.`patch` version.
15
+ * `prerelease NAME` Create or increment a pre-release version. If the current version is not a pre-release, the patch version will also be incremented.
16
+ * `build` Add or increment a build number.
17
+
18
+ Step types are an ordered-set. Incrementing a higher step resets all lower parameters.
19
+
20
+ ## Commands
21
+
22
+ ### `build version current`
23
+
24
+ Searches for the newest SCM tag that is a valid sem-ver string and writes it to VERSION in the project root.
25
+
26
+ ### `build version bump [STEP [PRERELEASE_NAME=alpha]]`
27
+
28
+ Increment the current version by the desired step and create a new SCM tag at HEAD.
29
+
30
+ If STEP is omitted, Builderator will scan messages of commits between HEAD and the current version tag for hash-tag style annotations indicating how to increment the version, finally defaulting to a `patch` step if no annotations are found. If multiple `#STEP` annotations are detected, the largest (e.g. `#major` > `#patch`) step will be applied.
31
+
32
+ ## Configuration
33
+
34
+ The `autoversion` namespace has two attributes:
35
+
36
+ * `create_tags BOOLEAN` enables auto-generation of SCM tags after `bump` tasks. Default `true`.
37
+ * `search_tags` enables detection of the current version from SCM tags. Default `true`.
38
+
39
+ ```ruby
40
+ autoversion do |version|
41
+ version.create_tags true
42
+ version.search_tags true
43
+ end
44
+ ```
45
+
46
+ ## Adding Providers
47
+
48
+ SCM providers must extend the `Builderator::Control::Version::SCM` module, and must implement two methods in their singleton class:
49
+
50
+ * `self._history` Return an array of hashes with the following keys:
51
+ - `:id` SCM commit identity
52
+ - `:message` SCM commit message
53
+ - `:tags` `nil` or an array of semver strings
54
+ * `self.supported?` Return `true` if the provider supports the build environment (e.g. the `Git` provider checks that `pwd` is a git repository), else return `false`.
55
+
56
+ To enable a provider module, pass it to `SCM.register`. See [blob/auto-version/lib/builderator/control/version/git.rb] for an example.
57
+
58
+ ## This looks like `thor-scmversion`
59
+
60
+ _Why aren't you using `thor-scmversion`?!_
61
+
62
+ Well yes, it's based upon `thor-scmversion`, which I've been using for a while. `thor-scmversion` provides a nice model to ensure correct versioning of automatically built modules, but the project is largely abandoned, lacks some key features required for Builderator, and has a very inefficient access path for reading Git SCM data.
63
+
64
+ This implementation adds `TYPE-prerelease` bump steps, improves semver matching regular-expressions, dramatically improves git-data access time for repositories with many tags (only reads from git-blobs once),
65
+ and de-couples core functionality for parsing and incrementing versions from Thor tasks and actual mutation of the repository.
@@ -1,4 +1,7 @@
1
1
  require 'builderator/metadata'
2
2
 
3
+ ##
4
+ # Start Namespace
5
+ ##
3
6
  module Builderator
4
7
  end
@@ -0,0 +1,93 @@
1
+ require_relative './config/file'
2
+ require_relative './config/defaults'
3
+
4
+ module Builderator
5
+ ##
6
+ # Global Configuration
7
+ ##
8
+ module Config
9
+ class << self
10
+ ## GLOBAL_DEFAULTS is the lowest-precedence layer, followed by dynamically
11
+ ## defined instance-defaults.
12
+ def layers
13
+ @layers ||= []
14
+ end
15
+
16
+ def all_layers
17
+ ([GLOBAL_DEFAULTS, defaults] + layers + [overrides, argv])
18
+ end
19
+
20
+ def defaults
21
+ @defaults ||= File.new({}, :source => 'defaults')
22
+ end
23
+
24
+ def overrides
25
+ @overrides ||= File.new({}, :source => 'overrides')
26
+ end
27
+
28
+ def argv(options = {})
29
+ @argv ||= File.new(options, :source => 'argv')
30
+ end
31
+
32
+ def append(path)
33
+ layers << File.from_file(path) if ::File.exist?(path)
34
+ end
35
+ alias_method :load, :append
36
+
37
+ def append_json(path)
38
+ layers << File.from_json(path) if ::File.exist?(path)
39
+ end
40
+ alias_method :load_json, :append_json
41
+
42
+ def prepend(path)
43
+ layers.unshift(File.from_file(path)) if ::File.exist?(path)
44
+ end
45
+
46
+ def prepend_json(path)
47
+ layers.unshift(File.from_json(path)) if ::File.exist?(path)
48
+ end
49
+
50
+ def compile(max_iterations = 4)
51
+ compiled.unseal
52
+ compile_iterations = 0
53
+
54
+ ## Automatically recompile while layers are dirty
55
+ loop do
56
+ fail "Re-compile iteration limit of #{max_iterations} has been exceeded" if compile_iterations >= max_iterations
57
+
58
+ ## Reset flags before next iteration
59
+ compiled.clean
60
+
61
+ ## Merge layers from lowest to highest
62
+ all_layers.each { |layer| compiled.merge(layer.compile) }
63
+
64
+ break unless dirty?
65
+ compile_iterations += 1
66
+ end
67
+
68
+ ## Don't auto-populate keys anymore
69
+ compiled.seal
70
+ end
71
+ alias_method :recompile, :compile
72
+
73
+ def dirty?
74
+ all_layers.any?(&:dirty) || compiled.dirty
75
+ end
76
+
77
+ def compiled
78
+ @compiled ||= File.new
79
+ end
80
+
81
+ def fetch(key, *args)
82
+ compiled.send(key, *args)
83
+ end
84
+ alias_method :[], :fetch
85
+
86
+ def method_missing(method_name, *args)
87
+ return super unless compiled.respond_to?(method_name)
88
+
89
+ compiled.send(method_name, *args)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,287 @@
1
+ require 'forwardable'
2
+ require 'json'
3
+
4
+ require_relative './rash'
5
+
6
+ module Builderator
7
+ module Config
8
+ ##
9
+ # Shared Attribute Mixin
10
+ ##
11
+ class Attributes
12
+ ##
13
+ # DSL Definition
14
+ ##
15
+ class << self
16
+ def attribute(attribute_name, default = nil, **options)
17
+ ##
18
+ # Helpers for Array-type attributes
19
+ ##
20
+ if options[:type] == :list
21
+ default = Array
22
+
23
+ ## Add an appender DSL method
24
+ define_method(options[:singular]) do |*args|
25
+ append_if_valid(attribute_name, args.flatten, default, options)
26
+ end if options.include?(:singular)
27
+ end
28
+
29
+ ## Getter/Setter
30
+ 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)
35
+ end
36
+
37
+ ## Setter
38
+ define_method("#{attribute_name}=") do |arg|
39
+ 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)
46
+ end
47
+
48
+ ##
49
+ # A Namespace is a singleton sub-node of the attribute-set
50
+ #
51
+ # e.g. `namespace :chef ...` maps to `attributes[:chef]` and adds a
52
+ # method `chef(&block)` to the DSL which is used as follows:
53
+ #
54
+ # ```
55
+ # chef do
56
+ # run_list 'foo', 'bar'
57
+ # ...
58
+ # end
59
+ # ```
60
+ #
61
+ # Multiple calls to the DSL method will are safe and will
62
+ # update the same sub-node.
63
+ ##
64
+ def namespace(namespace_name, &definition)
65
+ namespace_class = Namespace.create(namespace_name, &definition)
66
+
67
+ define_method(namespace_name) do |&block|
68
+ nodes[namespace_name] ||= namespace_class.new(
69
+ @attributes[namespace_name],
70
+ :name => namespace_name, &block).compile
71
+ end
72
+ end
73
+
74
+ ##
75
+ # A Collection is a named-set of items in a sub-node of the attribute-set.
76
+ #
77
+ # Like Namespaces, Collections map to a top-level key, but they also have
78
+ # multiple second-order keys:
79
+ #
80
+ # e.g. `collection :vagrant ...` adds a DSL method
81
+ # `vagrant(name = :default, &block)` which maps to
82
+ # `attributes[:vagrant][<name>]`
83
+ #
84
+ # Multiple entities can be added to the collection by calling the DSL method
85
+ # with unique `name` arguments. Multiple calls to the DSL method with the
86
+ # same name argument will update the existing entity in place
87
+ ##
88
+ def collection(collection_name, &definition)
89
+ collection_class = Collection.create(collection_name, &definition)
90
+
91
+ define_method(collection_name) do |instance_name = nil, &block|
92
+ nodes[collection_name] ||= collection_class.new(
93
+ @attributes[collection_name])
94
+
95
+ return nodes[collection_name] if instance_name.nil?
96
+ nodes[collection_name].fetch(instance_name, &block).compile
97
+ end
98
+ end
99
+ end
100
+
101
+ extend Forwardable
102
+ include Enumerable
103
+
104
+ ## Delegate enumerables to underlying storage structure
105
+ def_delegators :@attributes, :[], :fetch,
106
+ :keys, :values, :has?, :each,
107
+ :to_hash
108
+
109
+ def seal
110
+ attributes.seal
111
+ self
112
+ end
113
+
114
+ def unseal
115
+ attributes.unseal
116
+ self
117
+ end
118
+
119
+ attr_reader :attributes
120
+ attr_reader :nodes
121
+ attr_reader :dirty
122
+
123
+ def initialize(attributes = {}, &block)
124
+ @attributes = Rash.coerce(attributes)
125
+ @nodes = {}
126
+ @block = block
127
+
128
+ ## Track change status for comsumers
129
+ @dirty = false
130
+ end
131
+
132
+ ## Clear dirty state flag
133
+ def clean
134
+ @dirty = false
135
+ end
136
+
137
+ def compile
138
+ instance_eval(&@block) if @block
139
+ self
140
+ end
141
+
142
+ def merge(other)
143
+ attributes.merge!(other.attributes)
144
+ self
145
+ end
146
+ alias_method :includes, :merge
147
+
148
+ def to_json(*_)
149
+ JSON.pretty_generate(to_hash)
150
+ end
151
+
152
+ protected
153
+
154
+ def set_if_valid(key, arg, options = {})
155
+ ## TODO: define validation interface
156
+
157
+ ## Mutation helpers
158
+
159
+ # Input is a path relative to the working directory
160
+ arg = Util.relative_path(arg).to_s if options[:relative]
161
+
162
+ # Input is a path relative to the workspace
163
+ arg = Util.workspace(arg).to_s if options[:workspace]
164
+
165
+ ## Unchanged
166
+ return if @attributes[key] == arg
167
+
168
+ @dirty = true ## A mutation has occured
169
+ @attributes[key] = arg
170
+ end
171
+
172
+ def append_if_valid(key, arg, default = Array, **options)
173
+ ## TODO: define validation interface
174
+
175
+ attribute = set_or_return(key, nil, default, options)
176
+ arg.reject! { |item| attribute.include?(item) }
177
+
178
+ return if arg.empty?
179
+
180
+ @dirty = true ## A mutation has occured
181
+ attribute.push(*arg)
182
+ end
183
+
184
+ def set_or_return(key, arg = nil, default = nil, **options)
185
+ if arg.nil? || (arg.is_a?(Array) && arg.empty?)
186
+ return @attributes[key] if @attributes.has?(key)
187
+
188
+ ## Default
189
+ return if default.is_a?(NilClass) ## No default
190
+
191
+ ## Allow a default to be a static value, or instantiated
192
+ ## at call-time from a class (e.g. Array or Hash)
193
+ default_value = default.is_a?(Class) ? default.new : default
194
+ return default_value if @attributes.sealed
195
+
196
+ return set_if_valid(key, default_value, options)
197
+ end
198
+
199
+ ## Set value
200
+ set_if_valid(key, arg, options)
201
+ end
202
+
203
+ ##
204
+ # Define a namespace for attributes
205
+ ##
206
+ class Namespace < Attributes
207
+ class << self
208
+ attr_accessor :name
209
+
210
+ ##
211
+ # Construct a new child-class to define the interface. The constructor
212
+ # accepts an attributes argument, which should be a sub-node of the root
213
+ # attribute-set.
214
+ ##
215
+ def create(namespace_name, &definition)
216
+ space = Class.new(self)
217
+ space.name = namespace_name
218
+
219
+ ## Define DSL interface
220
+ space.instance_eval(&definition) if definition
221
+
222
+ space
223
+ end
224
+ end
225
+
226
+ attr_reader :name
227
+ attr_reader :collection
228
+
229
+ def initialize(attributes, options = {}, &block)
230
+ super(attributes, &block)
231
+
232
+ @name = options.fetch(:name, self.class.name)
233
+ @collection = options[:collection]
234
+ end
235
+
236
+ def compile
237
+ @block.call(self) if @block
238
+ self
239
+ end
240
+ end
241
+
242
+ ##
243
+ # Enumerable wrapper for collections
244
+ ##
245
+ class Collection < Attributes
246
+ class << self
247
+ attr_accessor :name
248
+ attr_accessor :namespace_class
249
+
250
+ def create(collection_name, &definition)
251
+ collection = Class.new(self)
252
+ collection.name = collection_name
253
+ collection.namespace_class = Namespace.create(collection_name, &definition)
254
+
255
+ collection
256
+ end
257
+ end
258
+
259
+ ## Allow a single instance to be selected
260
+ attr_reader :current
261
+ def use(instance_name)
262
+ @current = fetch(instance_name)
263
+ end
264
+
265
+ ## Enumerable methods return namespace instances
266
+ def each(&block)
267
+ attributes.each_key do |instance_name|
268
+ block.call(instance_name, fetch(instance_name))
269
+ end
270
+ end
271
+
272
+ def name
273
+ self.class.name
274
+ end
275
+
276
+ ## Get namespace instances
277
+ def fetch(instance_name, &block)
278
+ self.class.namespace_class.new(
279
+ attributes[instance_name],
280
+ :collection => self,
281
+ :name => instance_name, &block)
282
+ end
283
+ alias_method :[], :fetch
284
+ end
285
+ end
286
+ end
287
+ end