cfndk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README-ja.md +282 -0
- data/README.md +288 -0
- data/Rakefile +1 -0
- data/bin/cfndk +120 -0
- data/cfndk.gemspec +28 -0
- data/lib/cfndk/aws/credential_provider_chain.rb +115 -0
- data/lib/cfndk/parameter_string.rb +22 -0
- data/lib/cfndk/stack.rb +44 -0
- data/lib/cfndk/stacks.rb +308 -0
- data/lib/cfndk/version.rb +3 -0
- data/lib/cfndk.rb +8 -0
- data/sample/cfndk.yml +39 -0
- data/skel/cfndk.yml +23 -0
- metadata +146 -0
data/bin/cfndk
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
require 'rainbow/ext/string'
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'pathname'
|
9
|
+
require 'erb'
|
10
|
+
require 'yaml'
|
11
|
+
require 'json'
|
12
|
+
require 'aws-sdk'
|
13
|
+
require 'terminal-table'
|
14
|
+
require 'securerandom'
|
15
|
+
|
16
|
+
require 'cfndk.rb'
|
17
|
+
|
18
|
+
cur_dir = Dir.getwd
|
19
|
+
|
20
|
+
option = {
|
21
|
+
config_path: "#{cur_dir}/cfndk.yml",
|
22
|
+
uuid: ENV['CFNDK_UUID'] || nil,
|
23
|
+
properties: {},
|
24
|
+
}
|
25
|
+
|
26
|
+
opt = OptionParser.new do |o|
|
27
|
+
o.version = CFnDK::VERSION
|
28
|
+
o.summary_indent = ' ' * 4
|
29
|
+
o.banner = 'Usage: cfndk [cmd] [options]'
|
30
|
+
o.on_head('[cmd]',
|
31
|
+
' init create config YAML file',
|
32
|
+
' create create stacks',
|
33
|
+
' update update stacks',
|
34
|
+
' create-or-changeset create stacks or create changeset',
|
35
|
+
' destroy destroy stacks',
|
36
|
+
' generate-uuid generate UUID',
|
37
|
+
' report-event report stack event',
|
38
|
+
' report-stack report stack',
|
39
|
+
' report-stack-resource report stack resource',
|
40
|
+
'[enviroment variables]',
|
41
|
+
" AWS_PROFILE: #{ENV['AWS_PROFILE']}",
|
42
|
+
" AWS_DEFAULT_REGION: #{ENV['AWS_DEFAULT_REGION']}",
|
43
|
+
" AWS_REGION: #{ENV['AWS_REGION']}",
|
44
|
+
" AWS_ACCESS_KEY_ID: #{ENV['AWS_ACCESS_KEY_ID']}",
|
45
|
+
" AWS_SECRET_ACCESS_KEY: #{ENV['AWS_SECRET_ACCESS_KEY']}",
|
46
|
+
" AWS_SESSION_TOKEN: #{ENV['AWS_SECRET_ACCESS_KEY']}",
|
47
|
+
" AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: #{ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI']}",
|
48
|
+
'[options]')
|
49
|
+
o.on('-v', '--verbose', 'verbose mode') { |v| option[:v] = v }
|
50
|
+
o.on('-c', '--config_path <cfndi.yml>', "config path (default: #{option[:config_path]})") { |v| option[:config_path] = v }
|
51
|
+
o.on('-p', '--properties <name>=<value>', 'properties') do |v|
|
52
|
+
md = v.match(/^([a-zA-Z_]+[a-zA-Z0-9_]*)=(.*)$/)
|
53
|
+
if md
|
54
|
+
option[:properties][md[0]] = md[1]
|
55
|
+
else
|
56
|
+
raise "invalid properties: '#{v}'" unless md
|
57
|
+
end
|
58
|
+
end
|
59
|
+
o.on('-a', '--auto-uuid') { option[:uuid] = SecureRandom.uuid }
|
60
|
+
o.on('-u', '--uuid <uuid>') { |v| option[:uuid] = v }
|
61
|
+
o.permute!(ARGV)
|
62
|
+
end
|
63
|
+
|
64
|
+
if ARGV.length != 1
|
65
|
+
puts opt.help
|
66
|
+
exit 1
|
67
|
+
elsif ARGV[0] == 'generate-uuid'
|
68
|
+
puts SecureRandom.uuid
|
69
|
+
exit 0
|
70
|
+
end
|
71
|
+
|
72
|
+
unless File.file?(option[:config_path]) || ARGV[0] == 'init'
|
73
|
+
puts "File does not exist. #{option[:config_path]}".color :red
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
|
77
|
+
$LOAD_PATH.unshift "#{cur_dir}/lib"
|
78
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
79
|
+
|
80
|
+
data = open(option[:config_path], 'r') { |f| YAML.load(f) } if File.file?(option[:config_path]) && ARGV[0] != 'init'
|
81
|
+
|
82
|
+
credentials = CFnDK::Aws::CredentialProviderChain.new.resolve
|
83
|
+
client = Aws::CloudFormation::Client.new(credentials: credentials)
|
84
|
+
stacks = CFnDK::Stacks.new(data, option, client)
|
85
|
+
|
86
|
+
if ARGV[0] == 'create'
|
87
|
+
puts 'create...'.color :green
|
88
|
+
stacks.create
|
89
|
+
elsif ARGV[0] == 'update'
|
90
|
+
puts 'update...'.color :green
|
91
|
+
stacks.update
|
92
|
+
elsif ARGV[0] == 'create-or-changeset'
|
93
|
+
puts 'create or changeset...'.color :green
|
94
|
+
stacks.create_or_changeset
|
95
|
+
elsif ARGV[0] == 'destroy'
|
96
|
+
puts 'destroy...'.color :green
|
97
|
+
stacks.destroy
|
98
|
+
elsif ARGV[0] == 'report-event'
|
99
|
+
puts 'report event...'.color :green
|
100
|
+
stacks.report_event
|
101
|
+
elsif ARGV[0] == 'report-stack'
|
102
|
+
puts 'report stack...'.color :green
|
103
|
+
stacks.report_stack
|
104
|
+
elsif ARGV[0] == 'report-stack-resource'
|
105
|
+
puts 'report stack resource...'.color :green
|
106
|
+
stacks.report_stack_resource
|
107
|
+
elsif ARGV[0] == 'init'
|
108
|
+
if File.file?(option[:config_path])
|
109
|
+
puts "File exist. #{option[:config_path]}".color :red
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
puts 'init...'.color :green
|
113
|
+
FileUtils.cp_r(Dir.glob(File.dirname(__FILE__) + '/../skel/*'), './')
|
114
|
+
puts "create #{option[:config_path]}".color :green
|
115
|
+
else
|
116
|
+
puts opt.help
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
|
120
|
+
exit 0
|
data/cfndk.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cfndk/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cfndk'
|
8
|
+
spec.version = CFnDK::VERSION
|
9
|
+
spec.authors = ['Yoshihisa AMAKATA']
|
10
|
+
spec.email = ['amakata@gmail.com']
|
11
|
+
spec.summary = 'cfndk is AWS Cloud Formation Development Kit'
|
12
|
+
spec.description = 'cfndk is AWS Cloud Formation Development Kit'
|
13
|
+
spec.homepage = 'https://github.com/Amakata/cfndk'
|
14
|
+
spec.license = 'http://www.apache.org/licenses/license-2.0'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rake', '~> 11.1.2'
|
23
|
+
|
24
|
+
spec.add_dependency 'rainbow', '~> 2.1.0'
|
25
|
+
spec.add_dependency 'aws-sdk', '~> 3'
|
26
|
+
spec.add_dependency 'camelizable', '~> 0.0.3'
|
27
|
+
spec.add_dependency 'terminal-table', '~> 1'
|
28
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module CFnDK
|
2
|
+
module Aws
|
3
|
+
class CredentialProviderChain
|
4
|
+
def initialize(config = nil)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def resolve
|
9
|
+
providers.each do |method_name, options|
|
10
|
+
provider = send(method_name, options.merge(config: @config))
|
11
|
+
return provider if provider && provider.set?
|
12
|
+
end
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def providers
|
19
|
+
[
|
20
|
+
[:static_credentials, {}],
|
21
|
+
[:env_credentials, {}],
|
22
|
+
[:assume_role_credentials, {}],
|
23
|
+
[:shared_credentials, {}],
|
24
|
+
[:process_credentials, {}],
|
25
|
+
[:instance_profile_credentials, {
|
26
|
+
retries: @config ? @config.instance_profile_credentials_retries : 0,
|
27
|
+
http_open_timeout: @config ? @config.instance_profile_credentials_timeout : 1,
|
28
|
+
http_read_timeout: @config ? @config.instance_profile_credentials_timeout : 1,
|
29
|
+
}],
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def static_credentials(options)
|
34
|
+
if options[:config]
|
35
|
+
::Aws::Credentials.new(
|
36
|
+
options[:config].access_key_id,
|
37
|
+
options[:config].secret_access_key,
|
38
|
+
options[:config].session_token)
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def env_credentials(options)
|
45
|
+
key = %w(AWS_ACCESS_KEY_ID AMAZON_ACCESS_KEY_ID AWS_ACCESS_KEY)
|
46
|
+
secret = %w(AWS_SECRET_ACCESS_KEY AMAZON_SECRET_ACCESS_KEY AWS_SECRET_KEY)
|
47
|
+
token = %w(AWS_SESSION_TOKEN AMAZON_SESSION_TOKEN)
|
48
|
+
::Aws::Credentials.new(envar(key), envar(secret), envar(token))
|
49
|
+
end
|
50
|
+
|
51
|
+
def envar(keys)
|
52
|
+
keys.each do |key|
|
53
|
+
return ENV[key] if ENV.key?(key)
|
54
|
+
end
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def shared_credentials(options)
|
59
|
+
if options[:config]
|
60
|
+
::Aws::SharedCredentials.new(profile_name: options[:config].profile)
|
61
|
+
else
|
62
|
+
::Aws::SharedCredentials.new(
|
63
|
+
profile_name: ENV['AWS_PROFILE'].nil? ? 'default' : ENV['AWS_PROFILE'])
|
64
|
+
end
|
65
|
+
rescue ::Aws::Errors::NoSuchProfileError
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_credentials(options)
|
70
|
+
profile_name = options[:config].profile if options[:config]
|
71
|
+
profile_name ||= ENV['AWS_PROFILE'].nil? ? 'default' : ENV['AWS_PROFILE']
|
72
|
+
|
73
|
+
config = ::Aws.shared_config
|
74
|
+
if config.config_enabled? && process_provider = config.credentials_process(profile_name)
|
75
|
+
::Aws::ProcessCredentials.new(process_provider)
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
rescue ::Aws::Errors::NoSuchProfileError
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def assume_role_credentials(options)
|
84
|
+
if ::Aws.shared_config.config_enabled?
|
85
|
+
profile = nil
|
86
|
+
region = nil
|
87
|
+
if options[:config]
|
88
|
+
profile = options[:config].profile
|
89
|
+
region = options[:config].region
|
90
|
+
assume_role_with_profile(options[:config].profile, options[:config].region)
|
91
|
+
end
|
92
|
+
assume_role_with_profile(profile, region)
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def instance_profile_credentials(options)
|
99
|
+
if ENV['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI']
|
100
|
+
::Aws::ECSCredentials.new(options)
|
101
|
+
else
|
102
|
+
::Aws::InstanceProfileCredentials.new(options)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def assume_role_with_profile(prof, region)
|
107
|
+
::Aws.shared_config.assume_role_credentials_from_config(
|
108
|
+
profile: prof,
|
109
|
+
region: region,
|
110
|
+
chain_config: @config
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CFnDK
|
2
|
+
class ParameterString
|
3
|
+
attr_reader :uuid, :properties
|
4
|
+
def initialize(str, option)
|
5
|
+
@erb = ERB.new(str, nil, '-')
|
6
|
+
@properties = option[:properties]
|
7
|
+
@uuid = option[:uuid]
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
@erb.result(binding)
|
12
|
+
end
|
13
|
+
|
14
|
+
def append_uuid(glue = '-')
|
15
|
+
if uuid
|
16
|
+
glue + uuid
|
17
|
+
else
|
18
|
+
''
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/cfndk/stack.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module CFnDK
|
2
|
+
class Stack
|
3
|
+
attr_reader :template_file, :parameter_input, :capabilities, :depends, :timeout_in_minutes
|
4
|
+
def initialize(name, data, option)
|
5
|
+
@name = name
|
6
|
+
@template_file = data['template_file'] || ''
|
7
|
+
@parameter_input = data['parameter_input'] || ''
|
8
|
+
@capabilities = data['capabilities'] || []
|
9
|
+
@depends = data['depends'] || []
|
10
|
+
@timeout_in_minutes = data['timeout_in_minutes'] || 1
|
11
|
+
@override_parameters = data['parameters'] || {}
|
12
|
+
@option = option
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
[@name, @option[:uuid]].compact.join('-')
|
17
|
+
end
|
18
|
+
|
19
|
+
def template_body
|
20
|
+
File.open(@template_file, 'r').read
|
21
|
+
end
|
22
|
+
|
23
|
+
def parameters
|
24
|
+
json = JSON.load(open(@parameter_input).read)
|
25
|
+
json['Parameters'].map do |item|
|
26
|
+
next if item.empty?
|
27
|
+
{
|
28
|
+
parameter_key: item['ParameterKey'],
|
29
|
+
parameter_value: eval_override_parameter(item['ParameterKey'], item['ParameterValue']),
|
30
|
+
}
|
31
|
+
end.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def eval_override_parameter(k, v)
|
37
|
+
if @override_parameters[k]
|
38
|
+
CFnDK::ParameterString.new(@override_parameters[k], @option).value
|
39
|
+
else
|
40
|
+
v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/cfndk/stacks.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
module CFnDK
|
2
|
+
class Stacks
|
3
|
+
def initialize(data, option, cfn_client)
|
4
|
+
@option = option
|
5
|
+
@cfn_client = cfn_client
|
6
|
+
create_stack data
|
7
|
+
create_sequence
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
@sequence.each do |stacks|
|
12
|
+
stacks.each do |name|
|
13
|
+
puts(('creating ' + name).color(:green))
|
14
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
15
|
+
puts('Parametres :' + @stacks[name].parameters.inspect) if @option[:v]
|
16
|
+
puts('Capabilities:' + @stacks[name].capabilities.inspect) if @option[:v]
|
17
|
+
puts('timeout :' + @stacks[name].timeout_in_minutes.to_s) if @option[:v]
|
18
|
+
@cfn_client.create_stack(
|
19
|
+
stack_name: @stacks[name].name,
|
20
|
+
template_body: @stacks[name].template_body,
|
21
|
+
parameters: @stacks[name].parameters,
|
22
|
+
capabilities: @stacks[name].capabilities,
|
23
|
+
timeout_in_minutes: @stacks[name].timeout_in_minutes
|
24
|
+
)
|
25
|
+
end
|
26
|
+
stacks.each do |name|
|
27
|
+
begin
|
28
|
+
@cfn_client.wait_until(
|
29
|
+
:stack_create_complete,
|
30
|
+
stack_name: @stacks[name].name
|
31
|
+
)
|
32
|
+
puts(('created ' + name).color(:green))
|
33
|
+
rescue Aws::Waiters::Errors::FailureStateError => ex
|
34
|
+
puts ex.message
|
35
|
+
report_event
|
36
|
+
raise ex
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
@sequence.each do |stacks|
|
44
|
+
updating_stacks = []
|
45
|
+
stacks.each do |name|
|
46
|
+
puts(('updating ' + name).color(:green))
|
47
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
48
|
+
puts('Parametres :' + @stacks[name].parameters.inspect) if @option[:v]
|
49
|
+
puts('Capabilities:' + @stacks[name].capabilities.inspect) if @option[:v]
|
50
|
+
puts('timeout :' + @stacks[name].timeout_in_minutes.to_s) if @option[:v]
|
51
|
+
begin
|
52
|
+
@cfn_client.update_stack(
|
53
|
+
stack_name: @stacks[name].name,
|
54
|
+
template_body: @stacks[name].template_body,
|
55
|
+
parameters: @stacks[name].parameters,
|
56
|
+
capabilities: @stacks[name].capabilities
|
57
|
+
)
|
58
|
+
updating_stacks.push name
|
59
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ex
|
60
|
+
puts ex.message.color :red
|
61
|
+
end
|
62
|
+
end
|
63
|
+
updating_stacks.each do |name|
|
64
|
+
@cfn_client.wait_until(
|
65
|
+
:stack_update_complete,
|
66
|
+
stack_name: @stacks[name].name
|
67
|
+
)
|
68
|
+
puts(('updated ' + name).color(:green))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_or_changeset
|
74
|
+
@sequence.each do |stacks|
|
75
|
+
create_stacks = []
|
76
|
+
changeset_stacks = []
|
77
|
+
stacks.each do |name|
|
78
|
+
begin
|
79
|
+
@cfn_client.describe_stacks(
|
80
|
+
stack_name: @stacks[name].name
|
81
|
+
)
|
82
|
+
puts(('creating ' + name + @option[:uuid]).color(:green))
|
83
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
84
|
+
puts('Parametres :' + @stacks[name].parameters.inspect) if @option[:v]
|
85
|
+
puts('Capabilities:' + @stacks[name].capabilities.inspect) if @option[:v]
|
86
|
+
@cfn_client.create_change_set(
|
87
|
+
stack_name: @stacks[name].name,
|
88
|
+
template_body: @stacks[name].template_body,
|
89
|
+
parameters: @stacks[name].parameters,
|
90
|
+
capabilities: @stacks[name].capabilities,
|
91
|
+
change_set_name: @stacks[name].name + @option[:uuid]
|
92
|
+
)
|
93
|
+
changeset_stacks.push name
|
94
|
+
rescue Aws::CloudFormation::Errors::ValidationError
|
95
|
+
puts(('creating ' + name).color(:green))
|
96
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
97
|
+
puts('Parametres :' + @stacks[name].parameters.inspect) if @option[:v]
|
98
|
+
puts('Capabilities:' + @stacks[name].capabilities.inspect) if @option[:v]
|
99
|
+
puts('timeout :' + @stacks[name].timeout_in_minutes.to_s) if @option[:v]
|
100
|
+
@cfn_client.create_stack(
|
101
|
+
stack_name: @stacks[name].name,
|
102
|
+
template_body: @stacks[name].template_body,
|
103
|
+
parameters: @stacks[name].parameters,
|
104
|
+
capabilities: @stacks[name].capabilities,
|
105
|
+
timeout_in_minutes: @stacks[name].timeout_in_minutes
|
106
|
+
)
|
107
|
+
create_stacks.push name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
create_stacks.each do |name|
|
111
|
+
@cfn_client.wait_until(
|
112
|
+
:stack_create_complete,
|
113
|
+
stack_name: @stacks[name].name
|
114
|
+
)
|
115
|
+
puts(('created ' + name).color(:green))
|
116
|
+
end
|
117
|
+
changeset_stacks.each do |name|
|
118
|
+
begin
|
119
|
+
@cfn_client.wait_until(
|
120
|
+
:change_set_create_complete,
|
121
|
+
stack_name: @stacks[name].name,
|
122
|
+
change_set_name: @stacks[name].name + @option[:uuid]
|
123
|
+
)
|
124
|
+
puts(('created ' + @stacks[name].name + @option[:uuid]).color(:green))
|
125
|
+
rescue Aws::Waiters::Errors::FailureStateError => ex
|
126
|
+
resp = @cfn_client.describe_change_set(
|
127
|
+
change_set_name: @stacks[name].name + @option[:uuid],
|
128
|
+
stack_name: @stacks[name].name
|
129
|
+
)
|
130
|
+
if resp.status_reason != "The submitted information didn't contain changes. Submit different information to create a change set."
|
131
|
+
puts ex.message.color :red
|
132
|
+
raise ex
|
133
|
+
else
|
134
|
+
puts(('failed ' + @stacks[name].name + @option[:uuid]).color(:red))
|
135
|
+
puts resp.status_reason
|
136
|
+
@cfn_client.delete_change_set(
|
137
|
+
change_set_name: @stacks[name].name + @option[:uuid],
|
138
|
+
stack_name: @stacks[name].name
|
139
|
+
)
|
140
|
+
puts(('deleted ' + @stacks[name].name + @option[:uuid]).color(:red))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def report_stack
|
148
|
+
rows = @sequence.flat_map do |stacks|
|
149
|
+
stacks.flat_map do |name|
|
150
|
+
rows = []
|
151
|
+
begin
|
152
|
+
rows = @cfn_client.describe_stacks(
|
153
|
+
stack_name: @stacks[name].name
|
154
|
+
).stacks.map do |item|
|
155
|
+
[
|
156
|
+
item.stack_name,
|
157
|
+
item.creation_time,
|
158
|
+
item.deletion_time,
|
159
|
+
case item.stack_status
|
160
|
+
when 'CREATE_FAILED' then
|
161
|
+
item.stack_status.color :red
|
162
|
+
when 'ROLLBACK_IN_PROGRESS' then
|
163
|
+
item.stack_status.color :red
|
164
|
+
when 'ROLLBACK_COMPLETE' then
|
165
|
+
item.stack_status.color :red
|
166
|
+
when 'CREATE_COMPLETE' then
|
167
|
+
item.stack_status.color :green
|
168
|
+
when 'DELETE_COMPLETE' then
|
169
|
+
item.stack_status.color :gray
|
170
|
+
else
|
171
|
+
item.stack_status.color :orange
|
172
|
+
end,
|
173
|
+
item.stack_status_reason]
|
174
|
+
end
|
175
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ex
|
176
|
+
puts ex.message
|
177
|
+
end
|
178
|
+
rows
|
179
|
+
end
|
180
|
+
end
|
181
|
+
table = Terminal::Table.new headings: %w(Name Creation Deletion Status Reason), rows: rows
|
182
|
+
puts table
|
183
|
+
end
|
184
|
+
|
185
|
+
def report_stack_resource
|
186
|
+
@sequence.each do |stacks|
|
187
|
+
stacks.each do |name|
|
188
|
+
puts(('stack ' + name).color(:green))
|
189
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
190
|
+
begin
|
191
|
+
rows = @cfn_client.describe_stack_resources(
|
192
|
+
stack_name: @stacks[name].name
|
193
|
+
).stack_resources.map do |item|
|
194
|
+
[
|
195
|
+
item.logical_resource_id,
|
196
|
+
item.physical_resource_id,
|
197
|
+
item.resource_type,
|
198
|
+
item.timestamp,
|
199
|
+
case item.resource_status
|
200
|
+
when 'CREATE_FAILED' then
|
201
|
+
item.resource_status.color :red
|
202
|
+
when 'ROLLBACK_IN_PROGRESS' then
|
203
|
+
item.resource_status.color :red
|
204
|
+
when 'ROLLBACK_COMPLETE' then
|
205
|
+
item.resource_status.color :red
|
206
|
+
when 'CREATE_COMPLETE' then
|
207
|
+
item.resource_status.color :green
|
208
|
+
when 'DELETE_COMPLETE' then
|
209
|
+
item.resource_status.color :gray
|
210
|
+
else
|
211
|
+
item.resource_status.color :orange
|
212
|
+
end,
|
213
|
+
item.resource_status_reason,
|
214
|
+
item.description,
|
215
|
+
]
|
216
|
+
end
|
217
|
+
table = Terminal::Table.new headings: %w(L-name P-name Type Timestamp Status Reason Desc), rows: rows
|
218
|
+
puts table
|
219
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ex
|
220
|
+
puts ex.message
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def report_event
|
227
|
+
@sequence.each do |stacks|
|
228
|
+
stacks.each do |name|
|
229
|
+
puts(('stack ' + name).color(:green))
|
230
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
231
|
+
begin
|
232
|
+
rows = @cfn_client.describe_stack_events(
|
233
|
+
stack_name: @stacks[name].name
|
234
|
+
).stack_events.map do |item|
|
235
|
+
[
|
236
|
+
item.resource_type,
|
237
|
+
item.timestamp,
|
238
|
+
case item.resource_status
|
239
|
+
when 'CREATE_FAILED' then
|
240
|
+
item.resource_status.color :red
|
241
|
+
when 'ROLLBACK_IN_PROGRESS' then
|
242
|
+
item.resource_status.color :red
|
243
|
+
when 'ROLLBACK_COMPLETE' then
|
244
|
+
item.resource_status.color :red
|
245
|
+
when 'CREATE_COMPLETE' then
|
246
|
+
item.resource_status.color :green
|
247
|
+
when 'DELETE_COMPLETE' then
|
248
|
+
item.resource_status.color :gray
|
249
|
+
else
|
250
|
+
item.resource_status.color :orange
|
251
|
+
end,
|
252
|
+
item.resource_status_reason]
|
253
|
+
end
|
254
|
+
table = Terminal::Table.new headings: %w(Type Time Status Reason), rows: rows
|
255
|
+
puts table
|
256
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ex
|
257
|
+
puts ex.message
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def destroy
|
264
|
+
@sequence.reverse_each do |stacks|
|
265
|
+
stacks.each do |name|
|
266
|
+
puts(('deleting ' + name).color(:green))
|
267
|
+
puts('Name :' + @stacks[name].name) if @option[:v]
|
268
|
+
@cfn_client.delete_stack(
|
269
|
+
stack_name: @stacks[name].name
|
270
|
+
)
|
271
|
+
end
|
272
|
+
stacks.each do |name|
|
273
|
+
@cfn_client.wait_until(
|
274
|
+
:stack_delete_complete,
|
275
|
+
stack_name: @stacks[name].name
|
276
|
+
)
|
277
|
+
puts(('deleted ' + name).color(:green))
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
def create_stack(data)
|
285
|
+
@stacks = {}
|
286
|
+
data['stacks'].each do |name, properties|
|
287
|
+
@stacks[name] = Stack.new(name, properties, @option)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def create_sequence
|
292
|
+
@sequence = []
|
293
|
+
names_of_upprocessed_stack = @stacks.keys
|
294
|
+
names_of_processed_stack = []
|
295
|
+
until names_of_upprocessed_stack.empty?
|
296
|
+
names = names_of_upprocessed_stack.select do |name|
|
297
|
+
@stacks[name].depends.all? do |depend_name|
|
298
|
+
names_of_processed_stack.include? depend_name
|
299
|
+
end
|
300
|
+
end
|
301
|
+
raise 'There are cyclic dependency.' if names.empty?
|
302
|
+
names_of_processed_stack += names
|
303
|
+
names_of_upprocessed_stack -= names
|
304
|
+
@sequence.push names
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
data/lib/cfndk.rb
ADDED
data/sample/cfndk.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
stacks:
|
2
|
+
Network:
|
3
|
+
template_file: network/network.yaml
|
4
|
+
parameter_input: network/prod.json
|
5
|
+
parameters:
|
6
|
+
VpcName: Prod<%= append_uuid %>
|
7
|
+
InternalDnsName: prod<%= append_uuid %>.local
|
8
|
+
timeout_in_minutes: 8
|
9
|
+
Iam:
|
10
|
+
template_file: iam/iam.yaml
|
11
|
+
parameter_input: iam/prod.json
|
12
|
+
parameters:
|
13
|
+
WebRoleName: WebRole<%= append_uuid %>
|
14
|
+
WebInstanceProfileName: WebInstanceProfile<%= append_uuid %>
|
15
|
+
capabilities:
|
16
|
+
- CAPABILITY_IAM
|
17
|
+
- CAPABILITY_NAMED_IAM
|
18
|
+
timeout_in_minutes: 3
|
19
|
+
Sg:
|
20
|
+
template_file: sg/sg.yaml
|
21
|
+
parameter_input: sg/prod.json
|
22
|
+
parameters:
|
23
|
+
depends:
|
24
|
+
- Network
|
25
|
+
Web:
|
26
|
+
template_file: web/web.yaml
|
27
|
+
parameter_input: web/prod.json
|
28
|
+
parameters:
|
29
|
+
depends:
|
30
|
+
- Sg
|
31
|
+
- Iam
|
32
|
+
timeout_in_minutes: 2
|
33
|
+
Db:
|
34
|
+
template_file: db/db.yaml
|
35
|
+
parameter_input: db/prod.json
|
36
|
+
parameters:
|
37
|
+
depends:
|
38
|
+
- Sg
|
39
|
+
timeout_in_minutes: 30
|