cfndk 0.0.7 → 0.1.2

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.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +79 -0
  3. data/.gitignore +1 -1
  4. data/.rspec +2 -0
  5. data/.rspec_parallel +6 -0
  6. data/.simplecov +9 -0
  7. data/Gemfile +11 -1
  8. data/Gemfile.lock +815 -0
  9. data/README.md +269 -76
  10. data/bin/cfndk +3 -18
  11. data/cfndk.gemspec +15 -6
  12. data/docker/Dockerfile +8 -0
  13. data/docker/build.sh +3 -0
  14. data/docker/cfndk.sh +14 -0
  15. data/lib/cfndk.rb +36 -0
  16. data/lib/cfndk/change_set_command.rb +103 -0
  17. data/lib/cfndk/command.rb +125 -119
  18. data/lib/cfndk/config_file_loadable.rb +13 -0
  19. data/lib/cfndk/credential_provider_chain.rb +12 -42
  20. data/lib/cfndk/credential_resolvable.rb +10 -0
  21. data/lib/cfndk/diff.rb +38 -0
  22. data/lib/cfndk/global_config.rb +46 -0
  23. data/lib/cfndk/key_pair.rb +66 -14
  24. data/lib/cfndk/key_pair_command.rb +60 -0
  25. data/lib/cfndk/key_pairs.rb +22 -5
  26. data/lib/cfndk/logger.rb +12 -3
  27. data/lib/cfndk/stack.rb +427 -126
  28. data/lib/cfndk/stack_command.rb +128 -0
  29. data/lib/cfndk/stacks.rb +48 -22
  30. data/lib/cfndk/subcommand_help_returnable.rb +16 -0
  31. data/lib/cfndk/template_packager.rb +210 -0
  32. data/lib/cfndk/uuid.rb +10 -0
  33. data/lib/cfndk/version.rb +1 -1
  34. data/skel/cfndk.yml +4 -0
  35. data/spec/.gitignore +1 -0
  36. data/spec/cfndk_change_set_create_spec.rb +436 -0
  37. data/spec/cfndk_change_set_destroy_spec.rb +160 -0
  38. data/spec/cfndk_change_set_execute_spec.rb +179 -0
  39. data/spec/cfndk_change_set_report_spec.rb +107 -0
  40. data/spec/cfndk_change_set_spec.rb +37 -0
  41. data/spec/cfndk_create_spec.rb +504 -0
  42. data/spec/cfndk_destroy_spec.rb +148 -0
  43. data/spec/cfndk_keypiar_spec.rb +397 -0
  44. data/spec/cfndk_report_spec.rb +164 -0
  45. data/spec/cfndk_spec.rb +103 -0
  46. data/spec/cfndk_stack_create_spec.rb +814 -0
  47. data/spec/cfndk_stack_destroy_spec.rb +225 -0
  48. data/spec/cfndk_stack_report_spec.rb +181 -0
  49. data/spec/cfndk_stack_spec.rb +133 -0
  50. data/spec/cfndk_stack_update_spec.rb +553 -0
  51. data/spec/fixtures/big_vpc.yaml +533 -0
  52. data/spec/fixtures/empty_resource.yaml +2 -0
  53. data/spec/fixtures/iam.json +8 -0
  54. data/spec/fixtures/iam.yaml +38 -0
  55. data/spec/fixtures/iam_different.json +8 -0
  56. data/spec/fixtures/invalid_vpc.yaml +21 -0
  57. data/spec/fixtures/lambda_function/index.js +4 -0
  58. data/spec/fixtures/lambda_function/lambda_function.json +4 -0
  59. data/spec/fixtures/lambda_function/lambda_function.yaml +28 -0
  60. data/spec/fixtures/nested_stack.json +35 -0
  61. data/spec/fixtures/nested_stack.yaml +20 -0
  62. data/spec/fixtures/serverless_function/index.js +4 -0
  63. data/spec/fixtures/serverless_function/serverless_function.json +4 -0
  64. data/spec/fixtures/serverless_function/serverless_function.yaml +21 -0
  65. data/spec/fixtures/sg.json +8 -0
  66. data/spec/fixtures/sg.yaml +27 -0
  67. data/spec/fixtures/sg_different.yaml +22 -0
  68. data/spec/fixtures/stack.json +8 -0
  69. data/spec/fixtures/stack.template.json +39 -0
  70. data/spec/fixtures/stack.yaml +22 -0
  71. data/spec/fixtures/vpc.json +8 -0
  72. data/spec/fixtures/vpc.template.json +40 -0
  73. data/spec/fixtures/vpc.yaml +21 -0
  74. data/spec/fixtures/vpc_different.yaml +21 -0
  75. data/spec/spec_helper.rb +14 -0
  76. data/spec/support/aruba.rb +6 -0
  77. data/vagrant/Vagrantfile +89 -0
  78. metadata +259 -31
