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