aws-cft-tools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.gitignore +52 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +19 -0
  6. data/.travis.yml +5 -0
  7. data/.yardopts +1 -0
  8. data/BEST-PRACTICES.md +136 -0
  9. data/CONTRIBUTING.md +38 -0
  10. data/Gemfile +8 -0
  11. data/LICENSE +15 -0
  12. data/README.md +118 -0
  13. data/Rakefile +17 -0
  14. data/USAGE.adoc +404 -0
  15. data/aws-cft-tools.gemspec +53 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/code.json +24 -0
  19. data/exe/aws-cft +176 -0
  20. data/lib/aws-cft-tools.rb +3 -0
  21. data/lib/aws_cft_tools.rb +31 -0
  22. data/lib/aws_cft_tools/aws_enumerator.rb +55 -0
  23. data/lib/aws_cft_tools/change.rb +66 -0
  24. data/lib/aws_cft_tools/client.rb +84 -0
  25. data/lib/aws_cft_tools/client/base.rb +40 -0
  26. data/lib/aws_cft_tools/client/cft.rb +93 -0
  27. data/lib/aws_cft_tools/client/cft/changeset_management.rb +109 -0
  28. data/lib/aws_cft_tools/client/cft/stack_management.rb +85 -0
  29. data/lib/aws_cft_tools/client/ec2.rb +136 -0
  30. data/lib/aws_cft_tools/client/templates.rb +84 -0
  31. data/lib/aws_cft_tools/deletion_change.rb +43 -0
  32. data/lib/aws_cft_tools/dependency_tree.rb +109 -0
  33. data/lib/aws_cft_tools/dependency_tree/nodes.rb +71 -0
  34. data/lib/aws_cft_tools/dependency_tree/variables.rb +37 -0
  35. data/lib/aws_cft_tools/errors.rb +25 -0
  36. data/lib/aws_cft_tools/runbook.rb +166 -0
  37. data/lib/aws_cft_tools/runbook/report.rb +30 -0
  38. data/lib/aws_cft_tools/runbooks.rb +16 -0
  39. data/lib/aws_cft_tools/runbooks/common/changesets.rb +30 -0
  40. data/lib/aws_cft_tools/runbooks/common/templates.rb +38 -0
  41. data/lib/aws_cft_tools/runbooks/deploy.rb +107 -0
  42. data/lib/aws_cft_tools/runbooks/deploy/reporting.rb +50 -0
  43. data/lib/aws_cft_tools/runbooks/deploy/stacks.rb +109 -0
  44. data/lib/aws_cft_tools/runbooks/deploy/templates.rb +37 -0
  45. data/lib/aws_cft_tools/runbooks/deploy/threading.rb +37 -0
  46. data/lib/aws_cft_tools/runbooks/diff.rb +28 -0
  47. data/lib/aws_cft_tools/runbooks/diff/context.rb +86 -0
  48. data/lib/aws_cft_tools/runbooks/diff/context/reporting.rb +87 -0
  49. data/lib/aws_cft_tools/runbooks/hosts.rb +43 -0
  50. data/lib/aws_cft_tools/runbooks/images.rb +43 -0
  51. data/lib/aws_cft_tools/runbooks/init.rb +86 -0
  52. data/lib/aws_cft_tools/runbooks/retract.rb +69 -0
  53. data/lib/aws_cft_tools/runbooks/retract/templates.rb +44 -0
  54. data/lib/aws_cft_tools/runbooks/stacks.rb +43 -0
  55. data/lib/aws_cft_tools/stack.rb +83 -0
  56. data/lib/aws_cft_tools/template.rb +177 -0
  57. data/lib/aws_cft_tools/template/dsl_context.rb +14 -0
  58. data/lib/aws_cft_tools/template/file_system.rb +62 -0
  59. data/lib/aws_cft_tools/template/metadata.rb +144 -0
  60. data/lib/aws_cft_tools/template/properties.rb +129 -0
  61. data/lib/aws_cft_tools/template_set.rb +120 -0
  62. data/lib/aws_cft_tools/template_set/array_methods.rb +63 -0
  63. data/lib/aws_cft_tools/template_set/closure.rb +77 -0
  64. data/lib/aws_cft_tools/template_set/dependencies.rb +55 -0
  65. data/lib/aws_cft_tools/template_set/each_slice_state.rb +58 -0
  66. data/lib/aws_cft_tools/version.rb +8 -0
  67. data/rubycritic.reek +3 -0
  68. metadata +321 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diffy'