@@ -0,0 +1,13 @@
1
+ module CFnDK
2
+ module ConfigFileLoadable
3
+ private
4
+
5
+ def load_config_data(options)
6
+ raise "File does not exist. #{options[:config_path]}" unless File.file?(options[:config_path])
7
+ data = open(options[:config_path], 'r') { |f| YAML.load(f) }
8
+ return data if data
9
+ CFnDK.logger.error "File is empty. #{options[:config_path]}".color(:red)
10
+ nil
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,14 @@
1
1
  module CFnDK
2
2
  class CredentialProviderChain
3
- def initialize(config = nil)
4
- @config = config
3
+ def initialize(profile = nil)
4
+ @profile = profile
5
5
  end
6
6
 
7
7
  def resolve
8
8
  providers.each do |method_name, options|
9
- provider = send(method_name, options.merge(config: @config))
9
+ CFnDK.logger.debug "resolving: #{method_name}"
10
+ provider = send(method_name, options)
11
+ CFnDK.logger.debug "resolved: #{method_name}" if provider && provider.set?
10
12
  return provider if provider && provider.set?
11
13
  end
12
14
  nil
@@ -16,30 +18,17 @@ module CFnDK
16
18
 
17
19
  def providers
18
20
  [
19
- [:static_credentials, {}],
20
21
  [:env_credentials, {}],
21
22
  [:assume_role_credentials, {}],
22
- [:shared_credentials, {}],
23
- [:process_credentials, {}],
23
+ [:shared_credentials, {profile: @profile}],
24
24
  [:instance_profile_credentials, {
25
- retries: @config ? @config.instance_profile_credentials_retries : 0,
26
- http_open_timeout: @config ? @config.instance_profile_credentials_timeout : 1,
27
- http_read_timeout: @config ? @config.instance_profile_credentials_timeout : 1,
25
+ retries: 0,
26
+ http_open_timeout: 1,
27
+ http_read_timeout: 1,
28
28
  }],
29
29
  ]
30
30
  end
31
31
 
32
- def static_credentials(options)
33
- if options[:config]
34
- ::Aws::Credentials.new(
35
- options[:config].access_key_id,
36
- options[:config].secret_access_key,
37
- options[:config].session_token)
38
- else
39
- nil
40
- end
41
- end
42
-
43
32
  def env_credentials(options)
44
33
  key = %w(AWS_ACCESS_KEY_ID AMAZON_ACCESS_KEY_ID AWS_ACCESS_KEY)
45
34
  secret = %w(AWS_SECRET_ACCESS_KEY AMAZON_SECRET_ACCESS_KEY AWS_SECRET_KEY)
@@ -55,8 +44,8 @@ module CFnDK
55
44
  end
56
45
 
57
46
  def shared_credentials(options)
58
- if options[:config]
59
- ::Aws::SharedCredentials.new(profile_name: options[:config].profile)
47
+ if options[:profile]
48
+ ::Aws::SharedCredentials.new(profile_name: options[:profile])
60
49
  else
61
50
  ::Aws::SharedCredentials.new(
62
51
  profile_name: ENV['AWS_PROFILE'].nil? ? 'default' : ENV['AWS_PROFILE'])
@@ -65,29 +54,10 @@ module CFnDK
65
54
  nil
66
55
  end
67
56
 
68
- def process_credentials(options)
69
- profile_name = options[:config].profile if options[:config]
70
- profile_name ||= ENV['AWS_PROFILE'].nil? ? 'default' : ENV['AWS_PROFILE']
71
-
72
- config = ::Aws.shared_config
73
- if config.config_enabled? && process_provider = config.credentials_process(profile_name)
74
- ::Aws::ProcessCredentials.new(process_provider)
75
- else
76
- nil
77
- end
78
- rescue ::Aws::Errors::NoSuchProfileError
79
- nil
80
- end
81
-
82
57
  def assume_role_credentials(options)
83
58
  if ::Aws.shared_config.config_enabled?
84
59
  profile = nil
85
60
  region = nil
86
- if options[:config]
87
- profile = options[:config].profile
88
- region = options[:config].region
89
- assume_role_with_profile(options[:config].profile, options[:config].region)
90
- end
91
61
  assume_role_with_profile(profile, region)
92
62
  else
93
63
  nil
