builderator 0.3.10
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +30 -0
- data/Rakefile +2 -0
- data/Thorfile +1 -0
- data/VERSION +1 -0
- data/bin/build +17 -0
- data/builderator.gemspec +29 -0
- data/docs/clean.md +21 -0
- data/lib/builderator.rb +4 -0
- data/lib/builderator/control/ami.rb +64 -0
- data/lib/builderator/control/clean.rb +134 -0
- data/lib/builderator/metadata.rb +5 -0
- data/lib/builderator/model.rb +46 -0
- data/lib/builderator/model/images.rb +89 -0
- data/lib/builderator/model/instances.rb +55 -0
- data/lib/builderator/model/launch_configs.rb +46 -0
- data/lib/builderator/model/scaling_groups.rb +43 -0
- data/lib/builderator/model/snapshots.rb +49 -0
- data/lib/builderator/model/volumes.rb +48 -0
- data/lib/builderator/tasks.rb +37 -0
- data/lib/builderator/tasks/ami.rb +37 -0
- data/lib/builderator/tasks/berks.rb +68 -0
- data/lib/builderator/tasks/clean.rb +94 -0
- data/lib/builderator/tasks/cookbook.rb +42 -0
- data/lib/builderator/tasks/packer.rb +72 -0
- data/lib/builderator/tasks/vagrant.rb +66 -0
- data/lib/builderator/util.rb +53 -0
- data/lib/builderator/util/aws_exception.rb +31 -0
- data/lib/builderator/util/berkshim.rb +34 -0
- data/lib/builderator/util/cookbook.rb +42 -0
- data/lib/builderator/util/limit_exception.rb +38 -0
- data/lib/builderator/util/packer.rb +39 -0
- data/lib/builderator/util/shell.rb +44 -0
- data/lib/builderator/util/task_exception.rb +22 -0
- metadata +210 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require_relative '../control/clean'
|
3
|
+
|
4
|
+
module Builderator
|
5
|
+
module Tasks
|
6
|
+
class Clean < Thor
|
7
|
+
class_option :region,
|
8
|
+
:type => :string,
|
9
|
+
:default => 'us-east-1',
|
10
|
+
:aliases => :r,
|
11
|
+
:desc => 'AWS Region in which to perform tasks'
|
12
|
+
class_option :commit,
|
13
|
+
:type => :boolean,
|
14
|
+
:default => false,
|
15
|
+
:desc => 'Perform mutating API calls to cleanup resources'
|
16
|
+
class_option :filter,
|
17
|
+
:type => :array,
|
18
|
+
:aliases => :f,
|
19
|
+
:desc => 'Key/value pairs to filter resources (--filter name foo owner_id 123456789)'
|
20
|
+
class_option :limit,
|
21
|
+
:type => :boolean,
|
22
|
+
:default => true,
|
23
|
+
:desc => 'By default, limit the number of resources to remove'
|
24
|
+
|
25
|
+
def initialize(*_)
|
26
|
+
super
|
27
|
+
|
28
|
+
## Convert array of filter key-values to a hash
|
29
|
+
options['filters'] = Hash[*options['filter']] if options['filter'].is_a?(Array)
|
30
|
+
|
31
|
+
Control::Clean.options(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'configs', 'Delete unused launch configurations'
|
35
|
+
def configs
|
36
|
+
Control::Clean.configs!(&method(:say_status))
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'images', 'Deregister unused images'
|
40
|
+
option 'group-by',
|
41
|
+
:type => :array,
|
42
|
+
:desc => 'Tags/properties to group images by for pruning'
|
43
|
+
option 'sort-by',
|
44
|
+
:type => :string,
|
45
|
+
:default => 'creation_date',
|
46
|
+
:desc => 'Tag/property to sort grouped images on'
|
47
|
+
option :keep,
|
48
|
+
:type => :numeric,
|
49
|
+
:default => 5,
|
50
|
+
:desc => 'Number of images in each group to keep'
|
51
|
+
def images
|
52
|
+
Control::Clean.images!(&method(:say_status))
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'snapshots', 'Delete unused snapshots'
|
56
|
+
def snapshots
|
57
|
+
Control::Clean.snapshots!(&method(:say_status))
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'volumes', 'Delete unused volumes'
|
61
|
+
def volumes
|
62
|
+
Control::Clean.volumes!(&method(:say_status))
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'all', 'Clean volumes, launch configs, images, and snapshots in order'
|
66
|
+
option 'group-by',
|
67
|
+
:type => :array,
|
68
|
+
:desc => 'Tags/properties to group images by for pruning'
|
69
|
+
option 'sort-by',
|
70
|
+
:type => :string,
|
71
|
+
:default => 'creation_date',
|
72
|
+
:desc => 'Tag/property to sort grouped images on'
|
73
|
+
option :keep,
|
74
|
+
:type => :numeric,
|
75
|
+
:default => 5,
|
76
|
+
:desc => 'Number of images in each group to keep'
|
77
|
+
def all
|
78
|
+
invoke :volumes, [], options
|
79
|
+
invoke :configs, [], options
|
80
|
+
invoke :images, [], options
|
81
|
+
invoke :snapshots, [], options
|
82
|
+
|
83
|
+
## TODO Print resource counts here.
|
84
|
+
return if Control::Clean.exceptions.empty?
|
85
|
+
|
86
|
+
say_status :fail, 'Not all tasks completed successfully. The following '\
|
87
|
+
'exceptions occured:', :red
|
88
|
+
Control::Clean.exceptions.each do |e|
|
89
|
+
say_status(*e.status)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/actions'
|
3
|
+
require_relative '../util/cookbook'
|
4
|
+
|
5
|
+
module Builderator
|
6
|
+
module Tasks
|
7
|
+
class Cookbook < Thor
|
8
|
+
include Thor::Actions
|
9
|
+
class_option :version, :type => :boolean,
|
10
|
+
:default => true,
|
11
|
+
:desc => 'Write current verison to file'
|
12
|
+
|
13
|
+
desc 'metadata [PATH]', 'Use cookbook matadata file at PATH/metadata.rb to generate PATH/matadata.json'
|
14
|
+
def metadata(cookbook = nil)
|
15
|
+
Util::Cookbook.path(cookbook) unless cookbook.nil?
|
16
|
+
invoke 'version:current', [], options if options['version']
|
17
|
+
|
18
|
+
create_file File.join(Util::Cookbook.path, 'metadata.json'),
|
19
|
+
Util::Cookbook.metadata.to_json, :force => true
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'package PATH', 'Package a cookbook into a tarball'
|
23
|
+
def package(cookbook = nil)
|
24
|
+
Util::Cookbook.path(cookbook) unless cookbook.nil?
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'publish', 'Publish a cookbook to supermarket.chef.io'
|
29
|
+
def publish(cookbook = nil)
|
30
|
+
Util::Cookbook.path(cookbook) unless cookbook.nil?
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'version COOKBOOK', 'Print the current version of a vendored cookbook'
|
35
|
+
option :path, :default => Util::Cookbook::DEFAULT_VENDOR, :desc => 'Path to vendored cookbooks'
|
36
|
+
def version(cookbook)
|
37
|
+
Util::Cookbook.path(File.join(options['path'], cookbook))
|
38
|
+
puts Util::Cookbook.metadata.version
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/actions'
|
3
|
+
require_relative './berks'
|
4
|
+
require_relative '../util/packer'
|
5
|
+
require_relative '../util/shell'
|
6
|
+
|
7
|
+
module Builderator
|
8
|
+
module Tasks
|
9
|
+
class Packer < Thor
|
10
|
+
include Thor::Actions
|
11
|
+
include Util::Shell
|
12
|
+
|
13
|
+
class_option :config, :aliases => :c, :desc => "Path to Berkshelf's config.json"
|
14
|
+
class_option :berksfile, :aliases => :b, :desc => 'Path to the Berksfile to use'
|
15
|
+
|
16
|
+
desc 'install [VERSION = 0.8.2]', 'Ensure that the desired version of packer is installed'
|
17
|
+
def install(version = '0.8.2')
|
18
|
+
Util::Packer.use(version)
|
19
|
+
|
20
|
+
if Util::Packer.installed?
|
21
|
+
say_status :packer, "is already installed at version #{ version }"
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
say_status :packer, "install version #{ version } to #{ Util::Packer.path }"
|
26
|
+
run "wget #{ Util::Packer.url } -O packer.zip -q"
|
27
|
+
run "unzip -d #{ Util::Packer.path } -q packer.zip"
|
28
|
+
run "ln -sf #{ Util::Packer.path } $HOME/packer"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'build ARGS', 'Run a build with the installed version of packer'
|
32
|
+
option 'properties-file', :desc => 'Write build outputs to a properties file'
|
33
|
+
def build(*args)
|
34
|
+
invoke Tasks::Berks, 'vendor', [], options
|
35
|
+
|
36
|
+
packer_output = execute("#{ Util::Packer.bin } build #{ args.join(' ') }")
|
37
|
+
|
38
|
+
## Try to find the ID of the new AMI
|
39
|
+
ami_id_search = /AMI: (ami-[0-9a-f]{8})/.match(packer_output.string)
|
40
|
+
if ami_id_search.nil? && options.include?('properties-file')
|
41
|
+
say_status :failure, 'Unable to find AMI ID from packer build', :red
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
return unless options['properties-file']
|
46
|
+
say_status :success, "Created AMI #{ ami_id_search[1] }"
|
47
|
+
|
48
|
+
create_file options['properties-file'], '', :force => true
|
49
|
+
append_file options['properties-file'], "image.useast1=#{ ami_id_search[1] }"
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'find_ami', 'Find the ID of the new AMI from packer output'
|
53
|
+
option 'properties-file', :desc => 'Write build outputs to a properties file'
|
54
|
+
def find_ami
|
55
|
+
tee = Builderator::Util::Shell::BufferTee.new($stdout)
|
56
|
+
$stdin.each { |l| tee.write(l) } # Buffer stdin
|
57
|
+
|
58
|
+
ami_id_search = /AMI: (ami-[0-9a-f]{8})/.match(tee.string)
|
59
|
+
if ami_id_search.nil?
|
60
|
+
say_status :failure, 'Unable to find AMI ID from packer build', :red
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
say_status :success, "Created AMI #{ ami_id_search[1] }"
|
65
|
+
return unless options['properties-file']
|
66
|
+
|
67
|
+
create_file options['properties-file'], '', :force => true
|
68
|
+
append_file options['properties-file'], "image.useast1=#{ ami_id_search[1] }"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/actions'
|
3
|
+
require_relative './berks'
|
4
|
+
|
5
|
+
module Builderator
|
6
|
+
module Tasks
|
7
|
+
class Vagrant < Thor
|
8
|
+
include Thor::Actions
|
9
|
+
class_option :config, :aliases => :c, :desc => "Path to Berkshelf's config.json"
|
10
|
+
class_option :berksfile, :aliases => :b, :desc => 'Path to the Berksfile to use'
|
11
|
+
|
12
|
+
def initialize(*_)
|
13
|
+
unless Gem.loaded_specs.key?('vagrant')
|
14
|
+
say '!!! Vagrant is not available in this bundle !!!!', [:red, :bold]
|
15
|
+
puts ''
|
16
|
+
say 'Please add the following to your Gemfile and update your bundle to use the `vagrant` command:'
|
17
|
+
say ' +------------------------------------------------+', :green
|
18
|
+
say " | gem 'vagrant', :github => 'mitchellh/vagrant', |", :green
|
19
|
+
say " | :tag => 'v1.7.4' |", :green
|
20
|
+
say ' +------------------------------------------------+', :green
|
21
|
+
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'up ARGS', 'Start Vagrant VM(s)'
|
29
|
+
def up(*args)
|
30
|
+
command = 'ulimit -n 1024;'
|
31
|
+
command << ' VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET=true'
|
32
|
+
command << " vagrant up #{ args.join(' ') }"
|
33
|
+
|
34
|
+
invoke Tasks::Berks, 'local', [], options
|
35
|
+
run command
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'provision ARGS', 'Provision Vagrant VM(s)'
|
39
|
+
def provision(*args)
|
40
|
+
command = 'ulimit -n 1024;'
|
41
|
+
command << ' VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET=true'
|
42
|
+
command << " vagrant provision #{ args.join(' ') }"
|
43
|
+
|
44
|
+
invoke Tasks::Berks, 'local', [], options
|
45
|
+
run command
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'destroy ARGS', 'Destroy Vagrant VM(s)'
|
49
|
+
option :force, :aliases => :f, :type => :boolean
|
50
|
+
def destroy(*args)
|
51
|
+
command = 'ulimit -n 1024;'
|
52
|
+
command << ' VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET=true'
|
53
|
+
command << " vagrant destroy #{ args.join(' ') }"
|
54
|
+
command << ' -f' if options['force']
|
55
|
+
|
56
|
+
run command
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'rebuild ARGS', 'Destroy and recreate Vagrant VM(s)'
|
60
|
+
def rebuild(*args)
|
61
|
+
invoke Tasks::Vagrant, 'destroy', args, options.merge('force' => true)
|
62
|
+
invoke Tasks::Vagrant, 'up', args, options
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Builderator
|
2
|
+
module Util
|
3
|
+
class << self
|
4
|
+
def to_array(arg)
|
5
|
+
arg.is_a?(Array) ? arg : [arg]
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_tags(aws_tags)
|
9
|
+
{}.tap { |tt| aws_tags.each { |t| tt[t.key.to_s] = t.value } }
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter(resources, filters = {})
|
13
|
+
resources.select do |_, r|
|
14
|
+
filters.reduce(true) do |memo, (k, v)|
|
15
|
+
memo && r[:properties].include?(k.to_s) &&
|
16
|
+
r[:properties][k.to_s] == v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def filter!(resources, filters = {})
|
22
|
+
resources.select! do |_, r|
|
23
|
+
filters.reduce(true) do |memo, (k, v)|
|
24
|
+
memo && r[:properties].include?(k.to_s) &&
|
25
|
+
r[:properties][k.to_s] == v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
resources
|
30
|
+
end
|
31
|
+
|
32
|
+
def region(arg = nil)
|
33
|
+
return @region || 'us-east-1' if arg.nil?
|
34
|
+
@region = arg
|
35
|
+
end
|
36
|
+
|
37
|
+
def ec2
|
38
|
+
@ec2 ||= Aws::EC2::Client.new(:region => region)
|
39
|
+
end
|
40
|
+
|
41
|
+
def asg
|
42
|
+
@asg ||= Aws::AutoScaling::Client.new(:region => region)
|
43
|
+
end
|
44
|
+
|
45
|
+
def working_dir(relative = '.')
|
46
|
+
File.expand_path(relative, Dir.pwd)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require_relative './util/aws_exception'
|
53
|
+
require_relative './util/limit_exception'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative './task_exception'
|
3
|
+
|
4
|
+
module Builderator
|
5
|
+
module Util
|
6
|
+
##
|
7
|
+
# Exception raised if a safety limit is exceeded
|
8
|
+
##
|
9
|
+
class AwsException < TaskException
|
10
|
+
attr_reader :exception
|
11
|
+
|
12
|
+
def initialize(task, exception)
|
13
|
+
super(:fail, task, :red)
|
14
|
+
@exception = exception
|
15
|
+
end
|
16
|
+
|
17
|
+
def operation
|
18
|
+
@exception.context.operation_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameters
|
22
|
+
@exception.context.params
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"An error occured executing performing task #{ task }. #{ operation }"\
|
27
|
+
"(#{ JSON.generate(parameters) }): #{ exception.message }"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
##
|
2
|
+
# Roll up some shared logic for Berksfiles
|
3
|
+
##
|
4
|
+
require_relative './cookbook'
|
5
|
+
|
6
|
+
module Builderator
|
7
|
+
module Util
|
8
|
+
module Berkshim
|
9
|
+
def shims
|
10
|
+
|
11
|
+
## Root cookbook sources
|
12
|
+
metadata if ENV['BERKS_INSTALL_FROM'] == 'source'
|
13
|
+
|
14
|
+
if ENV['BERKS_INSTALL_FROM'] == 'release'
|
15
|
+
cookbook_spec = [].tap do |arguments|
|
16
|
+
arguments << Util::Cookbook.metadata.name
|
17
|
+
arguments << ENV['VERSION'] if ENV.include?('VERSION')
|
18
|
+
|
19
|
+
arguments << {}.tap do |options|
|
20
|
+
options[:path] = ENV['COOKBOOK_PATH'] if ENV.include?('COOKBOOK_PATH')
|
21
|
+
options[:git] = ENV['COOKBOOK_REPO'] if ENV.include?('COOKBOOK_REPO')
|
22
|
+
options[:github] = ENV['COOKBOOK_GITHUB'] if ENV.include?('COOKBOOK_GITHUB')
|
23
|
+
options[:branch] = ENV['COOKBOOK_BRANCH'] if ENV.include?('COOKBOOK_BRANCH')
|
24
|
+
options[:ref] = ENV['COOKBOOK_REF'] if ENV.include?('COOKBOOK_REF')
|
25
|
+
options[:tag] = ENV['COOKBOOK_TAG'] if ENV.include?('COOKBOOK_TAG')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
cookbook(*cookbook_spec)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'chef/cookbook/metadata'
|
2
|
+
require 'thor-scmversion'
|
3
|
+
|
4
|
+
require_relative '../util'
|
5
|
+
|
6
|
+
module Builderator
|
7
|
+
module Util
|
8
|
+
module Cookbook
|
9
|
+
DEFAULT_VENDOR = Util.working_dir('vendor/chef/cookbooks')
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def path(arg = nil)
|
13
|
+
## Set an explicit path to a cookbook
|
14
|
+
return @path = arg unless arg.nil?
|
15
|
+
return @path unless @path.nil?
|
16
|
+
|
17
|
+
## Check for an embedded cookbook? ('./cookbook')
|
18
|
+
return @path = Util.working_dir('cookbook') if File.exist?(Util.working_dir('cookbook'))
|
19
|
+
@path = Util.working_dir
|
20
|
+
end
|
21
|
+
|
22
|
+
def berksfile
|
23
|
+
File.join(path, 'Berksfile')
|
24
|
+
end
|
25
|
+
|
26
|
+
def metadata
|
27
|
+
Chef::Cookbook::Metadata.new.tap do |c|
|
28
|
+
if File.exist?(File.join(path, 'metadata.rb'))
|
29
|
+
c.from_file(File.join(path, 'metadata.rb'))
|
30
|
+
|
31
|
+
elsif File.exist?(File.join(path, 'metadata.json'))
|
32
|
+
c.from_json(IO.read(File.join(path, 'metadata.json')))
|
33
|
+
|
34
|
+
else
|
35
|
+
fail IOError, 'Unable to read metadata.rb or metadata.json!'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative './task_exception'
|
2
|
+
|
3
|
+
module Builderator
|
4
|
+
module Util
|
5
|
+
##
|
6
|
+
# Exception raised if a safety limit is exceeded
|
7
|
+
##
|
8
|
+
class LimitException < TaskException
|
9
|
+
attr_reader :resources
|
10
|
+
|
11
|
+
def initialize(klass, task, resources)
|
12
|
+
super(:limit, task, :yellow)
|
13
|
+
|
14
|
+
@klass = klass
|
15
|
+
@resources = resources
|
16
|
+
end
|
17
|
+
|
18
|
+
def count
|
19
|
+
@resources.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def limit
|
23
|
+
@klass::LIMIT
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource
|
27
|
+
@klass.name
|
28
|
+
end
|
29
|
+
|
30
|
+
def message
|
31
|
+
"Safety limit exceeded for task `#{ task }`: Count #{ count } is"\
|
32
|
+
" greater than the limit of #{ limit } set in #{ resource }. Please"\
|
33
|
+
" re-run this task with the --no-limit flag if you are sure this is"\
|
34
|
+
" the correct set of resources to delete."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|