cfndk 0.0.7 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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"