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.
- checksums.yaml +7 -0
- data/.editorconfig +10 -0
- data/.gitignore +52 -0
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/BEST-PRACTICES.md +136 -0
- data/CONTRIBUTING.md +38 -0
- data/Gemfile +8 -0
- data/LICENSE +15 -0
- data/README.md +118 -0
- data/Rakefile +17 -0
- data/USAGE.adoc +404 -0
- data/aws-cft-tools.gemspec +53 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/code.json +24 -0
- data/exe/aws-cft +176 -0
- data/lib/aws-cft-tools.rb +3 -0
- data/lib/aws_cft_tools.rb +31 -0
- data/lib/aws_cft_tools/aws_enumerator.rb +55 -0
- data/lib/aws_cft_tools/change.rb +66 -0
- data/lib/aws_cft_tools/client.rb +84 -0
- data/lib/aws_cft_tools/client/base.rb +40 -0
- data/lib/aws_cft_tools/client/cft.rb +93 -0
- data/lib/aws_cft_tools/client/cft/changeset_management.rb +109 -0
- data/lib/aws_cft_tools/client/cft/stack_management.rb +85 -0
- data/lib/aws_cft_tools/client/ec2.rb +136 -0
- data/lib/aws_cft_tools/client/templates.rb +84 -0
- data/lib/aws_cft_tools/deletion_change.rb +43 -0
- data/lib/aws_cft_tools/dependency_tree.rb +109 -0
- data/lib/aws_cft_tools/dependency_tree/nodes.rb +71 -0
- data/lib/aws_cft_tools/dependency_tree/variables.rb +37 -0
- data/lib/aws_cft_tools/errors.rb +25 -0
- data/lib/aws_cft_tools/runbook.rb +166 -0
- data/lib/aws_cft_tools/runbook/report.rb +30 -0
- data/lib/aws_cft_tools/runbooks.rb +16 -0
- data/lib/aws_cft_tools/runbooks/common/changesets.rb +30 -0
- data/lib/aws_cft_tools/runbooks/common/templates.rb +38 -0
- data/lib/aws_cft_tools/runbooks/deploy.rb +107 -0
- data/lib/aws_cft_tools/runbooks/deploy/reporting.rb +50 -0
- data/lib/aws_cft_tools/runbooks/deploy/stacks.rb +109 -0
- data/lib/aws_cft_tools/runbooks/deploy/templates.rb +37 -0
- data/lib/aws_cft_tools/runbooks/deploy/threading.rb +37 -0
- data/lib/aws_cft_tools/runbooks/diff.rb +28 -0
- data/lib/aws_cft_tools/runbooks/diff/context.rb +86 -0
- data/lib/aws_cft_tools/runbooks/diff/context/reporting.rb +87 -0
- data/lib/aws_cft_tools/runbooks/hosts.rb +43 -0
- data/lib/aws_cft_tools/runbooks/images.rb +43 -0
- data/lib/aws_cft_tools/runbooks/init.rb +86 -0
- data/lib/aws_cft_tools/runbooks/retract.rb +69 -0
- data/lib/aws_cft_tools/runbooks/retract/templates.rb +44 -0
- data/lib/aws_cft_tools/runbooks/stacks.rb +43 -0
- data/lib/aws_cft_tools/stack.rb +83 -0
- data/lib/aws_cft_tools/template.rb +177 -0
- data/lib/aws_cft_tools/template/dsl_context.rb +14 -0
- data/lib/aws_cft_tools/template/file_system.rb +62 -0
- data/lib/aws_cft_tools/template/metadata.rb +144 -0
- data/lib/aws_cft_tools/template/properties.rb +129 -0
- data/lib/aws_cft_tools/template_set.rb +120 -0
- data/lib/aws_cft_tools/template_set/array_methods.rb +63 -0
- data/lib/aws_cft_tools/template_set/closure.rb +77 -0
- data/lib/aws_cft_tools/template_set/dependencies.rb +55 -0
- data/lib/aws_cft_tools/template_set/each_slice_state.rb +58 -0
- data/lib/aws_cft_tools/version.rb +8 -0
- data/rubycritic.reek +3 -0
- 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
|