regentanz 0.3.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +268 -0
  3. data/bin/regentanz +16 -0
  4. data/lib/regentanz.rb +10 -11
  5. data/lib/regentanz/cli/common.rb +35 -0
  6. data/lib/regentanz/cli/compare.rb +85 -0
  7. data/lib/regentanz/cli/compile.rb +27 -0
  8. data/lib/regentanz/template_compiler.rb +263 -0
  9. data/lib/regentanz/version.rb +1 -2
  10. data/lib/regentanz/yaml-ext.rb +18 -0
  11. data/spec/regentanz/resources/test/unloaded.rb +11 -0
  12. data/spec/regentanz/template_compiler_spec.rb +692 -0
  13. data/spec/spec_helper.rb +2 -0
  14. metadata +45 -152
  15. data/.gitignore +0 -5
  16. data/.rvmrc +0 -4
  17. data/CHANGELOG.rdoc +0 -26
  18. data/Gemfile +0 -4
  19. data/LICENSE +0 -24
  20. data/README.rdoc +0 -54
  21. data/Rakefile +0 -23
  22. data/lib/regentanz/astronomy.rb +0 -69
  23. data/lib/regentanz/cache.rb +0 -2
  24. data/lib/regentanz/cache/base.rb +0 -51
  25. data/lib/regentanz/cache/file.rb +0 -86
  26. data/lib/regentanz/callbacks.rb +0 -18
  27. data/lib/regentanz/conditions.rb +0 -3
  28. data/lib/regentanz/conditions/base.rb +0 -16
  29. data/lib/regentanz/conditions/current.rb +0 -14
  30. data/lib/regentanz/conditions/forecast.rb +0 -14
  31. data/lib/regentanz/configuration.rb +0 -55
  32. data/lib/regentanz/configurator.rb +0 -22
  33. data/lib/regentanz/google_weather.rb +0 -151
  34. data/lib/regentanz/parser.rb +0 -1
  35. data/lib/regentanz/parser/google_weather.rb +0 -100
  36. data/lib/regentanz/test_helper.rb +0 -52
  37. data/regentanz.gemspec +0 -30
  38. data/test/factories.rb +0 -6
  39. data/test/support/tmp/.gitignore +0 -1
  40. data/test/support/valid_response.xml.erb +0 -26
  41. data/test/test_helper.rb +0 -14
  42. data/test/unit/astronomy_test.rb +0 -26
  43. data/test/unit/cache/base_test.rb +0 -53
  44. data/test/unit/cache/file_test.rb +0 -141
  45. data/test/unit/callbacks_test.rb +0 -27
  46. data/test/unit/configuration_test.rb +0 -57
  47. data/test/unit/current_condition_test.rb +0 -33
  48. data/test/unit/forecast_condition_test.rb +0 -35
  49. data/test/unit/google_weather_test.rb +0 -125
  50. data/test/unit/parser/google_weather_parser_test.rb +0 -71
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7dd89f8efd4c50cc933e407205c64dd257895ae6
4
+ data.tar.gz: 57910386c3c39c3c7de7aedd66dc2e133a4cd6a1
5
+ SHA512:
6
+ metadata.gz: 2147f883198e82a80475c32f2610267793fc54a31968974041be14e0d35eb0c4f86bde50c7ec16b99512db6f360ca64e180c96f8d295adc2dbdd27aa56a6cab9
7
+ data.tar.gz: 61c4ed079798cad6ae1e71d1838fafc4838c714a43e66d7056085eab1857940aadab2e0700052ed114b9f365d51a9e02f8890337cdf5767e52571ffd4a8196d9
@@ -0,0 +1,268 @@
1
+ # Regentanz
2
+
3
+ [![Build Status](https://travis-ci.org/burtcorp/regentanz.png?branch=master)](https://travis-ci.org/burtcorp/regentanz)
4
+
5
+ _If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the readme for a specific version via the release tags ([here is an example](https://github.com/burtcorp/regentanz/tree/1.0.0))._
6
+
7
+ Regentanz is a compiler and preprocessor for CloudFormation templates. It allows you to split up a template into one file per resource, and also build custom resources that can decrease the complexity of templates.
8
+
9
+ > _Looking for Regentanz, Carsten Zimmermann's "Library to access the Google Weather API"? It can still be found at https://github.com/carpodaster/regentanz and if you install v0.3.3 from Rubygems, you'll get that gem._
10
+
11
+ ## Installation
12
+
13
+ Install it on the command line:
14
+
15
+ ```shell
16
+ $ gem install regentanz --version '~> 1.0.0'
17
+ ```
18
+
19
+ or add it to your `Gemfile`:
20
+
21
+ ```ruby
22
+ gem 'regentanz', '~> 1.0.0'
23
+ ```
24
+
25
+ ## How to build and run the tests
26
+
27
+ The best place to see how to build and run the tests is to look at the `.travis.yml` file, but if you just want to get going run:
28
+
29
+ ```shell
30
+ $ bundle
31
+ $ rake
32
+ ```
33
+
34
+ ## Examples
35
+
36
+ The `examples` directory contains runnable example templates.
37
+
38
+ ## Usage
39
+
40
+ To compile a template you use the `regentanz` command like this:
41
+
42
+ ```shell
43
+ $ regentanz compile path/to/template > path/to/compiled/template.json
44
+ ```
45
+
46
+ ### Template validation
47
+
48
+ The compiler will validate the final template with CloudFormation, so you will need to run it with AWS credentials that permit `cloudformation:ValidateTemplate`.
49
+
50
+ For the validation to work with templates larger than 51200 bytes you need to specify an S3 bucket in a config file, and need AWS credentials that permit `s3:PutObject` in that bucket.
51
+
52
+ To configure a bucket to use for validation of large templates create a file called `.regentanz.yml` in the directory where you will run Regentanz, and add the following:
53
+
54
+ ```yaml
55
+ template_url: 's3://some-bucket-name/a_prefix/${TEMPLATE_NAME}.json'
56
+ ```
57
+
58
+ You can use `${AWS_REGION}` in the bucket portion, and `${TEMPLATE_NAME}` and `${TIMESTAMP}` in the key portion to create unique URLs. You can, for example, use the buckets that CloudFormation creates for template uploads, like this:
59
+
60
+ ```yaml
61
+ template_url: 's3://cf-templates-xyz-${AWS_REGION}/regentanz/${TEMPLATE_NAME}-${TIMESTAMP}.json'
62
+ ```
63
+
64
+ Where "xyz" is the unique letter combination for your account.
65
+
66
+ ### Anatomy of a template
67
+
68
+ Just like a CloudFormation template, a Regentanz template consists of conditions, mappings, outputs, parameters, and resources. In contrast with CloudFormation these are not properties of one big JSON or YAML document, but exists as separate files in a directory structure. Each resource has its own file, and there is one file each for conditions, mappings, and parameters. If the template doesn't need conditions, mappings, or parameters these files can be left out.
69
+
70
+ The Regentanz compiler will take a directory and output a CloudFormation template in JSON format that can be used with CloudFormation.
71
+
72
+ #### Example
73
+
74
+ Say you have an application with an auto scaling group, launch configuration, security group, an instance profile, and an IAM role for the instance profile. In a regular CloudFormation template you would declare five different resources in one big JSON or YAML file, but in Regentanz you would instead keep these in five separate files, something like this:
75
+
76
+ ```
77
+ my_application/
78
+ resources/
79
+ auto_scaling_group.yml
80
+ iam_role.yml
81
+ instance_profile.yml
82
+ launch_configuration.yml
83
+ security_group.yml
84
+ ```
85
+
86
+ You're free to name the files whatever you want, and you can put them in subdirectories too. This is an alternative way to structure the same template:
87
+
88
+ ```
89
+ my_application/
90
+ resources/
91
+ iam/
92
+ role.yml
93
+ instance_profile.yml
94
+ asg.yml
95
+ lc.yml
96
+ sg.yml
97
+ ```
98
+
99
+ If you have conditions, mappings, outputs, or parameters you put these in files at the top level:
100
+
101
+ my_application/
102
+ resources/
103
+ iam/
104
+ role.yml
105
+ instance_profile.yml
106
+ asg.yml
107
+ lc.yml
108
+ sg.yml
109
+ conditions.yml
110
+ mappings.yml
111
+ outputs.yml
112
+ parameters.yml
113
+ ```
114
+
115
+ You can use JSON or YAML for your files, and mix between these in the same template.
116
+
117
+ The contents of the files is the same thing you would put in a CloudFormation template. In other words, if your CloudFormation looks something like this:
118
+
119
+ ```yaml
120
+ Parameters:
121
+ ServerCount:
122
+ Type: Number
123
+ Default: 2
124
+ MinValue: 0
125
+
126
+ Resources:
127
+ Asg:
128
+ Type: AWS::EC2::AutoScalingGroup
129
+ Properties:
130
+ DesiredCapacity: !Ref ServerCount
131
+ # …
132
+ Lc:
133
+ Type: AWS::EC2::LaunchConfiguration
134
+ Properties:
135
+ # …
136
+ ```
137
+
138
+ You would put the parameters in `parameters.yml`:
139
+
140
+ ```yaml
141
+ Parameters:
142
+ ServerCount:
143
+ Type: Number
144
+ Default: 2
145
+ MinValue: 0
146
+ ```
147
+
148
+ The auto scaling group resource in `resources/asg.yml`:
149
+
150
+ ```yaml
151
+ Type: AWS::EC2::AutoScalingGroup
152
+ Properties:
153
+ DesiredCapacity: {Ref: DesiredCapacity}
154
+ # …
155
+ ```
156
+
157
+ Unfortunately CloudFormation's YAML syntax for intrinsic functions (e.g. `!Ref`) is not supported, you have to convert it to YAML/JSON (e.g. `{Ref: …}`), like above.
158
+
159
+ The launch configuration resource would go in a file called `resources/lc.yml`:
160
+
161
+ ```yaml
162
+ Type: AWS::EC2::LaunchConfiguration
163
+ Properties:
164
+ # …
165
+ ```
166
+
167
+ And when you compile this with the Regentanz compiler you would get the same CloudFormation template back, but in JSON format.
168
+
169
+ ### Resource references
170
+
171
+ The Regentanz compiler will generate resource names based on the relative paths of the files in the template. Currently the scheme is to take the path relative to the root of the template and create a `CamelCase` with no underscores or slashes. You should however not rely on this convention since it could change in the future. Instead you should use two macros provided by Regentanz that work similar to CloudFormation's `Ref` function.
172
+
173
+ Wherever you would have used `Ref` in CloudFormation you should use `ResolveRef`, and use the relative path, minus file ending as argument. In the template in the example above you could for example refer to the IAM role with `{ResolveRef: iam/role}`, and the auto scaling group as `{ResolveRef: asg}`.
174
+
175
+ In places where you in CloudFormation would have used the name of a resource directly you should use `ResolveName`. For example `{ResolveName: iam/role}` and `{ResolveName: asg}`. Almost the only place you will need `ResolveName` is in `GetAtt`.
176
+
177
+ Continuing on the example above the auto scaling group needs to refer to the launch configuration. Using `ResolveRef` that would look like this:
178
+
179
+ ```yaml
180
+ Type: AWS::EC2::AutoScalingGroup
181
+ Properties:
182
+ LaunchConfigurationName: {ResolveRef: asg}
183
+ # …
184
+ ```
185
+
186
+ ## Custom Resources
187
+
188
+ If you've written a lot of CloudFormation templates you probably feel like you're constantly repeating yourself. The same patterns appear again and again in multiple templates. To help with this Regentanz lets you write custom resources that can be used in templates and that generate other resources, parameters, mappings, etc. when compiled.
189
+
190
+ A custom resource is a Ruby class that lives in the `Regentanz::Resources` module that has a `#compile` method that returns a partial template.
191
+
192
+ ### Anatomy of a custom resource
193
+
194
+ Custom resources will be instantiated by the template compiler and their `#compile` method will be called with the name of the resource and the resource template. The result of this call must be a "template fragment", which is a hash with a `:resources` key, and optionally `:conditions`, `:mappings`, `:outputs`, and `:parameters`. Each of these must be (when specified) a hash that looks like the corresponding CloudFormation structure
195
+
196
+ Say you used a custom resource like the following, in a file with the relative path `app/sg.yml`
197
+
198
+ ```yaml
199
+ Type: Regentanz::Resources::MySpecialSecurityGroup
200
+ Properties:
201
+ Name: special-sg
202
+ PortsOpenToEveryone:
203
+ - 22
204
+ - 80
205
+ ```
206
+
207
+ The compiler will, conceptually, do this (`template_fragment` is a the contents of the file):
208
+
209
+ ```ruby
210
+ resource = Regentanz::Resources::MyCustomResource
211
+ result = resource.compile('AppSg', template_fragment)
212
+ ```
213
+
214
+ It will then take the result and merge it with the rest of the template.
215
+
216
+ This is how you could implement `MySpecialSecurityGroup`:
217
+
218
+ ```ruby
219
+ class Regentanz::Resources::MyCustomResource
220
+ def compile(name, template)
221
+ ingress_rules = template['Properties']['PortsOpenToEveryone'].map do |port|
222
+ {'IpProtocol' => 'tcp', 'FromPort' => port, 'ToPort' => port, 'CidrIp' => '0.0.0.0/0'}
223
+ end
224
+ {
225
+ :resources => {
226
+ name => {
227
+ 'Type' => 'AWS::EC2::SecurityGroup',
228
+ 'Properties' => {
229
+ 'GroupName' => properties['Name'],
230
+ 'SecurityGroupIngress' => ingress_rules
231
+ }
232
+ }
233
+ }
234
+ }
235
+ end
236
+ end
237
+ ```
238
+
239
+ You are free to ignore the `name` parameter, but it is strongly recommended that you use it. It is the name Regentanz has generated from the relative path of the file the resource is declared in, so ignoring it will make it harder and more confusing to refer to the resource the custom resource generates. If you generate more than one resource in your template it is recommended that you use `name` as a prefix. For example if you generate an auto scaling group and a launch configuration from the a custom resource generating resources with names like `"#{name}Asg"`, and `"#{name}Lc"` will make it possible to do `{ResolveRef: my_resource/asg}` in another resource.
240
+
241
+ If the template fragment returned by `#compile` contains `ResolveRef` or `ResolveName` these will be resolved as expected.
242
+
243
+ ### Adding custom resources to the load path
244
+
245
+ Custom resources must be available on the load path when the template compiler runs. The name of the file must also follow the standard Ruby naming convention, i.e. a resource called `Regentanz::Resources::MyCustomResource` must be declared in a file with the path `regentanz/resources/my_custom_resource.rb`, so that the compiler knows which file to load.
246
+
247
+ This can be achieved by manipulating environment variables like `RUBYLIB`, or by packaging your custom resources as a gem, but in most cases the easiest way is to add a config file that tells the template compiler how to modify the load path to be able to load your custom resources.
248
+
249
+ You can add a file called `.regentanz.yml` in any parent directory of the directory where you run the compiler. In the file you put a list of paths to add to the Ruby load path:
250
+
251
+ ```yaml
252
+ load_path:
253
+ - path/to/resources
254
+ - /an/absolute/path
255
+ ```
256
+
257
+ Relative paths will be resolved relative to the config file.
258
+
259
+ The config file above will allow the template compiler to find custom resources in files such as `path/to/resources/regentanz/resources/my_custom_resource.rb` (relative to the config file) and `/an/absolute/path/regentanz/resources/my_custom_resource.rb`.
260
+
261
+ ## Limitations
262
+
263
+ * Regentanz unfortunately does not support CloudFormation's YAML syntax for intrinsic functions.
264
+ * The support for validating large templates is currently broken.
265
+
266
+ # Copyright
267
+
268
+ © 2015-2018 Burt AB, see LICENSE.txt (BSD 3-Clause).
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'regentanz/yaml-ext'
6
+
7
+ case ARGV.first
8
+ when 'compare'
9
+ ARGV.shift
10
+ require 'regentanz/cli/compare'
11
+ exit(Regentanz::Cli::Compare.new.run(ARGV))
12
+ else
13
+ ARGV.shift if ARGV.first == 'compile'
14
+ require 'regentanz/cli/compile'
15
+ exit(Regentanz::Cli::Compile.new.run(ARGV))
16
+ end
@@ -1,11 +1,10 @@
1
- require 'active_support'
2
- require 'active_support/core_ext'
3
- require 'regentanz/version'
4
- require 'regentanz/cache'
5
- require 'regentanz/configuration'
6
- require 'regentanz/configurator'
7
- require 'regentanz/callbacks'
8
- require 'regentanz/astronomy'
9
- require 'regentanz/conditions'
10
- require 'regentanz/parser'
11
- require 'regentanz/google_weather'
1
+ require 'json'
2
+ require 'yaml'
3
+ require 'aws-sdk-s3'
4
+ require 'aws-sdk-cloudformation'
5
+
6
+ module Regentanz
7
+ Error = Class.new(StandardError)
8
+ end
9
+
10
+ require 'regentanz/template_compiler'
@@ -0,0 +1,35 @@
1
+ require 'yaml'
2
+
3
+ module Regentanz
4
+ module Cli
5
+ module Common
6
+ private def load_config
7
+ if (path = find_config('.'))
8
+ config = YAML.load_file(path)
9
+ Array(config['load_path']).each do |extra_load_path|
10
+ if extra_load_path.start_with?('/')
11
+ $LOAD_PATH << extra_load_path
12
+ else
13
+ $LOAD_PATH << File.absolute_path(extra_load_path, File.dirname(path))
14
+ end
15
+ end
16
+ if config['default_region'].nil? && (region = ENV['AWS_REGION'] || ENV['AWS_DEFAULT_REGION'])
17
+ config['default_region'] = region
18
+ end
19
+ config
20
+ end
21
+ end
22
+
23
+ private def find_config(path)
24
+ candidates = Dir[File.join(path, '.regentanz.{yaml,yml,json}')]
25
+ if candidates.first
26
+ candidates.first
27
+ elsif path != '/'
28
+ find_config(File.expand_path(File.join(path, '..')))
29
+ else
30
+ nil
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,85 @@
1
+ require 'regentanz'
2
+ require 'regentanz/cli/common'
3
+
4
+ module Regentanz
5
+ module Cli
6
+ class Diff
7
+ def initialize(new_object, old_object)
8
+ @new_object = new_object
9
+ @old_object = old_object
10
+ end
11
+
12
+ def to_json(*args)
13
+ %(\e[31m#{@old_object.to_json(*args)}\e[m\e[32m#{@new_object.to_json(*args)}\e[m)
14
+ end
15
+ end
16
+
17
+ class Compare
18
+ include Common
19
+
20
+ def run(args)
21
+ config = load_config
22
+ stack_name, stack_path, _ = *args
23
+ compiler = TemplateCompiler.new(config)
24
+ new_template = compiler.compile_from_path(stack_path)
25
+ compiler.validate_template(stack_path, new_template.to_json)
26
+ old_template = get_template(config, stack_name)
27
+ diff = compare(new_template, old_template)
28
+ if diff.to_json != new_template.to_json
29
+ output = JSON.pretty_generate(diff)
30
+ puts(output)
31
+ 1
32
+ else
33
+ 0
34
+ end
35
+ rescue Regentanz::Error => e
36
+ $stderr.puts(e.message)
37
+ 2
38
+ end
39
+
40
+ private
41
+
42
+ def get_template(config, stack_name)
43
+ cf_client = Aws::CloudFormation::Client.new(region: config['default_region'])
44
+ YAML.load(cf_client.get_template(stack_name: stack_name)[:template_body])
45
+ rescue Aws::Errors::MissingCredentialsError => e
46
+ raise Regentanz::Error, 'Retrieving template requires AWS credentials', e.backtrace
47
+ end
48
+
49
+ def compare(new_template, old_template)
50
+ if new_template.is_a?(Date) && old_template.is_a?(String)
51
+ new_template = new_template.to_s
52
+ end
53
+ if new_template.class != old_template.class
54
+ Diff.new(new_template, old_template)
55
+ else
56
+ case new_template
57
+ when Hash
58
+ result = {}
59
+ new_template.each do |(key, value)|
60
+ result[key] = compare(value, old_template[key])
61
+ end
62
+ old_template.each do |(key, value)|
63
+ result[key] = compare(nil, value) unless new_template.key?(key)
64
+ end
65
+ result
66
+ when Array
67
+ if new_template.size != old_template.size
68
+ Diff.new(new_template, old_template)
69
+ else
70
+ new_template.zip(old_template).map do |(left, right)|
71
+ compare(left, right)
72
+ end
73
+ end
74
+ else
75
+ if new_template == old_template
76
+ new_template
77
+ else
78
+ Diff.new(new_template, old_template)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,27 @@
1
+ require 'regentanz'
2
+ require 'regentanz/cli/common'
3
+
4
+ module Regentanz
5
+ module Cli
6
+ class Compile
7
+ include Common
8
+
9
+ def run(args)
10
+ config = load_config
11
+ stack_path = args.first
12
+ compiler = Regentanz::TemplateCompiler.new(config)
13
+ template = compiler.compile_from_path(stack_path)
14
+ template_json = JSON.pretty_generate(template)
15
+ if template_json.bytesize >= 51200
16
+ template_json = JSON.generate(template)
17
+ end
18
+ compiler.validate_template(stack_path, template_json)
19
+ puts(template_json)
20
+ 0
21
+ rescue Regentanz::Error => e
22
+ $stderr.puts(e.message)
23
+ 1
24
+ end
25
+ end
26
+ end
27
+ end