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