shelter 0.0.5 → 0.0.6

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