humidifier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/ext/humidifier/extconf.rb +2 -0
- data/ext/humidifier/humidifier.c +93 -0
- data/ext/humidifier/humidifier.h +14 -0
- data/lib/humidifier/aws_adapters/base.rb +64 -0
- data/lib/humidifier/aws_adapters/noop.rb +18 -0
- data/lib/humidifier/aws_adapters/sdkv1.rb +56 -0
- data/lib/humidifier/aws_adapters/sdkv2.rb +43 -0
- data/lib/humidifier/aws_shim.rb +60 -0
- data/lib/humidifier/condition.rb +17 -0
- data/lib/humidifier/fn.rb +28 -0
- data/lib/humidifier/humidifier.bundle +0 -0
- data/lib/humidifier/loader.rb +53 -0
- data/lib/humidifier/mapping.rb +17 -0
- data/lib/humidifier/output.rb +20 -0
- data/lib/humidifier/parameter.rb +27 -0
- data/lib/humidifier/props.rb +127 -0
- data/lib/humidifier/ref.rb +17 -0
- data/lib/humidifier/resource.rb +108 -0
- data/lib/humidifier/sdk_payload.rb +30 -0
- data/lib/humidifier/serializer.rb +18 -0
- data/lib/humidifier/sleeper.rb +25 -0
- data/lib/humidifier/stack.rb +66 -0
- data/lib/humidifier/utils.rb +21 -0
- data/lib/humidifier/version.rb +4 -0
- data/lib/humidifier.rb +50 -0
- data/specs/ApiGateway-Account.cf +6 -0
- data/specs/ApiGateway-ApiKey.cf +9 -0
- data/specs/ApiGateway-Authorizer.cf +13 -0
- data/specs/ApiGateway-BasePathMapping.cf +9 -0
- data/specs/ApiGateway-ClientCertificate.cf +6 -0
- data/specs/ApiGateway-Deployment.cf +9 -0
- data/specs/ApiGateway-Method.cf +15 -0
- data/specs/ApiGateway-Model.cf +10 -0
- data/specs/ApiGateway-Resource.cf +8 -0
- data/specs/ApiGateway-RestApi.cf +12 -0
- data/specs/ApiGateway-Stage.cf +14 -0
- data/specs/AutoScaling-AutoScalingGroup.cf +21 -0
- data/specs/AutoScaling-LaunchConfiguration.cf +22 -0
- data/specs/AutoScaling-LifecycleHook.cf +12 -0
- data/specs/AutoScaling-ScalingPolicy.cf +14 -0
- data/specs/AutoScaling-ScheduledAction.cf +12 -0
- data/specs/CloudFormation-Authentication.cf +18 -0
- data/specs/CloudFormation-CustomResource.cf +8 -0
- data/specs/CloudFormation-Interface.cf +9 -0
- data/specs/CloudFormation-Stack.cf +10 -0
- data/specs/CloudFormation-WaitCondition.cf +8 -0
- data/specs/CloudFormation-WaitConditionHandle.cf +5 -0
- data/specs/CloudFront-Distribution.cf +6 -0
- data/specs/CloudTrail-Trail.cf +16 -0
- data/specs/CloudWatch-Alarm.cf +20 -0
- data/specs/CodeDeploy-Application.cf +6 -0
- data/specs/CodeDeploy-DeploymentConfig.cf +7 -0
- data/specs/CodeDeploy-DeploymentGroup.cf +13 -0
- data/specs/CodePipeline-CustomActionType.cf +12 -0
- data/specs/CodePipeline-Pipeline.cf +11 -0
- data/specs/Config-ConfigRule.cf +11 -0
- data/specs/Config-ConfigurationRecorder.cf +8 -0
- data/specs/Config-DeliveryChannel.cf +10 -0
- data/specs/DataPipeline-Pipeline.cf +12 -0
- data/specs/DirectoryService-MicrosoftAD.cf +11 -0
- data/specs/DirectoryService-SimpleAD.cf +13 -0
- data/specs/DynamoDB-Table.cf +12 -0
- data/specs/EC2-CustomerGateway.cf +9 -0
- data/specs/EC2-DHCPOptions.cf +11 -0
- data/specs/EC2-EIP.cf +7 -0
- data/specs/EC2-EIPAssociation.cf +10 -0
- data/specs/EC2-FlowLog.cf +10 -0
- data/specs/EC2-Host.cf +8 -0
- data/specs/EC2-Instance.cf +32 -0
- data/specs/EC2-InternetGateway.cf +6 -0
- data/specs/EC2-NatGateway.cf +7 -0
- data/specs/EC2-NetworkAcl.cf +7 -0
- data/specs/EC2-NetworkAclEntry.cf +13 -0
- data/specs/EC2-NetworkInterface.cf +13 -0
- data/specs/EC2-NetworkInterfaceAttachment.cf +9 -0
- data/specs/EC2-PlacementGroup.cf +6 -0
- data/specs/EC2-Route.cf +12 -0
- data/specs/EC2-RouteTable.cf +7 -0
- data/specs/EC2-SecurityGroup.cf +10 -0
- data/specs/EC2-SecurityGroupEgress.cf +11 -0
- data/specs/EC2-SecurityGroupIngress.cf +14 -0
- data/specs/EC2-SpotFleet.cf +6 -0
- data/specs/EC2-Subnet.cf +10 -0
- data/specs/EC2-SubnetNetworkAclAssociation.cf +5 -0
- data/specs/EC2-SubnetRouteTableAssociation.cf +7 -0
- data/specs/EC2-VPC.cf +10 -0
- data/specs/EC2-VPCDHCPOptionsAssociation.cf +7 -0
- data/specs/EC2-VPCEndpoint.cf +9 -0
- data/specs/EC2-VPCGatewayAttachment.cf +8 -0
- data/specs/EC2-VPCPeeringConnection.cf +8 -0
- data/specs/EC2-VPNConnection.cf +10 -0
- data/specs/EC2-VPNConnectionRoute.cf +7 -0
- data/specs/EC2-VPNGateway.cf +7 -0
- data/specs/EC2-VPNGatewayRoutePropagation.cf +7 -0
- data/specs/EC2-Volume.cf +14 -0
- data/specs/EC2-VolumeAttachment.cf +8 -0
- data/specs/ECR-Repository.cf +7 -0
- data/specs/ECS-Cluster.cf +6 -0
- data/specs/ECS-Service.cf +11 -0
- data/specs/ECS-TaskDefinition.cf +7 -0
- data/specs/EFS-FileSystem.cf +6 -0
- data/specs/EFS-MountTarget.cf +9 -0
- data/specs/EMR-Cluster.cf +17 -0
- data/specs/EMR-InstanceGroupConfig.cf +14 -0
- data/specs/EMR-Step.cf +9 -0
- data/specs/ElastiCache-CacheCluster.cf +27 -0
- data/specs/ElastiCache-ParameterGroup.cf +8 -0
- data/specs/ElastiCache-ReplicationGroup.cf +23 -0
- data/specs/ElastiCache-SecurityGroup.cf +7 -0
- data/specs/ElastiCache-SecurityGroupIngress.cf +9 -0
- data/specs/ElastiCache-SubnetGroup.cf +7 -0
- data/specs/ElasticBeanstalk-Application.cf +7 -0
- data/specs/ElasticBeanstalk-ApplicationVersion.cf +8 -0
- data/specs/ElasticBeanstalk-ConfigurationTemplate.cf +11 -0
- data/specs/ElasticBeanstalk-Environment.cf +15 -0
- data/specs/ElasticLoadBalancing-LoadBalancer.cf +21 -0
- data/specs/Elasticsearch-Domain.cf +12 -0
- data/specs/Events-Rule.cf +12 -0
- data/specs/GameLift-Alias.cf +8 -0
- data/specs/GameLift-Build.cf +8 -0
- data/specs/GameLift-Fleet.cf +16 -0
- data/specs/IAM-AccessKey.cf +8 -0
- data/specs/IAM-Group.cf +8 -0
- data/specs/IAM-InstanceProfile.cf +7 -0
- data/specs/IAM-ManagedPolicy.cf +11 -0
- data/specs/IAM-Policy.cf +10 -0
- data/specs/IAM-Role.cf +9 -0
- data/specs/IAM-User.cf +10 -0
- data/specs/IAM-UserToGroupAddition.cf +7 -0
- data/specs/KMS-Key.cf +9 -0
- data/specs/Kinesis-Stream.cf +8 -0
- data/specs/KinesisFirehose-DeliveryStream.cf +9 -0
- data/specs/Lambda-Alias.cf +9 -0
- data/specs/Lambda-EventSourceMapping.cf +10 -0
- data/specs/Lambda-Function.cf +14 -0
- data/specs/Lambda-Permission.cf +10 -0
- data/specs/Lambda-Version.cf +8 -0
- data/specs/Logs-Destination.cf +9 -0
- data/specs/Logs-LogGroup.cf +6 -0
- data/specs/Logs-LogStream.cf +7 -0
- data/specs/Logs-MetricFilter.cf +8 -0
- data/specs/Logs-SubscriptionFilter.cf +9 -0
- data/specs/OpsWorks-App.cf +16 -0
- data/specs/OpsWorks-ElasticLoadBalancerAttachment.cf +7 -0
- data/specs/OpsWorks-Instance.cf +19 -0
- data/specs/OpsWorks-Layer.cf +21 -0
- data/specs/OpsWorks-Stack.cf +23 -0
- data/specs/RDS-DBCluster.cf +23 -0
- data/specs/RDS-DBClusterParameterGroup.cf +9 -0
- data/specs/RDS-DBInstance.cf +38 -0
- data/specs/RDS-DBParameterGroup.cf +9 -0
- data/specs/RDS-DBSecurityGroup.cf +10 -0
- data/specs/RDS-DBSecurityGroupIngress.cf +10 -0
- data/specs/RDS-DBSubnetGroup.cf +8 -0
- data/specs/RDS-EventSubscription.cf +10 -0
- data/specs/RDS-OptionGroup.cf +10 -0
- data/specs/Redshift-Cluster.cf +30 -0
- data/specs/Redshift-ClusterParameterGroup.cf +8 -0
- data/specs/Redshift-ClusterSecurityGroup.cf +6 -0
- data/specs/Redshift-ClusterSecurityGroupIngress.cf +9 -0
- data/specs/Redshift-ClusterSubnetGroup.cf +7 -0
- data/specs/Route53-HealthCheck.cf +7 -0
- data/specs/Route53-HostedZone.cf +9 -0
- data/specs/Route53-RecordSet.cf +19 -0
- data/specs/Route53-RecordSetGroup.cf +9 -0
- data/specs/S3-Bucket.cf +15 -0
- data/specs/S3-BucketPolicy.cf +7 -0
- data/specs/SDB-Domain.cf +6 -0
- data/specs/SNS-Topic.cf +8 -0
- data/specs/SNS-TopicPolicy.cf +8 -0
- data/specs/SQS-Queue.cf +12 -0
- data/specs/SQS-QueuePolicy.cf +7 -0
- data/specs/SSM-Document.cf +6 -0
- data/specs/WAF-ByteMatchSet.cf +7 -0
- data/specs/WAF-IPSet.cf +7 -0
- data/specs/WAF-Rule.cf +8 -0
- data/specs/WAF-SizeConstraintSet.cf +7 -0
- data/specs/WAF-SqlInjectionMatchSet.cf +7 -0
- data/specs/WAF-WebACL.cf +9 -0
- data/specs/WAF-XssMatchSet.cf +7 -0
- data/specs/WorkSpaces-Workspace.cf +11 -0
- metadata +356 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fdbf719f5542ef196ffba68323350327492e9259
|
4
|
+
data.tar.gz: c21dc198cc7a5f8d6d249eb7a7f8e6ad697f33bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9be95a4d6a8f9baa91cb70c272508d718cc60aecaaa57531036484107cea0126dd01b59f6d54702696a764e630b67ef3d8db97d5776351d72ac3291cfb316533
|
7
|
+
data.tar.gz: 1ae9dbe8cbead00b4e0ea7f4e7c927944af148d9c4a0399b1502cb6cfa00b74a33adfff1c24a5b4de564e0570f9c2283c6c01cc6c52370b95ef78b20a4c9deb1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Localytics http://www.localytics.com
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Humidifier
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/localytics/humidifier.svg?branch=master)](https://travis-ci.org/localytics/humidifier)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/localytics/humidifier/badge.svg?branch=master&t=52zybb)](https://coveralls.io/github/localytics/humidifier?branch=master)
|
5
|
+
[![Gem Version](https://img.shields.io/gem/v/humidifier.svg?maxAge=2592000)](https://rubygems.org/gems/humidifier)
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
For the full docs, go to [https://localytics.github.io/humidifier/](http://localytics.github.io/humidifier/). For local development instructions, see the [Development](https://localytics.github.io/humidifier/#label-Development) section.
|
10
|
+
|
11
|
+
Humidifier is tested with Ruby 2.0.0 and higher.
|
12
|
+
|
13
|
+
## Getting started
|
14
|
+
|
15
|
+
Stacks are represented by the `Humidifier::Stack` class. You can set any of the top-level JSON attributes through the initializer. Resources are represented by an exact mapping from `AWS` resource names to `Humidifier` resources names (e.g. `AWS::EC2::Instance` becomes `Humidifier::EC2::Instance`). Resources have accessors for each JSON attribute. Each attribute can also be set through the `initialize`, `update`, and `update_attribute` methods.
|
16
|
+
|
17
|
+
### Example usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
stack = Humidifier::Stack.new(name: 'Example-Stack', aws_template_format_version: '2010-09-09')
|
21
|
+
|
22
|
+
load_balancer = Humidifier::ElasticLoadBalancing::LoadBalancer.new(
|
23
|
+
listeners: [{ LoadBalancerPort: 80, Protocol: 'http', InstancePort: 80, InstanceProtocol: 'http' }]
|
24
|
+
)
|
25
|
+
load_balancer.scheme = 'internal'
|
26
|
+
|
27
|
+
auto_scaling_group = Humidifier::AutoScaling::AutoScalingGroup.new(min_size: '1', max_size: '20')
|
28
|
+
auto_scaling_group.update(
|
29
|
+
availability_zones: ['us-east-1a'],
|
30
|
+
load_balancer_names: [Humidifier.ref('LoadBalancer')]
|
31
|
+
)
|
32
|
+
|
33
|
+
stack.add('LoadBalancer', load_balancer)
|
34
|
+
stack.add('AutoScalingGroup', auto_scaling_group)
|
35
|
+
stack.deploy_and_wait
|
36
|
+
```
|
37
|
+
|
38
|
+
### Interfacing with AWS
|
39
|
+
|
40
|
+
Once stacks have the appropriate resources, you can query AWS to handle all stack CRUD operations. The operations themselves are intuitively named (i.e. `create`, `update`, `delete`). There are also convenience methods for validating a stack body (`valid?`), checking the existence of a stack (`exists?`), and creating or updating based on existence (`deploy`). The `create`, `update`, `delete`, and `deploy` methods all have `_and_wait` corollaries that will cause the main ruby thread to sleep until the operation is complete.
|
41
|
+
|
42
|
+
#### SDK version
|
43
|
+
|
44
|
+
Humidifier assumes you have an `aws-sdk` gem installed when you call these operations. It detects the version of the gem you have installed and uses the appropriate API depending on what is available. If Humidifier cannot find any way to use the AWS SDK, it will warn you on every API call and simply return false.
|
45
|
+
|
46
|
+
#### CloudFormation functions
|
47
|
+
|
48
|
+
You can use CFN intrinsic functions and references using `Humidifier.fn.[name]` and `Humidifier.ref`. Those will build appropriate structures that know how to be dumped to CFN syntax appropriately.
|
49
|
+
|
50
|
+
#### Change Sets
|
51
|
+
|
52
|
+
Instead of immediately pushing your changes to CloudFormation, Humidifier also supports change sets. Change sets are a powerful feature that allow you to see the changes that will be made before you make them. To read more about change sets see the [announcement article](https://aws.amazon.com/blogs/aws/new-change-sets-for-aws-cloudformation/). To use them in Humidifier, `Stack` has the `create_change_set` and `deploy_change_set` methods. The `create_change_set` method will create a change set on the stack. The `deploy_change_set` method will create a change set if the stack currently exists, and otherwise will create the stack.
|
53
|
+
|
54
|
+
### Introspection
|
55
|
+
|
56
|
+
To see the template body, you can check the `to_cf` method on stacks, resources, fns, and refs. All of them will output a hash of what will be uploaded (except the stack, which will output a string representation).
|
57
|
+
|
58
|
+
Humidifier itself contains a registry of all possible resources that it supports. You can access it with `Humidifier.registry` which is a hash of AWS resource name pointing to the class.
|
59
|
+
|
60
|
+
Resources have an `aws_name` method to see how AWS references them. They also contain a `props` method that contains a hash of the name that Humidifier uses to reference the prop pointing to the appropriate prop object.
|
61
|
+
|
62
|
+
## Development
|
63
|
+
|
64
|
+
To get started, ensure you have ruby installed, version 2.0 or later. From there, install the `bundler` gem: `gem install bundler` and then `bundle install` in the root of the repository.
|
65
|
+
|
66
|
+
### Testing
|
67
|
+
|
68
|
+
The default rake task runs the tests. Coverage is reported on the command line, and to coveralls.io in CI. Styling is governed by rubocop. The docs are generated with yard. To run all three of these, run:
|
69
|
+
|
70
|
+
$ bundle exec rake
|
71
|
+
$ bundle exec rubocop
|
72
|
+
$ bundle exec rake yard
|
73
|
+
|
74
|
+
### Specs
|
75
|
+
|
76
|
+
The specs pulled from the CFN docs live under `/specs`. You can update them by running `bin/get-specs`. This script will scrape the docs by going to the [listings page](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html), finding the list of CFN resources, and then downloading the spec for each resource by going to the individual page.
|
77
|
+
|
78
|
+
### Extension
|
79
|
+
|
80
|
+
The `underscore` string utility method does a lot of the heavy lifting of changing AWS property names over to ruby method names. As such, it's been extracted to a native extension to increase speed and efficiency. To compile it locally run `rake compile`.
|
81
|
+
|
82
|
+
### Contributing
|
83
|
+
|
84
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/localytics/humidifier.
|
85
|
+
|
86
|
+
### License
|
87
|
+
|
88
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#include <humidifier.h>
|
2
|
+
|
3
|
+
// copies from the source string to the destination string after passing through a character whitelist filter
|
4
|
+
// note that this is exclusively built for AWS::CloudFormation::Interface, so if AWS ever fixes their docs this can
|
5
|
+
// be replaced by strcpy
|
6
|
+
static void filter(char* dest, const char* source)
|
7
|
+
{
|
8
|
+
int source_idx, dest_idx;
|
9
|
+
|
10
|
+
for (source_idx = 0, dest_idx = 0; source[source_idx] != '\0'; source_idx++) {
|
11
|
+
if (source[source_idx] != ':') {
|
12
|
+
dest[dest_idx++] = source[source_idx];
|
13
|
+
}
|
14
|
+
}
|
15
|
+
dest[dest_idx] = '\0';
|
16
|
+
}
|
17
|
+
|
18
|
+
// takes a substring from underscore_preprocess like EC2T or AWST and converts it to Ec2T or AwsT
|
19
|
+
static void format_substring(char* substr, const int substr_idx, const int capitalize)
|
20
|
+
{
|
21
|
+
int idx;
|
22
|
+
|
23
|
+
for (idx = 0; idx < substr_idx; idx++) {
|
24
|
+
if (idx == 0) {
|
25
|
+
substr[idx] = toupper(substr[idx]);
|
26
|
+
}
|
27
|
+
else if ((idx != substr_idx - 1) || capitalize == 0) {
|
28
|
+
substr[idx] = tolower(substr[idx]);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
// finds occurences of EC2Thing or AWSThing and makes them into Ec2Thing and AwsThing so that underscore can be
|
34
|
+
// simpler and just look for capitals
|
35
|
+
static void preprocess(char* str)
|
36
|
+
{
|
37
|
+
char substr[strlen(str)];
|
38
|
+
int idx, substr_idx;
|
39
|
+
|
40
|
+
for (idx = 0, substr_idx = 0; str[idx] != '\0'; idx++) {
|
41
|
+
if (isupper(str[idx])) {
|
42
|
+
substr[substr_idx++] = str[idx];
|
43
|
+
|
44
|
+
if (str[idx + 1] == '\0') {
|
45
|
+
format_substring(substr, substr_idx, 0);
|
46
|
+
memcpy(str + (idx - substr_idx) + 1, substr, substr_idx);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
else if (isdigit(str[idx]) || (substr_idx != 0)) {
|
50
|
+
if (substr_idx != 1) {
|
51
|
+
format_substring(substr, substr_idx, islower(str[idx]));
|
52
|
+
memcpy(str + (idx - substr_idx), substr, substr_idx);
|
53
|
+
}
|
54
|
+
substr_idx = 0;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
// takes a string and returns a downcased version where capitals are now separated by underscores
|
60
|
+
static VALUE underscore(VALUE self, VALUE str)
|
61
|
+
{
|
62
|
+
if (TYPE(str) == T_NIL) return Qnil;
|
63
|
+
|
64
|
+
char *str_value = rb_string_value_cstr(&str);
|
65
|
+
char orig_str[strlen(str_value) + 1];
|
66
|
+
|
67
|
+
filter(orig_str, str_value);
|
68
|
+
preprocess(orig_str);
|
69
|
+
|
70
|
+
// manually null-terminating the string because on Fedora for strings of length 16 this breaks otherwise
|
71
|
+
orig_str[strlen(str_value)] = '\0';
|
72
|
+
|
73
|
+
char new_str[strlen(orig_str) * 2];
|
74
|
+
char prev;
|
75
|
+
int orig_idx, new_idx;
|
76
|
+
|
77
|
+
for (orig_idx = 0, new_idx = 0; orig_str[orig_idx] != '\0'; orig_idx++) {
|
78
|
+
if (orig_idx != 0 && isupper(orig_str[orig_idx])) {
|
79
|
+
new_str[new_idx++] = '_';
|
80
|
+
}
|
81
|
+
new_str[new_idx++] = tolower(orig_str[orig_idx]);
|
82
|
+
prev = tolower(orig_str[orig_idx]);
|
83
|
+
}
|
84
|
+
|
85
|
+
return rb_str_new(new_str, new_idx);
|
86
|
+
}
|
87
|
+
|
88
|
+
void Init_humidifier()
|
89
|
+
{
|
90
|
+
VALUE Humidifier = rb_define_module("Humidifier");
|
91
|
+
VALUE Utils = rb_define_module_under(Humidifier, "Utils");
|
92
|
+
rb_define_singleton_method(Utils, "underscore", underscore, 1);
|
93
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#ifndef RUBY_HUMIDIFIER
|
2
|
+
#define RUBY_HUMIDIFIER
|
3
|
+
|
4
|
+
#include <ctype.h>
|
5
|
+
#include <ruby.h>
|
6
|
+
|
7
|
+
static void filter(char* dest, const char* source);
|
8
|
+
static void format_substring(char* substr, const int substr_idx, const int capitalize);
|
9
|
+
static void preprocess(char* str);
|
10
|
+
static VALUE underscore(VALUE self, VALUE str);
|
11
|
+
|
12
|
+
void Init_humidifier();
|
13
|
+
|
14
|
+
#endif
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Humidifier
|
2
|
+
|
3
|
+
# A container module for all adapters for the SDK
|
4
|
+
module AwsAdapters
|
5
|
+
|
6
|
+
# The parent class for the adapters for both versions of the SDK
|
7
|
+
class Base
|
8
|
+
|
9
|
+
# Create a CFN stack
|
10
|
+
def create(payload)
|
11
|
+
try_valid do
|
12
|
+
params = { stack_name: payload.name, template_body: payload.to_cf }.merge(payload.options)
|
13
|
+
response = client.create_stack(params)
|
14
|
+
payload.id = response.stack_id
|
15
|
+
response
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delete a CFN stack
|
20
|
+
def delete(payload)
|
21
|
+
client.delete_stack({ stack_name: payload.identifier }.merge(payload.options))
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# Update a CFN stack if it exists, otherwise create it
|
26
|
+
def deploy(payload)
|
27
|
+
exists?(payload) ? update(payload) : create(payload)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Update a CFN stack
|
31
|
+
def update(payload)
|
32
|
+
try_valid do
|
33
|
+
params = { stack_name: payload.identifier, template_body: payload.to_cf }.merge(payload.options)
|
34
|
+
client.update_stack(params)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Validate a template in CFN
|
39
|
+
def valid?(payload)
|
40
|
+
try_valid { client.validate_template({ template_body: payload.to_cf }.merge(payload.options)) }
|
41
|
+
end
|
42
|
+
|
43
|
+
%i[create delete deploy update].each do |method|
|
44
|
+
define_method(:"#{method}_and_wait") do |payload|
|
45
|
+
perform_and_wait(method, payload)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def client
|
52
|
+
@client ||= base_module::CloudFormation::Client.new(region: AwsShim::REGION)
|
53
|
+
end
|
54
|
+
|
55
|
+
def try_valid
|
56
|
+
yield || true
|
57
|
+
rescue base_module::CloudFormation::Errors::ValidationError => error
|
58
|
+
$stderr.puts error.message
|
59
|
+
$stderr.puts error.backtrace
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Humidifier
|
2
|
+
module AwsAdapters
|
3
|
+
|
4
|
+
# An adapter used when neither SDK is loaded
|
5
|
+
class Noop
|
6
|
+
|
7
|
+
# Capture all STACK_METHODS method calls and warn that they will not be run
|
8
|
+
def method_missing(method, *)
|
9
|
+
if AwsShim::STACK_METHODS.include?(method)
|
10
|
+
puts "WARNING: Cannot run #{method} because aws-sdk not loaded."
|
11
|
+
false
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Humidifier
|
2
|
+
module AwsAdapters
|
3
|
+
|
4
|
+
# The adapter for v1 of aws-sdk
|
5
|
+
class SDKV1 < Base
|
6
|
+
|
7
|
+
# Cannot create change sets in V1
|
8
|
+
def create_change_set(*)
|
9
|
+
unsupported('create change set')
|
10
|
+
end
|
11
|
+
|
12
|
+
# Cannot deploy change sets in V1
|
13
|
+
def deploy_change_set(*)
|
14
|
+
unsupported('deploy change set')
|
15
|
+
end
|
16
|
+
|
17
|
+
# True if the stack exists in CFN
|
18
|
+
def exists?(payload)
|
19
|
+
base_module::CloudFormation::Stack.new(payload.identifier).exists?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def base_module
|
25
|
+
AWS
|
26
|
+
end
|
27
|
+
|
28
|
+
def handle_failure(payload)
|
29
|
+
reasons = []
|
30
|
+
client.describe_stack_events(stack_name: payload.identifier).stack_events.each do |event|
|
31
|
+
next unless event.resource_status.include?('FAILED') && event.key?(:resource_status_reason)
|
32
|
+
reasons.unshift(event.resource_status_reason)
|
33
|
+
end
|
34
|
+
raise "#{payload.name} stack failed:\n#{reasons.join("\n")}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform_and_wait(method, payload)
|
38
|
+
response = public_send(method, payload)
|
39
|
+
|
40
|
+
aws_stack = nil
|
41
|
+
Sleeper.new(payload.max_wait) do
|
42
|
+
aws_stack = client.describe_stacks(stack_name: payload.identifier).stacks.first
|
43
|
+
!aws_stack.stack_status.end_with?('IN_PROGRESS')
|
44
|
+
end
|
45
|
+
|
46
|
+
handle_failure(payload) if aws_stack.stack_status =~ /(FAILED|ROLLBACK)/
|
47
|
+
response
|
48
|
+
end
|
49
|
+
|
50
|
+
def unsupported(method)
|
51
|
+
puts "WARNING: Cannot #{method} because that functionality is not supported in V1 of aws-sdk."
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Humidifier
|
2
|
+
module AwsAdapters
|
3
|
+
|
4
|
+
# The adapter for v2 of aws-sdk
|
5
|
+
class SDKV2 < Base
|
6
|
+
|
7
|
+
# Create a change set in CFN
|
8
|
+
def create_change_set(payload)
|
9
|
+
payload.merge(change_set_name: "changeset-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
|
10
|
+
params = { stack_name: payload.identifier, template_body: payload.to_cf }.merge(payload.options)
|
11
|
+
try_valid { client.create_change_set(params) }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a change set if the stack exists, otherwise create the stack
|
15
|
+
def deploy_change_set(payload)
|
16
|
+
exists?(payload) ? create_change_set(payload) : create(payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
# True if the stack exists in CFN
|
20
|
+
def exists?(payload)
|
21
|
+
base_module::CloudFormation::Stack.new(name: payload.identifier).exists?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def base_module
|
27
|
+
Aws
|
28
|
+
end
|
29
|
+
|
30
|
+
def perform_and_wait(method, payload)
|
31
|
+
method = exists?(payload) ? :update : :create if method == :deploy
|
32
|
+
response = public_send(method, payload)
|
33
|
+
|
34
|
+
client.wait_until(:"stack_#{method}_complete", stack_name: payload.identifier) do |waiter|
|
35
|
+
waiter.max_attempts = payload.max_wait / 5
|
36
|
+
waiter.delay = 5
|
37
|
+
end
|
38
|
+
|
39
|
+
response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'humidifier/aws_adapters/base'
|
2
|
+
require 'humidifier/aws_adapters/noop'
|
3
|
+
require 'humidifier/aws_adapters/sdkv1'
|
4
|
+
require 'humidifier/aws_adapters/sdkv2'
|
5
|
+
|
6
|
+
module Humidifier
|
7
|
+
|
8
|
+
# Optionally provides aws-sdk functionality if the gem is loaded
|
9
|
+
class AwsShim
|
10
|
+
|
11
|
+
# The AWS region, can be set through the environment, defaults to us-east-1
|
12
|
+
REGION = ENV['AWS_REGION'] || 'us-east-1'
|
13
|
+
|
14
|
+
# Methods that are sent over to the aws adapter from the stack
|
15
|
+
STACK_METHODS = %i[
|
16
|
+
create delete deploy exists? update valid?
|
17
|
+
create_and_wait delete_and_wait deploy_and_wait update_and_wait
|
18
|
+
create_change_set deploy_change_set
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
attr_accessor :shim
|
22
|
+
|
23
|
+
# Attempt to require both aws-sdk-v1 and aws-sdk, then set the shim based on what successfully loaded
|
24
|
+
def initialize
|
25
|
+
try_require_sdk('aws-sdk-v1')
|
26
|
+
try_require_sdk('aws-sdk')
|
27
|
+
|
28
|
+
self.shim =
|
29
|
+
if Object.const_defined?(:AWS)
|
30
|
+
AwsAdapters::SDKV1.new
|
31
|
+
elsif Object.const_defined?(:Aws)
|
32
|
+
AwsAdapters::SDKV2.new
|
33
|
+
else
|
34
|
+
AwsAdapters::Noop.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
extend Forwardable
|
40
|
+
def_delegators :shim, *STACK_METHODS
|
41
|
+
|
42
|
+
# The shim singleton
|
43
|
+
def instance
|
44
|
+
@instance ||= new
|
45
|
+
end
|
46
|
+
|
47
|
+
# The target of all of the forwarding
|
48
|
+
def shim
|
49
|
+
instance.shim
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def try_require_sdk(name)
|
56
|
+
require name
|
57
|
+
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Humidifier
|
2
|
+
|
3
|
+
# Builds CFN function calls
|
4
|
+
class Fn
|
5
|
+
|
6
|
+
# The list of all internal functions provided by AWS from
|
7
|
+
# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
|
8
|
+
FUNCTIONS = Utils.underscored(%w[And Base64 Equals FindInMap GetAtt GetAZs If Join Not Or Select])
|
9
|
+
|
10
|
+
attr_accessor :name, :value
|
11
|
+
|
12
|
+
def initialize(name, value)
|
13
|
+
self.name = "Fn::#{name}"
|
14
|
+
self.value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
# CFN stack syntax
|
18
|
+
def to_cf
|
19
|
+
{ name => value }
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
FUNCTIONS.each do |name, func|
|
24
|
+
define_method(func) { |value| new(name, value) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
Binary file
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Humidifier
|
2
|
+
# Pre-setting this module because AWS has a "Config" module and the below register method dynamically looks up the
|
3
|
+
# module to see whether or not it exists, which before ruby 2.2 would result in the warning:
|
4
|
+
# `const_defined?': Use RbConfig instead of obsolete and deprecated Config.
|
5
|
+
# @aws AWS::Config
|
6
|
+
module Config
|
7
|
+
end
|
8
|
+
|
9
|
+
# Reads each of the files under /specs/ and loads them each as a class
|
10
|
+
class Loader
|
11
|
+
|
12
|
+
# loop through the specs and register each class
|
13
|
+
def load
|
14
|
+
spec_directory = File.expand_path(File.join('..', '..', '..', 'specs', '*'), __FILE__)
|
15
|
+
Dir[spec_directory].each { |filepath| load_from(filepath) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# convenience class method to build a new loader and call load
|
19
|
+
def self.load
|
20
|
+
new.load
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def build_class(aws_name, spec)
|
26
|
+
Class.new(Resource) do
|
27
|
+
self.aws_name = aws_name
|
28
|
+
self.props = spec.each_with_object({}) do |spec_line, props|
|
29
|
+
prop = Props.from(spec_line)
|
30
|
+
props[prop.name] = prop unless prop.name.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_from(filepath)
|
36
|
+
group, resource = Pathname.new(filepath).basename('.cf').to_s.split('-')
|
37
|
+
spec = File.readlines(filepath).select do |line|
|
38
|
+
# flipflop operator (http://stackoverflow.com/questions/14456634)
|
39
|
+
true if line.include?('Properties')...(line.strip == '}')
|
40
|
+
end
|
41
|
+
register(group, resource, spec[1..-2])
|
42
|
+
end
|
43
|
+
|
44
|
+
def register(group, resource, spec)
|
45
|
+
aws_name = "AWS::#{group}::#{resource}"
|
46
|
+
resource_class = build_class(aws_name, spec)
|
47
|
+
|
48
|
+
Humidifier.const_set(group, Module.new) unless Humidifier.const_defined?(group)
|
49
|
+
Humidifier.const_get(group).const_set(resource, resource_class)
|
50
|
+
Humidifier.registry[aws_name] = resource_class
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Humidifier
|
2
|
+
|
3
|
+
# Represents a CFN stack output
|
4
|
+
class Output
|
5
|
+
|
6
|
+
attr_accessor :description, :value
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
self.description = opts[:description]
|
10
|
+
self.value = opts[:value]
|
11
|
+
end
|
12
|
+
|
13
|
+
# CFN stack syntax
|
14
|
+
def to_cf
|
15
|
+
cf = { 'Value' => Serializer.dump(value) }
|
16
|
+
cf['Description'] = description if description
|
17
|
+
cf
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Humidifier
|
2
|
+
|
3
|
+
# Represents a CFN stack parameter
|
4
|
+
class Parameter
|
5
|
+
|
6
|
+
# The allowed properties of all stack parameters
|
7
|
+
PROPERTIES = Utils.underscored(%w[AllowedPattern AllowedValues ConstraintDescription Default Description
|
8
|
+
MaxLength MaxValue MinLength MinValue NoEcho])
|
9
|
+
|
10
|
+
attr_accessor :type, *PROPERTIES.values
|
11
|
+
|
12
|
+
def initialize(opts = {})
|
13
|
+
PROPERTIES.values.each { |prop| send(:"#{prop}=", opts[prop]) }
|
14
|
+
self.type = opts.fetch(:type, 'String')
|
15
|
+
end
|
16
|
+
|
17
|
+
# CFN stack syntax
|
18
|
+
def to_cf
|
19
|
+
cf = { 'Type' => type }
|
20
|
+
PROPERTIES.each do |name, prop|
|
21
|
+
val = send(prop)
|
22
|
+
cf[name] = Serializer.dump(val) if val
|
23
|
+
end
|
24
|
+
cf
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|