builderator 0.3.10

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