cfn-events 0.1.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.
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: []