convection 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7f560ff0230cc8a78bdc3e59c57d2b5befbfe37
4
- data.tar.gz: 54969c701ca627f77a22a38a67e70aa43e1c93a8
3
+ metadata.gz: b3de07eb04f40d2d56959e6df5572e98f8447ce7
4
+ data.tar.gz: d0925f4ae7e4f4093606a7cf1fb05bb643172bed
5
5
  SHA512:
6
- metadata.gz: 1db320b7089911c0e28b80709348897131f11f4a15e3529e406c78d09760cb65d0aa8df505e98c1f2d84e707b3383311cc5c044198cc9f5ba25648beb5d3b01d
7
- data.tar.gz: 11e1bce3c3781d8899f6ad2c5ac1a0acc6ba1ec3766160c0e1cc0c96f279330831b67121aab7e8173cf5ea55fae9f52ed3d1260be86aa17274a0410be0a9cd95
6
+ metadata.gz: 69a32d02abbc59d1bdf62703de9cd72ceb8cb8a88c5efc69a4790d2fee51f21c248e976f76eb0ff0db3ea39819136709c807343b04d49a822fa4878798ba4bc2
7
+ data.tar.gz: dc372422b28b5eab1d908b98cb798cff7b42c54f483b5ce86498d1c408b46f0fa79f703292ee8a7315101d2365897f0438f21c854a3608180b67988416ff5344
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2.2
1
+ 2.3.0
data/bin/convection CHANGED
@@ -28,6 +28,34 @@ module Convection
28
28
  end
29
29
  end
30
30
 
31
+ desc 'delete STACK', 'Delete stack(s) from your cloud'
32
+ option :stack_group, :type => :string, :desc => 'The name of a stack group defined in your cloudfile to delete'
33
+ option :stacks, :type => :array, :desc => 'A ordered space separated list of stacks to delete'
34
+ def delete(stack = nil)
35
+ @cloud.configure(File.absolute_path(options['cloudfile'], @cwd))
36
+ print_status = lambda do |event, *errors|
37
+ say_status(*event.to_thor)
38
+ errors.flatten.each do |error|
39
+ say "* #{ error.message }"
40
+ error.backtrace.each { |b| say " #{ b }" }
41
+ end unless errors.nil?
42
+ end
43
+
44
+ stacks = @cloud.stacks_until(stack, options, &print_status)
45
+ if stacks.empty?
46
+ say_status(:delete_failed, 'No stacks found matching the provided input (STACK, --stack-group, and/or --stacks).', :red)
47
+ return
48
+ end
49
+ say_status(:delete, "Deleting the following stack(s): #{stacks.map(&:name).join(', ')}", :red)
50
+
51
+ confirmation = ask('Are you sure you want to delete the above stack(s)?', limited_to: %w(yes no))
52
+ if confirmation.eql?('yes')
53
+ @cloud.delete(stacks, &print_status)
54
+ else
55
+ say_status(:delete_aborted, 'Aborted deletion of the above stack(s).', :green)
56
+ end
57
+ end
58
+
31
59
  desc 'diff STACK', 'Show changes that will be applied by converge'
32
60
  option :verbose, :type => :boolean, :aliases => '--v', :desc => 'Show stack progress'
33
61
  option :'very-verbose', :type => :boolean, :aliases => '--vv', :desc => 'Show unchanged stacks'
