steam_donkey 0.3.0

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,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
+