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,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'aws_cft_tools'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "measurementType": { "method": "linesOfCode" },
4
+ "agency": "SBA",
5
+ "releases": [
6
+ {
7
+ "name": "aws-cft-tools",
8
+ "repositoryURL": "https://github.com/USSBA/aws-cft-tools",
9
+ "description": "Command line tools to ease management of AWS infrastructure through CloudFormation",
10
+ "permissions": {
11
+ "license": {
12
+ "name": "Apache v2",
13
+ "URL": "https://www.apache.org/licenses/LICENSE-2.0"
14
+ },
15
+ "usageType": "openSource",
16
+ "exemptionText": null
17
+ },
18
+ "vcs": "git",
19
+ "laborHours": 1,
20
+ "tags": ["SBA", "DevOps", "AWS"],
21
+ "contact": { "email": "Andrew.Davy@sba.gov" }
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,176 @@
1
+ #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'aws_cft_tools'
6
+ require 'clamp'
7
+ require 'yaml'
8
+
9
+ Clamp do
10
+ option ['-c', '--[no-]check'], :flag, 'only do non-destructive operations to check validity of request'
11
+ option ['-f', '--file'], 'FILE', 'set configuration file relative to the project root',
12
+ default: '.aws_cft', attribute_name: :config_file
13
+ option ['-n', '--[no-]noop'], :flag, 'only do operations that do not require modifying AWS',
14
+ attribute_name: :noop
15
+ option ['-p', '--profile'], 'PROFILE', 'set profile', default: 'default'
16
+ option ['-R', '--region'], 'REGION', 'set AWS region', environment_name: 'AWS_REGION',
17
+ default: 'us-east-1'
18
+ option ['-t', '--root'], 'DIRECTORY', 'set infrastructure project root' do |v|
19
+ Pathname.new(v)
20
+ end
21
+ option ['-T', '--tag'], 'NAME:VALUE', 'require a tag have the given value (may be given more than once)',
22
+ multivalued: true
23
+ option ['-v', '--[no-]verbose'], :flag, 'verbose narration of actions'
24
+ option '--version', :flag, 'Show version' do
25
+ puts AwsCftTools::VERSION
26
+ exit(0)
27
+ end
28
+
29
+ def default_root
30
+ Pathname.new(ENV['AWS_CFT_HOME'] || '.')
31
+ end
32
+
33
+ subcommand 'deploy', 'Deploy templates to AWS' do
34
+ option ['-e', '--environment'], 'ENVIRONMENT', 'set environment on which to operate' # , required: true
35
+ option ['-r', '--role'], 'ROLE', 'set role filter'
36
+ option ['-j', '--jobs'], 'INTEGER', 'maximum number of parallel stacks to build simultaneously',
37
+ default: 1 do |j|
38
+ if j == '-'
39
+ 65_535
40
+ else
41
+ j.to_i
42
+ end
43
+ end
44
+ parameter '[TEMPLATES] ...', 'the templates to deploy'
45
+
46
+ def execute
47
+ AwsCftTools::Runbooks::Deploy.new(
48
+ with_file_options(
49
+ environment: environment,
50
+ role: role,
51
+ jobs: jobs,
52
+ templates: templates_list
53
+ )
54
+ )._run
55
+ rescue AwsCftTools::ToolingException => exception
56
+ puts "Unable to deploy: #{exception}"
57
+ end
58
+ end
59
+
60
+ subcommand 'diff', 'List differences between templates and AWS' do
61
+ option '--[no-]color', :flag, 'colorize output', default: $stdout.tty?, attribute_name: :color
62
+ option ['-e', '--environment'], 'ENVIRONMENT', 'set environment on which to operate'
63
+ option ['-r', '--role'], 'ROLE', 'set role filter'
64
+ parameter '[TEMPLATES] ...', 'the templates to diff'
65
+
66
+ def execute
67
+ AwsCftTools::Runbooks::Diff.new(
68
+ with_file_options(
69
+ colorize: color?,
70
+ environment: environment,
71
+ role: role,
72
+ templates: templates_list
73
+ )
74
+ )._run
75
+ rescue AwsCftTools::ToolingException => exception
76
+ puts "Unable to diff: #{exception}"
77
+ end
78
+ end
79
+
80
+ subcommand 'hosts', 'List running instances' do
81
+ option ['-e', '--environment'], 'ENVIRONMENT', 'set environment on which to operate'
82
+ option ['-r', '--role'], 'ROLE', 'set role filter'
83
+
84
+ def execute
85
+ AwsCftTools::Runbooks::Hosts.new(
86
+ with_file_options(
87
+ environment: environment,
88
+ role: role
89
+ )
90
+ )._run
91
+ end
92
+ end
93
+
94
+ subcommand 'images', 'List available AMIs' do
95
+ option ['-e', '--environment'], 'ENVIRONMENT', 'set environment on which to operate'
96
+ option ['-r', '--role'], 'ROLE', 'set role filter'
97
+
98
+ def execute
99
+ AwsCftTools::Runbooks::Images.new(
100
+ with_file_options(
101
+ environment: environment,
102
+ role: role
103
+ )
104
+ )._run
105
+ end
106
+ end
107
+
108
+ subcommand 'init', 'Initialize a new infrastructure project' do
109
+ def execute
110
+ AwsCftTools::Runbooks::Init.new(with_file_options)._run
111
+ end
112
+ end
113
+
114
+ subcommand 'retract', 'Retract templates from AWS' do
115
+ option ['-e', '--environment'], 'STRING', 'set environment on which to operate', required: true
116
+ option ['-r', '--role'], 'STRING', 'set role filter'
117
+ parameter '[TEMPLATES] ...', 'the templates to retract'
118
+
119
+ def execute
120
+ AwsCftTools::Runbooks::Retract.new(
121
+ with_file_options(
122
+ environment: environment,
123
+ role: role,
124
+ templates: templates_list
125
+ )
126
+ )._run
127
+ rescue AwsCftTools::ToolingException => exception
128
+ puts "Unable to retract: #{exception}"
129
+ end
130
+ end
131
+
132
+ subcommand 'stacks', 'List deployed stacks in AWS' do
133
+ option ['-e', '--environment'], 'ENVIRONMENT', 'set environment on which to operate'
134
+ option ['-r', '--role'], 'ROLE', 'set role filter'
135
+
136
+ def execute
137
+ AwsCftTools::Runbooks::Stacks.new(
138
+ with_file_options(
139
+ environment: environment,
140
+ role: role
141
+ )
142
+ )._run
143
+ end
144
+ end
145
+
146
+ def with_file_options(opts = {})
147
+ opts = global_options.merge(opts)
148
+
149
+ config_file_with_path = root + config_file
150
+ return opts unless config_file_with_path.exist?
151
+ config_options = YAML.safe_load(config_file_with_path.read, [Symbol])
152
+ config_options.merge(opts.reject { |_, v| v.nil? })
153
+ end
154
+
155
+ private
156
+
157
+ def global_options
158
+ {
159
+ config_file: config_file,
160
+ profile: profile,
161
+ root: root,
162
+ region: region,
163
+ noop: noop?,
164
+ check: check?,
165
+ verbose: verbose?,
166
+ tags: tag_hash
167
+ }
168
+ end
169
+
170
+ def tag_hash
171
+ tag_list.each_with_object({}) do |tag, acc|
172
+ k, v = tag.split(':', 2)
173
+ acc[k] = v
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws_cft_tools'
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws_cft_tools/version'
4
+
5
+ ##
6
+ # = AWS CloudFormation Tools
7
+ #
8
+ # A collection of classes and methods to manage AWS Infrastructure using CloudFormation as the fundamental
9
+ # unit of infrastructure state. Aws::Cft supports JSON, YAML, and DSL templates with minimal decoration
10
+ # to establish dependencies between templates.
11
+ #
12
+ # == Command Line Interface
13
+ #
14
+ # A CLI is provided through the +aws-cft+ command that will run any of the "runbooks" under the
15
+ # +AwsCftTools::Runbooks::+ namespace.
16
+ #
17
+ # To find a complete list of subcommands, run +aws-cft --help+.
18
+ #
19
+ module AwsCftTools
20
+ require 'aws_cft_tools/errors'
21
+ require 'aws_cft_tools/aws_enumerator'
22
+ require 'aws_cft_tools/change'
23
+ require 'aws_cft_tools/deletion_change'
24
+ require 'aws_cft_tools/client'
25
+ require 'aws_cft_tools/dependency_tree'
26
+ require 'aws_cft_tools/stack'
27
+ require 'aws_cft_tools/template'
28
+ require 'aws_cft_tools/template_set'
29
+ require 'aws_cft_tools/runbook'
30
+ require 'aws_cft_tools/runbooks'
31
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AwsCftTools
4
+ ##
5
+ # Provides common "closure" of paged results for the CFT client.
6
+ #
7
+ class AWSEnumerator < Enumerator
8
+ #
9
+ # @param client [Object] The client object used to retrieve the next set of responses.
10
+ # @param method [Symbol] The method to call on the client object.
11
+ # @param args [Hash] Any arguments that are the same across calls to the client object.
12
+ # @yield [Object] The response from calling the +method+ on the +client+.
13
+ #
14
+ # @example Enumerating All Stacks
15
+ #
16
+ # aws_client = Aws::CloudFormation::Client.new
17
+ # all_stacks = AWSEnumerator.new(aws_client, :describe_stacks, &:stacks).to_a
18
+ #
19
+ def initialize(client, method, args = {}, &block)
20
+ @client = client
21
+ @method = method
22
+ @next_token = nil
23
+ @args = args
24
+
25
+ super() do |yielder|
26
+ run_loop(yielder, &block)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def run_loop(yielder, &block)
33
+ resp = poll_client
34
+ loop do
35
+ process_response(yielder, resp, &block)
36
+ break unless @next_token
37
+ resp = poll_client
38
+ end
39
+ end
40
+
41
+ def process_response(yielder, resp)
42
+ feed_results(yielder, yield(resp))
43
+ end
44
+
45
+ def poll_client
46
+ resp = @client.public_send(@method, @args.merge(next_token: @next_token))
47
+ @next_token = resp.next_token
48
+ resp
49
+ end
50
+
51
+ def feed_results(yielder, results)
52
+ results.each { |item| yielder << item }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module AwsCftTools
6
+ # Represents a change in a changeset.
7
+ class Change
8
+ extend Forwardable
9
+
10
+ attr_reader :resource
11
+
12
+ # @param change [Aws::CloudFormation::Types::Change] The AWS SDK change object to be wrapped.
13
+ def initialize(change)
14
+ @resource = change.resource_change
15
+ end
16
+
17
+ def_delegators :resource, :action, :replacement
18
+ def_delegator :resource, :logical_resource_id, :logical_id
19
+ def_delegator :resource, :physical_resource_id, :physical_id
20
+
21
+ ###
22
+ #
23
+ # @return [String] human readable type of resource being changed
24
+ #
25
+ # @example EC2::Network::ACL
26
+ #
27
+ # "ec2 network acl"
28
+ #
29
+ def type
30
+ humanize_camelized(resource.resource_type)
31
+ end
32
+
33
+ ###
34
+ #
35
+ # @return [String] a comma-separated list of scopes
36
+ #
37
+ def scopes
38
+ resource.scope.sort.join(', ')
39
+ end
40
+
41
+ ###
42
+ #
43
+ # @return [Hash] information useful for creating a tabular report
44
+ #
45
+ def to_narrative
46
+ {
47
+ action: action,
48
+ logical_id: logical_id,
49
+ physical_id: physical_id,
50
+ type: type,
51
+ scopes: scopes,
52
+ replacement: replacement
53
+ }
54
+ end
55
+
56
+ protected
57
+
58
+ def humanize_camelized(string)
59
+ string.sub(/^AWS::/, '')
60
+ .gsub(/:+/, ' ')
61
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2')
62
+ .gsub(/([a-z\d])([A-Z])/, '\1 \2')
63
+ .downcase
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk'
4
+ require 'forwardable'
5
+
6
+ module AwsCftTools
7
+ ##
8
+ # = AWS Tools Client
9
+ #
10
+ # A collection of higher-level business methods built on top of the AWS API.
11
+ #
12
+ class Client
13
+ require_relative 'client/base'
14
+ require_relative 'client/ec2'
15
+ require_relative 'client/cft'
16
+ require_relative 'client/templates'
17
+
18
+ extend Forwardable
19
+
20
+ ##
21
+ # Create a new client instance.
22
+ #
23
+ # Options are passed on to domain-specific client objects within the +AwsCftTools::Client::+ namespace.
24
+ #
25
+ # @param options [Hash] client configuration
26
+ # @option options [String] :environment Environment with which the client is concerned.
27
+ # @option options [String] :parameter_dir The location of parameter files within the project.
28
+ # @option options [String] :profile The profile to use from the shared credentials file.
29
+ # @option options [String] :region The AWS region in which to operate.
30
+ # @option options [String] :role The role that resources are attached to.
31
+ # @option options [Pathname] :root The location of the top-level directory of the project.
32
+ # @option options [String] :template_dir The location of tmeplate files within the project.
33
+ #
34
+ def initialize(options)
35
+ @client_options = options.merge(client: self)
36
+ end
37
+
38
+ # @!method instances
39
+ # @see AwsCftTools::Client::EC2#instances
40
+ # @!method images
41
+ # @see AwsCftTools::Client::EC2#images
42
+ def_delegators :ec2_client, :instances, :images
43
+
44
+ # @!method exports
45
+ # @see AwsCftTools::Client::CFT#exports
46
+ # @!method stacks
47
+ # @see AwsCftTools::Client::CFT#stacks
48
+ # @!method create_stack
49
+ # @see AwsCftTools::Client::CFT::StackManagement#create_stack
50
+ # @!method delete_stack
51
+ # @see AwsCftTools::Client::CFT::StackManagement#delete_stack
52
+ # @!method update_stack
53
+ # @see AwsCftTools::Client::CFT::StackManagement#update_stack
54
+ # @!method all_stacks
55
+ # @see AwsCftTools::Client::CFT#all_stacks
56
+ # @!method changes_on_stack_update
57
+ # @see AwsCftTools::Client::CFT::ChangesetManagement#changes_on_stack_update
58
+ # @!method changes_on_stack_delete
59
+ # @see AwsCftTools::Client::CFT::ChangesetManagement#changes_on_stack_delete
60
+ # @!method changes_on_stack_create
61
+ # @see AwsCftTools::Client::CFT::ChangesetManagement#changes_on_stack_create
62
+ def_delegators :cft_client, :exports, :stacks, :create_stack, :update_stack, :all_stacks,
63
+ :changes_on_stack_update, :changes_on_stack_create, :changes_on_stack_delete,
64
+ :delete_stack
65
+
66
+ # @!method templates
67
+ # @see AwsCftTools::Client::Templates#templates
68
+ def_delegators :template_client, :templates
69
+
70
+ private
71
+
72
+ def ec2_client
73
+ @ec2_client ||= AwsCftTools::Client::EC2.new(@client_options)
74
+ end
75
+
76
+ def cft_client
77
+ @cft_client ||= AwsCftTools::Client::CFT.new(@client_options)
78
+ end
79
+
80
+ def template_client
81
+ @template_client ||= AwsCftTools::Client::Templates.new(@client_options)
82
+ end
83
+ end
84
+ end