shelter 0.0.5 → 0.0.6

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: 0e6809cf1cc3418ab086795b255461754ce0be7a
4
- data.tar.gz: 40bd1fd16a6f62e754a840f0941a7acfe8d653f1
3
+ metadata.gz: 62ec7754e2e7dcd08f0dba471c3ebeeb09836edb
4
+ data.tar.gz: 5ccd488f997f030c0c2268192c70c38ef72f70b8
5
5
  SHA512:
6
- metadata.gz: f1637a566b08c10a54ff2253af02e047decdfa67a4c0a3582c4c91b34b57aa70f6de04357b32e0e5e90d884e9f39d5f1f0a3c6a7d4a81d8b2267f2bbf5581cb4
7
- data.tar.gz: 27ec4c6ed3bc358586bd60ad66c5c00d9a7380024aa1277dafb2beb00ae4a819f3e92b9bc79ab7574aea144725130a73f88057d4302fa0862f0ff0a1000a097a
6
+ metadata.gz: d61b98c4356bff597e3a990af0570a4b0aa87caadbf922ad33e5bd1f816d29f4da9362e293fff87c509a67992f6c8809614c67896d1b17998a0c182fb071e163
7
+ data.tar.gz: 501b0a2d33ba7e989481054813d2ad235386df9b5248a390a44f2ea2a34d3a2892376b1f984e2b37a7effb46c2843cadf6f3ec4e88ff69dbd5d32074d17fe3e9
File without changes
data/README.md CHANGED
@@ -1,95 +1,55 @@
1
- [![Gem Version](https://badge.fury.io/rb/shelter.svg)](https://badge.fury.io/rb/shelter)
2
- [![Build Status](https://travis-ci.org/Yitsushi/shelter.svg?branch=master)](https://travis-ci.org/Yitsushi/shelter)
1
+ Manage your infrastructure in one place Edit
3
2
 
4
- # Configuration
3
+ # Getting Started
5
4
 
6
- Create `Shelterfile.rb`:
5
+ 1. Create a directory for your project:
6
+
7
+ ```
8
+ $ mkdir myinfra && cd myinfra
9
+ ```
10
+
11
+ 2. Create your `Shelterfile.rb` and define your environment:
7
12
 
8
13
  ```ruby
9
14
  Shelter::CLI::App.config do |c|
15
+ # All of them are optional
10
16
  c.ansible_directory = 'ansible'
11
17
  c.stack_directory = 'stack'
12
18
  c.plugin_directory = 'plugin'
13
19
  c.inventory_directory = 'inventory'
20
+ c.resource_directory = 'resources'
14
21
  c.secure_root = ENV.fetch('CHEPPERS_SECURE')
15
22
  end
16
23
  ```
17
24
 
18
- # Inventory script
19
-
20
- Create a ruby file under your inventory directory:
25
+ 3. Create the directory structure
21
26
 
22
- ```ruby
23
- # inventory/my_inventory.rb
24
- module Inventory
25
- class MyInventory
26
- def load(inventory)
27
- inventory.add_group_vars 'groupname', ch_env: 'value'
28
- inventory.add_server 'groupname', 'ip-address'
29
- end
30
- end
31
- end
32
27
  ```
33
-
34
- # Define a stack
35
-
36
- Create a directory under your `stack` directory (eg: `random`)
37
- and create your template there. `cli.rb` will be loaded.
38
-
39
- ```ruby
40
- # stack/random_stack/cli.rb:
41
- module Stack
42
- class RandomStack < Shelter::CLI::Stack::CloudFormation
43
- set_attr :stack_name, 'random'
44
- set_attr :template_file, File.expand_path('template.yaml', File.dirname(__FILE__))
45
- set_attr :meta, {
46
- type: 'development',
47
- client: 'cheppers',
48
- application: 'random'
49
- }
50
- end
51
- end
28
+ $ mkdir -p ansible stack plugin inventory resources/templates
52
29
  ```
53
30
 
54
- # Create plugin
55
-
56
- Create a directory under your `plugin` directory and place your code there.
57
- `main.rb` will be loaded.
58
-
59
- ### Example #1: extra command
60
-
61
- ```ruby
62
- # plugin/check/main.rb
63
- require 'thor'
64
-
65
- module Plugin
66
- class Check < Thor
67
- desc 'environment', 'Check environment'
68
- def environment
69
- puts "check"
70
- end
71
- end
72
- end
31
+ 4. Create your first Ansible playbook: `ansible/configuration.yaml`
73
32
 
74
- Shelter::CLI::App.register(Plugin::Check, 'check', 'check [COMMAND]', 'check plugin')
33
+ ```yaml
34
+ ---
35
+ - name: Ping all hosts
36
+ hosts: all
37
+ tasks:
38
+ - ping:
75
39
  ```
76
40
 
77
- ### Example #2: extra command under a specific namespace
41
+ # Documentation
78
42
 
79
- ```ruby
80
- # plugin/ansible_scanner/main.rb
81
- require 'thor'
43
+ - [Resource management](https://yitsushi.github.io/shelter/docs/resource-management/)
44
+ - [Inventory scripts](https://yitsushi.github.io/shelter/docs/inventory-scripts/)
45
+ - [Define a Stack](https://yitsushi.github.io/shelter/docs/define-a-stack/)
46
+ - [Write Your Own Plugin](https://yitsushi.github.io/shelter/docs/write-your-own-plugin/)
82
47
 
83
- module Plugin
84
- class Ansible < Thor
85
- desc 'scanner', 'scanner'
86
- def scanner
87
- puts "scan"
88
- end
48
+ ## Code Status
89
49
 
90
- default_task :scanner
91
- end
92
- end
50
+ [![Gem Version](https://badge.fury.io/rb/shelter.svg)](https://badge.fury.io/rb/shelter)
51
+ [![Build Status](https://travis-ci.org/Yitsushi/shelter.svg?branch=master)](https://travis-ci.org/Yitsushi/shelter)
93
52
 
94
- Shelter::CLI::Command::Ansible.register(Plugin::Ansible, 'scanner', 'scanner', 'Scan')
95
- ```
53
+ ## License
54
+
55
+ Shelter is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/lib/cli/app.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'thor'
4
4
 
5
+ require_all(File.join(File.dirname(__FILE__), 'helpers'))
5
6
  require_all(File.join(File.dirname(__FILE__), 'command'))
6
7
  require_all(File.join(File.dirname(__FILE__), 'stack'))
7
8
 
@@ -24,7 +25,14 @@ module Shelter
24
25
  'ansible',
25
26
  'ansible [COMMAND]',
26
27
  'Ansible related commands'
27
- )
28
+ ) if File.directory?(App.config.ansible_directory)
29
+
30
+ register(
31
+ Shelter::CLI::Command::Resource,
32
+ 'resource',
33
+ 'resource [COMMAND]',
34
+ 'Resource management'
35
+ ) if File.directory?(App.config.resource_directory)
28
36
 
29
37
  # Register server managers
30
38
  base_path = File.join(App.config.stack_directory, '*')
@@ -21,6 +21,7 @@ module Shelter
21
21
  type: :string,
22
22
  default: nil,
23
23
  desc: 'Specify inventory file/script'
24
+ # Calls Ansible with the `configuration.yaml` playbook
24
25
  def update
25
26
  ansible_execute(
26
27
  'configuration',
@@ -32,7 +33,7 @@ module Shelter
32
33
  end
33
34
 
34
35
  no_commands do
35
- include Shelter::CLI::AnsibleHelpers
36
+ include Shelter::CLI::Helpers::Ansible
36
37
  end
37
38
  end
38
39
  end
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'yaml'
5
+
6
+ module Shelter
7
+ module CLI
8
+ module Command
9
+ ##
10
+ # Resource subcommand for Shelter
11
+ #
12
+ # = Basic Directory Structure
13
+ #
14
+ # By default Shelter is looking for a directory named +resources+
15
+ # with a subdirectory name +templates+.
16
+ #
17
+ # In the +templates+ subdirectory we can define different templates.
18
+ # They have to be a valid CloudFormation +.yaml+ file.
19
+ #
20
+ # In the +resources+ directory we can define different resources.
21
+ # They have to be defined in +.yaml+ format with a few specific
22
+ # tags in it.
23
+ #
24
+ # +-- resources
25
+ # | +-- templates
26
+ # | | `-- restricted-s3.yaml
27
+ # | `-- testbucketresource.yaml
28
+ #
29
+ # You can specify where is your +resources+ directory in +Shelterfile.rb+
30
+ # with +resource_directory+.
31
+ #
32
+ # == Templates definition
33
+ #
34
+ # In our example above, we have only one template named +restricted-s3+.
35
+ # This template defined a CloudFormation stack that contains
36
+ # an S3 bucket, an IAM User and a Policy for that specific user which
37
+ # restricts the user to be able to reach only our new
38
+ # S3 Bucket, but nothing else. User we create the IAM User,
39
+ # we create a new AccessKey pair, so we can use the credentials in our
40
+ # infrastucrute. Now, for the simplicity we did not define a KMS key.
41
+ #
42
+ # As an output we export the newly created +AccessKeyID+
43
+ # and +AccessKeySecret+ pair.
44
+ #
45
+ # We have three template parameters:
46
+ #
47
+ # - S3 Bucket name
48
+ # - Client name for tagging our S3 Bucket (reason: billing)
49
+ # - Project name for tagging our S3 Bucket (reason: billing)
50
+ #
51
+ # resources/templates/restricted-s3.yaml:
52
+ #
53
+ # ---
54
+ # AWSTemplateFormatVersion: "2010-09-09"
55
+ # Parameters:
56
+ # BucketName:
57
+ # Type: String
58
+ # Description: Created S3 bucket
59
+ # Client:
60
+ # Type: String
61
+ # Description: Name of the client for tagging
62
+ # Project:
63
+ # Type: String
64
+ # Description: Project tag
65
+ # Resources:
66
+ # Bucket:
67
+ # Type: AWS::S3::Bucket
68
+ # Properties:
69
+ # BucketName: !Ref BucketName
70
+ # Tags:
71
+ # - { Key: "project", Value: !Ref Project }
72
+ # - { Key: "client", Value: !Ref Client }
73
+ # S3Policy:
74
+ # Type: "AWS::IAM::Policy"
75
+ # Properties:
76
+ # PolicyName: !Join ["-", ["s3", !Ref BucketName]]
77
+ # Users:
78
+ # - !Ref NewUser
79
+ # PolicyDocument:
80
+ # Version: "2012-10-17"
81
+ # Statement:
82
+ # - Effect: "Allow"
83
+ # Action:
84
+ # - "s3:PutObject"
85
+ # - "s3:GetObjectAcl"
86
+ # - "s3:GetObject"
87
+ # - "s3:GetObjectTorrent"
88
+ # - "s3:GetBucketTagging"
89
+ # - "s3:GetObjectTagging"
90
+ # - "s3:ListBucket"
91
+ # - "s3:PutObjectTagging"
92
+ # - "s3:DeleteObject"
93
+ # Resource:
94
+ # - !GetAtt [Bucket, Arn]
95
+ # - !Join ["", [!GetAtt [Bucket, Arn], "/*"]]
96
+ # NewUser:
97
+ # Type: "AWS::IAM::User"
98
+ # Properties:
99
+ # UserName: !Join ["-", ["s3", !Ref BucketName, "user"]]
100
+ # Path: /
101
+ # AccessKey:
102
+ # Type: AWS::IAM::AccessKey
103
+ # Properties:
104
+ # UserName: !Ref NewUser
105
+ #
106
+ # Outputs:
107
+ # AccessKeyID:
108
+ # Value:
109
+ # !Ref AccessKey
110
+ # SecretKeyID:
111
+ # Value: !GetAtt AccessKey.SecretAccessKey
112
+ #
113
+ # == Resource definition
114
+ #
115
+ # Now we have a template named +restricted-s3+. We can start creating
116
+ # resources with this template. For now we can create a backup user
117
+ # and S3 bucket, so all of our servers with a specific label can
118
+ # call AWS API to make some backup on our specific S3 bucket.
119
+ #
120
+ # Let's create a file under +resources+:
121
+ #
122
+ # ---
123
+ # name: testbucketresource
124
+ # template: restricted-s3
125
+ # capabilities:
126
+ # - CAPABILITY_NAMED_IAM
127
+ # tags:
128
+ # random: yes
129
+ # project: test
130
+ # client: cheppers
131
+ # extra: something
132
+ # parameters:
133
+ # BucketName: my-testresource
134
+ # Client: cheppers
135
+ # Project: test
136
+ #
137
+ # Here we go. Basically all of the keys in this file are required,
138
+ # or if we don't define them, they will be empty (like tags).
139
+ #
140
+ # ==== Name
141
+ #
142
+ # This will be the name of our resource. It will be stack name as well,
143
+ # but prefixed with the +res-+ string.
144
+ # In this case +res-testbucketresource+.
145
+ #
146
+ # ==== Template
147
+ #
148
+ # This value defines which one of our template we want to use.
149
+ # In this case we want to use our only one +restricted-s3+ which
150
+ # is defined in +resources/templates/restricted-s3.yaml+.
151
+ #
152
+ # ==== Capabilities
153
+ #
154
+ # AWS CloudFormation capabilities. For more details check.
155
+ # [AWS API Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html).
156
+ # Now we want to manage IAM resources, so we need +CAPABILITY_IAM+
157
+ # in general, but now we give them custom names
158
+ # so we need +CAPABILITY_NAMED_IAM+.
159
+ #
160
+ # ==== Tags
161
+ #
162
+ # This is a simple key-value list. Our CloudFormation stack
163
+ # will be tagged with these tags.
164
+ #
165
+ # === Parameters
166
+ #
167
+ # This is a simple ket-value list. That's how we can
168
+ # define parameters for our CloudFormation template.
169
+ ##
170
+ class Resource < Thor
171
+ desc 'list', 'List all managed resources'
172
+ # With +list+ we can check our resource inventory.
173
+ #
174
+ # $ bundle exec shelter resource list
175
+ # testbucketresource
176
+ def list
177
+ Dir.glob("#{App.config.resource_directory}/*.yaml").each do |res|
178
+ puts File.basename(res, '.yaml')
179
+ end
180
+ end
181
+
182
+ desc 'status <resource-name>', 'Resource status'
183
+ # With +status+ we can ask for the stack status.
184
+ #
185
+ # $ bundle exec shelter resource status testbucketresource
186
+ # Resource ID: AKIXXXXXXXXXXXXXXXXX
187
+ # Resource Type: AWS::IAM::AccessKey
188
+ # Resource Status: CREATE_COMPLETE
189
+ # Resource ID: cheppers-testresource
190
+ # Resource Type: AWS::S3::Bucket
191
+ # Resource Status: CREATE_COMPLETE
192
+ # Resource ID: s3-cheppers-testresource-user
193
+ # Resource Type: AWS::IAM::User
194
+ # Resource Status: CREATE_COMPLETE
195
+ # Resource ID: res-t-S3Po-W66XXXXXXXXX
196
+ # Resource Type: AWS::IAM::Policy
197
+ # Resource Status: CREATE_COMPLETE
198
+ def status(resource_name)
199
+ resource = read_resource(resource_name)
200
+
201
+ stack = cf_client.describe_stacks(
202
+ stack_name: resource['name']
203
+ ).stacks.first
204
+
205
+ cf_client.describe_stack_resources(
206
+ stack_name: stack.stack_name
207
+ ).stack_resources.each { |r| display_stack_resource(r) }
208
+ rescue Aws::CloudFormation::Errors::ValidationError
209
+ puts "#{resource_name} does not exist"
210
+ end
211
+
212
+ desc 'output <resource-name>', 'Display resource output'
213
+ # If we defined Outputs in our template, we can easily
214
+ # list them all with +output+ command
215
+ #
216
+ # $ bundle exec shelter resource output testbucketresource
217
+ # AccessKeyID: AKIXXXXXXXXXXXXXXXXX
218
+ # SecretKeyID: 3cXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXuI
219
+ def output(resource_name)
220
+ resource = read_resource(resource_name)
221
+
222
+ stack = cf_client.describe_stacks(
223
+ stack_name: resource['name']
224
+ ).stacks.first
225
+
226
+ stack.outputs.each { |out| display_stack_output(out) }
227
+ end
228
+
229
+ desc 'update <resource-name>', 'Update a specific resource'
230
+ # With +update+, we can update a specific resource.
231
+ #
232
+ # $ bundle exec shelter resource update testbucketresource
233
+ # Waiting for 'stack_update_complete' on 'res-testbucketresource'...
234
+ def update(resource_name)
235
+ res = read_resource(resource_name)
236
+ cf_client.update_stack(
237
+ stack_name: res['name'], capabilities: res['capabilities'],
238
+ template_body: read_template(res['template']),
239
+ tags: res['tags'], parameters: res['parameters']
240
+ )
241
+ wait_until(:stack_update_complete, resource['name'])
242
+ rescue Aws::CloudFormation::Errors::ValidationError => e
243
+ puts e.message
244
+ end
245
+
246
+ desc 'delete <resource-name>', 'Delete a specific resource'
247
+ # With +delete+ we can delete the whole stack.
248
+ #
249
+ # $ bundle exec shelter resource delete testbucketresource
250
+ # Waiting for 'stack_delete_complete' on 'res-testbucketresource'...
251
+ def delete(resource_name)
252
+ resource = read_resource(resource_name)
253
+ cf_client.delete_stack(stack_name: resource['name'])
254
+ wait_until(:stack_delete_complete, resource['name'])
255
+ end
256
+
257
+ desc 'create <resource-name>', 'Create a specific resource'
258
+ # With +create+, we can create a specific resource.
259
+ #
260
+ # $ bundle exec shelter resource create testbucketresource
261
+ # Waiting for 'stack_create_complete' on 'res-testbucketresource'...
262
+ def create(resource_name)
263
+ res = read_resource(resource_name)
264
+ cf_client.create_stack(
265
+ stack_name: res['name'], capabilities: res['capabilities'],
266
+ template_body: read_template(res['template']),
267
+ tags: res['tags'], parameters: res['parameters']
268
+ )
269
+ wait_until(:stack_create_complete, res['name'])
270
+ end
271
+
272
+ private
273
+
274
+ no_commands do
275
+ include Shelter::CLI::Helpers::CloudFormation
276
+
277
+ # Reads and validates a resource description file.
278
+ #
279
+ # If mandatory fields are not defined, it will rais an error.
280
+ # For every other fields, it just fills with default value.
281
+ def read_resource(name)
282
+ res = YAML.load_file(
283
+ "#{App.config.resource_directory}/#{name}.yaml"
284
+ )
285
+ raise 'No name specified...' if res['name'].nil?
286
+
287
+ res['name'] = "res-#{res['name']}"
288
+ res['capabilities'] ||= []
289
+ res['tags'] = stack_meta(res['tags'] || {})
290
+ res['parameters'] = stack_params(res['parameters'] || {})
291
+ res
292
+ end
293
+
294
+ # Easier to reference on templates directory
295
+ def resource_template_dir
296
+ "#{App.config.resource_directory}/templates"
297
+ end
298
+
299
+ # It's simply reads a speciifc template file content
300
+ def read_template(name)
301
+ File.open("#{resource_template_dir}/#{name}.yaml").read
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shelter
4
+ module CLI
5
+ module Helpers
6
+ # Mixin module for Ansible
7
+ module Ansible
8
+ def vault_password_file
9
+ [
10
+ '--vault-password-file',
11
+ "#{App.config.secure_root}/ansible_vault_password"
12
+ ].join(' ')
13
+ end
14
+
15
+ def inventory_file(inventory)
16
+ inventory ||= App.config.inventory_script
17
+ "--inventory #{inventory}"
18
+ end
19
+
20
+ def command_bin
21
+ 'ansible-playbook'
22
+ end
23
+
24
+ def new_server_params(server_user)
25
+ command = []
26
+ command << "--user #{server_user} --ask-pass" unless server_user.nil?
27
+ command
28
+ end
29
+
30
+ def optional_params(opts)
31
+ command = []
32
+ command << "--tags '#{opts[:tags].join(',')}'" unless
33
+ opts[:tags].empty?
34
+ command << "--skip-tags '#{opts[:skip].join(',')}'" unless
35
+ opts[:skip].empty?
36
+ command << "--limit '#{opts[:limit]}'" unless opts[:limit].nil?
37
+ command
38
+ end
39
+
40
+ # server_user: nil, inventory: nil,
41
+ # tags: [], skip: [], limit: nil
42
+ def ansible_execute(playbook, options = {})
43
+ params = {
44
+ inventory: nil, server_user: nil, tags: [], skip: [], limit: nil
45
+ }.merge(options)
46
+ command = [command_bin, inventory_file(params[:inventory]),
47
+ vault_password_file]
48
+ command += new_server_params(params[:server_user])
49
+ command += optional_params(params)
50
+ command << "#{App.config.ansible_directory}/#{playbook}.yaml"
51
+
52
+ full_command = command.join(' ')
53
+ system full_command
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shelter
4
+ module CLI
5
+ module Helpers
6
+ # Mixin module for CloudFormation
7
+ module CloudFormation
8
+ def cf_client
9
+ @cf_client ||= Aws::CloudFormation::Client.new(
10
+ credentials: Aws::Credentials.new(
11
+ ENV.fetch('AWS_ACCESS_KEY_ID'),
12
+ ENV.fetch('AWS_SECRET_ACCESS_KEY')
13
+ )
14
+ )
15
+ end
16
+
17
+ def wait_until(status, stack_name)
18
+ puts "Waiting for '#{status}' on '#{stack_name}'..."
19
+ cf_client.wait_until(
20
+ status,
21
+ stack_name: stack_name
22
+ )
23
+ end
24
+
25
+ def display_stack_resource(r)
26
+ puts "Resource ID: #{r.physical_resource_id}"
27
+ puts " Resource Type: #{r.resource_type}"
28
+ puts " Resource Status: #{r.resource_status}"
29
+ end
30
+
31
+ def display_stack_output(out)
32
+ puts out.description unless out.description.nil?
33
+ puts "#{out.output_key}: #{out.output_value}"
34
+ end
35
+
36
+ def stack_meta(res, type: 'resource')
37
+ tags = []
38
+
39
+ res.each do |key, value|
40
+ tags << { key: key.to_s, value: value.to_s }
41
+ end
42
+
43
+ tags << { key: 'type', value: type }
44
+
45
+ tags
46
+ end
47
+
48
+ def stack_params(res)
49
+ params = []
50
+
51
+ res.each do |key, value|
52
+ params << { parameter_key: key.to_s, parameter_value: value.to_s }
53
+ end
54
+
55
+ params
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -14,38 +14,51 @@ module Shelter
14
14
  stack_name: get_attr(:stack_name)
15
15
  ).stacks.first
16
16
 
17
- puts "#{stack.stack_name} exists | #{stack.creation_time}"
18
-
19
17
  cf_client.describe_stack_resources(
20
18
  stack_name: stack.stack_name
21
19
  ).stack_resources.each { |r| display_stack_resource(r) }
20
+ rescue Aws::CloudFormation::Errors::ValidationError
21
+ puts "#{get_attr(:stack_name)} does not exist"
22
+ end
23
+
24
+ desc 'output', 'Display resource output'
25
+ def output
26
+ stack = cf_client.describe_stacks(
27
+ stack_name: get_attr(:stack_name)
28
+ ).stacks.first
29
+
30
+ stack.outputs.each { |out| display_stack_output(out) }
22
31
  end
23
32
 
24
33
  desc 'create', 'Create stack'
25
34
  def create
26
35
  cf_client.create_stack(
27
36
  stack_name: get_attr(:stack_name),
37
+ capabilities: get_attr(:capabilities),
28
38
  template_body: File.open(get_attr(:template_file)).read,
29
- capabilities: ['CAPABILITY_IAM'],
30
- tags: stack_meta
31
- )
32
- cf_client.wait_until(
33
- :stack_create_complete,
34
- stack_name: get_attr(:stack_name)
39
+ tags: meta, parameters: stack_params(get_attr(:parameters))
35
40
  )
41
+ wait_until(:stack_create_complete, get_attr(:stack_name))
36
42
  end
37
43
 
38
44
  desc 'update', 'Update stack'
39
45
  def update
40
46
  cf_client.update_stack(
41
47
  stack_name: get_attr(:stack_name),
48
+ capabilities: get_attr(:capabilities),
42
49
  template_body: File.open(get_attr(:template_file)).read,
43
- capabilities: ['CAPABILITY_IAM']
44
- )
45
- cf_client.wait_until(
46
- :stack_update_complete,
47
- stack_name: get_attr(:stack_name)
50
+ tags: meta, parameters: stack_params(get_attr(:parameters))
48
51
  )
52
+ wait_until(:stack_update_complete, get_attr(:stack_name))
53
+ rescue Aws::CloudFormation::Errors::ValidationError => e
54
+ puts e.message
55
+ end
56
+
57
+ desc 'delete', 'Delete the stack'
58
+ def delete
59
+ raise 'Stack is not deletable!!!' unless get_attr(:deletable)
60
+ cf_client.delete_stack(stack_name: get_attr(:stack_name))
61
+ wait_until(:stack_delete_complete, get_attr(:stack_name))
49
62
  end
50
63
 
51
64
  # Attribute helpers
@@ -53,7 +66,10 @@ module Shelter
53
66
  class << self
54
67
  attr_accessor :_attr
55
68
  def set_attr(name, value)
56
- self._attr ||= {}
69
+ self._attr ||= {
70
+ tags: {},
71
+ parameters: {}
72
+ }
57
73
  self._attr[name] = value
58
74
  end
59
75
  end
@@ -65,28 +81,16 @@ module Shelter
65
81
 
66
82
  # AWS helpers
67
83
  no_commands do
68
- def cf_client
69
- @cf_client ||= Aws::CloudFormation::Client.new(
70
- credentials: Aws::Credentials.new(
71
- ENV.fetch('AWS_ACCESS_KEY_ID'),
72
- ENV.fetch('AWS_SECRET_ACCESS_KEY')
73
- )
74
- )
75
- end
76
-
77
- def display_stack_resource(r)
78
- puts "Stack Name: #{r.stack_name}"
79
- puts "Resource ID: #{r.physical_resource_id}"
80
- puts "Resource Type: #{r.resource_type}"
81
- puts "Resource Status: #{r.resource_status}"
82
- end
84
+ include Shelter::CLI::Helpers::CloudFormation
83
85
 
84
- def stack_meta
85
- [
86
- { key: 'client', value: get_attr(:meta)[:client] },
87
- { key: 'type', value: get_attr(:meta)[:type] },
88
- { key: 'application', value: get_attr(:meta)[:application] }
89
- ]
86
+ def meta
87
+ stack_meta(
88
+ get_attr(:tags).merge(
89
+ client: get_attr(:meta)[:client],
90
+ application: get_attr(:meta)[:application]
91
+ ),
92
+ type: get_attr(:meta)[:type]
93
+ )
90
94
  end
91
95
  end
92
96
  end
data/lib/configuration.rb CHANGED
@@ -8,13 +8,15 @@ module Shelter
8
8
  :secure_root,
9
9
  :inventory_script,
10
10
  :inventory_directory,
11
- :plugin_directory
11
+ :plugin_directory,
12
+ :resource_directory
12
13
 
13
14
  attr_reader :project_root
14
15
 
15
16
  def initialize
16
17
  @ansible_directory = 'ansible'
17
18
  @stack_directory = 'stacks'
19
+ @resource_directory = 'resources'
18
20
  @secure_root = ENV.fetch('SECURE', 'secure')
19
21
  @inventory_script = File.join(
20
22
  File.dirname($PROGRAM_NAME),
data/lib/shelter.rb CHANGED
@@ -27,9 +27,15 @@ LIB_ROOT = File.expand_path('../', __FILE__)
27
27
 
28
28
  def require_all(dir)
29
29
  path = File.join(dir, '*')
30
+ later = []
30
31
  Dir.glob(path).each do |file|
31
32
  require file if File.file?(file)
32
- require_all(file) if File.directory?(file)
33
+ later << file if File.directory?(file)
34
+ end
35
+
36
+ # Require subdirs
37
+ later.each do |file|
38
+ require_all(file)
33
39
  end
34
40
  end
35
41
 
data/lib/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Shelter
4
4
  # Current version to publish
5
5
  # Gemspec also uses this constant
6
- VERSION = '0.0.5'
6
+ VERSION = '0.0.6'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shelter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Balazs Nadasdi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-17 00:00:00.000000000 Z
11
+ date: 2017-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -86,16 +86,20 @@ executables:
86
86
  - shelter
87
87
  - shelter-inventory
88
88
  extensions: []
89
- extra_rdoc_files: []
89
+ extra_rdoc_files:
90
+ - LICENSE.md
91
+ - README.md
90
92
  files:
91
- - LICENSE
93
+ - LICENSE.md
92
94
  - README.md
93
95
  - bin/shelter
94
96
  - bin/shelter-inventory
95
97
  - lib/ansible/inventory.rb
96
- - lib/cli/ansible_helper.rb
97
98
  - lib/cli/app.rb
98
99
  - lib/cli/command/ansible.rb
100
+ - lib/cli/command/resource.rb
101
+ - lib/cli/helpers/ansible_helper.rb
102
+ - lib/cli/helpers/cloudformation_helper.rb
99
103
  - lib/cli/stack/cloud_formation.rb
100
104
  - lib/configuration.rb
101
105
  - lib/shelter.rb
@@ -105,7 +109,11 @@ licenses:
105
109
  - MIT
106
110
  metadata: {}
107
111
  post_install_message:
108
- rdoc_options: []
112
+ rdoc_options:
113
+ - "--main"
114
+ - README.md
115
+ - "--line-numbers"
116
+ - "--all"
109
117
  require_paths:
110
118
  - lib
111
119
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Shelter
4
- module CLI
5
- # Mixin module for Ansible
6
- module AnsibleHelpers
7
- def vault_password_file
8
- [
9
- '--vault-password-file',
10
- "#{App.config.secure_root}/ansible_vault_password"
11
- ].join(' ')
12
- end
13
-
14
- def inventory_file(inventory)
15
- inventory ||= App.config.inventory_script
16
- "--inventory #{inventory}"
17
- end
18
-
19
- def command_bin
20
- 'ansible-playbook'
21
- end
22
-
23
- def new_server_params(server_user)
24
- command = []
25
- command << "--user #{server_user} --ask-pass" unless server_user.nil?
26
- command
27
- end
28
-
29
- def optional_params(opts)
30
- command = []
31
- command << "--tags '#{opts[:tags].join(',')}'" unless opts[:tags].empty?
32
- command << "--skip-tags '#{opts[:skip].join(',')}'" unless
33
- opts[:skip].empty?
34
- command << "--limit '#{opts[:limit]}'" unless opts[:limit].nil?
35
- command
36
- end
37
-
38
- # server_user: nil, inventory: nil,
39
- # tags: [], skip: [], limit: nil
40
- def ansible_execute(playbook, options = {})
41
- params = {
42
- inventory: nil, server_user: nil, tags: [], skip: [], limit: nil
43
- }.merge(options)
44
- command = [command_bin, inventory_file(params[:inventory]),
45
- vault_password_file]
46
- command += new_server_params(params[:server_user])
47
- command += optional_params(params)
48
- command << "#{App.config.ansible_directory}/#{playbook}.yaml"
49
-
50
- full_command = command.join(' ')
51
- system full_command
52
- end
53
- end
54
- end
55
- end