steam_donkey 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ require 'aws-sdk'
2
+
3
+ def client_options(options)
4
+ result = {}
5
+ result[:profile] = options[:profile] if !options[:profile].nil?
6
+ result[:region] = options[:region] if !options[:region].nil?
7
+ result
8
+ end
9
+
10
+ def cf_client(options)
11
+ Aws::CloudFormation::Client.new(client_options(options))
12
+ end
13
+
14
+ def ec2_client(options)
15
+ Aws::EC2::Client.new(client_options(options))
16
+ end
17
+
18
+ def s3_client(options)
19
+ Aws::S3::Client.new(client_options(options))
20
+ end
@@ -0,0 +1,62 @@
1
+ require 'command_line_reporter'
2
+
3
+ module SteamDonkey
4
+ module Cli
5
+ class Output
6
+ include CommandLineReporter
7
+
8
+ def initialize(render_headings, format)
9
+ @format = format
10
+ @render_headings = render_headings
11
+ end
12
+
13
+ def render(headings, rows)
14
+ case @format
15
+ when 'pretty'
16
+ table do
17
+ row :header => true do
18
+ headings.each do |label|
19
+ column label, :width => max_width(rows, label)+ 2, :color => 'blue'
20
+ end
21
+ end
22
+
23
+ rows.each do |result|
24
+ row do
25
+ headings.each do |label|
26
+ value = result.detect { |f| f[:label] == label }[:value]
27
+ value = '-' if value.nil?
28
+ if value.is_a? Time
29
+ value = value.iso8601.to_s
30
+ else
31
+ value = '-' if value.methods.include? :empty? and value.empty?
32
+ end
33
+ column value
34
+ end
35
+ end
36
+ end
37
+ end
38
+ else
39
+ puts headings.join(',') if @render_headings
40
+ rows.each do |result|
41
+ puts result.map { |c| c[:value] }.join(',')
42
+ end
43
+ end
44
+ end
45
+
46
+ def max_width(results, label)
47
+ width = results.map do |result|
48
+ value = result.detect { |f| f[:label] == label }[:value]
49
+ if value.nil?
50
+ 0
51
+ elsif value.is_a? Time
52
+ value.iso8601.to_s.length
53
+ else
54
+ value.to_s.length
55
+ end
56
+ end
57
+ width << label.length
58
+ width.max
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ module SteamDonkey
2
+ module Cloudformation
3
+ class DeployStack
4
+
5
+ def initialize(client, options)
6
+ @client = client
7
+ @options = options
8
+ end
9
+
10
+ def deploy
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,108 @@
1
+ module SteamDonkey
2
+ module Cloudformation
3
+ class EventLog
4
+
5
+ def initialize(client, options)
6
+ @client = client
7
+ @options = options
8
+ @events_seen = []
9
+ end
10
+
11
+ BOLD = "[1m"
12
+ BLACK = "[30m"
13
+ RED = "[31m"
14
+ GREEN = "[32m"
15
+ YELLOW = "[33m"
16
+ BLUE = "[34m"
17
+ MAGENTA = "[35m"
18
+ CYAN = "[36m"
19
+ WHITE = "[37m"
20
+
21
+ def status_colors
22
+ {
23
+ "REVIEW_IN_PROGRESS" => "\033" + YELLOW,
24
+
25
+ "CREATE_IN_PROGRESS" => "\033" + YELLOW,
26
+ "CREATE_FAILED" => "\033" + RED,
27
+ "CREATE_COMPLETE" => "\033" + GREEN,
28
+
29
+ "DELETE_IN_PROGRESS" => "\033" + RED,
30
+ "DELETE_FAILED" => "\033" + RED,
31
+ "DELETE_COMPLETE" => "\033" + BOLD,
32
+ "DELETE_SKIPPED" => "\033" + BOLD,
33
+
34
+ "UPDATE_IN_PROGRESS" => "\033" + YELLOW,
35
+ "UPDATE_FAILED" => "\033" + RED,
36
+ "UPDATE_COMPLETE" => "\033" + GREEN,
37
+ "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS" => "\033" + YELLOW,
38
+
39
+ "UPDATE_ROLLBACK_IN_PROGRESS" => "\033" + RED,
40
+ "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" => "\033" + RED,
41
+ "UPDATE_ROLLBACK_COMPLETE" => "\033" + GREEN
42
+ }
43
+ end
44
+
45
+ def list
46
+ printf("%s%-20s %-44s %-26s %s%s\n",
47
+ "\033[1m",
48
+ "Timestamp",
49
+ "Status",
50
+ "Resource Type",
51
+ "Logical Id",
52
+ "\033[0m"
53
+ )
54
+
55
+ trap("SIGINT") { throw :ctrl_c }
56
+ catch :ctrl_c do
57
+ begin
58
+ events = []
59
+ stack_events.each do |response|
60
+ new_events(response).each do |event|
61
+ @events_seen << event.event_id
62
+ if after_desired_time(event)
63
+ events.unshift event
64
+ end
65
+ end
66
+ end
67
+ print_events(events)
68
+ sleep 5 if @options[:follow]
69
+ end while @options[:follow]
70
+ end
71
+ end
72
+
73
+ def after_desired_time(event)
74
+ event.timestamp > @options[:since]
75
+ end
76
+
77
+ def stack_name
78
+ @options[:stack_name]
79
+ end
80
+
81
+ def stack_events
82
+ @client.describe_stack_events(:stack_name => stack_name)
83
+ end
84
+
85
+ def new_events(response)
86
+ response.stack_events.select(&self.method(:is_new?))
87
+ end
88
+
89
+ def is_new?(event)
90
+ !@events_seen.include? event.event_id
91
+ end
92
+
93
+ def print_events(events)
94
+ events.each do |event|
95
+ # puts "#{event.timestamp.iso8601}"
96
+ printf("%-20s %s%-44s%s %-26s %s\n",
97
+ event.timestamp.iso8601,
98
+ status_colors[event.resource_status], event.resource_status, "\033[0m",
99
+ event.resource_type,
100
+ event.logical_resource_id
101
+ )
102
+ end
103
+ STDOUT.flush
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,49 @@
1
+ require 'aws-sdk'
2
+
3
+ module SteamDonkey
4
+ module Cloudformation
5
+ class ExportsListing
6
+ include SteamDonkey::ResourceListing
7
+
8
+ def initialize(client, options = {})
9
+ @client = client
10
+ init(options[:sort], options[:filters], options[:columns])
11
+ end
12
+
13
+ def aliases
14
+ [
15
+ { test: /^StackId$/i, value: 'ExportingStackId' }
16
+ ]
17
+ end
18
+
19
+ def search
20
+ result = []
21
+ exports = @client.list_exports
22
+ begin
23
+ last_token = exports.next_token
24
+ exports.exports.map do |export|
25
+ result << export
26
+ end
27
+ exports = @client.list_exports({ :next_token => exports.next_token })
28
+ end while last_token != exports.next_token
29
+
30
+ result
31
+ end
32
+
33
+ def select_column(column, stack)
34
+ begin
35
+ c = column.clone
36
+ case column[:name]
37
+ when 'nil'
38
+ else
39
+ c[:value] = stack.send(column[:name].underscore)
40
+ end
41
+ c
42
+ rescue
43
+ raise "Unknown column #{column[:name]}"
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,100 @@
1
+ require 'pathname'
2
+
3
+ module SteamDonkey
4
+ module Cloudformation
5
+
6
+
7
+ class Template
8
+
9
+ def initialize(dirname, filename)
10
+ @dirname = dirname
11
+ @filename = filename
12
+ end
13
+
14
+ def path
15
+ File.join(@dirname, @filename)
16
+ end
17
+
18
+ def name
19
+ @filename
20
+ end
21
+
22
+ def contains_child_templates?
23
+ !templates.empty?
24
+ end
25
+
26
+ def templates
27
+ @child_references ||= scan_for_child_templates path
28
+ end
29
+
30
+ def relative_path(root)
31
+ template_path = Pathname.new(path)
32
+ root_path = Pathname.new(root)
33
+ template_path.relative_path_from(root_path)
34
+ end
35
+
36
+ def packaged_template(bucket, prefix, root_path)
37
+ lines = []
38
+ File.readlines(path).each do |line|
39
+ new_line = line
40
+ line.scan /\.\/(.+)$/ do |child_path|
41
+ unless child_path.empty?
42
+ new_line = line.gsub(/\.\/.+$/, "https://s3-eu-west-1.amazonaws.com/#{bucket}/#{prefix}/#{child_path.first}")
43
+ end
44
+ end
45
+ lines << new_line
46
+ end
47
+ lines.join
48
+ end
49
+
50
+ private
51
+
52
+ def scan_for_child_templates(template)
53
+ child_templates = []
54
+ child_templates << self
55
+ File.readlines(template).each do |line|
56
+ line.scan /\.\/(.+)$/ do |child_path|
57
+ unless child_path.empty?
58
+ child = create_child_template(child_path.first)
59
+ child_templates << child
60
+ child_templates.push(*(child.templates))
61
+ end
62
+ end
63
+ end
64
+ child_templates.uniq
65
+ end
66
+
67
+ def create_child_template(path)
68
+ child_path = File.join(@dirname, path)
69
+ Template.new File.dirname(child_path), File.basename(child_path)
70
+ end
71
+ end
72
+
73
+ class Package
74
+ def initialize(client, quiet, dry_run)
75
+ @client = client
76
+ @quiet = quiet || false
77
+ @dry_run = dry_run || false
78
+ end
79
+
80
+ def package(path, bucket, prefix)
81
+ template_dir = File.dirname(File.absolute_path(path))
82
+ template_name = File.basename(path)
83
+
84
+ root_template = Template.new(template_dir, template_name)
85
+
86
+ root_template.templates.each do |template|
87
+ puts "Uploading s3://#{bucket}/#{prefix}/#{template.relative_path(template_dir)}" unless @quiet
88
+ @client.put_object({
89
+ body: template.packaged_template(bucket, prefix, template_dir),
90
+ bucket: bucket,
91
+ key: "#{prefix}/#{template.relative_path(template_dir)}"
92
+ }) unless @dry_run
93
+ end
94
+
95
+ "https://s3-eu-west-1.amazonaws.com/#{bucket}/#{prefix}/#{root_template.name}"
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,47 @@
1
+ require 'aws-sdk'
2
+
3
+ module SteamDonkey
4
+ module Cloudformation
5
+ class StackListing
6
+ include SteamDonkey::ResourceListing
7
+
8
+ def initialize(client, options = {})
9
+ @client = client
10
+ init(options[:sort], options[:filters], options[:columns])
11
+ end
12
+
13
+ def aliases
14
+ [
15
+ { test: /^Id$/i, value: 'StackId' },
16
+ { test: /^Name$/i, value: 'StackName' },
17
+ { test: /^Status/i, value: 'StackStatus' },
18
+ { test: /^StatusReason/i, value: 'StackStatusReason' },
19
+ ]
20
+ end
21
+
22
+ def search
23
+ @client.list_stacks.map do |response|
24
+ response.stack_summaries.map do |stack|
25
+ stack
26
+ end
27
+ end.flatten
28
+ end
29
+
30
+ def select_column(column, stack)
31
+ begin
32
+ c = column.clone
33
+ case column[:name]
34
+ when /^Tags\./i
35
+ c[:value] = find_tag(stack, column[:name].split('.').last)
36
+ else
37
+ c[:value] = stack.send(column[:name].underscore)
38
+ end
39
+ c
40
+ rescue
41
+ raise "Unknown column,#{column[:name]}"
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,102 @@
1
+ require 'chronic'
2
+
3
+ desc 'Manage and view cloudformation stacks and templates'
4
+ command [:cf] do |cf|
5
+
6
+ cf.desc 'List cloudformation stacks'
7
+ cf.command [:list, :ls] do |list|
8
+
9
+ list.switch [:raw, :r], :default_value => false, :negatable => false, :desc => "Output unformatted, useful when piping results to other commands"
10
+ list.switch [:headings, :h], :default_value => false, :desc => "Toggle column headings"
11
+
12
+ list.flag [:filters, :f], :default_value => 'Status=!/DELETE_COMPLETE/', :desc => ""
13
+ list.flag [:columns, :c], :default_value => 'Name,CreationTime,Status', :desc => ""
14
+ list.flag [:sort, :s], :default_value => 'CreationTime=desc,Name', :desc => ""
15
+
16
+ list.flag [:output, :o], :default_value => 'pretty', :must_match => { "pretty" => :pretty, "raw" => :raw }
17
+
18
+ list.action do |global_options, options, args|
19
+ listing_options = {
20
+ :filters => options[:filters],
21
+ :columns => options[:columns],
22
+ :sort => options[:sort]
23
+ }
24
+
25
+ stack_listing = SteamDonkey::Cloudformation::StackListing.new(cf_client(global_options), listing_options)
26
+
27
+ output = SteamDonkey::Cli::Output.new true, options[:output]
28
+ output.render stack_listing.column_labels, stack_listing.list
29
+ end
30
+ end
31
+
32
+ cf.desc 'List stack events'
33
+ cf.command [:events] do |events|
34
+
35
+ events.flag ['stack-name', :s], :arg_name => 'STACK_NAME', :required => true, :desc => "Stack name"
36
+ events.switch ['follow', :f], :default_value => false, :negatable => false
37
+
38
+ events.action do |global_options, options, args|
39
+ list_options = {
40
+ :follow => options[:follow],
41
+ :stack_name => options[:'stack-name'],
42
+ :since => Chronic.parse("2 years ago")
43
+ }
44
+
45
+ event_log = SteamDonkey::Cloudformation::EventLog.new(cf_client(global_options), list_options)
46
+ event_log.list
47
+ end
48
+ end
49
+
50
+ cf.desc 'List cloudformation exports'
51
+ cf.command [:exports] do |exports|
52
+
53
+ exports.switch [:raw, :r], :default_value => false, :negatable => false, :desc => "Output unformatted, useful when piping results to other commands"
54
+ exports.switch [:headings, :h], :default_value => false, :desc => "Toggle column headings"
55
+
56
+ exports.flag [:filters, :f], :default_value => '', :desc => ""
57
+ exports.flag [:columns, :c], :default_value => 'Name,Value', :desc => ""
58
+ exports.flag [:sort, :s], :default_value => 'Name', :desc => ""
59
+
60
+ exports.flag [:output, :o], :default_value => 'pretty', :must_match => { "pretty" => :pretty, "raw" => :raw }
61
+
62
+ exports.action do |global_options, options, args|
63
+ listing_options = {
64
+ :filters => options[:filters],
65
+ :columns => options[:columns],
66
+ :sort => options[:sort]
67
+ }
68
+
69
+ exports_listing = SteamDonkey::Cloudformation::ExportsListing.new(cf_client(global_options), listing_options)
70
+
71
+ output = SteamDonkey::Cli::Output.new true, options[:output]
72
+ output.render exports_listing.column_labels, exports_listing.list
73
+ end
74
+ end
75
+
76
+ cf.desc 'Package cloudformation template and upload to S3'
77
+ cf.command [:package] do |p|
78
+
79
+ p.flag [:template, :t], :required => true, :desc => "Path to template to package and upload"
80
+ p.flag [:bucket, :b], :desc => "Name of the S3 bucket to upload packaged templates to"
81
+ p.flag [:prefix, :p], :desc => "Prefix to prepend to uploaded templates"
82
+ p.switch ['launch-console', :l], :default_value => false, :negatable => false, :desc => "Launch CloudFormation console with uploaded template"
83
+ p.switch ['dry-run', :d], :default_value => false, :negatable => false
84
+ p.switch [:quiet, :q], :default_value => false, :negatable => false
85
+
86
+ p.action do |global_options, options, args|
87
+ bucket_name = options[:bucket] || global_options[:rc][:cloudformation][:package]["bucketName"]
88
+ bucket_path_prefix = options[:prefix] || global_options[:rc][:cloudformation][:package]["bucketPathPrefix"]
89
+
90
+ package = SteamDonkey::Cloudformation::Package.new(s3_client(global_options), options[:quiet], options['dry-run'])
91
+
92
+ template_url = package.package options[:template], bucket_name, bucket_path_prefix
93
+
94
+ puts template_url unless options[:quiet]
95
+
96
+ if options['launch-console']
97
+ `open https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?templateURL=#{template_url}`
98
+ end
99
+ end
100
+ end
101
+ end
102
+