@@ -106,7 +76,7 @@ module CFnDK
106
76
  ::Aws.shared_config.assume_role_credentials_from_config(
107
77
  profile: prof,
108
78
  region: region,
109
- chain_config: @config
79
+ chain_config: nil
110
80
  )
111
81
  end
112
82
  end
@@ -0,0 +1,10 @@
1
+ module CFnDK
2
+ module CredentialResolvable
3
+ private
4
+
5
+ def resolve_credential(data, option)
6
+ global_config = CFnDK::GlobalConfig.new(data, option)
7
+ CFnDK::CredentialProviderChain.new(global_config.profile).resolve
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ module CFnDK
2
+ FORMAT = :unified
3
+ LINES = 3
4
+
5
+ def self.diff(data_old, data_new)
6
+ result = ''
7
+
8
+ file_length_difference = 0
9
+
10
+ data_old = data_old.split($/).map { |e| e.chomp }
11
+ data_new = data_new.split($/).map { |e| e.chomp }
12
+
13
+ diffs = Diff::LCS.diff(data_old, data_new)
14
+ diffs = nil if diffs.empty?
15
+
16
+ return '' unless diffs
17
+
18
+ oldhunk = hunk = nil
19
+
20
+ diffs.each do |piece|
21
+ begin
22
+ hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, LINES, file_length_difference)
23
+ file_length_difference = hunk.file_length_difference
24
+
25
+ next unless oldhunk
26
+ next if LINES.positive? and hunk.merge(oldhunk)
27
+
28
+ result << oldhunk.diff(FORMAT) << "\n"
29
+ ensure
30
+ oldhunk = hunk
31
+ end
32
+ end
33
+
34
+ last = oldhunk.diff(FORMAT)
35
+ last << "\n" if last.respond_to?(:end_with?) && !last.end_with?("\n")
36
+ result << last
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ module CFnDK
2
+ class GlobalConfig
3
+ attr_reader :timeout_in_minutes, :s3_template_bucket, :s3_template_hash, :region, :role_arn, :package, :profile, :pre_command, :post_command
4
+ def initialize(data, option)
5
+ @timeout_in_minutes = 1
6
+ @s3_template_bucket = 'cfndk-templates'
7
+ @s3_template_hash = Uuid.instance.uuid
8
+ @region = ENV['AWS_REGION'] || 'us-east-1'
9
+ @package = false
10
+ @profile = ENV['AWS_PROFILE'] || nil
11
+ return unless data['global'].is_a?(Hash)
12
+ @timeout_in_minutes = data['global']['timeout_in_minutes'] || 1
13
+ @s3_template_bucket = data['global']['s3_template_bucket'] || 'cfndk-templates'
14
+ @region = data['global']['region'] || ENV['AWS_REGION'] || 'us-east-1'
15
+ @package = data['global']['package'] === 'true' ? true : false
16
+ @role_arn = data['global']['role_arn'] || nil
17
+ @profile = ENV['AWS_PROFILE'] || data['global']['default_profile'] || nil
18
+ @pre_command = data['global']['pre_command'] || nil
19
+ @post_command = data['global']['post_command'] || nil
20
+ end
21
+
22
+ def pre_command_execute
23
+ if @pre_command
24
+ CFnDK.logger.info(('execute global pre command: ' + @pre_command).color(:green))
25
+ IO.popen(@pre_command, :err => [:child, :out]) do |io|
26
+ io.each_line do |line|
27
+ CFnDK.logger.info((line).color(:green))
28
+ end
29
+ end
30
+ raise 'global pre command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @pre_command if $?.exitstatus != 0
31
+ end
32
+ end
33
+
34
+ def post_command_execute
35
+ if @post_command
36
+ CFnDK.logger.info(('execute global post command: ' + @post_command).color(:green))
37
+ IO.popen(@post_command, :err => [:child, :out]) do |io|
38
+ io.each_line do |line|
39
+ CFnDK.logger.info((line).color(:green))
40
+ end
41
+ end
42
+ raise 'global post command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @post_command if $?.exitstatus != 0
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,43 +1,95 @@
1
1
  module CFnDK
2
2
  class KeyPair
3
- attr_reader :key_file
4
- def initialize(name, data, option, credentials)
3
+ attr_reader :key_file, :enabled, :pre_command, :post_command
4
+ def initialize(name, data, option, global_config, credentials)
5
+ @global_config = global_config
5
6
  @name = name
