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 +7 -0
- data/bin/cfn-events +56 -0
- data/lib/cfn-events/client.rb +25 -0
- data/lib/cfn-events/config.rb +32 -0
- data/lib/cfn-events/runner.rb +104 -0
- data/lib/cfn-events/version.rb +3 -0
- data/lib/cfn-events.rb +4 -0
- metadata +67 -0
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
|
data/lib/cfn-events.rb
ADDED
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: []
|