cfndk 0.0.1
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/.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
|