cfer 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81546f97136e9bea0bb5197e14b7cded7ab7c440
4
- data.tar.gz: da24c76c102c3988c08790805e9d587c015c66bc
3
+ metadata.gz: 9e97e0ff7a93e2064e001ebe5d02275d10d652b4
4
+ data.tar.gz: 6814fbea31c860591f6034facf0db09aff48bd6e
5
5
  SHA512:
6
- metadata.gz: 129f8123bdfff9e159c8d98282e5a084d69bb14e5701562c009832d93dc668f2cd432e9dc440fdc5e06cc4459c02c18429b21f25b356df49621b5667bd96286d
7
- data.tar.gz: ae98b4262c9008926894e1cf6a81331809706efa0f5e2e8f9147e49032281cf6c0477eebc1d584ed0fc457d1aed75a556da1f59d0351d57c9411820bd7316b3b
6
+ metadata.gz: c39189c5fd1dea4a81cd95837d512e71342d60361aa310e7619af2c0abbb8c5f68525263048734864775199a1a25316ede965c01aad1ece07722e02151c00ce7
7
+ data.tar.gz: dcac2d43384fc8cde476c1f3ad92a4d2153fd66fb48da3da73aa00872ee7d22367a235a903ac2b7fbfd7640ae6713e47aa92d5f54492b7eb83cd3e70bd2d84a8
data/.gitignore CHANGED
@@ -8,6 +8,6 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  *.swp
11
- */.tags
11
+ **/.tags
12
12
 
13
13
  !/doc/cfer-demo.gif