6
- @key_file = nil
7
- @key_file = data['key_file'] || nil if data
7
+ data = {} unless data
8
+ @key_file = data['key_file'] || nil
9
+ @region = data['region'] || @global_config.region
10
+ @pre_command = data['pre_command'] || nil
11
+ @post_command = data['post_command'] || nil
12
+ @enabled = true
13
+ @enabled = false if data['enabled'] === false
8
14
  @option = option
9
- @logger = CFnDK::Logger.new(option)
10
- @client = Aws::EC2::Client.new(credentials: credentials)
15
+ @client = Aws::EC2::Client.new(credentials: credentials, region: @region)
11
16
  end
12
17
 
13
18
  def create
14
- @logger.info(('creating keypair: ' + name).color(:green))
19
+ return unless @enabled
20
+ CFnDK.logger.info(('creating keypair: ' + name).color(:green))
15
21
  key_pair = @client.create_key_pair(
16
22
  key_name: name
17
23
  )
18
- @logger.info(('created keypair: ' + name).color(:green))
24
+ CFnDK.logger.info(('created keypair: ' + name).color(:green))
19
25
 
20
26
  create_key_file(key_pair)
21
27
  end
22
28
 
23
29
  def destroy
24
- @logger.info(('deleting keypair: ' + name).color(:green))
25
- @client.delete_key_pair(
26
- key_name: name
27
- )
28
- @logger.info(('deleted keypair: ' + name).color(:green))
30
+ return unless @enabled
31
+ if exists?
32
+ CFnDK.logger.info(('deleting keypair: ' + name).color(:green))
33
+ @client.delete_key_pair(
34
+ key_name: name
35
+ )
36
+ CFnDK.logger.info(('deleted keypair: ' + name).color(:green))
37
+ else
38
+ CFnDK.logger.info(('do not delete keypair: ' + name).color(:red))
39
+ end
40
+ end
41
+
42
+ def exists?
43
+ !@client.describe_key_pairs(
44
+ key_names: [
45
+ name,
46
+ ]
47
+ ).key_pairs.empty?
48
+ rescue Aws::EC2::Errors::InvalidKeyPairNotFound
49
+ false
29
50
  end
30
51
 
31
52
  def name
32
53
  [@name, @option[:uuid]].compact.join('-')
33
54
  end
34
55
 
56
+ def original_name
57
+ @name
58
+ end
59
+
60
+ def pre_command_execute
61
+ return unless @enabled
62
+ if @pre_command
63
+ CFnDK.logger.info(('execute pre command: ' + @pre_command).color(:green))
64
+ IO.popen(@pre_command, :err => [:child, :out]) do |io|
65
+ io.each_line do |line|
66
+ CFnDK.logger.info((line).color(:green))
67
+ end
68
+ end
69
+ raise 'pre command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @pre_command if $?.exitstatus != 0
70
+ end
71
+ end
72
+
73
+ def post_command_execute
74
+ return unless @enabled
75
+ if @post_command
76
+ CFnDK.logger.info(('execute post command: ' + @post_command).color(:green))
77
+ IO.popen(@post_command, :err => [:child, :out]) do |io|
78
+ io.each_line do |line|
79
+ CFnDK.logger.info((line).color(:green))
80
+ end
81
+ end
82
+ raise 'post command is error. status: ' + $?.exitstatus.to_s + ' command: ' + @post_command if $?.exitstatus != 0
83
+ end
84
+ end
85
+
35
86
  private
36
87
 
37
88
  def create_key_file(key_pair)
38
89
  return unless @key_file
39
90
  key_file = CFnDK::ErbString.new(@key_file, @option).value
40
- @logger.info(('create key file: ' + key_file).color(:green))
91
+ CFnDK.logger.info(('create key file: ' + key_file).color(:green))
92
+ FileUtils.mkdir_p(File.dirname(key_file))
41
93
  File.write(key_file, key_pair.key_material)
42
94
  end
43
95
  end
