humidifier 3.0.1 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +265 -2
- data/lib/humidifier/cli.rb +107 -0
- data/lib/humidifier/config/mapper.rb +116 -0
- data/lib/humidifier/config/mapping.rb +40 -0
- data/lib/humidifier/config.rb +99 -20
- data/lib/humidifier/directory.rb +132 -0
- data/lib/humidifier/stack.rb +72 -36
- data/lib/humidifier/version.rb +1 -1
- data/lib/humidifier.rb +15 -4
- metadata +33 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a3a43a433113df153ba4a97f549ac007299f91fb5c0a266dea0cbb70303712
|
4
|
+
data.tar.gz: 1e81ad0413783a00d1c0067334319634ceac86185de55e73fbe2c747f237b328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7626be05d2483a66ab1909fcf7bb515609fc5eb7fb404b648b10554af1501fb801c0625250258d2675ed056803acfc4a42d238f8ca1696447c1dc16165da795d
|
7
|
+
data.tar.gz: 4f9d3167302db844968d433b12e82c8128f538fad1f27da6d0ecdcd8cd7398c5d8fd2fc36da15bf03bd1a2334b065307a4709e7f7e10aeb22e1198345da8d56f
|
data/README.md
CHANGED
@@ -5,7 +5,35 @@
|
|
5
5
|
|
6
6
|
Humidifier allows you to build AWS CloudFormation (CFN) templates programmatically. CFN stacks and resources are represented as Ruby objects with accessors for all their supported properties. Stacks and resources have `to_cf` methods that allow you to quickly inspect what will be uploaded.
|
7
7
|
|
8
|
-
|
8
|
+
- [Installation](#installation)
|
9
|
+
- [Getting started](#getting-started)
|
10
|
+
- [Example usage](#example-usage)
|
11
|
+
- [Interfacing with AWS](#interfacing-with-aws)
|
12
|
+
- [CloudFormation functions](#cloudformation-functions)
|
13
|
+
- [Change Sets](#change-sets)
|
14
|
+
- [Introspection](#introspection)
|
15
|
+
- [Large templates](#large-templates)
|
16
|
+
- [Forcing uploading](#forcing-uploading)
|
17
|
+
- [CLI](#cli)
|
18
|
+
- [Resource files](#resource-files)
|
19
|
+
- [Mappers](#mappers)
|
20
|
+
- [Using the CLI](#using-the-cli)
|
21
|
+
- [`change [?stack]`](#change-stack)
|
22
|
+
- [`deploy [?stack] [*parameters]`](#deploy-stack-parameters)
|
23
|
+
- [`display [stack] [?pattern]`](#display-stack-pattern)
|
24
|
+
- [`stacks`](#stacks)
|
25
|
+
- [`upload [?stack]`](#upload-stack)
|
26
|
+
- [`validate [?stack]`](#validate-stack)
|
27
|
+
- [Parameters](#parameters)
|
28
|
+
- [Shortcuts](#shortcuts)
|
29
|
+
- [Automatic id properties](#automatic-id-properties)
|
30
|
+
- [Anonymous mappers](#anonymous-mappers)
|
31
|
+
- [Cross-stack references](#cross-stack-references)
|
32
|
+
- [Development](#development)
|
33
|
+
- [Testing](#testing)
|
34
|
+
- [Specs](#specs)
|
35
|
+
- [Contributing](#contributing)
|
36
|
+
- [License](#license)
|
9
37
|
|
10
38
|
## Installation
|
11
39
|
|
@@ -72,7 +100,7 @@ There are additionally four functions on `Humidifier::Stack` that support waitin
|
|
72
100
|
|
73
101
|
#### CloudFormation functions
|
74
102
|
|
75
|
-
You can use CFN intrinsic functions and references using `Humidifier.fn.[name]` and `Humidifier.ref`.
|
103
|
+
You can use CFN intrinsic functions and references using `Humidifier.fn.[name]` and `Humidifier.ref`. They will build appropriate structures that know how to be dumped to CFN syntax.
|
76
104
|
|
77
105
|
#### Change Sets
|
78
106
|
|
@@ -114,6 +142,241 @@ Humidifier.configure do |config|
|
|
114
142
|
end
|
115
143
|
```
|
116
144
|
|
145
|
+
## CLI
|
146
|
+
|
147
|
+
`Humidifier` can also be used as a CLI for managing resources through configuration files. To get started, build a ruby script (for example `bin/humidifier`) that executes the `Humidifier::CLI` class, like so:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
#!/usr/bin/env ruby
|
151
|
+
require 'humidifier'
|
152
|
+
|
153
|
+
Humidifier.configure do |config|
|
154
|
+
# optional, defaults to the current working directory, so that all of the
|
155
|
+
# directories from the location that you run the CLI are assumed to contain
|
156
|
+
# resource specifications
|
157
|
+
config.stack_path = 'stacks'
|
158
|
+
|
159
|
+
# optional, a default prefix to use before deploying to AWS
|
160
|
+
config.stack_prefix = 'humidifier-'
|
161
|
+
|
162
|
+
# specifies that `users.yml` files contain specifications for `AWS::IAM::User`
|
163
|
+
# resources
|
164
|
+
config.map :users, to: 'IAM::User'
|
165
|
+
end
|
166
|
+
|
167
|
+
Humidifier::CLI.start(ARGV)
|
168
|
+
```
|
169
|
+
|
170
|
+
### Resource files
|
171
|
+
|
172
|
+
Inside of the `stacks` directory configured above, create a subdirectory for each CloudFormation stack that you want to deploy. With the above configuration, we can create YAML files in the form of `users.yml` for each stack, which will specify IAM users to create. The file format looks like the below:
|
173
|
+
|
174
|
+
```yaml
|
175
|
+
EngUser:
|
176
|
+
path: /humidifier/
|
177
|
+
user_name: EngUser
|
178
|
+
groups:
|
179
|
+
- Engineering
|
180
|
+
- Testing
|
181
|
+
- Deployment
|
182
|
+
|
183
|
+
AdminUser:
|
184
|
+
path: /humidifier/
|
185
|
+
user_name: AdminUser
|
186
|
+
groups:
|
187
|
+
- Management
|
188
|
+
- Administration
|
189
|
+
```
|
190
|
+
|
191
|
+
The top-level keys are the logical resource names that will be displayed in the CloudFormation screen. They point to a map of key/value pairs that will be passed on to `humidifier`. Any `humidifier` (and therefore any CloudFormation) attribute may be specified. For more information on CloudFormation templates and which attributes may be specified, see both the [`humidifier` docs](http://localytics.github.io/humidifier) and the [CloudFormation docs](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-guide.html).
|
192
|
+
|
193
|
+
### Mappers
|
194
|
+
|
195
|
+
Oftentimes, specifying these attributes can become repetitive, e.g., each user should automatically receive the same "path" attribute. Other times, you may want custom logic to execute depending on which AWS environment you're running in. Finally, you may want to reference resources in the same or other stacks.
|
196
|
+
|
197
|
+
`Humidifier`'s solution for this is to allow customized "mapper" classes to take the user-provided attributes and transform them into the attributes that CloudFormation expects. Consider the following example for mapping a user:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class UserMapper < Humidifier::Config::Mapper
|
201
|
+
GROUPS = {
|
202
|
+
'eng' => %w[Engineering Testing Deployment],
|
203
|
+
'admin' => %w[Management Administration]
|
204
|
+
}
|
205
|
+
|
206
|
+
defaults do |logical_name|
|
207
|
+
{ path: '/humidifier/', user_name: logical_name }
|
208
|
+
end
|
209
|
+
|
210
|
+
attribute :group do |group|
|
211
|
+
groups = GROUPS[group]
|
212
|
+
groups.any? ? { groups: GROUPS[group] } : {}
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
Humidifier.configure do |config|
|
217
|
+
config.map :users, to: 'IAM::User', using: UserMapper
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
This means that by default, all entries in the `users.yml` files will get a `/humidifier/` path, the `user_name` attribute will be set based on the logical name that was provided for the resource, and you can additionally specify a `group` attribute, even though it is not native to CloudFormation. With this `group` attribute, it will actually map to the `groups` attribute that CloudFormation expects.
|
222
|
+
|
223
|
+
With this new mapper in place, we can simplify our YAML file to:
|
224
|
+
|
225
|
+
```yaml
|
226
|
+
EngUser:
|
227
|
+
group: eng
|
228
|
+
|
229
|
+
AdminUser:
|
230
|
+
group: admin
|
231
|
+
```
|
232
|
+
|
233
|
+
### Using the CLI
|
234
|
+
|
235
|
+
Now that you've configured your CLI, your resources, and your mappers, you can use the CLI to display, validate, and deploy your infrastructure to CloudFormation. Run your script without any arguments to get the help message and explanations for each command.
|
236
|
+
|
237
|
+
Each command has an `--aws-profile` (or `-p`) option for specifying which profile to authenticate against when querying AWS. You should ensure that this profile has the correct permissions for creating whatever resources are going to part of your stack. You can also rely on the `AWS_*` environment variables, or the EC2 instance profile if you're deploying from an instance. For more information, see the [AWS docs](http://docs.aws.amazon.com/sdkforruby/api/) under the "Configuration" section.
|
238
|
+
|
239
|
+
Below are the list of commands and some of their options.
|
240
|
+
|
241
|
+
#### `change [?stack]`
|
242
|
+
|
243
|
+
Creates a change set for either the specified stack or all stacks in the repo. The change set represents the changes between what is currently deployed versus
|
244
|
+
the resources represented by the configuration.
|
245
|
+
|
246
|
+
#### `deploy [?stack] [*parameters]`
|
247
|
+
|
248
|
+
Creates or updates (depending on if the stack already exists) one or all stacks in the repo.
|
249
|
+
|
250
|
+
The `deploy` command also allows a `--prefix` command line argument that will override the default prefix (if one is configured) for the stack that is being deployed. This is especially useful when you're deploying multiple copies of the same stack (for instance, multiple autoscaling groups) that have different purposes or semantically mean newer versions of resources.
|
251
|
+
|
252
|
+
#### `display [stack] [?pattern]`
|
253
|
+
|
254
|
+
Displays the specified stack in JSON format on the command line. If you optionally pass a pattern argument, it will filter the resources down to just ones whose names match the given pattern.
|
255
|
+
|
256
|
+
#### `stacks`
|
257
|
+
|
258
|
+
Displays the names of all of the stacks that `humidifier` is managing.
|
259
|
+
|
260
|
+
#### `upload [?stack]`
|
261
|
+
|
262
|
+
Upload one or all stacks in the repo to S3 for reference later. Note that this must be combined with the `humidifier` `s3_bucket` configuration option.
|
263
|
+
|
264
|
+
#### `validate [?stack]`
|
265
|
+
|
266
|
+
Validate that one or all stacks in the repo are properly configured and using values that CloudFormation understands.
|
267
|
+
|
268
|
+
### Parameters
|
269
|
+
|
270
|
+
CloudFormation template parameters can be specified by having a special `parameters.yml` file in your stack directory. This file should contain a YAML-encoded object whose keys are the names of the parameters and whose values are the parameter configuration (using the same underscore paradigm as `humidifier` resources for specifying configuration).
|
271
|
+
|
272
|
+
You can pass values to the CLI deploy command after the stack name on the command line as in:
|
273
|
+
|
274
|
+
```sh
|
275
|
+
bin/humidifier deploy foobar Param1=Foo Param2=Bar
|
276
|
+
```
|
277
|
+
|
278
|
+
Those parameters will get passed in as values when the stack is deployed.
|
279
|
+
|
280
|
+
### Shortcuts
|
281
|
+
|
282
|
+
A couple of convenient shortcuts are built into `humidifier` so that writing templates and mappers both can be more concise.
|
283
|
+
|
284
|
+
#### Automatic id properties
|
285
|
+
|
286
|
+
There are a lot of properties in the AWS CloudFormation resource specification that are simply pointers to other entities within the AWS ecosystem. For example, an `AWS::EC2::VPCGatewayAttachment` entity has a `VpcId` property that represents the ID of the associated `AWS::EC2::VPC`.
|
287
|
+
|
288
|
+
Because this pattern is so common, `humidifier` detects all properties ending in `Id` and allows you to specify them without the suffix. If you choose to use this format, `humidifier` will automatically turn that value into a [CloudFormation resource reference](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html).
|
289
|
+
|
290
|
+
#### Anonymous mappers
|
291
|
+
|
292
|
+
A lot of the time, mappers that you create will not be overly complicated, especially if you're using automatic id properties. So, the `config.map` method optionally takes a block, and allows you to specify the mapper inline. This is recommended for mappers that aren't too complicated as to warrant their own class (for instance, for testing purposes). An example of this using the `UserMapper` from above is below:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
Humidifier.configure do |config|
|
296
|
+
config.map :users, to: 'IAM::User' do
|
297
|
+
GROUPS = {
|
298
|
+
'eng' => %w[Engineering Testing Deployment],
|
299
|
+
'admin' => %w[Management Administration]
|
300
|
+
}
|
301
|
+
|
302
|
+
defaults do |logical_name|
|
303
|
+
{ path: '/humidifier/', user_name: logical_name }
|
304
|
+
end
|
305
|
+
|
306
|
+
attribute :group do |group|
|
307
|
+
groups = GROUPS[group]
|
308
|
+
groups.any? ? { groups: GROUPS[group] } : {}
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
314
|
+
#### Cross-stack references
|
315
|
+
|
316
|
+
AWS allows cross-stack references through the [intrinsic `Fn::ImportValue` function](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html). You can take advantage of this with `humidifier` by using the `export: true` option on resources in your stacks. For instance, if in one stack you have a subnet that you need to reference in another, you could (`stacks/vpc/subnets.yml`):
|
317
|
+
|
318
|
+
```yaml
|
319
|
+
ProductionPrivateSubnet2a:
|
320
|
+
vpc: ProductionVPC
|
321
|
+
cidr_block: 10.0.0.0/19
|
322
|
+
availability_zone: us-west-2a
|
323
|
+
export: true
|
324
|
+
|
325
|
+
ProductionPrivateSubnet2b:
|
326
|
+
vpc: ProductionVPC
|
327
|
+
cidr_block: 10.0.64.0/19
|
328
|
+
availability_zone: us-west-2b
|
329
|
+
export: true
|
330
|
+
|
331
|
+
ProductionPrivateSubnet2c:
|
332
|
+
vpc: ProductionVPC
|
333
|
+
cidr_block: 10.0.128.0/19
|
334
|
+
availability_zone: us-west-2c
|
335
|
+
export: true
|
336
|
+
```
|
337
|
+
|
338
|
+
And then in another stack, you could reference those values (`stacks/rds/db_subnets_groups.yml`):
|
339
|
+
|
340
|
+
```yaml
|
341
|
+
ProductionDBSubnetGroup:
|
342
|
+
db_subnet_group_description: Production DB private subnet group
|
343
|
+
subnets:
|
344
|
+
- ProductionPrivateSubnet2a
|
345
|
+
- ProductionPrivateSubnet2b
|
346
|
+
- ProductionPrivateSubnet2c
|
347
|
+
```
|
348
|
+
|
349
|
+
Within the configuration, you would specify to use the `Fn::ImportValue` function like so:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
Humidifier.configure do |config|
|
353
|
+
config.stack_path = 'stacks'
|
354
|
+
|
355
|
+
config.map :subnets, to: 'EC2::Subnet'
|
356
|
+
|
357
|
+
config.map :db_subnet_groups, to: 'RDS::DBSubnetGroup' do
|
358
|
+
attribute :subnets do |subnet_names|
|
359
|
+
subnet_ids =
|
360
|
+
subnet_names.map do |subnet_name|
|
361
|
+
Humidifier.fn.import_value(subnet_name)
|
362
|
+
end
|
363
|
+
|
364
|
+
{ subnet_ids: subnet_ids }
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
```
|
369
|
+
|
370
|
+
If you specify `export: true` it will by default export a reference to the resource listed in the stack. You can also choose to export a different attribute by specifying the attribute as the value to export. For example, if we were creating instance profiles and wanted to export the `Arn` so that it could be referenced by an instance later, we could:
|
371
|
+
|
372
|
+
```yaml
|
373
|
+
APIRoleInstanceProfile:
|
374
|
+
depends_on: APIRole
|
375
|
+
roles:
|
376
|
+
- APIRole
|
377
|
+
export: Arn
|
378
|
+
```
|
379
|
+
|
117
380
|
## Development
|
118
381
|
|
119
382
|
To get started, ensure you have ruby installed, version 2.4 or later. From there, install the `bundler` gem: `gem install bundler` and then `bundle install` in the root of the repository.
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Humidifier
|
4
|
+
# A CLI for running commands to manipulate the stacks that Humidifier knows
|
5
|
+
# about.
|
6
|
+
class CLI < Thor
|
7
|
+
class_option :aws_profile, desc: 'The AWS profile to authenticate with',
|
8
|
+
aliases: ['-p']
|
9
|
+
|
10
|
+
class_option :debug, desc: 'Sets up debug mode', aliases: ['-d']
|
11
|
+
class_around :safe_execute
|
12
|
+
|
13
|
+
desc 'change [?stack]', 'Create changesets for one or all stacks'
|
14
|
+
def change(name = nil)
|
15
|
+
authorize
|
16
|
+
|
17
|
+
stack_names_from(name).each do |stack_name|
|
18
|
+
directory = Directory.new(stack_name)
|
19
|
+
|
20
|
+
puts "Creating a changeset for #{directory.stack_name}"
|
21
|
+
directory.create_change_set
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'deploy [?stack] [*parameters]', 'Update one or all stacks'
|
26
|
+
option :wait, desc: 'Wait for the stack to create/update',
|
27
|
+
type: :boolean, default: false
|
28
|
+
option :prefix, desc: 'The prefix to use for the stack'
|
29
|
+
def deploy(name = nil, *parameters)
|
30
|
+
authorize
|
31
|
+
|
32
|
+
stack_names_from(name).each do |stack_name|
|
33
|
+
directory = Directory.new(stack_name, prefix: options[:prefix])
|
34
|
+
|
35
|
+
puts "Deploying #{directory.stack_name}"
|
36
|
+
directory.deploy(options[:wait], parameters_from(parameters))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'display [stack] [?pattern]',
|
41
|
+
'Display the CloudFormation JSON for a given stack'
|
42
|
+
def display(name, pattern = nil)
|
43
|
+
puts Directory.new(name, pattern: pattern && /#{pattern}/i).to_cf
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'stacks', 'List the stacks known to Humidifier'
|
47
|
+
def stacks
|
48
|
+
puts Humidifier.config.stack_names.sort
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'upload [?stack]', 'Upload one or all stacks to S3'
|
52
|
+
def upload(name = nil)
|
53
|
+
authorize
|
54
|
+
|
55
|
+
stack_names_from(name).each do |stack_name|
|
56
|
+
directory = Directory.new(stack_name)
|
57
|
+
|
58
|
+
puts "Uploading #{directory.stack_name}"
|
59
|
+
directory.upload
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'validate [?stack]',
|
64
|
+
'Validate that one or all stacks are valid with CloudFormation'
|
65
|
+
def validate(name = nil)
|
66
|
+
authorize
|
67
|
+
|
68
|
+
print 'Validating... '
|
69
|
+
|
70
|
+
valid =
|
71
|
+
stack_names_from(name).all? do |stack_name|
|
72
|
+
Directory.new(stack_name).valid?
|
73
|
+
end
|
74
|
+
|
75
|
+
puts valid ? 'Valid.' : 'Invalid.'
|
76
|
+
end
|
77
|
+
|
78
|
+
no_commands do
|
79
|
+
def authorize
|
80
|
+
return unless options[:aws_profile]
|
81
|
+
|
82
|
+
Aws.config[:credentials] =
|
83
|
+
Aws::SharedCredentials.new(profile_name: options[:aws_profile])
|
84
|
+
end
|
85
|
+
|
86
|
+
def parameters_from(opts)
|
87
|
+
opts.map do |opt|
|
88
|
+
key, value = opt.split('=')
|
89
|
+
{ parameter_key: key, parameter_value: value }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def safe_execute
|
94
|
+
yield
|
95
|
+
rescue Error => error
|
96
|
+
raise error if options[:debug]
|
97
|
+
|
98
|
+
puts error.message
|
99
|
+
exit 1
|
100
|
+
end
|
101
|
+
|
102
|
+
def stack_names_from(name)
|
103
|
+
name ? [name] : Humidifier.config.stack_names
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Humidifier
|
4
|
+
class Config
|
5
|
+
# The parent class for mapper classes. These classes are used to transform
|
6
|
+
# arbitrary attributes coming from the user-provided YAML files into valid
|
7
|
+
# CloudFormation props that can then be used in the template. This class
|
8
|
+
# provides an easy-to-extend DSL that allows for default attributes
|
9
|
+
# specifying custom attributes.
|
10
|
+
class Mapper
|
11
|
+
# Raised when the attribute given in the file could not be matched to an
|
12
|
+
# attribute in the mapper.
|
13
|
+
class InvalidResourceAttributeError < Error
|
14
|
+
def initialize(clazz, key)
|
15
|
+
super("Invalid attribute name given for #{clazz.aws_name}: #{key}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# The list of attributes that are common to all resources that need to be
|
20
|
+
# handled separately.
|
21
|
+
COMMON_ATTRIBUTES = Resource::COMMON_ATTRIBUTES.values
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Defines a custom attribute. The given block will receive the
|
25
|
+
# user-provided value for the attribute. The block should return a hash
|
26
|
+
# where the keys are valid humidifier properties and the values are
|
27
|
+
# valid values for those properties. In the below example, we specify
|
28
|
+
# the group attribute which maps to the groups attribute after some
|
29
|
+
# transformation.
|
30
|
+
#
|
31
|
+
# attribute :group do |group|
|
32
|
+
# groups = GROUPS[group]
|
33
|
+
# groups.any? ? { groups: GROUPS[group] } : {}
|
34
|
+
# end
|
35
|
+
def attribute(name, &block)
|
36
|
+
define_method(:"attribute_#{name}", &block)
|
37
|
+
attribute_methods << name
|
38
|
+
end
|
39
|
+
|
40
|
+
# The names of the custom attribute methods.
|
41
|
+
def attribute_methods
|
42
|
+
@attribute_methods ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Defines the default attributes that should be applied to all resources
|
46
|
+
# of this type. The given block will be passed the logical resource
|
47
|
+
# name that the user specified for the resource. The block should return
|
48
|
+
# a hash where the keys are valid humidifier properties and the values
|
49
|
+
# are valid values for those properties. In the example below, the
|
50
|
+
# user_name property is set based on the logical name.
|
51
|
+
#
|
52
|
+
# defaults do |name|
|
53
|
+
# { user_name: name }
|
54
|
+
# end
|
55
|
+
def defaults(&block)
|
56
|
+
define_method(:attribute_defaults, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Builds a humidifier resource using the given humidifier resource class,
|
61
|
+
# the logical name for the resource, and the user-specified attributes.
|
62
|
+
def resource_for(clazz, name, attributes)
|
63
|
+
mapped =
|
64
|
+
respond_to?(:attribute_defaults) ? attribute_defaults(name) : {}
|
65
|
+
|
66
|
+
attributes.each do |key, value|
|
67
|
+
mapped.merge!(mapped_from(clazz, key, value))
|
68
|
+
end
|
69
|
+
|
70
|
+
common_attributes = common_attributes_from(mapped)
|
71
|
+
|
72
|
+
resource = clazz.new(mapped)
|
73
|
+
resource.update_attributes(common_attributes)
|
74
|
+
resource
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def common_attributes_from(mapped)
|
80
|
+
COMMON_ATTRIBUTES.each_with_object({}) do |common_attribute, extract|
|
81
|
+
extracted = mapped.delete(common_attribute)
|
82
|
+
extract[common_attribute] = extracted if extracted
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def mapped_from(clazz, key, value) # rubocop:disable Metrics/MethodLength
|
87
|
+
if self.class.attribute_methods.include?(key.to_sym)
|
88
|
+
# The given attribute name has been defined using the `::attribute`
|
89
|
+
# DSL method, so send the given value to that method and return the
|
90
|
+
# resulting hash.
|
91
|
+
public_send(:"attribute_#{key}", value)
|
92
|
+
elsif clazz.prop?(key)
|
93
|
+
# The given attribute name is a valid property on the resource, so
|
94
|
+
# directly map the attribute to the given value.
|
95
|
+
{ key.to_sym => value }
|
96
|
+
elsif clazz.prop?("#{key}_id")
|
97
|
+
# The given attribute name corresponds to a property on the resource
|
98
|
+
# that takes the ID of another resource (for example, specifying the
|
99
|
+
# vpc option in the file when the resource has a vpc_id property). In
|
100
|
+
# this case, automatically convert the given value into a
|
101
|
+
# CloudFormation reference.
|
102
|
+
{ "#{key}_id": Humidifier.ref(value) }
|
103
|
+
elsif COMMON_ATTRIBUTES.include?(key.to_sym)
|
104
|
+
# The given attribute name is one of the attributes common to all
|
105
|
+
# resources (for example creation_policy), so map that directly to the
|
106
|
+
# given value.
|
107
|
+
{ key.to_sym => value }
|
108
|
+
else
|
109
|
+
# The given attribute name did not match one of the valid options, so
|
110
|
+
# raise an error to alert the user.
|
111
|
+
raise InvalidResourceAttributeError.new(clazz, key)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Humidifier
|
4
|
+
class Config
|
5
|
+
class Mapping
|
6
|
+
attr_reader :clazz, :mapper
|
7
|
+
|
8
|
+
def initialize(opts = {}, &block)
|
9
|
+
@clazz = Humidifier[normalized(opts[:to])]
|
10
|
+
raise Error, "Invalid resource: #{opts[:to].inspect}" if @clazz.nil?
|
11
|
+
|
12
|
+
if opts[:using] && block_given?
|
13
|
+
raise Error, 'Cannot specify :using and provide an anonymous mapper'
|
14
|
+
end
|
15
|
+
|
16
|
+
@mapper = mapper_from(opts, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_for(name, attributes)
|
20
|
+
mapper.resource_for(clazz, name, attributes)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def mapper_from(opts, &block)
|
26
|
+
if opts[:using]
|
27
|
+
opts[:using].new
|
28
|
+
elsif block_given?
|
29
|
+
Class.new(Mapper, &block).new
|
30
|
+
else
|
31
|
+
Mapper.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def normalized(name)
|
36
|
+
name.start_with?('AWS') ? name : "AWS::#{name}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/humidifier/config.rb
CHANGED
@@ -16,31 +16,110 @@ module Humidifier
|
|
16
16
|
# An optional prefix for the JSON file names.
|
17
17
|
attr_accessor :s3_prefix
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# The path to the various directories containing the YAML files representing
|
20
|
+
# stacks. If blank, it's assumed to be the current working direction from
|
21
|
+
# which the CLI is executing.
|
22
|
+
attr_reader :stack_path
|
23
|
+
|
24
|
+
# An optional prefix for the stack names before they get uploaded to AWS.
|
25
|
+
attr_accessor :stack_prefix
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@mappings = {}
|
29
|
+
@stack_path = '.'
|
30
|
+
end
|
31
|
+
|
32
|
+
def files_for(name)
|
33
|
+
Dir["#{stack_path}/#{name}/*.yml"]
|
34
|
+
end
|
35
|
+
|
36
|
+
# `#map` is a declaration of a link between a file name and a mapper
|
37
|
+
# configuration. It is used to declare the manner in which a set of
|
38
|
+
# attributes read from a resource file is converted into instantiations of
|
39
|
+
# that type.
|
40
|
+
#
|
41
|
+
# For more information about the mapping DSL and how attributes get
|
42
|
+
# converted into props, see the `Humidifier::Config::Mapper` class.
|
43
|
+
#
|
44
|
+
# == Basic mapping
|
45
|
+
#
|
46
|
+
# For the most basic of mappings, you can just map a file name to a
|
47
|
+
# resource, which effectively means that each attribute you provide must
|
48
|
+
# map directly to an AWS attribute for that resource, and that no additional
|
49
|
+
# attributes will be provided. For example, the following code indicates
|
50
|
+
# that files named `routes.yml` will contain `AWS::EC2::Route` resources,
|
51
|
+
# and that every attribute read will directly correspond to one from AWS:
|
52
|
+
#
|
53
|
+
# Humidifier.configure do |config|
|
54
|
+
# config.map :routes, to: 'EC2::Route'
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# == Using the DSL
|
58
|
+
#
|
59
|
+
# For mappings for which you want to use the `Humidifier::Config::Mapper`
|
60
|
+
# DSL, you can pass a block which will get used to create a new mapper
|
61
|
+
# class. This is useful for shorter mapper declarations. For example, in the
|
62
|
+
# following code we map files named `instance_profiles.yml` to
|
63
|
+
# `AWS::IAM::InstanceProfile` resources. In that mapping we default the
|
64
|
+
# `path` prop to "/" and we allow the `roles` prop to just pass a list of
|
65
|
+
# roles as an array, which we then convert into CloudFormation references.
|
66
|
+
#
|
67
|
+
# Humidifier.configure do |config|
|
68
|
+
# config.map :instance_profiles, to: 'IAM::InstanceProfile' do
|
69
|
+
# defaults do |_|
|
70
|
+
# { path: '/' }
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# attribute :roles do |names|
|
74
|
+
# { roles: names.map { |name| Humidifier.ref(name) } }
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# == Reusing mappers
|
80
|
+
#
|
81
|
+
# Finally, if you want to pull the mapper out for reuse, testing, or just
|
82
|
+
# separation of code, you can pass the `:using` key with a mapper as a
|
83
|
+
# value. This will cause the given file type to be mapped using whatever
|
84
|
+
# class you provided. For example, the following code creates a mapper that
|
85
|
+
# automatically tags the resource with the logical name from the stack. It
|
86
|
+
# then configures network ACLs to use that so that all network ACL resource
|
87
|
+
# declarations automatically have a tag on them with their name.
|
88
|
+
#
|
89
|
+
# class NameToTag < Humidifier::Config::Mapper
|
90
|
+
# defaults do |logical_name|
|
91
|
+
# { tags: [{ key: 'Name', value: logical_name }] }
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# Humidifier.configure do |config|
|
96
|
+
# config.map :network_acls, to: 'EC2::NetworkAcl', using: NameToTag
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
def map(type, opts = {}, &block)
|
100
|
+
mappings[type.to_sym] = Mapping.new(opts, &block)
|
23
101
|
end
|
24
102
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
return if s3_bucket
|
103
|
+
def mapping_for(type)
|
104
|
+
mappings[type.to_sym]
|
105
|
+
end
|
29
106
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
config on the top-level Humidifier object like so:
|
107
|
+
def stack_path=(stack_path)
|
108
|
+
unless File.exist?(stack_path)
|
109
|
+
raise Error, "Invalid filepath: #{stack_path}"
|
110
|
+
end
|
35
111
|
|
36
|
-
|
37
|
-
|
38
|
-
config.s3_prefix = 'my-prefix/' # optional
|
39
|
-
end
|
40
|
-
MSG
|
112
|
+
@stack_path = stack_path
|
113
|
+
end
|
41
114
|
|
42
|
-
|
115
|
+
def stack_names
|
116
|
+
Dir["#{stack_path}/*"].each_with_object([]) do |name, names|
|
117
|
+
names << File.basename(name) if File.directory?(name)
|
118
|
+
end
|
43
119
|
end
|
44
|
-
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :mappings
|
45
124
|
end
|
46
125
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Humidifier
|
4
|
+
# Represents a directory on the filesystem containing YAML files that
|
5
|
+
# correspond to resources belonging to a stack. Contains all of the logic for
|
6
|
+
# interfacing with humidifier to deploy stacks, validate them, display them.
|
7
|
+
class Directory
|
8
|
+
# Represents an exported resource in a stack for use in cross-stack
|
9
|
+
# references.
|
10
|
+
Export =
|
11
|
+
Struct.new(:name, :attribute) do
|
12
|
+
def value
|
13
|
+
if attribute.is_a?(String)
|
14
|
+
Humidifier.fn.get_att([name, attribute])
|
15
|
+
else
|
16
|
+
Humidifier.ref(name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name, :pattern, :prefix, :exports, :stack_name
|
22
|
+
|
23
|
+
def initialize(name, pattern: nil, prefix: nil)
|
24
|
+
@name = name
|
25
|
+
@pattern = pattern
|
26
|
+
@prefix = prefix
|
27
|
+
@exports = []
|
28
|
+
@stack_name = "#{prefix || Humidifier.config.stack_prefix}#{name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_change_set
|
32
|
+
return unless valid?
|
33
|
+
|
34
|
+
stack.create_change_set(
|
35
|
+
capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def deploy(wait = false, parameter_values = {})
|
40
|
+
return unless valid?
|
41
|
+
|
42
|
+
stack.public_send(
|
43
|
+
wait ? :deploy_and_wait : :deploy,
|
44
|
+
capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM],
|
45
|
+
parameters: parameter_values
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_cf
|
50
|
+
stack.to_cf
|
51
|
+
end
|
52
|
+
|
53
|
+
def upload
|
54
|
+
stack.upload if valid?
|
55
|
+
end
|
56
|
+
|
57
|
+
def valid?
|
58
|
+
stack.valid?
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def stack
|
64
|
+
Stack.new(
|
65
|
+
name: stack_name,
|
66
|
+
description: "Resources for #{stack_name}",
|
67
|
+
resources: resources,
|
68
|
+
outputs: outputs,
|
69
|
+
parameters: parameters
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def outputs
|
74
|
+
exports.each_with_object({}) do |export, exported|
|
75
|
+
exported[export.name] =
|
76
|
+
Output.new(value: export.value, export_name: export.name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def parameters
|
81
|
+
@parameters ||=
|
82
|
+
begin
|
83
|
+
parameter_filepath =
|
84
|
+
Humidifier.config.files_for(name).detect do |filepath|
|
85
|
+
File.basename(filepath, '.yml') == 'parameters'
|
86
|
+
end
|
87
|
+
|
88
|
+
parameter_filepath ? parameters_from(parameter_filepath) : {}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parameters_from(filepath)
|
93
|
+
loaded = YAML.load_file(filepath)
|
94
|
+
return {} unless loaded
|
95
|
+
|
96
|
+
loaded.each_with_object({}) do |(name, opts), params|
|
97
|
+
opts = opts.map { |key, value| [key.to_sym, value] }.to_h
|
98
|
+
params[name] = Parameter.new(opts)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse(filepath, type)
|
103
|
+
mapping = Humidifier.config.mapping_for(type)
|
104
|
+
return {} if mapping.nil?
|
105
|
+
|
106
|
+
loaded = YAML.load_file(filepath)
|
107
|
+
return {} unless loaded
|
108
|
+
|
109
|
+
loaded.each_with_object({}) do |(name, attributes), resources|
|
110
|
+
next if pattern && name !~ pattern
|
111
|
+
|
112
|
+
attribute = attributes.delete('export')
|
113
|
+
exports << Export.new(name, attribute) if attribute
|
114
|
+
|
115
|
+
resources[name] = mapping.resource_for(name, attributes)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def resources
|
120
|
+
filepaths = Humidifier.config.files_for(name)
|
121
|
+
|
122
|
+
filepaths.each_with_object({}) do |filepath, resources|
|
123
|
+
basename = File.basename(filepath, '.yml')
|
124
|
+
|
125
|
+
# Explicitly skip past parameters so we can pull them out later
|
126
|
+
next if basename == 'parameters'
|
127
|
+
|
128
|
+
resources.merge!(parse(filepath, basename))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/humidifier/stack.rb
CHANGED
@@ -3,7 +3,13 @@
|
|
3
3
|
module Humidifier
|
4
4
|
# Represents a CFN stack
|
5
5
|
class Stack
|
6
|
-
class
|
6
|
+
class NoResourcesError < Error
|
7
|
+
def initialize(stack, action)
|
8
|
+
super("Refusing to #{action} stack #{stack.name} with no resources")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TemplateTooLargeError < Error
|
7
13
|
def initialize(bytesize)
|
8
14
|
super(
|
9
15
|
"Cannot use a template > #{MAX_TEMPLATE_URL_SIZE} bytes " \
|
@@ -14,6 +20,22 @@ module Humidifier
|
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
23
|
+
class UploadNotConfiguredError < Error
|
24
|
+
def initialize(identifier)
|
25
|
+
super(<<~MSG)
|
26
|
+
The #{identifier} stack's body is too large to be use the
|
27
|
+
template_body option, and therefore must use the template_url option
|
28
|
+
instead. You can configure Humidifier to do this automatically by
|
29
|
+
setting up the s3 config on the top-level Humidifier object like so:
|
30
|
+
|
31
|
+
Humidifier.configure do |config|
|
32
|
+
config.s3_bucket = 'my.s3.bucket'
|
33
|
+
config.s3_prefix = 'my-prefix/' # optional
|
34
|
+
end
|
35
|
+
MSG
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
17
39
|
# The AWS region, can be set through the environment, defaults to us-east-1
|
18
40
|
AWS_REGION = ENV['AWS_REGION'] || 'us-east-1'
|
19
41
|
|
@@ -109,6 +131,8 @@ module Humidifier
|
|
109
131
|
end
|
110
132
|
|
111
133
|
def create_change_set(opts = {})
|
134
|
+
raise NoResourcesError.new(self, :change) unless resources.any?
|
135
|
+
|
112
136
|
params = {
|
113
137
|
stack_name: identifier,
|
114
138
|
change_set_name: "changeset-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}"
|
@@ -128,6 +152,8 @@ module Humidifier
|
|
128
152
|
end
|
129
153
|
|
130
154
|
def deploy(opts = {})
|
155
|
+
raise NoResourcesError.new(self, :deploy) unless resources.any?
|
156
|
+
|
131
157
|
exists? ? update(opts) : create(opts)
|
132
158
|
end
|
133
159
|
|
@@ -144,8 +170,12 @@ module Humidifier
|
|
144
170
|
end
|
145
171
|
|
146
172
|
def update(opts = {})
|
147
|
-
params =
|
148
|
-
|
173
|
+
params = {
|
174
|
+
capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM],
|
175
|
+
stack_name: identifier
|
176
|
+
}
|
177
|
+
|
178
|
+
params.merge!(template_for(opts)).merge!(opts)
|
149
179
|
|
150
180
|
try_valid { client.update_stack(params) }
|
151
181
|
end
|
@@ -154,15 +184,29 @@ module Humidifier
|
|
154
184
|
perform_and_wait(:update, opts)
|
155
185
|
end
|
156
186
|
|
157
|
-
def upload
|
158
|
-
|
159
|
-
|
187
|
+
def upload # rubocop:disable Metrics/AbcSize
|
188
|
+
raise NoResourcesError.new(self, :upload) unless resources.any?
|
189
|
+
|
190
|
+
bucket = Humidifier.config.s3_bucket
|
191
|
+
raise UploadNotConfiguredError, identifier unless bucket
|
192
|
+
|
193
|
+
Aws.config.update(region: AWS_REGION)
|
194
|
+
key = "#{Humidifier.config.s3_prefix}#{identifier}.json"
|
195
|
+
|
196
|
+
Aws::S3::Client.new.put_object(body: to_cf, bucket: bucket, key: key)
|
197
|
+
Aws::S3::Object.new(bucket, key).presigned_url(:get)
|
160
198
|
end
|
161
199
|
|
162
200
|
def valid?(opts = {})
|
163
201
|
params = template_for(opts).merge!(opts)
|
164
202
|
|
165
203
|
try_valid { client.validate_template(params) }
|
204
|
+
rescue Aws::CloudFormation::Errors::AccessDenied
|
205
|
+
raise Error, <<~MSG
|
206
|
+
The authenticated AWS profile does not have the requisite permissions
|
207
|
+
to run this command. Ensure the profile has the
|
208
|
+
"cloudformation:ValidateTemplate" IAM permission.
|
209
|
+
MSG
|
166
210
|
end
|
167
211
|
|
168
212
|
def self.next_default_identifier
|
@@ -175,33 +219,12 @@ module Humidifier
|
|
175
219
|
|
176
220
|
attr_reader :default_identifier
|
177
221
|
|
178
|
-
def
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
client.wait_until(signal, stack_name: identifier) do |waiter|
|
183
|
-
waiter.max_attempts = (opts.delete(:max_wait) || MAX_WAIT) / 5
|
184
|
-
waiter.delay = 5
|
185
|
-
end
|
222
|
+
def bytesize
|
223
|
+
to_cf.bytesize.tap do |size|
|
224
|
+
raise TemplateTooLargeError, size if size > MAX_TEMPLATE_URL_SIZE
|
186
225
|
end
|
187
226
|
end
|
188
227
|
|
189
|
-
def try_valid
|
190
|
-
yield || true
|
191
|
-
rescue Aws::CloudFormation::Errors::ValidationError => error
|
192
|
-
warn(error.message)
|
193
|
-
warn(error.backtrace)
|
194
|
-
false
|
195
|
-
end
|
196
|
-
|
197
|
-
def upload_object(key)
|
198
|
-
Aws.config.update(region: AWS_REGION)
|
199
|
-
bucket = Humidifier.config.s3_bucket
|
200
|
-
|
201
|
-
Aws::S3::Client.new.put_object(body: to_cf, bucket: bucket, key: key)
|
202
|
-
Aws::S3::Object.new(bucket, key).presigned_url(:get)
|
203
|
-
end
|
204
|
-
|
205
228
|
def enumerable_resources
|
206
229
|
ENUMERABLE_RESOURCES.each_with_object({}) do |(name, prop), list|
|
207
230
|
resources = public_send(prop)
|
@@ -214,6 +237,17 @@ module Humidifier
|
|
214
237
|
end
|
215
238
|
end
|
216
239
|
|
240
|
+
def perform_and_wait(method, opts)
|
241
|
+
public_send(method, opts).tap do
|
242
|
+
signal = :"stack_#{method}_complete"
|
243
|
+
|
244
|
+
client.wait_until(signal, stack_name: identifier) do |waiter|
|
245
|
+
waiter.max_attempts = (opts.delete(:max_wait) || MAX_WAIT) / 5
|
246
|
+
waiter.delay = 5
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
217
251
|
def static_resources
|
218
252
|
STATIC_RESOURCES.each_with_object({}) do |(name, prop), list|
|
219
253
|
resource = public_send(prop)
|
@@ -221,12 +255,6 @@ module Humidifier
|
|
221
255
|
end
|
222
256
|
end
|
223
257
|
|
224
|
-
def bytesize
|
225
|
-
to_cf.bytesize.tap do |size|
|
226
|
-
raise TemplateTooLargeError, size if size > MAX_TEMPLATE_URL_SIZE
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
258
|
def template_for(opts)
|
231
259
|
@template ||=
|
232
260
|
if opts.delete(:force_upload) ||
|
@@ -238,5 +266,13 @@ module Humidifier
|
|
238
266
|
{ template_body: to_cf }
|
239
267
|
end
|
240
268
|
end
|
269
|
+
|
270
|
+
def try_valid
|
271
|
+
yield || true
|
272
|
+
rescue Aws::CloudFormation::Errors::ValidationError => error
|
273
|
+
warn(error.message)
|
274
|
+
warn(error.backtrace)
|
275
|
+
false
|
276
|
+
end
|
241
277
|
end
|
242
278
|
end
|
data/lib/humidifier/version.rb
CHANGED
data/lib/humidifier.rb
CHANGED
@@ -8,6 +8,8 @@ require 'yaml'
|
|
8
8
|
require 'aws-sdk-cloudformation'
|
9
9
|
require 'aws-sdk-s3'
|
10
10
|
require 'fast_underscore'
|
11
|
+
require 'thor'
|
12
|
+
require 'thor/hollaback'
|
11
13
|
|
12
14
|
# Hook into the string extension and ensure it works for certain AWS acronyms
|
13
15
|
String.prepend(
|
@@ -20,6 +22,9 @@ String.prepend(
|
|
20
22
|
|
21
23
|
# container module for all gem classes
|
22
24
|
module Humidifier
|
25
|
+
# A parent class for all Humidifier errors for easier rescuing.
|
26
|
+
class Error < StandardError; end
|
27
|
+
|
23
28
|
class << self
|
24
29
|
# the configuration instance
|
25
30
|
def config
|
@@ -58,18 +63,24 @@ module Humidifier
|
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
61
|
-
require 'humidifier/condition'
|
62
|
-
require 'humidifier/config'
|
63
66
|
require 'humidifier/fn'
|
67
|
+
require 'humidifier/ref'
|
68
|
+
require 'humidifier/props'
|
69
|
+
|
70
|
+
require 'humidifier/cli'
|
71
|
+
require 'humidifier/condition'
|
72
|
+
require 'humidifier/directory'
|
64
73
|
require 'humidifier/loader'
|
65
74
|
require 'humidifier/mapping'
|
66
75
|
require 'humidifier/output'
|
67
76
|
require 'humidifier/parameter'
|
68
|
-
require 'humidifier/ref'
|
69
77
|
require 'humidifier/resource'
|
70
78
|
require 'humidifier/serializer'
|
71
79
|
require 'humidifier/stack'
|
72
80
|
require 'humidifier/version'
|
73
|
-
|
81
|
+
|
82
|
+
require 'humidifier/config'
|
83
|
+
require 'humidifier/config/mapper'
|
84
|
+
require 'humidifier/config/mapping'
|
74
85
|
|
75
86
|
Humidifier::Loader.load
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: humidifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Localytics
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.20'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.20'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor-hollaback
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: bundler
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,8 +204,12 @@ files:
|
|
176
204
|
- LICENSE
|
177
205
|
- README.md
|
178
206
|
- lib/humidifier.rb
|
207
|
+
- lib/humidifier/cli.rb
|
179
208
|
- lib/humidifier/condition.rb
|
180
209
|
- lib/humidifier/config.rb
|
210
|
+
- lib/humidifier/config/mapper.rb
|
211
|
+
- lib/humidifier/config/mapping.rb
|
212
|
+
- lib/humidifier/directory.rb
|
181
213
|
- lib/humidifier/fn.rb
|
182
214
|
- lib/humidifier/loader.rb
|
183
215
|
- lib/humidifier/mapping.rb
|