humidifier 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +88 -0
  4. data/ext/humidifier/extconf.rb +2 -0
  5. data/ext/humidifier/humidifier.c +93 -0
  6. data/ext/humidifier/humidifier.h +14 -0
  7. data/lib/humidifier/aws_adapters/base.rb +64 -0
  8. data/lib/humidifier/aws_adapters/noop.rb +18 -0
  9. data/lib/humidifier/aws_adapters/sdkv1.rb +56 -0
  10. data/lib/humidifier/aws_adapters/sdkv2.rb +43 -0
  11. data/lib/humidifier/aws_shim.rb +60 -0
  12. data/lib/humidifier/condition.rb +17 -0
  13. data/lib/humidifier/fn.rb +28 -0
  14. data/lib/humidifier/humidifier.bundle +0 -0
  15. data/lib/humidifier/loader.rb +53 -0
  16. data/lib/humidifier/mapping.rb +17 -0
  17. data/lib/humidifier/output.rb +20 -0
  18. data/lib/humidifier/parameter.rb +27 -0
  19. data/lib/humidifier/props.rb +127 -0
  20. data/lib/humidifier/ref.rb +17 -0
  21. data/lib/humidifier/resource.rb +108 -0
  22. data/lib/humidifier/sdk_payload.rb +30 -0
  23. data/lib/humidifier/serializer.rb +18 -0
  24. data/lib/humidifier/sleeper.rb +25 -0
  25. data/lib/humidifier/stack.rb +66 -0
  26. data/lib/humidifier/utils.rb +21 -0
  27. data/lib/humidifier/version.rb +4 -0
  28. data/lib/humidifier.rb +50 -0
  29. data/specs/ApiGateway-Account.cf +6 -0
  30. data/specs/ApiGateway-ApiKey.cf +9 -0
  31. data/specs/ApiGateway-Authorizer.cf +13 -0
  32. data/specs/ApiGateway-BasePathMapping.cf +9 -0
  33. data/specs/ApiGateway-ClientCertificate.cf +6 -0
  34. data/specs/ApiGateway-Deployment.cf +9 -0
  35. data/specs/ApiGateway-Method.cf +15 -0
  36. data/specs/ApiGateway-Model.cf +10 -0
  37. data/specs/ApiGateway-Resource.cf +8 -0
  38. data/specs/ApiGateway-RestApi.cf +12 -0
  39. data/specs/ApiGateway-Stage.cf +14 -0
  40. data/specs/AutoScaling-AutoScalingGroup.cf +21 -0
  41. data/specs/AutoScaling-LaunchConfiguration.cf +22 -0
  42. data/specs/AutoScaling-LifecycleHook.cf +12 -0
  43. data/specs/AutoScaling-ScalingPolicy.cf +14 -0
  44. data/specs/AutoScaling-ScheduledAction.cf +12 -0
  45. data/specs/CloudFormation-Authentication.cf +18 -0
  46. data/specs/CloudFormation-CustomResource.cf +8 -0
  47. data/specs/CloudFormation-Interface.cf +9 -0
  48. data/specs/CloudFormation-Stack.cf +10 -0
  49. data/specs/CloudFormation-WaitCondition.cf +8 -0
  50. data/specs/CloudFormation-WaitConditionHandle.cf +5 -0
  51. data/specs/CloudFront-Distribution.cf +6 -0
  52. data/specs/CloudTrail-Trail.cf +16 -0
  53. data/specs/CloudWatch-Alarm.cf +20 -0
  54. data/specs/CodeDeploy-Application.cf +6 -0
  55. data/specs/CodeDeploy-DeploymentConfig.cf +7 -0
  56. data/specs/CodeDeploy-DeploymentGroup.cf +13 -0
  57. data/specs/CodePipeline-CustomActionType.cf +12 -0
  58. data/specs/CodePipeline-Pipeline.cf +11 -0
  59. data/specs/Config-ConfigRule.cf +11 -0
  60. data/specs/Config-ConfigurationRecorder.cf +8 -0
  61. data/specs/Config-DeliveryChannel.cf +10 -0
  62. data/specs/DataPipeline-Pipeline.cf +12 -0
  63. data/specs/DirectoryService-MicrosoftAD.cf +11 -0
  64. data/specs/DirectoryService-SimpleAD.cf +13 -0
  65. data/specs/DynamoDB-Table.cf +12 -0
  66. data/specs/EC2-CustomerGateway.cf +9 -0
  67. data/specs/EC2-DHCPOptions.cf +11 -0
  68. data/specs/EC2-EIP.cf +7 -0
  69. data/specs/EC2-EIPAssociation.cf +10 -0
  70. data/specs/EC2-FlowLog.cf +10 -0
  71. data/specs/EC2-Host.cf +8 -0
  72. data/specs/EC2-Instance.cf +32 -0
  73. data/specs/EC2-InternetGateway.cf +6 -0
  74. data/specs/EC2-NatGateway.cf +7 -0
  75. data/specs/EC2-NetworkAcl.cf +7 -0
  76. data/specs/EC2-NetworkAclEntry.cf +13 -0
  77. data/specs/EC2-NetworkInterface.cf +13 -0
  78. data/specs/EC2-NetworkInterfaceAttachment.cf +9 -0
  79. data/specs/EC2-PlacementGroup.cf +6 -0
  80. data/specs/EC2-Route.cf +12 -0
  81. data/specs/EC2-RouteTable.cf +7 -0
  82. data/specs/EC2-SecurityGroup.cf +10 -0
  83. data/specs/EC2-SecurityGroupEgress.cf +11 -0
  84. data/specs/EC2-SecurityGroupIngress.cf +14 -0
  85. data/specs/EC2-SpotFleet.cf +6 -0
  86. data/specs/EC2-Subnet.cf +10 -0
  87. data/specs/EC2-SubnetNetworkAclAssociation.cf +5 -0
  88. data/specs/EC2-SubnetRouteTableAssociation.cf +7 -0
  89. data/specs/EC2-VPC.cf +10 -0
  90. data/specs/EC2-VPCDHCPOptionsAssociation.cf +7 -0
  91. data/specs/EC2-VPCEndpoint.cf +9 -0
  92. data/specs/EC2-VPCGatewayAttachment.cf +8 -0
  93. data/specs/EC2-VPCPeeringConnection.cf +8 -0
  94. data/specs/EC2-VPNConnection.cf +10 -0
  95. data/specs/EC2-VPNConnectionRoute.cf +7 -0
  96. data/specs/EC2-VPNGateway.cf +7 -0
  97. data/specs/EC2-VPNGatewayRoutePropagation.cf +7 -0
  98. data/specs/EC2-Volume.cf +14 -0
  99. data/specs/EC2-VolumeAttachment.cf +8 -0
  100. data/specs/ECR-Repository.cf +7 -0
  101. data/specs/ECS-Cluster.cf +6 -0
  102. data/specs/ECS-Service.cf +11 -0
  103. data/specs/ECS-TaskDefinition.cf +7 -0
  104. data/specs/EFS-FileSystem.cf +6 -0
  105. data/specs/EFS-MountTarget.cf +9 -0
  106. data/specs/EMR-Cluster.cf +17 -0
  107. data/specs/EMR-InstanceGroupConfig.cf +14 -0
  108. data/specs/EMR-Step.cf +9 -0
  109. data/specs/ElastiCache-CacheCluster.cf +27 -0
  110. data/specs/ElastiCache-ParameterGroup.cf +8 -0
  111. data/specs/ElastiCache-ReplicationGroup.cf +23 -0
  112. data/specs/ElastiCache-SecurityGroup.cf +7 -0
  113. data/specs/ElastiCache-SecurityGroupIngress.cf +9 -0
  114. data/specs/ElastiCache-SubnetGroup.cf +7 -0
  115. data/specs/ElasticBeanstalk-Application.cf +7 -0
  116. data/specs/ElasticBeanstalk-ApplicationVersion.cf +8 -0
  117. data/specs/ElasticBeanstalk-ConfigurationTemplate.cf +11 -0
  118. data/specs/ElasticBeanstalk-Environment.cf +15 -0
  119. data/specs/ElasticLoadBalancing-LoadBalancer.cf +21 -0
  120. data/specs/Elasticsearch-Domain.cf +12 -0
  121. data/specs/Events-Rule.cf +12 -0
  122. data/specs/GameLift-Alias.cf +8 -0
  123. data/specs/GameLift-Build.cf +8 -0
  124. data/specs/GameLift-Fleet.cf +16 -0
  125. data/specs/IAM-AccessKey.cf +8 -0
  126. data/specs/IAM-Group.cf +8 -0
  127. data/specs/IAM-InstanceProfile.cf +7 -0
  128. data/specs/IAM-ManagedPolicy.cf +11 -0
  129. data/specs/IAM-Policy.cf +10 -0
  130. data/specs/IAM-Role.cf +9 -0
  131. data/specs/IAM-User.cf +10 -0
  132. data/specs/IAM-UserToGroupAddition.cf +7 -0
  133. data/specs/KMS-Key.cf +9 -0
  134. data/specs/Kinesis-Stream.cf +8 -0
  135. data/specs/KinesisFirehose-DeliveryStream.cf +9 -0
  136. data/specs/Lambda-Alias.cf +9 -0
  137. data/specs/Lambda-EventSourceMapping.cf +10 -0
  138. data/specs/Lambda-Function.cf +14 -0
  139. data/specs/Lambda-Permission.cf +10 -0
  140. data/specs/Lambda-Version.cf +8 -0
  141. data/specs/Logs-Destination.cf +9 -0
  142. data/specs/Logs-LogGroup.cf +6 -0
  143. data/specs/Logs-LogStream.cf +7 -0
  144. data/specs/Logs-MetricFilter.cf +8 -0
  145. data/specs/Logs-SubscriptionFilter.cf +9 -0
  146. data/specs/OpsWorks-App.cf +16 -0
  147. data/specs/OpsWorks-ElasticLoadBalancerAttachment.cf +7 -0
  148. data/specs/OpsWorks-Instance.cf +19 -0
  149. data/specs/OpsWorks-Layer.cf +21 -0
  150. data/specs/OpsWorks-Stack.cf +23 -0
  151. data/specs/RDS-DBCluster.cf +23 -0
  152. data/specs/RDS-DBClusterParameterGroup.cf +9 -0
  153. data/specs/RDS-DBInstance.cf +38 -0
  154. data/specs/RDS-DBParameterGroup.cf +9 -0
  155. data/specs/RDS-DBSecurityGroup.cf +10 -0
  156. data/specs/RDS-DBSecurityGroupIngress.cf +10 -0
  157. data/specs/RDS-DBSubnetGroup.cf +8 -0
  158. data/specs/RDS-EventSubscription.cf +10 -0
  159. data/specs/RDS-OptionGroup.cf +10 -0
  160. data/specs/Redshift-Cluster.cf +30 -0
  161. data/specs/Redshift-ClusterParameterGroup.cf +8 -0
  162. data/specs/Redshift-ClusterSecurityGroup.cf +6 -0
  163. data/specs/Redshift-ClusterSecurityGroupIngress.cf +9 -0
  164. data/specs/Redshift-ClusterSubnetGroup.cf +7 -0
  165. data/specs/Route53-HealthCheck.cf +7 -0
  166. data/specs/Route53-HostedZone.cf +9 -0
  167. data/specs/Route53-RecordSet.cf +19 -0
  168. data/specs/Route53-RecordSetGroup.cf +9 -0
  169. data/specs/S3-Bucket.cf +15 -0
  170. data/specs/S3-BucketPolicy.cf +7 -0
  171. data/specs/SDB-Domain.cf +6 -0
  172. data/specs/SNS-Topic.cf +8 -0
  173. data/specs/SNS-TopicPolicy.cf +8 -0
  174. data/specs/SQS-Queue.cf +12 -0
  175. data/specs/SQS-QueuePolicy.cf +7 -0
  176. data/specs/SSM-Document.cf +6 -0
  177. data/specs/WAF-ByteMatchSet.cf +7 -0
  178. data/specs/WAF-IPSet.cf +7 -0
  179. data/specs/WAF-Rule.cf +8 -0
  180. data/specs/WAF-SizeConstraintSet.cf +7 -0
  181. data/specs/WAF-SqlInjectionMatchSet.cf +7 -0
  182. data/specs/WAF-WebACL.cf +9 -0
  183. data/specs/WAF-XssMatchSet.cf +7 -0
  184. data/specs/WorkSpaces-Workspace.cf +11 -0
  185. 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,2 @@
1
+ require 'mkmf'
2
+ create_makefile 'humidifier/humidifier'
@@ -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,17 @@
1
+ module Humidifier
2
+
3
+ # Represents a CFN stack condition
4
+ class Condition
5
+
6
+ attr_accessor :opts
7
+
8
+ def initialize(opts)
9
+ self.opts = opts
10
+ end
11
+
12
+ # CFN stack syntax
13
+ def to_cf
14
+ Serializer.dump(opts)
15
+ end
16
+ end
17
+ 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,17 @@
1
+ module Humidifier
2
+
3
+ # Represents a CFN stack mapping
4
+ class Mapping
5
+
6
+ attr_accessor :opts
7
+
8
+ def initialize(opts = {})
9
+ self.opts = opts
10
+ end
11
+
12
+ # CFN stack syntax
13
+ def to_cf
14
+ Serializer.dump(opts)
15
+ end
16
+ end
17
+ 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