data/README.md CHANGED
@@ -15,7 +15,9 @@ Read about Cfer [here](http://tilmonedwards.com/2015/07/28/cfer.html).
15
15
 
16
16
  Cfer is pre-1.0 software, and may contain bugs or incomplete features. Please see the [license](https://github.com/seanedwards/cfer/blob/master/LICENSE.txt) for disclaimers.
17
17
 
18
- If you would like support or guidance on Cfer, or CloudFormation in general, I offer DevOps consulting services. Please [Contact Bitlancer](http://www.bitlancer.com/contact-us/) and we'll be happy to discuss your needs.
18
+ If you would like support or guidance on Cfer, or CloudFormation in general, I offer DevOps consulting services. Please [Contact me](mailto:stedwards87+cfer@gmail.com) and I'll be happy to discuss your needs.
19
+
20
+ You can also find me at [@tilmonedwards](https://twitter.com/tilmonedwards). If you use Cfer, or are considering it, I'd love to hear from you.
19
21
 
20
22
  ## Installation
21
23
 
@@ -77,9 +79,14 @@ The following options may be used with the `converge` command:
77
79
  * `--follow` (`-f`): Follows stack events on standard output as the create/update process takes place.
78
80
  * `--stack-file <template.rb>`: Reads this file from the filesystem, rather than the default `<stack-name>.rb`
79
81
  * `--parameters <Key1>:<Value1> <Key2>:<Value2> ...`: Specifies input parameters, which will be available to Ruby in the `parameters` hash, or to CloudFormation by using the `Fn::ref` function
82
+ * `--parameter-file <params_file.[yaml|json]`: Specifies input parameters from a YAML or JSON file
83
+ * `--parameter-environment <env_name>`: Requires `--parameter-file`. Merges the specified key in the YAML or JSON file into the root of the parameter file before passing it into the Cfer stack, i.e. to provide different constants for different AWS environments. The priority for parameters is, in ascending order, **stack default**, **file**, **environment**, and **command line**.
80
84
  * `--on-failure <DELETE|ROLLBACK|DO_NOTHING>`: Specifies the action to take when a stack creation fails. Has no effect if the stack already exists and is being updated.
81
85
  * `--stack-policy <filename|URL|JSON string>` (`-s`): Stack policy to apply to the stack in order to control updates; takes a local filename containing the policy, a URL to an S3 object, or a raw JSON string.
82
86
  * `--stack-policy-during-update <filename|URL|JSON string>` (`-u`): Stack policy as in `--stack-policy` option above, but applied as a temporary override to the permanent policy during stack update.
87
+ * `--s3-path <S3_PATH>`: Path to an S3 bucket location where the template will be stored. This is required if the template output exceeds [51,200 bytes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html).
88
+ * `--force-s3`: Forces Cfer to upload the template to S3, even if it's small enough to be uploaded directly to the cloudformation API.
89
+ * `--change <CHANGE_NAME>`: Creates a [CloudFormation Change Set](https://aws.amazon.com/blogs/aws/new-change-sets-for-aws-cloudformation/), rather than immediately updating the stack.
83
90
 
84
91
  #### `tail <stack-name>`
85
92
 
@@ -90,6 +97,19 @@ The following options may be used with the `tail` command:
90
97
  * `--follow` (`-f`): Follows stack events on standard output as the create/update process takes place.
91
98
  * `--number` (`-n`): Print the last `n` stack events.
92
99
 
100
+ #### `remove <stack-name>`
101
+
102
+ Removes or deletes an existing CloudFormation stack.
103
+
104
+ ```bash
105
+ cfer remove vpc --profile [YOUR-PROFILE] --region [YOUR-REGION]
106
+ ```
107
+
108
+ This can also be done in the following way with the awscli tools, but now eliminates the need to install that package.
109
+ ```bash
110
+ aws cloudformation delete-stack --stack-name vpc
111
+ ```
112
+
93
113
  ### Template Anatomy
94
114
 
95
115
  See the `examples` directory for some examples of complete templates.
@@ -278,6 +298,24 @@ This project also contains a [Code of Conduct](https://github.com/seanedwards/cf
278
298
 
279
299
  # Release Notes
280
300
 
301
+ ## 0.4.0
302
+
303
+ ### **BREAKING CHANGES**
304
+ * Provisioning is removed from Cfer core and moved to [cfer-provisioning](https://github.com/seanedwards/cfer-provisioning)
305
+
306
+ ### Enhancements
307
+ * Adds support for assume-role authentication with MFA (see: https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html)
308
+ * Adds support for yml-format parameter files with environment-specific sections.
309
+ * Adds a DSL for IAM policies.
310
+ * Adds `cfer estimate` command to estimate the cost of a template using the AWS CloudFormation cost estimation API.
311
+ * Enhancements to chef provisioner to allow for references in chef attributes. (Thanks to @eropple)
312
+ * Adds continue/rollback/quit selection when `^C` is caught during a converge.
313
+ * Stores Cfer version and Git repo information in the Repo metadata.
314
+ * Added support for uploading templates to S3 with the `--s3-path` and `--force-s3` options.
315
+ * Added new way of extending resources, making plugins easier.
316
+
317
+ ### Bugfixes
318
+
281
319
  ## 0.3.0
282
320
 
283
321
  ### Enhancements:
data/Rakefile CHANGED
@@ -1,56 +1 @@
1
- #require "bundler/gem_tasks"
2
- gem 'cfer'
3
- require 'cfer'
4
- require 'highline'
5
-
6
- task :default => [:spec]
7
-
8
- task :config_aws, [:profile] do |t, args|
9
- Aws.config.update region: ENV['AWS_REGION'] || ask('AWS Region?') { |q| q.default = 'us-east-1' },
10
- credentials: Aws::SharedCredentials.new(profile_name: ENV['AWS_PROFILE'] || ask('AWS Profile?') { |q| q.default = 'default' })
11
- end
12
-
13
- task :vpc => :config_aws do |t, args|
14
- Cfer.converge! 'vpc',
15
- template: 'examples/vpc.rb',
16
- follow: true
17
- end
18
-
19
- task :describe_vpc => :config_aws do
20
- Cfer.describe! 'vpc'
21
- end
22
-
23
- task :instance => :vpc do |t, args|
24
- key_pair = ask("Enter your EC2 KeyPair name: ")
25
-
26
- Cfer.converge! 'instance',
27
- template: 'examples/instance.rb',
28
- parameters: {
29
- :KeyName => key_pair
30
- },
31
- follow: true
32
- end
33
-
34
- task :describe_instance => :config_aws do
35
- Cfer.describe! 'instance'
36
- end
37
-
38
- task :converge => [:vpc, :instance]
39
-
40
-
41
- ########################
42
- ##### END OF DEMO ######
43
- ########################
44
-
45
-
46
- # This task isn't really part of Cfer.
47
- # It just makes it easier for me to release new versions.
48
- task :release do
49
- `git checkout master`
50
- `git merge develop --no-ff -m 'Merge from develop for release'`
51
-
52
- require_relative 'lib/cfer/version.rb'
53
-
54
- `git tag -m "Release v#{Cfer::VERSION}" #{Cfer::VERSION}`
55
- end
56
-
1
+ require "bundler/gem_tasks"
@@ -19,19 +19,18 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = 'cfer'
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_runtime_dependency 'docile'
23
- spec.add_runtime_dependency 'thor'
24
- spec.add_runtime_dependency 'activesupport'
25
- spec.add_runtime_dependency 'aws-sdk'
26
- spec.add_runtime_dependency 'aws-sdk-resources'
27
- spec.add_runtime_dependency 'preconditions'
28
- spec.add_runtime_dependency 'semantic'
29
- spec.add_runtime_dependency 'rainbow'
30
- spec.add_runtime_dependency 'highline'
31
- spec.add_runtime_dependency 'table_print'
32
- spec.add_runtime_dependency "rake"
33
- spec.add_runtime_dependency "erubis"
22
+ spec.add_runtime_dependency 'docile', '~> 1.1', '>= 1.1.5'
23
+ spec.add_runtime_dependency 'thor', '~> 0.19.1'
24
+ spec.add_runtime_dependency 'activesupport', '~> 4.2', '>= 4.2.6'
25
+ spec.add_runtime_dependency 'aws-sdk', '~> 2.2', '>= 2.2.33'
26
+ spec.add_runtime_dependency 'aws-sdk-resources', '~> 2.2', '>= 2.2.33'
27
+ spec.add_runtime_dependency 'preconditions', '~> 0.3.0'
28
+ spec.add_runtime_dependency 'semantic', '~> 1.4', '>= 1.4.1'
29
+ spec.add_runtime_dependency 'rainbow', '~> 2.1'
30
+ spec.add_runtime_dependency 'highline', '~> 1.7', '>= 1.7.8'
31
+ spec.add_runtime_dependency 'table_print', '~> 1.5', '>= 1.5.6'
32
+ spec.add_runtime_dependency "git", '~> 1.3'
33
+ spec.add_runtime_dependency "bundler"
34
34
 
35
- spec.add_development_dependency "bundler"
36
- spec.add_development_dependency "yard"
35
+ spec.add_development_dependency "yard", '~> 0.8.7.6'
37
36
  end
@@ -8,12 +8,12 @@ parameter :KeyName
8
8
  # If you created the VPC stack with a different name, you can overwrite these default values
9
9
  # by adding `Vpc:<vpc_stack_name> to your `--parameters` option
10
10
  parameter :Vpc, default: 'vpc'
11
- parameter :VpcId, default: lookup_output(parameters[:Vpc], 'vpcid')
12
- parameter :SubnetId, default: lookup_output(parameters[:Vpc], 'subnetid1')
11
+ parameter :VpcId, default: (lookup_output(parameters[:Vpc], 'vpcid') rescue nil)
12
+ parameter :SubnetId, default: (lookup_output(parameters[:Vpc], 'subnetid1') rescue nil)
13
13
 
14
14
  # This is the Ubuntu 14.04 LTS HVM AMI provided by Amazon.
15
15
  parameter :ImageId, default: 'ami-fce3c696'
16
- parameter :InstanceType, default: 't2.nano'
16
+ parameter :InstanceType, default: 't2.micro'
17
17
 
18
18
  # Define a security group to be applied to an instance.
19
19
  # This one will allow SSH access from anywhere, and no other inbound traffic.
@@ -2,30 +2,24 @@ description 'Example stack template for a small EC2 instance'
2
2
 
3
3
  # NOTE: This template depends on vpc.rb
4
4
 
5
- # Include common template code that will be used for examples that create EC2 instances.
5
+ # You can use the `include_template` function to include other ruby files into this Cloudformation template.
6
6
  include_template 'common/instance_deps.rb'
7
7
 
8
- # We can define extension objects, which extend the basic JSON-building
8
+ # We can define extensions to resources, which extend the basic JSON-building
9
9
  # functionality of Cfer. Cfer provides a few of these, but you're free
10
- # to define your own by creating a class that matches the name of an
11
- # CloudFormation resource type, inheriting from `Cfer::AWS::Resource`
12
- # inside the `CferExt` module:
13
- module CferExt::AWS::EC2
14
- # This class adds methods to resources with the type `AWS::EC2::Instance`
15
- # Remember, this class could go in your own gem to be shared between your templates
16
- # in a way that works with the rest of your infrastructure.
17
- class Instance < Cfer::Cfn::Resource
18
- def boot_script(data)
19
- # This function simply wraps a bash script in the little bit of extra
20
- # sugar (hashbang + base64 encoding) that EC2 requires for userdata boot scripts.
21
- # See the AWS docs here: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
22
- script = <<-EOS.strip_heredoc
23
- #!/bin/bash
24
- #{data}
25
- EOS
26
-
27
- user_data Base64.encode64(script)
28
- end
10
+ # to define your own by using `Cfer::Core::Resource.extend_resource` and specifying a
11
+ # CloudFormation resource type. Inside the block, define any methods you'd like to use:
12
+ Cfer::Core::Resource.extend_resource "AWS::EC2::Instance" do
13
+ def boot_script(data)
14
+ # This function simply wraps a bash script in the little bit of extra
15
+ # sugar (hashbang + base64 encoding) that EC2 requires for userdata boot scripts.
16
+ # See the AWS docs here: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
17
+ script = <<-EOS.strip_heredoc
18
+ #!/bin/bash
19
+ #{data}
20
+ EOS
21
+
22
+ user_data Base64.encode64(script)
29
23
  end
30
24
  end
31
25
 
@@ -56,12 +56,34 @@ module Cfer
56
56
  cfn = options[:aws_options] || {}
57
57
 
58
58
  cfn_stack = options[:cfer_client] || Cfer::Cfn::Client.new(cfn.merge(stack_name: stack_name))
59
- stack = options[:cfer_stack] || Cfer::stack_from_file(tmpl, options.merge(client: cfn_stack))
59
+ stack = options[:cfer_stack] ||
60
+ Cfer::stack_from_file(tmpl,
61
+ options.merge(
62
+ client: cfn_stack,
63
+ parameters: generate_final_parameters(options)
64
+ )
65
+ )
60
66
 
61
67
  begin
62
- cfn_stack.converge(stack, options)
63
- if options[:follow]
64
- tail! stack_name, options
68
+ operation = stack.converge!(options)
69
+ if options[:follow] && !options[:change]
70
+ begin
71
+ tail! stack_name, options.merge(cfer_client: cfn_stack)
72
+ rescue Interrupt
73
+ puts "Caught interrupt. What would you like to do?"
74
+ case HighLine.new($stdin, $stderr).choose('Continue', 'Quit', 'Rollback')
75
+ when 'Continue'
76
+ retry
77
+ when 'Rollback'
78
+ case operation
79
+ when :created
80
+ cfn_stack.delete_stack stack_name: stack_name
81
+ when :updated
82
+ cfn_stack.cancel_update_stack stack_name: stack_name
83
+ end
84
+ retry
85
+ end
86
+ end
65
87
  end
66
88
  rescue Aws::CloudFormation::Errors::ValidationError => e
67
89
  Cfer::LOGGER.info "CFN validation error: #{e.message}"
@@ -118,11 +140,31 @@ module Cfer
118
140
 
119
141
  def generate!(tmpl, options = {})
120
142
  config(options)
121
- cfn_stack = Cfer::Cfn::Client.new(options[:aws_options] || {})
122
- stack = Cfer::stack_from_file(tmpl, options.merge(client: cfn_stack)).to_h
143
+ cfn = options[:aws_options] || {}
144
+
145
+ cfn_stack = options[:cfer_client] || Cfer::Cfn::Client.new(cfn)
146
+ stack = options[:cfer_stack] || Cfer::stack_from_file(tmpl,
147
+ options.merge(client: cfn_stack, parameters: generate_final_parameters(options))).to_h
123
148
  puts render_json(stack, options)
124
149
  end
125
150
 
151
+ def estimate!(tmpl, options = {})
152
+ config(options)
153
+ cfn = options[:aws_options] || {}
154
+
155
+ cfn_stack = options[:cfer_client] || Cfer::Cfn::Client.new(cfn)
156
+ stack = options[:cfer_stack] || Cfer::stack_from_file(tmpl,
157
+ options.merge(client: cfn_stack, parameters: generate_final_parameters(options)))
158
+ puts cfn_stack.estimate(stack)
159
+ end
160
+
161
+ def delete!(stack_name, options = {})
162
+ config(options)
163
+ cfn = options[:aws_options] || {}
164
+ cfn_stack = options[:cfer_client] || cfn_stack = Cfer::Cfn::Client.new(cfn.merge(stack_name: stack_name))
165
+ cfn_stack.delete_stack(stack_name)
166
+ end
167
+
126
168
  # Builds a Cfer::Core::Stack from a Ruby block
127
169
  #
128
170
  # @param options [Hash] The stack options
@@ -156,8 +198,40 @@ module Cfer
156
198
  Cfer::LOGGER.debug "Options: #{options}"
157
199
  Cfer::LOGGER.level = Logger::DEBUG if options[:verbose]
158
200
 
201
+ require 'rubygems'
202
+ require 'bundler/setup'
203
+
159
204
  Aws.config.update region: options[:region] if options[:region]
160
- Aws.config.update credentials: Aws::SharedCredentials.new(profile_name: options[:profile]) if options[:profile]
205
+ Aws.config.update credentials: Cfer::Cfn::CferCredentialsProvider.new(profile_name: options[:profile]) if options[:profile]
206
+ end
207
+
208
+ def generate_final_parameters(options)
209
+ raise "parameter-environment set but parameter_file not set" \
210
+ if options[:parameter_environment] && options[:parameter_file].nil?
211
+
212
+ file_params =
213
+ if options[:parameter_file]
214
+ case File.extname(options[:parameter_file])
215
+ when '.yaml'
216
+ require 'yaml'
217
+ YAML.load_file(options[:parameter_file])
218
+ when '.json'
219
+ JSON.parse(IO.read(options[:parameter_file]))
220
+ else
221
+ raise "Unrecognized parameter file format: #{File.extname(options[:parameter_file])}"
222
+ end
223
+ else
224
+ {}
225
+ end
226
+
227
+ if options[:parameter_environment]
228
+ raise "no key '#{options[:parameter_environment]}' found in parameters file." \
229
+ unless file_params.key?(options[:parameter_environment])
230
+
231
+ file_params = file_params.deep_merge(file_params[options[:parameter_environment]])
232
+ end
233
+
234
+ file_params.deep_merge(options[:parameters] || {})
161
235
  end
162
236
 
163
237
  def render_json(obj, options = {})
@@ -170,6 +244,8 @@ module Cfer
170
244
 
171
245
  def templatize_errors(base_loc)
172
246
  yield
247
+ rescue Cfer::Util::CferError => e
248
+ raise e
173
249
  rescue SyntaxError => e
174
250
  raise Cfer::Util::TemplateError.new([]), e.message
175
251
  rescue StandardError => e
@@ -30,4 +30,46 @@ module Cfer
30
30
  def post_block
31
31
  end
32
32
  end
33
+
34
+ class BlockHash < Block
35
+ NON_PROXIED_METHODS = [:parameters, :options, :lookup_output]
36
+
37
+ def properties(keyvals = {})
38
+ self.merge!(keyvals)
39
+ end
40
+
41
+ def get_property(key)
42
+ self.fetch key
43
+ end
44
+
45
+ def respond_to?(method_sym)
46
+ !non_proxied_methods.include?(method_sym)
47
+ end
48
+
49
+ def method_missing(method_sym, *arguments, &block)
50
+ key = camelize_property(method_sym)
51
+ properties key =>
52
+ case arguments.size
53
+ when 0
54
+ if block
55
+ BlockHash.new.build_from_block(&block)
56
+ else
57
+ raise "Expected a value or block when setting property #{key}"
58
+ end
59
+ when 1
60
+ arguments.first
61
+ else
62
+ arguments
63
+ end
64
+ end
65
+
66
+ private
67
+ def non_proxied_methods
68
+ NON_PROXIED_METHODS
69
+ end
70
+
71
+ def camelize_property(sym)
72
+ sym.to_s.camelize.to_sym
73
+ end
74
+ end
33
75
  end
@@ -0,0 +1,72 @@
1
+ require 'yaml'
2
+
3
+ module Cfer
4
+ module Cfn
5
+ class CferCredentialsProvider < Aws::SharedCredentials
6
+ private
7
+
8
+ def load_from_path
9
+ profile = load_profile
10
+ credentials = Aws::Credentials.new(
11
+ profile['aws_access_key_id'],
12
+ profile['aws_secret_access_key'],
13
+ profile['aws_session_token']
14
+ )
15
+ @credentials =
16
+ if role_arn = profile['role_arn']
17
+ role_creds =
18
+ begin
19
+ YAML::load_file('.cfer-role')
20
+ rescue
21
+ {}
22
+ end
23
+
24
+ if stored_creds = role_creds[profile_name]
25
+ if (Time.now.to_i + 5 * 60) > stored_creds[:expiration].to_i
26
+ stored_creds = nil
27
+ end
28
+ end
29
+
30
+ if stored_creds == nil
31
+ role_credentials_options = {
32
+ role_session_name: [*('A'..'Z')].sample(16).join,
33
+ role_arn: role_arn,
34
+ credentials: credentials
35
+ }
36
+
37
+ if profile['mfa_serial']
38
+ role_credentials_options[:serial_number] ||= profile['mfa_serial']
39
+ role_credentials_options[:token_code] ||= HighLine.new($stdin, $stderr).ask('Enter MFA Code:')
40
+ end
41
+
42
+ creds = Aws::AssumeRoleCredentials.new(role_credentials_options)
43
+ stored_creds = {
44
+ expiration: creds.expiration,
45
+ credentials: creds.credentials
46
+ }
47
+ role_creds[profile_name] = stored_creds
48
+ end
49
+
50
+ IO.write('.cfer-role', YAML.dump(role_creds))
51
+ stored_creds[:credentials]
52
+ else
53
+ credentials
54
+ end
55
+ end
56
+
57
+ def load_profile
58
+ if profile = profiles[profile_name]
59
+ # Add all options from source profile
60
+ if source = profile.delete('source_profile')
61
+ profiles[source].merge(profile)
62
+ else
63
+ profile
64
+ end
65
+ else
66
+ msg = "Profile `#{profile_name}' not found in #{path}"
67
+ raise Aws::Errors::NoSuchProfileError, msg
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end