aws-cft-tools 0.1.0

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 (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