4
+
5
+ module AwsCftTools
6
+ module Runbooks
7
+ ##
8
+ # Images - report on available AMIs
9
+ #
10
+ # @example
11
+ # % aws-cli diff -e QA # list the differences between deployed and local definitions for QA
12
+ # % aws-cli diff -e QA -r App # list the differences between deployed and local definitions for
13
+ # # the App role in QA
14
+ #
15
+ class Diff < Runbook
16
+ require_relative 'diff/context'
17
+
18
+ def run
19
+ context = Context.new(client.stacks, client.templates, options)
20
+
21
+ # now match them up
22
+ context.report_on_missing_templates
23
+ context.report_on_missing_stacks
24
+ context.report_on_differences
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diffy'
4
+
5
+ module AwsCftTools
6
+ module Runbooks
7
+ class Diff
8
+ ##
9
+ # The context of stacks and templates for a Diff report.
10
+ #
11
+ class Context
12
+ attr_reader :stacks, :templates, :options
13
+
14
+ ##
15
+ # The options provided to the +diff+ command to build template diffs.
16
+ #
17
+ DIFF_OPTIONS = %w[-w -U5 -t].freeze
18
+
19
+ require_relative '../common/templates'
20
+ require_relative 'context/reporting'
21
+
22
+ include Common::Templates
23
+ include Context::Reporting
24
+
25
+ ##
26
+ # @param stacks [Array<AwsCftTools::Stack>]
27
+ # @param templates [AwsCftTools::TemplateSet]
28
+ # @param options [Hash]
29
+ def initialize(stacks, templates, options = {})
30
+ @stacks = build_map(stacks)
31
+ @templates = build_map(templates)
32
+ @options = options
33
+ end
34
+
35
+ ##
36
+ # Reports out stacks that do not have corresponding templates.
37
+ #
38
+ def report_on_missing_templates
39
+ output_report_on_missing_templates(stacks.keys - templates.keys)
40
+ end
41
+
42
+ ##
43
+ # Reports out templates that do not have corresponding stacks.
44
+ #
45
+ def report_on_missing_stacks
46
+ output_report_on_missing_stacks(templates.keys - stacks.keys)
47
+ end
48
+
49
+ ##
50
+ # Reports on the differences in the template bodies between the set of templates and the
51
+ # deployed stacks.
52
+ #
53
+ def report_on_differences
54
+ # these are stacks with templates
55
+ output_report_on_differences(build_diffs)
56
+ end
57
+
58
+ private
59
+
60
+ def build_map(list)
61
+ list.each_with_object({}) do |thing, map|
62
+ map[thing.name] = thing
63
+ end
64
+ end
65
+
66
+ def build_diffs
67
+ stacks
68
+ .keys
69
+ .sort
70
+ .select { |fn| templates[fn] }
71
+ .each_with_object({}) do |name, acc|
72
+ acc[name] = build_diff(stacks[name], templates[name])
73
+ end
74
+ end
75
+
76
+ def build_diff(stack, template)
77
+ output_type = options[:colorize] ? :color : :text
78
+ Diffy::Diff.new(
79
+ stack.template_source, template.template_source_for_aws,
80
+ include_diff_info: true, diff: DIFF_OPTIONS
81
+ ).to_s(output_type)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diffy'
4
+
5
+ module AwsCftTools
6
+ module Runbooks
7
+ class Diff
8
+ class Context
9
+ ##
10
+ # Reporting functions for the Diff context
11
+ #
12
+ module Reporting
13
+ def output_report_on_missing_templates(missing)
14
+ return if missing.empty?
15
+ puts "\nStacks with no template:\n #{missing.sort.join("\n ")}\n"
16
+ end
17
+
18
+ def output_report_on_missing_stacks(missing)
19
+ missing = template_filenames(missing)
20
+ return if missing.empty?
21
+ puts "\nUndeployed templates:\n #{missing.sort.join("\n ")}\n"
22
+ end
23
+
24
+ def output_report_on_differences(diffs)
25
+ report_on_blank_diffs(diffs)
26
+ report_on_real_diffs(diffs)
27
+ end
28
+
29
+ private
30
+
31
+ def report_on_blank_diffs(diffs)
32
+ no_diffs = diffs.keys.select { |name| diffs[name].match(/\A\s*\Z/) }
33
+ return if no_diffs.empty?
34
+
35
+ puts "\nTemplates with no changes:\n #{template_filenames(no_diffs).sort.join("\n ")}\n"
36
+ end
37
+
38
+ def report_on_real_diffs(diffs)
39
+ real_diffs = diffs.keys.reject { |name| diffs[name].match(/\A\s*\Z/) }
40
+
41
+ return if real_diffs.empty?
42
+
43
+ if options[:verbose]
44
+ report_full_diffs(real_diffs, diffs)
45
+ else
46
+ report_list_of_diffs(real_diffs)
47
+ end
48
+ end
49
+
50
+ def report_full_diffs(names, diffs)
51
+ names.sort.each do |name|
52
+ report_pos_diff(templates[name].filename.to_s, diffs[name])
53
+ end
54
+ end
55
+
56
+ def report_list_of_diffs(real_diffs)
57
+ puts "\nTemplates with changes:\n #{template_filenames(real_diffs).sort.join("\n ")}\n"
58
+ end
59
+
60
+ def template_filenames(stack_names)
61
+ set = templates.values_at(*stack_names).map(&:filename).map(&:to_s)
62
+ allowed = options[:templates]
63
+ if allowed && allowed.any?
64
+ set & allowed
65
+ else
66
+ set
67
+ end
68
+ end
69
+
70
+ def report_pos_diff(filename, diff)
71
+ return unless template_in_consideration(options[:templates], filename)
72
+ puts diff_with_filenames(diff, filename), ''
73
+ end
74
+
75
+ def diff_with_filenames(diff, filename)
76
+ diff.sub(%r{--- /.*$}, "--- #{filename} @ AWS")
77
+ .sub(%r{\+\+\+ /.*$}, "+++ #{filename} @ Local")
78
+ end
79
+
80
+ def template_in_consideration(list, filename)
81
+ !list || list.empty? || list.include?(filename)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsCftTools
4
+ module Runbooks
5
+ ##
6
+ # Hosts - report on EC2 instances
7
+ #
8
+ # @example
9
+ # % aws-cli hosts # list all known EC2 instances
10
+ # % aws-cli hosts -e QA # list all known EC2 instances in the QA environment
11
+ # % aws-cli hosts -r Bastion -e QA # list all known Bastion hosts in the QA environment
12
+ #
13
+ class Hosts < Runbook::Report
14
+ ###
15
+ # @return [Array<OpenStruct>]
16
+ #
17
+ def items
18
+ client.instances.sort_by(&method(:sort_key))
19
+ end
20
+
21
+ ###
22
+ # @return [Array<String>]
23
+ #
24
+ def columns
25
+ %w[public_ip private_ip] + environment_column + role_column + ['instance']
26
+ end
27
+
28
+ private
29
+
30
+ def sort_key(host)
31
+ [host.environment, host.role, host.ip].compact
32
+ end
33
+
34
+ def environment_column
35
+ options[:environment] ? [] : ['environment']
36
+ end
37
+
38
+ def role_column
39
+ options[:role] ? [] : ['role']
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsCftTools
4
+ module Runbooks
5
+ ##
6
+ # Images - report on available AMIs
7
+ #
8
+ # @example
9
+ # % aws-cli images # list all known AMIs
10
+ # % aws-cli images -e QA # list all known AMIs tagged for the QA environment
11
+ # % aws-cli images -e QA -r App # list all known AMIs tagged for the QA environment and App role
12
+ #
13
+ class Images < Runbook::Report
14
+ ###
15
+ # @return [Array<OpenStruct>]
16
+ #
17
+ def items
18
+ client.images.sort_by(&method(:sort_key))
19
+ end
20
+
21
+ ###
22
+ # @return [Array<String>]
23
+ #
24
+ def columns
25
+ environment_column + role_column + %w[created_at public type image_id]
26
+ end
27
+
28
+ private
29
+
30
+ def sort_key(image)
31
+ [image.environment, image.role, image.created_at].compact
32
+ end
33
+
34
+ def environment_column
35
+ options[:environment] ? [] : ['environment']
36
+ end
37
+
38
+ def role_column
39
+ options[:role] ? [] : ['role']
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsCftTools
4
+ module Runbooks
5
+ ##
6
+ # Deploy - manage CloudFormation stack deployment
7
+ #
8
+ # @example
9
+ # % aws-cli init # create skeleton project in the current directory
10
+ #
11
+ class Init < Runbook
12
+ ##
13
+ # The default project configuration file.
14
+ #
15
+ DEFAULT_CONFIG = <<~EOF
16
+ ---
17
+ ###
18
+ # Values in this file override the defaults in aws-cft. Command line options override these values.
19
+ #
20
+ # This is a good place to put project- or account-wide defaults for teams using the templates in this
21
+ # repo.
22
+ ###
23
+
24
+ ###
25
+ # By default, we want as much detail as possible.
26
+ #
27
+ :verbose: true
28
+
29
+ ###
30
+ # When different templates have nothing indicating their relative ordering, they are ordered based on the
31
+ # directory/folder in which they appear ordered by this list.
32
+ #
33
+ :template_folder_priorities:
34
+ - vpcs
35
+ - networks
36
+ - security
37
+ - data-resources
38
+ - data-services
39
+ - applications
40
+ EOF
41
+
42
+ ##
43
+ # The template role directories to build out when creating a project.
44
+ #
45
+ TEMPLATE_ROLES = %w[applications data-resources data-services networks security vpcs].freeze
46
+
47
+ ##
48
+ # The different types of files used when managing templates and stacks.
49
+ #
50
+ FILE_TYPES = %w[parameters templates].freeze
51
+
52
+ def run
53
+ ensure_project_directory
54
+ ensure_cloudformation_directories
55
+ ensure_config_file
56
+ end
57
+
58
+ private
59
+
60
+ def ensure_config_file
61
+ operation('Creating configuration file') do
62
+ file = options[:root] + options[:config_file]
63
+ if file.exist?
64
+ narrative 'Configuration file already exists. Not overwriting.'
65
+ else
66
+ doing { file.write(DEFAULT_CONFIG) }
67
+ end
68
+ end
69
+ end
70
+
71
+ def ensure_project_directory
72
+ ensure_directory(options[:root])
73
+ end
74
+
75
+ def ensure_cloudformation_directories
76
+ FILE_TYPES.product(TEMPLATE_ROLES).map { |list| list.join('/') }.each do |dir|
77
+ ensure_directory(options[:root] + 'cloudformation/' + dir)
78
+ end
79
+ end
80
+
81
+ def ensure_directory(dir)
82
+ operation("Ensure #{dir} exists") { dir.mkpath }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'table_print'
5
+ require 'securerandom'
6
+
7
+ module AwsCftTools
8
+ module Runbooks
9
+ ##
10
+ # Retract - manage CloudFormation stack retraction
11
+ #
12
+ # @example
13
+ # % aws-cft retract -e QA # delete all templates in the QA environment
14
+ # % aws-cft retract -e Staging -n -v # narrate the templates that would be deleted in Staging
15
+ # % aws-cft retract -e Production -c -v # narrate the changes implied by deleting stacks in Production
16
+ #
17
+ class Retract < Runbook
18
+ require_relative 'common/changesets'
19
+ require_relative 'retract/templates'
20
+
21
+ extend Forwardable
22
+
23
+ include Common::Changesets
24
+ include Templates
25
+
26
+ def_delegators :client, :images, :stacks
27
+
28
+ def run
29
+ report_template_dependencies
30
+
31
+ detail do
32
+ tp(free_templates, ['filename'])
33
+ end
34
+
35
+ remove_deployed_templates
36
+ end
37
+
38
+ private
39
+
40
+ ##
41
+ # run appropriate update function against deployed templates/stacks
42
+ #
43
+ def remove_deployed_templates
44
+ free_templates.each(&method(:remove_deployed_template))
45
+ end
46
+
47
+ def remove_deployed_template(template)
48
+ operation("Removing: #{template.name}") do
49
+ checking { narrate_changes(client.changes_on_stack_delete(template, changeset_set)) }
50
+ doing { client.delete_stack(template) }
51
+ end
52
+ end
53
+
54
+ ##
55
+ # report_undefined_image - provide list of undefined imports that block stack deployment
56
+ #
57
+ def report_template_dependencies
58
+ diff = (templates - free_templates).map { |template| template.filename.to_s }
59
+ error_on_dependencies(diff) if diff.any?
60
+ end
61
+
62
+ def error_on_dependencies(templates)
63
+ puts '*** Unable to remove templates.'
64
+ puts 'The following templates are dependencies for templates not marked for removal: ', templates
65
+ exit 1
66
+ end
67
+ end
68
+ end
69
+ end