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.
@@ -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