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,80 @@
1
+ module Builderator
2
+ module Config
3
+ ##
4
+ # A self-populating sparse Hash by Rapid7 ([R]apid7 h[ASH]). Definetly
5
+ # not a Mash or Smash...
6
+ ##
7
+ class Rash < Hash
8
+ class << self
9
+ def coerce(somehting)
10
+ return somehting if somehting.is_a?(self)
11
+ return new(somehting) if somehting.is_a?(Hash)
12
+
13
+ ## `somehting` is not a valid input. Just give back an instance.
14
+ new
15
+ end
16
+ end
17
+
18
+ attr_accessor :sealed
19
+
20
+ def initialize(from = {}, seal = false)
21
+ @sealed = seal
22
+ super() do |_, k|
23
+ self[k] = self.class.new unless sealed
24
+ end
25
+
26
+ merge!(from) ## Clone a Rash or coerce a Hash to a new Rash
27
+ end
28
+
29
+ def seal(action = true)
30
+ @sealed = action
31
+ each_value { |v| v.seal(action) if v.is_a?(self.class) }
32
+ end
33
+
34
+ def unseal
35
+ seal(false)
36
+ end
37
+
38
+ alias_method :has?, :include?
39
+
40
+ ## Symbolize keys
41
+ [:include?, :[], :fetch, :[]=, :store].each do |m|
42
+ define_method(m) do |key, *args|
43
+ super(key.to_sym, *args)
44
+ end
45
+ end
46
+
47
+ def merge!(other)
48
+ fail TypeError, 'Argument other of `Rash#merge!(other)` must be a Hash.'\
49
+ " Recieved #{other.class}" unless other.is_a?(Hash)
50
+
51
+ other.each do |k, v|
52
+ ## Replace `-`s with `_`s in in String keys
53
+ k = k.gsub(/\-/, '_') if k.is_a?(String)
54
+
55
+ ## Merge Arrays
56
+ next self[k] |= v if fetch(k, nil).is_a?(Array) && v.is_a?(Array)
57
+
58
+ ## Overwrite non-Hash values
59
+ next self[k] = v unless v.is_a?(Hash)
60
+
61
+ ## Replace `self[k]` with a new Rash unless it already is one
62
+ self[k] = self.class.new unless fetch(k, nil).is_a?(self.class)
63
+
64
+ ## Merge recursivly coerces `v` to a Rash
65
+ self[k].merge!(v)
66
+ end
67
+ end
68
+
69
+ def to_hash
70
+ each_with_object({}) do |(k, v), hash|
71
+ ## Not a hash-value
72
+ next hash[k] = v unless v.is_a?(self.class)
73
+
74
+ ## Recursivly coerces `v` to a Hash
75
+ hash[k] = v.to_hash
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,138 @@
1
+ require_relative '../model/cleaner'
2
+ require_relative '../util'
3
+
4
+ module Builderator
5
+ module Control
6
+ ##
7
+ # Control logic for cleanup tasks
8
+ ##
9
+ module Cleaner
10
+ class << self
11
+ def configs!
12
+ resources = Model::Cleaner.launch_configs.unused
13
+
14
+ yield :launch_configs, "Found #{resources.length} Launch Configurations to remove"
15
+ verify!(:launch_configs, 'Cleanup Launch Configurations', resources, &Proc.new)
16
+ aborted!(&Proc.new)
17
+
18
+ resources.keys.sort.each do |id|
19
+ yield :remove, "Launch Configuration #{id}", :red
20
+ Model::Cleaner.launch_configs.resources.delete(id)
21
+
22
+ next unless commit?
23
+ Util.asg.delete_launch_configuration(:launch_configuration_name => id)
24
+ end
25
+ rescue Aws::AutoScaling::Errors::ServiceError => e
26
+ exceptions << Util::AwsException.new('Cleanerup Launch Configurations', e)
27
+ yield(*exceptions.last.status)
28
+ end
29
+
30
+ def images!
31
+ resources = Model::Cleaner.images.unused
32
+
33
+ yield :images, "Found #{resources.length} Images to remove"
34
+ yield :grouping, "Groupd images by #{Config.cleaner.group_by}" if Config.cleaner.group_by
35
+ yield :keep, "Keeping #{Config.cleaner.keep} images in each group"
36
+ verify!(:images, 'Cleanup Images', resources, &Proc.new)
37
+ aborted!(&Proc.new)
38
+
39
+ resources.values
40
+ .sort { |a, b| a[:properties]['name'] <=> b[:properties]['name'] }
41
+ .each do |image|
42
+ yield :remove, "Image #{image[:id]} (#{image[:properties]['name']})", :red
43
+ Model::Cleaner.images.resources.delete(image[:id])
44
+
45
+ next unless commit?
46
+ Util.ec2.deregister_image(:image_id => image[:id])
47
+ end
48
+
49
+ rescue Aws::EC2::Errors::ServiceError => e
50
+ exceptions << Util::AwsException.new('Cleanerup Images', e)
51
+ yield(*exceptions.last.status)
52
+ end
53
+
54
+ def snapshots!
55
+ resources = Model::Cleaner.snapshots.unused
56
+
57
+ yield :snapshots, "Found #{resources.length} Snapshots to remove"
58
+ verify!(:snapshots, 'Cleanup Snapshots', resources, &Proc.new)
59
+ aborted!(&Proc.new)
60
+
61
+ resources.keys.sort.each do |id|
62
+ yield :remove, "Snapshot #{id}", :red
63
+ Model::Cleaner.snapshots.resources.delete(id)
64
+
65
+ next unless commit?
66
+ Util.ec2.delete_snapshot(:snapshot_id => id)
67
+ end
68
+ rescue Aws::EC2::Errors::ServiceError => e
69
+ exceptions << Util::AwsException.new('Cleanerup Snapshots', e)
70
+ yield(*exceptions.last.status)
71
+ end
72
+
73
+ def volumes!
74
+ resources = Model::Cleaner.volumes.unused
75
+
76
+ yield :volumes, "Found #{resources.length} Volumes to remove"
77
+ verify!(:volumes, 'Cleanup Volumes', resources, &Proc.new)
78
+ aborted!(&Proc.new)
79
+
80
+ resources.keys.sort.each do |id|
81
+ yield :remove, "Volume #{id}", :red
82
+ Model::Cleaner.volumes.resources.delete(id)
83
+
84
+ next unless commit?
85
+ Util.ec2.delete_volume(:volume_id => id)
86
+ end
87
+ rescue Aws::EC2::Errors::ServiceError => e
88
+ exceptions << Util::AwsException.new('Cleanerup Volumes', e)
89
+ yield(*exceptions.last.status)
90
+ end
91
+
92
+ def commit?
93
+ @commit && !@abort
94
+ end
95
+
96
+ def aborted?
97
+ @commit && @abort
98
+ end
99
+
100
+ def exceptions
101
+ @exceptions ||= []
102
+ end
103
+
104
+ private
105
+
106
+ def aborted!
107
+ yield :aborted, 'The following resources will NOT be removed because'\
108
+ ' safty constraints have not been met!', :yellow if aborted?
109
+ end
110
+
111
+ def verify!(resource_name, task, resources)
112
+ if Config.cleaner.commit
113
+ yield :commit, 'This is not a dry-run. Press CTL-C to stop! '\
114
+ '(continuing in 5 seconds)', :red
115
+
116
+ sleep(5) ## Give $USER a few seconds to stop
117
+ end
118
+
119
+ return unless resources.size >= Config.cleaner.limits[resource_name]
120
+
121
+ ex = Util::LimitException.new(resource_name, task, resources)
122
+ yield(*ex.status)
123
+
124
+ if Config.cleaner.force
125
+ yield :force, 'Limits will be ignored. Press CTL-C to stop! '\
126
+ '(continuing in 5 seconds)', :red
127
+ sleep(5) ## Give $USER a few seconds to stop
128
+
129
+ return
130
+ end
131
+
132
+ exceptions << ex
133
+ @abort = true
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,16 @@
1
+ require 'pathname'
2
+
3
+ module Builderator
4
+ module Control
5
+ ##
6
+ # Cookbook logic and helpers
7
+ ##
8
+ module Cookbook
9
+ class << self
10
+ def exist?
11
+ Pathname.new(Config.cookbook.path).join('metadata.rb').exist?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Builderator
2
+ module Control
3
+ ##
4
+ # Wrapper module for lookup controllers
5
+ ##
6
+ module Data
7
+ def self.lookup(source, query)
8
+ fail "#{ source } is not a valid data type!" unless respond_to?(source)
9
+
10
+ send(source, query)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ require_relative './data/image'
@@ -0,0 +1,98 @@
1
+ require 'aws-sdk'
2
+ require 'date'
3
+
4
+ require_relative '../../util'
5
+
6
+ module Builderator
7
+ module Control
8
+ # :nodoc:
9
+ module Data
10
+ def self.image(query = {})
11
+ Image.search(query)
12
+ end
13
+
14
+ ##
15
+ # Find AMI IDs to use for sources
16
+ ##
17
+ module Image
18
+ ## Account IDs of public image owners
19
+ OWNERS = {
20
+ :self => 'self'.freeze,
21
+ :ubuntu => '099720109477'.freeze,
22
+ :amazon => 'amazon'.freeze,
23
+ :marketplace => 'aws-marketplace'.freeze
24
+ }
25
+
26
+ ## Pre-defined filters
27
+ FILTERS = {
28
+ 'ubuntu-14.04-daily' => {
29
+ 'owner' => OWNERS[:ubuntu],
30
+ 'architecture' => 'x86_64',
31
+ 'root-device-type' => 'ebs',
32
+ 'virtualization-type' => 'hvm',
33
+ 'block-device-mapping.volume-type' => 'gp2',
34
+ 'name' => '*ubuntu-trusty-daily-amd64-server-201*'
35
+ },
36
+ 'windows-server2012-r2' => {
37
+ 'owner' => OWNERS[:amazon],
38
+ 'architecture' => 'x86_64',
39
+ 'root-device-type' => 'ebs',
40
+ 'virtualization-type' => 'hvm',
41
+ 'block-device-mapping.volume-type' => 'gp2',
42
+ 'name' => 'Windows_Server-2012-R2_RTM-English-64Bit-Base*'
43
+ },
44
+ 'windows-server2012-r2-core' => {
45
+ 'owner' => OWNERS[:amazon],
46
+ 'architecture' => 'x86_64',
47
+ 'root-device-type' => 'ebs',
48
+ 'virtualization-type' => 'hvm',
49
+ 'block-device-mapping.volume-type' => 'gp2',
50
+ 'name' => 'Windows_Server-2012-R2_RTM-English-64Bit-Core*'
51
+ }
52
+ }
53
+
54
+ ## Filter fields defined in http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Builderator::Util.ec2.html#describe_images-instance_method
55
+ PROPERTIES = %w(architecture block-device-mapping.delete-on-termination
56
+ block-device-mapping.device-name block-device-mapping.snapshot-id
57
+ block-device-mapping.volume-size block-device-mapping.volume-type
58
+ description hypervisor image-id image-type is-public kernel-id
59
+ manifest-location name owner-alias owner-id platform product-code
60
+ product-code.type ramdisk-id root-device-name root-device-type
61
+ state state-reason-code state-reason-message virtualization-type).freeze
62
+
63
+ class << self
64
+ def search(query = {})
65
+ options = {}
66
+
67
+ ## Reverse-merge a pre-defined filter into the query
68
+ if query.include?('filter')
69
+ query = FILTERS[query['filter']].merge(query) if FILTERS.include?(query['filter'])
70
+
71
+ query.delete('filter')
72
+ end
73
+
74
+ options['image_ids'] = Util.to_array(query.delete('image_id')) if query.include?('image_id')
75
+ options['owners'] = Util.to_array(query.delete('owner') { 'self' })
76
+
77
+ options['filters'] = query.each_with_object([]) do |(k, v), memo|
78
+ next if v.nil?
79
+
80
+ ## Construct filter objects. Assume that non-enumerated keys are tags
81
+ memo << {
82
+ :name => PROPERTIES.include?(k.to_s) ? k.to_s : "tag:#{ k }",
83
+ :values => Util.to_array(v)
84
+ }
85
+ end
86
+
87
+ ## Don't send an empty filters array
88
+ options.delete('filters') if options['filters'].empty?
89
+
90
+ Util.ec2.describe_images(options)
91
+ .each_with_object([]) { |page, images| images.push(*page.images) }
92
+ .sort { |a, b| DateTime.iso8601(b.creation_date) <=> DateTime.iso8601(a.creation_date) }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,128 @@
1
+ require_relative './version/auto'
2
+ require_relative './version/bump'
3
+ require_relative './version/comparable'
4
+ require_relative './version/scm'
5
+ require_relative './version/git'
6
+
7
+ module Builderator
8
+ module Control
9
+ ##
10
+ # Version management tools
11
+ #
12
+ # Initial version boosted shamelessly from
13
+ # https://github.com/RiotGamesMinions/thor-scmversion
14
+ ##
15
+ class Version
16
+ FORMAT = /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?<prerelease>-(?<prerelease_name>[A-Za-z0-9]+)\.(?<prerelease_iteration>\d+))?(?:\+build\.(?<build>\d+))?$/
17
+ DEFAULT_PRERELEASE_NAME = 'alpha'.freeze
18
+
19
+ ## Order of precedence for release types
20
+ RELEASE_TYPES = {
21
+ 'major' => 0,
22
+ 'major-prerelease' => 5,
23
+ 'minor' => 10,
24
+ 'minor-prerelease' => 15,
25
+ 'patch' => 20,
26
+ 'patch-prerelease' => 25,
27
+ 'release' => 30,
28
+ 'prerelease' => 35,
29
+ 'build' => 40
30
+ }
31
+
32
+ class << self
33
+ def current
34
+ @current ||= SCM.tags.last
35
+ end
36
+
37
+ def set_config_version
38
+ Config.defaults.version = current.to_s
39
+ Config.recompile
40
+ end
41
+
42
+ def write
43
+ current.write
44
+ end
45
+
46
+ ##
47
+ # Alias `bump` to the current version
48
+ ##
49
+ def bump(type = nil, prerelease_name = nil)
50
+ @current = current.clone
51
+
52
+ current.bump(type, prerelease_name)
53
+ SCM.tags << current
54
+
55
+ current
56
+ end
57
+
58
+ ## Parse a SemVer string into a Version
59
+ def from_string(arg, options = {})
60
+ matchdata = arg.match(FORMAT)
61
+ fail "Builderator::Control::Version.from_string: #{arg} is not a supported semver string" if matchdata.nil?
62
+
63
+ new(matchdata[:major], matchdata[:minor], matchdata[:patch], matchdata[:build], options).tap do |version|
64
+ version.is_prerelease = !matchdata[:prerelease].nil?
65
+ if version.is_prerelease
66
+ version.prerelease_name = matchdata[:prerelease_name]
67
+ version.prerelease_iteration = matchdata[:prerelease_iteration].to_i
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(major, minor, patch, build = nil, **options)
74
+ @major = major.to_i
75
+ @minor = minor.to_i
76
+ @patch = patch.to_i
77
+ @build = build.to_i unless build.nil?
78
+
79
+ @ref = options[:ref]
80
+ end
81
+
82
+ include Auto
83
+ include Bump
84
+ include Comparable
85
+
86
+ attr_accessor :ref
87
+
88
+ attr_accessor :major
89
+ attr_accessor :minor
90
+ attr_accessor :patch
91
+
92
+ attr_accessor :is_prerelease
93
+ attr_accessor :prerelease_name
94
+ attr_accessor :prerelease_iteration
95
+
96
+ attr_accessor :build
97
+
98
+ ## Create or bump a new prerelease train
99
+ def prerelease(name = nil)
100
+ self.build = nil ## Reset the build counter
101
+
102
+ ## Increment current prerelease train
103
+ if is_prerelease && (name.nil? || name == prerelease_name)
104
+ self.prerelease_iteration += 1
105
+ return self
106
+ end
107
+
108
+ ## New prerelease train
109
+ self.is_prerelease = true
110
+ self.prerelease_name = name.nil? ? DEFAULT_PRERELEASE_NAME : name
111
+ self.prerelease_iteration = 0
112
+
113
+ self
114
+ end
115
+
116
+ def write
117
+ Util.relative_path('VERSION').write(to_s)
118
+ end
119
+
120
+ def to_s
121
+ string = [major, minor, patch].join('.')
122
+ string << "-#{prerelease_name}.#{prerelease_iteration}" if is_prerelease
123
+ string << "+build.#{build}" unless build.nil?
124
+ string
125
+ end
126
+ end
127
+ end
128
+ end