cfn-events 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 621408083d1bba4edfe098f3f181776314710623
4
+ data.tar.gz: d9dcb6f239a057a245b8bd69dce57c31f9506a25
5
+ SHA512:
6
+ metadata.gz: 64e98618f9850875f448b5a239b5034ce8a67042e57d8292008650271646fecf35c1ca55a5bda1fd4ba58375495f6fe8678d8c60042227f86b1612223f71d870
7
+ data.tar.gz: a6946e16c668f60fcb3811d4f49475d6c74944f1e700ddfd88c059e36de7bd3ba77fa61df72adb3804d3e59ccb55b6b66bbb9bd32b8f59bdc649eb484fc69d8f
data/bin/cfn-events ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aws-sdk'
5
+ require 'json'
6
+ require 'time'
7
+
8
+ require 'cfn-events'
9
+
10
+ config = CfnEvents::Config.new
11
+
12
+ opts_parser = OptionParser.new do |opts|
13
+ opts.banner = "
14
+ cfn-events [OPTIONS] STACK-NAME-OR-ID
15
+ "
16
+ opts.separator "Controlling which events to select:"
17
+ opts.on("-r", "--region=s", "CloudFormation region"){|r| config.client_options = { region: r } }
18
+ opts.on("-s", "--since=s", "Don't show any events earlier than this time"){|s| config.since = Time.parse(s).to_time }
19
+ opts.separator ""
20
+ opts.separator "Controlling the output format:"
21
+ opts.on("-j", "--json", "Output events as json"){ config.output_json = true }
22
+ opts.separator ""
23
+ opts.separator "Controlling when to stop:"
24
+ opts.on("-w", "--wait", "Stop once the stack reaches a stable state"){ config.wait = true }
25
+ opts.on("-f", "--forever", "Keep showing events (until killed)"){ config.forever = true }
26
+ opts.on("-p", "--poll-interval=n", "Poll interval, in seconds"){|t| config.poll_seconds = t.to_f }
27
+ opts.separator <<-EOF
28
+
29
+ If neither --wait nor --forever are used, then cfn-events exits once any
30
+ currently-available events have been displayed (and --poll-interval has no
31
+ effect).
32
+
33
+ If --wait is used, then cfn-events keeps reading events (respecting the given
34
+ --poll-interval) until the stack reaches a non-"IN_PROGRESS" state. The exit
35
+ status depends on what state the stack ends up in. If it's a FAILED state,
36
+ the exit status is 2; if it's a ROLLBACK state; the exit status is 1;
37
+ otherwise, the exit status is 0.
38
+
39
+ If --forever is used, then cfn-events keeps reading events until killed, or
40
+ an error occurs.
41
+
42
+ EOF
43
+ end
44
+ opts_parser.parse!
45
+
46
+ unless ARGV.count == 1
47
+ $stderr.puts "Usage: cfn-events [OPTIONS] STACK-NAME-OR-ID"
48
+ $stderr.puts "See 'cfn-events --help' for more"
49
+ exit 2
50
+ end
51
+ config.stack_name_or_id = ARGV.first
52
+
53
+ rc = CfnEvents::Runner.new(config).run
54
+ exit rc
55
+
56
+ # eof cfn-events
@@ -0,0 +1,25 @@
1
+ module CfnEvents
2
+
3
+ class Client
4
+
5
+ def self.configure
6
+ Aws.config.merge! core_v2_options
7
+ end
8
+
9
+ def self.core_v2_options
10
+ {
11
+ http_proxy: get_proxy,
12
+ user_agent_suffix: "cfn-events #{VERSION}",
13
+ # http_wire_trace: true,
14
+ }
15
+ end
16
+
17
+ def self.get_proxy
18
+ e = ENV['https_proxy']
19
+ e = "https://#{e}" if e && !e.empty? && !e.start_with?('http')
20
+ return e
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,32 @@
1
+ module CfnEvents
2
+
3
+ class Config
4
+ # Stronger builder pattern would be nice
5
+ attr_accessor :client_options, :cfn_client,
6
+ :stack_name_or_id,
7
+ :output_json,
8
+ :since, :wait, :forever, :poll_seconds
9
+
10
+ def initialize
11
+ @client_options = {}
12
+ @since = nil
13
+ @output_json = false
14
+ @wait = false
15
+ @forever = false
16
+ @poll_seconds = 5
17
+ end
18
+
19
+ def build
20
+ if !@stack_name_or_id
21
+ raise "Missing stack_name_or_id"
22
+ end
23
+
24
+ if @wait and @forever
25
+ raise "wait and forever cannot be combined"
26
+ end
27
+
28
+ self
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,104 @@
1
+ module CfnEvents
2
+
3
+ class Runner
4
+
5
+ def initialize(config)
6
+ @config = config
7
+
8
+ @config.cfn_client ||= begin
9
+ effective_options = CfnEvents::Client.core_v2_options.merge(config.client_options)
10
+ Aws::CloudFormation::Client.new(effective_options)
11
+ end
12
+ end
13
+
14
+ def core_v2_options
15
+ i
16
+ end
17
+
18
+ def resolve_stack(stack_name_or_id)
19
+ ans = @config.cfn_client.describe_stacks(stack_name: stack_name_or_id).data.stacks[0].stack_id
20
+ if ans != stack_name_or_id
21
+ $stderr.puts "Resolved #{stack_name_or_id} to #{ans}"
22
+ end
23
+ ans
24
+ end
25
+
26
+ def all_events
27
+ @config.cfn_client.describe_stack_events(stack_name: @stack_id).data.stack_events.reverse
28
+ end
29
+
30
+ def events_since_time(t)
31
+ # There may be a more efficient algorithm
32
+ all_events.select {|e| e.timestamp > t }
33
+ end
34
+
35
+ def events_since_id(id)
36
+ # There may be a more efficient algorithm
37
+ events = all_events
38
+ i = events.index {|e| e.event_id == id }
39
+ if i < 0
40
+ events
41
+ else
42
+ events[i+1..-1]
43
+ end
44
+ end
45
+
46
+ def show_events(events)
47
+ events.each do |e|
48
+ if @config.output_json
49
+ puts JSON.generate(e.to_h)
50
+ else
51
+ puts [
52
+ e.timestamp.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
53
+ e.resource_type,
54
+ e.resource_status,
55
+ e.logical_resource_id,
56
+ e.physical_resource_id,
57
+ e.resource_status_reason,
58
+ ].join " "
59
+ end
60
+ end
61
+ end
62
+
63
+ def steady_state?(e)
64
+ e.resource_type == "AWS::CloudFormation::Stack" and not e.resource_status.match(/IN_PROGRESS/)
65
+ end
66
+
67
+ # Calls $stdout.sync. Returns 0/1/2, like the command line exit code.
68
+ def run
69
+ @stack_id = resolve_stack(@config.stack_name_or_id)
70
+
71
+ events = if @config.since
72
+ events_since_time(@config.since)
73
+ else
74
+ all_events
75
+ end
76
+
77
+ show_events(events)
78
+
79
+ return 0 unless @config.forever or @config.wait
80
+
81
+ # I'm assuming / pretending that by this point, events.empty? is never true
82
+ # FIXME! It's false. If --since was used, and there have been no events since
83
+ # that time. That's a bug. :-(
84
+
85
+ while @config.forever or not steady_state?(events.last)
86
+ $stdout.sync
87
+ sleep @config.poll_seconds
88
+
89
+ new_events = events_since_id(events.last.event_id)
90
+
91
+ unless new_events.empty?
92
+ show_events(new_events)
93
+ events = new_events
94
+ end
95
+ end
96
+
97
+ return 2 if events.last.resource_status.match /FAILED/
98
+ return 1 if events.last.resource_status.match /ROLLBACK/
99
+ return 0
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,3 @@
1
+ module CfnEvents
2
+ VERSION = '0.1.0'
3
+ end
data/lib/cfn-events.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative './cfn-events/client.rb'
2
+ require_relative './cfn-events/config.rb'
3
+ require_relative './cfn-events/runner.rb'
4
+ require_relative './cfn-events/version.rb'
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfn-events
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rachel Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: "\n cfn-events reads the events for an AWS CloudFormation stack. It
28
+ can\n be used to \"tail\" the log, and to wait until a stack update is resolved,\n
29
+ \ successfully or otherwise.\n\n Defaults to eu-west-1, or whatever $AWS_REGION
30
+ is set to.\n Respects $https_proxy.\n "
31
+ email: cfn-events-git@rve.org.uk
32
+ executables:
33
+ - cfn-events
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - bin/cfn-events
38
+ - lib/cfn-events.rb
39
+ - lib/cfn-events/client.rb
40
+ - lib/cfn-events/config.rb
41
+ - lib/cfn-events/runner.rb
42
+ - lib/cfn-events/version.rb
43
+ homepage: https://github.com/rvedotrc/cfn-events
44
+ licenses:
45
+ - Apache-2.0
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.5.1
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Watch AWS CloudFormation stack events and wait for completion
67
+ test_files: []