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,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