@@ -0,0 +1,148 @@
1
+ # Creating a custom resource collection
2
+ **NOTE**: Examples in this file can be found in `example/web-service-resource-collection`.
3
+
4
+ This guide will walk you through creating a custom resource collection.
5
+
6
+ For the purpose of this guide we'll create a resource collection to provision a web server listening on port 80 with security group ingress rules to only allow specific CIDR ranges.
7
+
8
+ ## Attach your custom resource collection to the Template DSL
9
+ The below code snippet does the following:
10
+ * Defines `WebService` as a subclass of `Convection::Model::Template::ResourceCollection` (this provides a standard initialize function and mixins in convection helper functions).
11
+ * Defines `Template#web_service` which can later be called in the Convection template configuration.
12
+
13
+ ```ruby
14
+ class WebService < Convection::Model::Template::ResourceCollection
15
+ # Attach this class to the Template DSL with the method name "web_service".
16
+ attach_to_dsl(:web_service)
17
+ end
18
+ ```
19
+
20
+ ## Override `ResourceCollection#execute`
21
+ When rendering resource collections convection internally calls `#run_definition` to resolve the configuration block passed in during template definition. After this call `#execute` is called. This method should be overridden for resource collections to call other [basic] convection resources.
22
+
23
+ ```ruby
24
+ class WebService < Convection::Model::Template::ResourceCollection
25
+ # ...
26
+
27
+ def execute
28
+ # Preserve scope by exposing a local "web_service" variable.
29
+ web_service = self
30
+ generate_security_groups(web_service)
31
+ generate_ec2_instance(web_service)
32
+ end
33
+
34
+ def generate_ec2_instance(web_service)
35
+ # TODO: Actually generate the ec2 instance resource!
36
+ end
37
+
38
+ def generate_security_groups(web_service)
39
+ # TODO: Actually generate the ec2 security group resource!
40
+ end
41
+ end
42
+ ```
43
+
44
+ ### Generating a security group for our web service
45
+ The below code can be used to generate a security group with a few tags and ingress rules defined for TCP over port 80 on any CIDR range in a CSV list from the `ALLOWED_CIDR_RANGES` environment variable.
46
+
47
+ ```ruby
48
+ def cidr_ranges
49
+ return ENV['ALLOWED_CIDR_RANGES'].split(/[, ]+/) if ENV.key?('ALLOWED_CIDR_RANGES')
50
+
51
+ raise ArgumentError, "You must export $ALLOWED_CIDR_RANGES to diff/converge #{stack.cloud_name}."
52
+ end
53
+
54
+ def generate_security_groups(web_service)
55
+ ec2_security_group "#{web_service.name}SecurityGroup" do
56
+ description "EC2 Security Group for the #{web_service.name} web service."
57
+
58
+ web_service.cidr_ranges.each do |range|
59
+ ingress_rule(:tcp, 80, range)
60
+ end
61
+
62
+ tag 'Name', "sg-#{web_service.name}-#{stack.cloud}".downcase
63
+ tag 'Service', web_service.name
64
+ tag 'Stack', stack.cloud
65
+
66
+ with_output
67
+ end
68
+ end
69
+ ```
70
+
71
+ ### Generating a web server running on port 80
72
+ #### Create an EC2 instance
73
+ ##### Support defining the image ID and instance user data via the template DSL
74
+ ```ruby
75
+ class WebService < Convection::Model::Template::ResourceCollection
76
+ # ...
77
+
78
+ attribute :ec2_instance_image_id
79
+ attribute :user_data
80
+ end
81
+ ```
82
+
83
+ ##### Generate the EC2 instance resource
84
+ ```ruby
85
+ def generate_ec2_instance(web_service)
86
+ ec2_instance "#{name}Frontend" do
87
+ image_id web_service.ec2_instance_image_id
88
+ security_group fn_ref("#{web_service.name}SecurityGroup")
89
+
90
+ tag 'Name', "#{web_service.name}Frontend"
91
+ tag 'Stack', stack.cloud
92
+
93
+ user_data base64(web_service.user_data)
94
+
95
+ with_output 'Hostname', get_att(name, 'PublicDnsName') do
96
+ description 'The public hostname of this web service.'
97
+ end
98
+
99
+ with_output 'HttpEndpoint', join('', 'http://', get_att(name, 'PublicDnsName')) do
100
+ description 'The URL to visit this web service at.'
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ ## Define your convection template using your resource collection
107
+ ### Call your custom resource like any other resource
108
+ ```ruby
109
+ require_relative '../path/to/your/resources/web_service.rb'
110
+
111
+ EXAMPLE_DOT_ORG = Convection.template do
112
+ description 'An example website to demonstrate using custom resource collections.'
113
+
114
+ web_service 'ExampleDotOrg' do
115
+ # Set the instance ID attribute so the instance can be converged.
116
+ ec2_instance_image_id 'ami-45026036' # ubuntu <3!
117
+
118
+ user_data <<~USER_DATA
119
+ #!/bin/bash
120
+ echo 'Hello World!'
121
+ USER_DATA
122
+ end
123
+ end
124
+ ```
125
+
126
+ ### Set the user data up as a shell script to install nginx
127
+ A quick and simple way to expose an HTTP server on port 80 is to install nginx.
128
+
129
+ You can do this with the bash script embedded as user data in your template like so:
130
+ ```ruby
131
+ user_data <<~USER_DATA
132
+ #!/bin/bash
133
+ apt-get update
134
+ apt-get install -y unzip curl nginx
135
+ service nginx start
136
+ update-rc.d nginx defaults
137
+ USER_DATA
138
+ ```
139
+
140
+ ## Converge your template
141
+ 1. Export your AWS credentials.
142
+ 2. Export the `ALLOWED_CIDR_RANGES` we use for setting our ingress rules.
143
+ * This should be set to your external IP address so the instance allows connections from your machine.
144
+ 3. Run `convection diff` to show what will be converged.
145
+ 4. Run `convection converge` to deploy your stack.
146
+ 5. Open the stack outputs section CloudFormation for this stack.
147
+ 6. Click on the HttpEndpoint value.
148
+ 7. Look at the "Welcome to nginx!" page.
data/docs/template.html CHANGED
@@ -102,6 +102,7 @@
102
102
  <li class="list-group-item"><a href="/{{NAME}}/deleting-stacks">Deleting Stacks</a></li>
103
103
  <li class="list-group-item"><a href="/{{NAME}}/canceling-stack-updates">Canceling Stack Updates</a></li>
104
104
  <li class="list-group-item"><a href="/{{NAME}}/adding-new-resource-coverage">Adding New Resource Coverage</a></li>
105
+ <li class="list-group-item"><a href="/{{NAME}}/creating-a-custom-resource-collection">Creating a custom resource collection</a></li>
105
106
  <li class="list-group-item"><a href="/{{NAME}}/stackgroups">Stackgroups</a></li>
106
107
 
107
108
  </ul>
@@ -0,0 +1,10 @@
1
+ # Cloudfile
2
+ require_relative './templates/example_dot_org.rb'
3
+
4
+ user = ENV['USER'] || 'anon'
5
+ aws_region = ENV['AWS_REGION'] || 'us-east-1'
6
+
7
+ name "#{user}-web-service-example"
8
+ region aws_region
9
+
10
+ stack 'example-org', Templates::EXAMPLE_DOT_ORG
@@ -0,0 +1,63 @@
1
+ require 'convection'
2
+
3
+ class WebService < Convection::Model::Template::ResourceCollection
4
+ attach_to_dsl(:web_service)
5
+
6
+ attribute :ec2_instance_image_id
7
+ attribute :user_data
8
+
9
+ def execute
10
+ web_service = self
11
+
12
+ generate_security_groups(web_service)
13
+ generate_ec2_instance(web_service)
14
+ end
15
+
16
+ def cidr_ranges
17
+ @cidr_ranges ||= allowed_csv_ranges.split(/[, ]+/)
18
+ end
19
+
20
+ # Resource generator methods
21
+
22
+ def generate_ec2_instance(web_service)
23
+ ec2_instance "#{name}Frontend" do
24
+ image_id web_service.ec2_instance_image_id
25
+ security_group fn_ref("#{web_service.name}SecurityGroup")
26
+
27
+ tag 'Name', "#{web_service.name}Frontend"
28
+ tag 'Stack', stack.cloud
29
+
30
+ user_data base64(web_service.user_data)
31
+
32
+ with_output 'Hostname', get_att(name, 'PublicDnsName') do
33
+ description 'The public hostname of this web service.'
34
+ end
35
+
36
+ with_output 'HttpEndpoint', join('', 'http://', get_att(name, 'PublicDnsName')) do
37
+ description 'The URL to visit this web service at.'
38
+ end
39
+ end
40
+ end
41
+
42
+ def generate_security_groups(web_service)
43
+ ec2_security_group "#{web_service.name}SecurityGroup" do
44
+ description "EC2 Security Group for the #{web_service.name} web service."
45
+
46
+ web_service.cidr_ranges.each do |range|
47
+ ingress_rule(:tcp, 80, range)
48
+ end
49
+
50
+ tag 'Name', "sg-#{web_service.name}-#{stack.cloud}".downcase
51
+ tag 'Service', web_service.name
52
+ tag 'Stack', stack.cloud
53
+
54
+ with_output
55
+ end
56
+ end
57
+
58
+ def allowed_csv_ranges
59
+ return ENV['ALLOWED_CIDR_RANGES'] if ENV.key?('ALLOWED_CIDR_RANGES')
60
+
61
+ raise ArgumentError, "You must export $ALLOWED_CIDR_RANGES to diff/converge #{stack.cloud_name}."
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ require 'convection'
2
+ require_relative '../resources/web_service.rb'
3
+
4
+ module Templates
5
+ EXAMPLE_DOT_ORG = Convection.template do
6
+ description 'An example website to demonstrate using custom resource collections.'
7
+
8
+ web_service 'ExampleDotOrg' do
9
+ ec2_instance_image_id 'ami-45026036'
10
+
11
+ user_data <<~USER_DATA
12
+ #!/bin/bash
13
+ apt-get update
14
+ apt-get install -y unzip curl nginx
15
+ service nginx start
16
+ update-rc.d nginx defaults
17
+ USER_DATA
18
+ end
19
+ end
20
+ end
@@ -49,6 +49,20 @@ module Convection
49
49
  end
50
50
  end
51
51
 
52
+ def stacks_until(last_stack_name, options = {}, &block)
53
+ stacks = filter_deck(options, &block).values
54
+ return stacks if last_stack_name.nil?
55
+
56
+ last_stack = stacks.find { |stack| stack.name == last_stack_name }
57
+ unless last_stack
58
+ block.call(Model::Event.new(:error, "Stacks filter did not include last stack #{ last_stack }", :error)) if block
59
+ return []
60
+ end
61
+
62
+ last_stack_index = stacks.index(last_stack)
63
+ stacks[0..last_stack_index]
64
+ end
65
+
52
66
  def converge(to_stack, options = {}, &block)
53
67
  if to_stack && !stacks.include?(to_stack)
54
68
  block.call(Model::Event.new(:error, "Undefined Stack #{ to_stack }", :error)) if block
@@ -73,6 +87,27 @@ module Convection
73
87
  end
74
88
  end
75
89
 
90
+ def delete(stacks, &block)
91
+ stacks.each do |stack|
92
+ unless stack.exist?
93
+ block.call(Model::Event.new(:delete_skipped, "Stack #{ stack.cloud_name } does not exist remotely", :warn))
94
+ next
95
+ end
96
+
97
+ block.call(Model::Event.new(:deleted, "Delete remote stack #{ stack.cloud_name }", :info)) if block
98
+ stack.delete(&block)
99
+
100
+ if stack.error?
101
+ block.call(Model::Event.new(:error, "Error deleting stack #{ stack.name }", :error), stack.errors) if block
102
+ break
103
+ end
104
+
105
+ break unless stack.delete_success?
106
+
107
+ sleep rand @cloudfile.splay || 2
108
+ end
109
+ end
110
+
76
111
  def diff(to_stack, options = {}, &block)
77
112
  if to_stack && !stacks.include?(to_stack)
78
113
  block.call(Model::Event.new(:error, "Undefined Stack #{ to_stack }", :error)) if block
@@ -209,6 +209,17 @@ module Convection
209
209
  [CREATE_COMPLETE, ROLLBACK_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE].include?(status)
210
210
  end
211
211
 
212
+ # @return [Boolean] whether the CloudFormation Stack is in one of
213
+ # the several *_COMPLETE states.
214
+ def delete_complete?
215
+ DELETE_COMPLETE == status
216
+ end
217
+
218
+ # @return [Boolean] whether the Stack state is now {#delete_complete?} (with no errors present).
219
+ def delete_success?
220
+ !error? && delete_complete?
221
+ end
222
+
212
223
  # @return [Boolean] whether the CloudFormation Stack is in one of
213
224
  # the several *_FAILED states.
214
225
  def fail?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: convection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Manero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-08 00:00:00.000000000 Z
11
+ date: 2016-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -92,6 +92,7 @@ files:
92
92
  - convection.gemspec
93
93
  - docs/adding-new-resource-coverage.md
94
94
  - docs/canceling-stack-updates.md
95
+ - docs/creating-a-custom-resource-collection.md
95
96
  - docs/deleting-stacks.md
96
97
  - docs/getting-started.md
97
98
  - docs/index.md
@@ -100,13 +101,15 @@ files:
100
101
  - docs/stackgroups.md
101
102
  - docs/stacks.md
102
103
  - docs/template.html
103
- - example/.ruby-version
104
104
  - example/demo-cloud/Cloudfile
105
105
  - example/getting-started-guide/Cloudfile
106
106
  - example/getting-started-guide/vpc.rb
107
107
  - example/stacks/Cloudfile
108
108
  - example/stacks/tasks/lookup_vpc_task.rb
109
109
  - example/stacks/templates/vpc.rb
110
+ - example/web-service-resource-collection/Cloudfile
111
+ - example/web-service-resource-collection/resources/web_service.rb
112
+ - example/web-service-resource-collection/templates/example_dot_org.rb
110
113
  - ext/resource_generator.sh
111
114
  - lib/convection.rb
112
115
  - lib/convection/control/cloud.rb
@@ -1 +0,0 @@
1
- 2.2.2