@@ -0,0 +1,60 @@
1
+ module CFnDK
2
+ class KeyPairCommand < Thor
3
+ include SubcommandHelpReturnable
4
+ include ConfigFileLoadable
5
+ include CredentialResolvable
6
+
7
+ class_option :verbose, type: :boolean, aliases: 'v', desc: 'More verbose output.'
8
+ class_option :color, type: :boolean, default: true, desc: 'Use colored output'
9
+ class_option :config_path, type: :string, aliases: 'c', default: "#{Dir.getwd}/cfndk.yml", desc: 'The configuration file to use'
10
+ class_option :uuid, type: :string, aliases: 'u', default: ENV['CFNDK_UUID'] || nil, desc: 'Use UUID'
11
+ class_option :keypair_names, type: :array, desc: 'Target keypair names'
12
+
13
+ desc 'create', 'Create keypair'
14
+ option :properties, type: :hash, aliases: 'p', default: {}, desc: 'Set property'
15
+ def create
16
+ CFnDK.logger.info 'create...'.color(:green)
17
+ data = load_config_data(options)
18
+ credentials = resolve_credential(data, options)
19
+ global_config = CFnDK::GlobalConfig.new(data, options)
20
+ keypairs = CFnDK::KeyPairs.new(data, options, credentials)
21
+
22
+ global_config.pre_command_execute
23
+ keypairs.pre_command_execute
24
+ keypairs.create
25
+ keypairs.post_command_execute
26
+ global_config.post_command_execute
27
+ return 0
28
+ rescue => e
29
+ CFnDK.logger.error "#{e.class}: #{e.message}".color(:red)
30
+ e.backtrace_locations.each do |line|
31
+ CFnDK.logger.debug line
32
+ end
33
+ return 1
34
+ end
35
+
36
+ desc 'destroy', 'Destroy keypair'
37
+ option :force, type: :boolean, aliases: 'f', default: false, desc: 'Say yes to all prompts for confirmation'
38
+ def destroy
39
+ CFnDK.logger.info 'destroy...'.color(:green)
40
+ data = load_config_data(options)
41
+ credentials = resolve_credential(data, options)
42
+
43
+ keypairs = CFnDK::KeyPairs.new(data, options, credentials)
44
+
45
+ if options[:force] || yes?('Are you sure you want to destroy? (y/n)', :yellow)
46
+ keypairs.destroy
47
+ return 0
48
+ else
49
+ CFnDK.logger.info 'destroy command was canceled'.color(:green)
50
+ return 2
51
+ end
52
+ rescue => e
53
+ CFnDK.logger.error "#{e.class}: #{e.message}".color(:red)
54
+ e.backtrace_locations.each do |line|
55
+ CFnDK.logger.debug line
56
+ end
57
+ return 1
58
+ end
59
+ end
60
+ end
@@ -3,17 +3,34 @@ module CFnDK
3
3
  def initialize(data, option, credentials)
4
4
  @option = option
5
5
  @credentials = credentials
6
+ @global_config = CFnDK::GlobalConfig.new(data, option)
6
7
  prepare_keypairs(data)
7
8
  end
8
9
 
9
10
  def create
10
- return if @option[:stack_names].instance_of?(Array)
11
- @keypairs.each_value(&:create)
11
+ @keypairs.each_value do |keypair|
12
+ next if @option[:keypair_names].instance_of?(Array) && !@option[:keypair_names].include?(keypair.original_name)
13
+ keypair.create
14
+ end
12
15
  end
13
16
 
14
17
  def destroy
15
- return if @option[:stack_names].instance_of?(Array)
16
- @keypairs.each_value(&:destroy)
18
+ @keypairs.each_value do |keypair|
19
+ next if @option[:keypair_names].instance_of?(Array) && !@option[:keypair_names].include?(keypair.original_name)
20
+ keypair.destroy
21
+ end
22
+ end
23
+
24
+ def pre_command_execute
25
+ @keypairs.each_value do |keypair|
26
+ keypair.pre_command_execute
27
+ end
28
+ end
29
+
30
+ def post_command_execute
31
+ @keypairs.each_value do |keypair|
32
+ keypair.post_command_execute
33
+ end
17
34
  end
18
35
 
19
36
  private
@@ -22,7 +39,7 @@ module CFnDK
22
39
  @keypairs = {}
23
40
  return unless data['keypairs']
24
41
  data['keypairs'].each do |name, properties|
25
- @keypairs[name] = KeyPair.new(name, properties, @option, @credentials)
42
+ @keypairs[name] = KeyPair.new(name, properties, @option, @global_config, @credentials)
26
43
  end
27
44
  end
28
45
  end
@@ -1,8 +1,17 @@
1
1
  module CFnDK
2
- class Logger < Logger
3
- def initialize(option)
2
+ def self.logger
3
+ @logger = CFnDKLogger.new({}) if @logger.nil?
4
+ @logger
5
+ end
6
+
7
+ def self.logger=(logger)
8
+ @logger = logger
9
+ end
10
+
11
+ class CFnDKLogger < Logger
12
+ def initialize(options)
4
13
  super(STDOUT)
5
- self.level = Logger::INFO unless option[:v]
14
+ self.level = Logger::INFO unless options[:verbose]
6
15
  self.formatter = proc { |severity, datetime, progname, message|
7
16
  message.to_s.split(/\n/).map do |line|
8
17
  "#{datetime} #{severity} #{line}\n"