builderator 0.3.15 → 1.0.0.pre.rc.1

Sign up to get free protection for your applications and to get access to all the features.
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