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 +4 -4
- data/{LICENSE → LICENSE.md} +0 -0
- data/README.md +31 -71
- data/lib/cli/app.rb +9 -1
- data/lib/cli/command/ansible.rb +2 -1
- data/lib/cli/command/resource.rb +307 -0
- data/lib/cli/helpers/ansible_helper.rb +58 -0
- data/lib/cli/helpers/cloudformation_helper.rb +60 -0
- data/lib/cli/stack/cloud_formation.rb +39 -35
- data/lib/configuration.rb +3 -1
- data/lib/shelter.rb +7 -1
- data/lib/version.rb +1 -1
- metadata +14 -6
- data/lib/cli/ansible_helper.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62ec7754e2e7dcd08f0dba471c3ebeeb09836edb
|
4
|
+
data.tar.gz: 5ccd488f997f030c0c2268192c70c38ef72f70b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d61b98c4356bff597e3a990af0570a4b0aa87caadbf922ad33e5bd1f816d29f4da9362e293fff87c509a67992f6c8809614c67896d1b17998a0c182fb071e163
|
7
|
+
data.tar.gz: 501b0a2d33ba7e989481054813d2ad235386df9b5248a390a44f2ea2a34d3a2892376b1f984e2b37a7effb46c2843cadf6f3ec4e88ff69dbd5d32074d17fe3e9
|
data/{LICENSE → LICENSE.md}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,95 +1,55 @@
|
|
1
|
-
|
2
|
-
[](https://travis-ci.org/Yitsushi/shelter)
|
1
|
+
Manage your infrastructure in one place Edit
|
3
2
|
|
4
|
-
#
|
3
|
+
# Getting Started
|
5
4
|
|
6
|
-
Create
|
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
|
-
|
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
|
-
|
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
|
-
|
33
|
+
```yaml
|
34
|
+
---
|
35
|
+
- name: Ping all hosts
|
36
|
+
hosts: all
|
37
|
+
tasks:
|
38
|
+
- ping:
|
75
39
|
```
|
76
40
|
|
77
|
-
|
41
|
+
# Documentation
|
78
42
|
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
84
|
-
class Ansible < Thor
|
85
|
-
desc 'scanner', 'scanner'
|
86
|
-
def scanner
|
87
|
-
puts "scan"
|
88
|
-
end
|
48
|
+
## Code Status
|
89
49
|
|
90
|
-
|
91
|
-
|
92
|
-
end
|
50
|
+
[](https://badge.fury.io/rb/shelter)
|
51
|
+
[](https://travis-ci.org/Yitsushi/shelter)
|
93
52
|
|
94
|
-
|
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, '*')
|
data/lib/cli/command/ansible.rb
CHANGED
@@ -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::
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
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
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.
|
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
|
+
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
|
data/lib/cli/ansible_helper.rb
DELETED
@@ -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
|