rubycfn 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4442707e07573f3c2da20d53671910caf3f8c8d
4
- data.tar.gz: 10183704a3a12f0d77b8dabe8f1b5c3570454e01
3
+ metadata.gz: 85ac61bfe1b03a66e40625c1b060e1bb95cbbe22
4
+ data.tar.gz: 9f15208720740bf70662579c0e59dd211ac94376
5
5
  SHA512:
6
- metadata.gz: 5941864e3125790d0a59da7fa26862e5e2929e020f0342ff670378502075f2208086f858333ec060fd1d620974a3f7a7333c2559f828c966ac17bf82d5ea3c12
7
- data.tar.gz: 8dc9c51d380d69b36c2b65bff6528c2b9fa1768d3382777894344d5d8e8b140698ba5ad29a4a5c478eabf91ac3fad47712b105c2dd381ee2f41d9022812cffdd
6
+ metadata.gz: 8495e7b2a758bddddd44b2c343f4ed36208dcaa350549cdf635cd460da63fd68094fb294630e9dd187d0d296e9a7f2c0cc2dc9e93a639ef3bef1c14af8819e61
7
+ data.tar.gz: f7f4cace496dec204bf5ae8bc7ec0c1e149d351fab3d8ed05b3f28af500c4c5a3b847ea990588529ad1c8d06d02480ff53fd895baf649e8bc67bc654a7884513
data/CHANGELOG.md CHANGED
@@ -2,7 +2,12 @@
2
2
  All notable changes to Rubycfn will be documented in this file.
3
3
  This project uses [Semantic Versioning](http://semver.org/).
4
4
 
5
- ## 0.3.7 (Next Release)
5
+ ## 0.3.8 (Next Release)
6
+
7
+ ## 0.3.7
8
+
9
+ * Allow symbols to be passed to Ref as argument for Fn::GetAtt -- [@dennisvink][@dennisvink]
10
+ * Added .gitignore to default project template -- [@dennisvink][@dennisvink]
6
11
 
7
12
  ## 0.3.6
8
13
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubycfn (0.3.6)
4
+ rubycfn (0.3.7)
5
5
  activesupport (~> 5.1.5)
6
6
  dotenv (~> 2.4.0)
7
7
  json (~> 2.1.0)
data/README.md CHANGED
@@ -1,20 +1,59 @@
1
1
  # RubyCfn
2
2
 
3
- [RubyCfn](https://rubycfn.com/) is a light-weight tiny CloudFormation, Deployment Manager and ARM DSL to make expressing
3
+ [RubyCfn](https://rubycfn.com/) is a light-weight tiny CloudFormation, and Deployment Manager to make expressing
4
4
  AWS templates as Ruby code a bit more pleasing to the eye.
5
5
 
6
6
  You can find the [CloudFormation Compiler](https://rubycfn.com/) at https://rubycfn.com with examples.
7
7
 
8
- Note, as of 0.3.3 the default project structure changed quite a bit. The README.md does not reflect
9
- those changes yet. A rubycfn project is now provisioned with a nested stack set up, and includes
10
- a nested VPC and a nested ECS stack.
8
+ ## Philosophy
9
+
10
+ Standardisation is key to keep your engineering team agile. Time spent on projects that deviate from a standard implementation is time taken away from delivering value. Custom implementations are detrimental to a team’s velocity and scalability. It hinders knowledge sharing as a select few have knowledge about the specifics of such a custom implementation, and because the wheel is reinvented many times over proper testing is tedious at best. We’ve automated best practices and ensured that new projects automatically incorporate our principles. Our tooling has been built with cloud engineer happiness in mind.
11
11
 
12
12
  ## Quick start
13
13
 
14
14
  Install Rubycfn:
15
- `gem install rubycfn`
15
+ ```
16
+ gem install rubycfn
17
+ echo "resource :my_s3_bucket, type: 'AWS::S3::Bucket'" | rubycfn
18
+ ```
19
+
20
+ The Rubycfn CLI can be piped to or takes a file name as argument. You can start
21
+ by cloning the [Rubycfn Examples Repository](https://github.com/dennisvink/rubycfn-examples/). To generate a CloudFormation template type:
22
+
23
+ `cat "3. Deploying a Serverless function.rb" | rubycfn`
24
+
25
+ or
26
+
27
+ `rubycfn "3. Deploying a Serverless function.rb"`
28
+
29
+ to generate the CloudFormation template for that example.
30
+
31
+ Now take this `template.rb` as an example:
32
+
33
+ ```ruby
34
+ parameter :bucket_name,
35
+ description: "Bucket name"
36
+
37
+ resource :foobar,
38
+ type: "AWS::S3::Bucket" do |r|
39
+ r.property(:name) { :bucket_name.ref }
40
+ end
41
+ ```
42
+
43
+ You can generate a CloudFormation template from this script in the following ways:
44
+ `cat template.rb | rubycfn`
45
+
46
+ or
47
+
48
+ `rubycfn template.rb`
49
+
50
+ Both commands will output the CloudFormation template without the need for you to set up a project.
51
+
52
+ ## Setting up a Rubycfn project
53
+
54
+ For projects that extend beyond a simnple stack or those that require unit
55
+ testing you can create a Rubycfn project in the following way:
16
56
 
17
- Starting a new Rubycfn project:
18
57
  `rubycfn`
19
58
  ```$ rubycfn
20
59
  __________ ____ __________________.___._________ _____________________
@@ -22,12 +61,14 @@ __________ ____ __________________.___._________ _____________________
22
61
  | _/ | /| | _// | |/ \ \/ | __) | | _/
23
62
  | | \ | / | | \\____ |\ \____| \ | | \
24
63
  |____|_ /______/ |______ // ______| \______ /\___ / |______ /
25
- \/ \/ \/ \/ \/ \/ [v0.3.6]
64
+ \/ \/ \/ \/ \/ \/ [v0.3.7]
26
65
  Project name? example
27
66
  Account ID? 1234567890
28
67
  Select region EU (Frankfurt)
29
68
  ```
30
69
 
70
+ ## Project commands
71
+
31
72
  Installing project dependencies:
32
73
  `bundle`
33
74
 
@@ -52,366 +93,245 @@ Checking difference between local and remote stack:
52
93
  Deploying stack to AWS:
53
94
  `rake apply`
54
95
 
55
- ## Philosophy
96
+ ## Anatomy of a Rubycfn project
56
97
 
57
- Standardisation is key to keep your engineering team agile. Time spent on projects that deviate from a standard implementation is time taken away from delivering value. Custom implementations are detrimental to a team’s velocity and scalability. It hinders knowledge sharing as a select few have knowledge about the specifics of such a custom implementation, and because the wheel is reinvented many times over proper testing is tedious at best. We’ve automated best practices and ensured that new projects automatically incorporate our principles. Our tooling has been built with cloud engineer happiness in mind.
58
-
59
- ## Overview of Rubycfn
60
-
61
- RubyCfn is an abstraction layer around several Cloud templates such as CloudFormation (AWS). Rubycfn projects are set up for easy grouping of resources that have a mutual cohesion, and structured in such a way to make it easy for developers to quickly find what they need. Rubycfn is a so-called ‘DSL’ on top of these template formats and presents templates as code that is friendly to the eye and easy to read and understand. In addition of being an alternate representation of a template, Rubycfn allows you to combine template generation with programming logic making it far more versatile than what AWS offers in their templates. Last but not least Rubycfn enforces code quality by testing the generated templates against unit tests, checking if the expected resources and their configuration matches with what was actually generated, and by LINTing the templates and the underlying code that generates the templates.
62
-
63
- Out of the box Rubycfn comes with a CI/CD pipeline. It’s a serverless pipeline running on Amazon Web Services (AWS), which you can fully configure using the complimentary `buildspec.yml`. The CI/CD pipeline is linked to a Github repository, and a change in this repository triggers the CI/CD pipeline to execute the steps you’ve defined in the buildspec.yml.
64
-
65
- Typically a commit to your application GIT repository triggers the build process and the following things happen:
66
- - Application code is checked out
67
- - Infrastructure as code (Rubycfn project) is checked out
68
- - Rubycfn project is ‘built’, kicking off unit tests against the underlying code, and against the resulting build artifact (template(s))
69
- - Application (unit) tests are ran
70
- - The build artifact is stored (versioned), so you can use it as input for your delivery pipeline
71
- - The artifact may or may not include the application code. A part of the build process could - for example - also be that the application is dockerized and pushed to a docker registry.
72
- - The resulting artifact is the complete recipe to deploy the application and associated resources to AWS
73
-
74
- ## Example code
75
-
76
- You can find stack examples at [https://github.com/dennisvink/rubycfn-example](https://github.com/dennisvink/rubycfn-example/)
77
-
78
- ## Installing Gems
79
-
80
- Type `bundle install` to install all dependencies that are listed in the Gemfile.
81
-
82
- ## Running specs
83
-
84
- Type `rake` to run the tests. It tests if RubyCfn creates a valid
85
- CloudFormation stack.
86
-
87
- ## Example usage
88
-
89
- ```ruby
90
- require "rubycfn"
91
-
92
- module DnsStack
93
- extend ActiveSupport::Concern
94
- include Rubycfn
98
+ A new Rubycfn project has the following structure:
95
99
 
96
- included do
97
- parameter :domain_name,
98
- default: "example.com",
99
- description: "Domain name for the HostedZone"
100
-
101
- resource :hosted_zone,
102
- type: "AWS::Route53::HostedZone" do |r|
103
- r.property(:hosted_zone_config) do
104
- {
105
- Comment: [
106
- "Hosted Zone for ",
107
- "DomainName".ref
108
- ].fnjoin
109
- }
110
- end
111
- r.property(:name) { "DomainName".ref }
112
- end
100
+ ```
101
+ total 112
102
+ drwxr-xr-x 14 dennis staff 448 Jul 15 20:43 .
103
+ drwxr-xr-x 17 dennis staff 544 Jul 15 20:43 ..
104
+ -rw-r--r-- 1 dennis staff 92 Jul 15 20:43 .env
105
+ -rw-r--r-- 1 dennis staff 325 Jul 15 20:43 .env.production
106
+ -rw-r--r-- 1 dennis staff 207 Jul 15 20:43 .env.rspec
107
+ -rw-r--r-- 1 dennis staff 298 Jul 15 20:43 .env.test
108
+ drwxr-xr-x 10 dennis staff 320 Jul 15 20:43 .git
109
+ -rw-r--r-- 1 dennis staff 1344 Jul 15 20:43 .rubocop.yml
110
+ -rw-r--r-- 1 dennis staff 502 Jul 15 20:43 Gemfile
111
+ -rw-r--r-- 1 dennis staff 27158 Jul 15 20:43 Gemfile.lock
112
+ -rw-r--r-- 1 dennis staff 998 Jul 15 20:43 Rakefile
113
+ drwxr-xr-x 2 dennis staff 64 Jul 15 20:43 build
114
+ drwxr-xr-x 7 dennis staff 224 Jul 15 20:43 lib
115
+ drwxr-xr-x 4 dennis staff 128 Jul 15 20:43 spec
116
+ ```
113
117
 
114
- resource :hosted_zone_ses,
115
- amount: 1,
116
- type: "AWS::SES::ConfigurationSet" do |r|
117
- r.depends_on "HostedZone"
118
- r.property(:name) { ["HostedZone".ref, "_SESConfigurationSet"].fnjoin }
119
- end
118
+ Lets first discuss the files in the root folder.
120
119
 
121
- output :hosted_zone_id,
122
- value: "HostedZone".ref,
123
- description: "HostedZoneId",
124
- export: {
125
- "Name": ["AWS::StackName".ref, "HostedZoneId"].fnjoin(":")
126
- }
120
+ ```
121
+ .env Global environment variables, available in every environment
122
+ .env.production Environment variables available in production environment
123
+ .env.test Environment variables available in test environment
124
+ .env.rspec Environment variables available to unit tests
125
+ .rubocop.yml Ruby LINTer configuration to enforce good code style
126
+ Gemfile Ruby gem dependencies
127
+ Gemfile.lock Resolved gem dependencies
128
+ Rakefile Contains all Rubycfn rake tasks
129
+ ```
127
130
 
128
- end
129
- end
131
+ #### .env
130
132
 
131
- MyDemoStack = include DnsStack
132
- puts MyDemoStack.render_template
133
+ The `.env` file contains environment variables that are available, regardless of
134
+ the environment you're building for. For example:
133
135
 
134
136
  ```
135
-
136
- The above code renders into this CloudFormation template:
137
-
138
- ```json
139
- {
140
- "AWSTemplateFormatVersion": "2010-09-09",
141
- "Parameters": {
142
- "DomainName": {
143
- "Default": "example.com",
144
- "Description": "Domain name for the HostedZone",
145
- "Type": "String"
146
- }
147
- },
148
- "Resources": {
149
- "HostedZone": {
150
- "Properties": {
151
- "HostedZoneConfig": {
152
- "Comment": {
153
- "Fn::Join": [
154
- "",
155
- [
156
- "Hosted Zone for ",
157
- {
158
- "Ref": "DomainName"
159
- }
160
- ]
161
- ]
162
- }
163
- },
164
- "Name": {
165
- "Ref": "DomainName"
166
- }
167
- },
168
- "Type": "AWS::Route53::HostedZone"
169
- },
170
- "HostedZoneSes": {
171
- "DependsOn": [
172
- "HostedZone"
173
- ],
174
- "Properties": {
175
- "Name": {
176
- "Fn::Join": [
177
- "",
178
- [
179
- {
180
- "Ref": "HostedZone"
181
- },
182
- "_SESConfigurationSet"
183
- ]
184
- ]
185
- }
186
- },
187
- "Type": "AWS::SES::ConfigurationSet"
188
- }
189
- },
190
- "Outputs": {
191
- "HostedZoneId": {
192
- "Description": "HostedZoneId",
193
- "Export": {
194
- "Name": {
195
- "Fn::Join": [
196
- ":",
197
- [
198
- {
199
- "Ref": "AWS::StackName"
200
- },
201
- "HostedZoneId"
202
- ]
203
- ]
204
- }
205
- },
206
- "Value": {
207
- "Ref": "HostedZone"
208
- }
209
- }
210
- }
211
- }
137
+ AWS_ACCOUNT_ID="1234567890"
138
+ AWS_REGION="eu-west-1"
139
+ ENVIRONMENT="rspec"
140
+ PROJECT_NAME="sample"
212
141
  ```
213
142
 
214
- I've deliberately added amount: 1 to the hosted_zone_ses resources, which is not
215
- necessary as this defaults to 1 if you omit it. If you want multiple resources of
216
- the same type just increase the amount. The resource names will be enumerated,
217
- e.g. Resource, Resource2, Resource3, etc.
218
-
219
- With |r, index| you will be have to access the index number of the generated
220
- resource in the `index` variable.
143
+ #### .env.production and .env.test
221
144
 
222
- For example:
223
-
224
- ```ruby
225
- resource :my_s3_bucket,
226
- amount: 10,
227
- type: "AWS::S3::Bucket" do |r, index|
145
+ The `.env.production` and `.env.test` files contain environment variables that
146
+ are specific to production or test respectively. For example `.env.test` can
147
+ contain something like this:
228
148
 
229
- r.property(:name) { "MyAwesomeBucket#{index+1}" }
230
- end
149
+ ```
150
+ # ENV vars for test environment
151
+ CLOUD_TRAIL_MONITOR_SNS_RECIPIENTS="changeme@example.com,changemetoo@example.com"
152
+ ROOT_MONITOR_SNS_RECIPIENTS="changeme@example.com,changemetoo@example.com"
153
+ VPC_CIDR_BLOCK="10.100.0.0/16"
154
+ ARTIFACT_BUCKET="my-awesome-cloudformation-artifact-bucket"
155
+ STACK_NAME="test"
231
156
  ```
232
157
 
233
- ## Generating CloudFormation using the Rubycfn CLI
234
-
235
- It is not necessary to start a Rubycfn project to generate CloudFormation templates.
236
- The examples from the [Rubycfn CloudFormation Compiler](https://rubycfn.com/) can be used with the Rubycfn cli directly.
237
- Take this `template.rb` as an example:
238
-
239
- ```ruby
240
- parameter :bucket_name,
241
- description: "Bucket name"
158
+ While .env.production can look something like this:
242
159
 
243
- resource :foobar,
244
- type: "AWS::S3::Bucket" do |r|
245
- r.property(:name) { :bucket_name.ref }
246
- end
160
+ ```
161
+ # ENV vars for production environment
162
+ CLOUD_TRAIL_MONITOR_SNS_RECIPIENTS="changeme@example.com,changemetoo@example.com"
163
+ ROOT_MONITOR_SNS_RECIPIENTS="changeme@example.com,changemetoo@example.com"
164
+ VPC_CIDR_BLOCK="10.200.0.0/16"
165
+ ARTIFACT_BUCKET="my-awesome-cloudformation-artifact-bucket-for-production"
166
+ STACK_NAME="production"
247
167
  ```
248
168
 
249
- You can generate a CloudFormation template from this script in the following ways:
250
- `cat template.rb | rubycfn`
169
+ You can reuse these environment variables in your project code.
251
170
 
252
- or
171
+ #### .env.rspec
253
172
 
254
- `rubycfn template.rb`
173
+ The `.env.rspec` is used when running unit tests. It contains mock variables
174
+ so that you can test the resulting CloudFormation templates properly.
255
175
 
256
- Both commands will output the CloudFormation template without the need for you to set up a project.
176
+ #### The missing .env.private file
257
177
 
258
- ## Implemented AWS functions
178
+ There is one file that is not generated by default but does need mentioning:
179
+ the `.env.private` file. This is a special file that allows you to override
180
+ environment variables. An environment variable set in .env.private always takes
181
+ precedence over environment variables set in other .env* files.
259
182
 
260
- You can Ref by postpending the .ref method to any string. E.g. "string".ref
261
- If you supply an argument to .ref it'll be rendered as Fn::GetAtt. Last but
262
- not least, calling Fn::Join is achieved by postpending .fnjoin to an array.
263
- You can provide it with an argument for the separator. By default its "".
183
+ #### .rubocop.yml
264
184
 
265
- You can use the following methods in the same fashion:
185
+ The `.rubocop.yml` file contains configuration for the code linter. When running
186
+ `rubocop` from the root folder of your project it will error on code style
187
+ violations.
266
188
 
267
- fnsplit, fnbase64, fnjoin and fnselect
189
+ ### Rubycfn project directories
268
190
 
269
- ## Outputting to YAML
191
+ As shown before, a Rubycfn project contains three directories:
270
192
 
271
- `brew install cfnflip`
193
+ ```
194
+ drwxr-xr-x 2 dennis staff 64 Jul 15 20:43 build
195
+ drwxr-xr-x 7 dennis staff 224 Jul 15 20:43 lib
196
+ drwxr-xr-x 4 dennis staff 128 Jul 15 20:43 spec
197
+ ```
272
198
 
273
- or...
199
+ #### build
274
200
 
275
- Paste the CloudFormation output in [cfnflip.com](https://cfnflip.com/) to
276
- convert it to YAML format ;)
201
+ The build directory is where your resulting CloudFormation templates will be
202
+ stored.
277
203
 
278
- ## The anatomy of a Rubycfn project
204
+ #### lib
279
205
 
280
- When you start a new Rubycfn project it comes structured out of the box. We standardise this structure so that it’s uniform from project to project. A colleague Cloud Engineer needs to be able to take over or troubleshoot a project immediately without having to learn the inner working of the project first. This allows us to remain agile. In addition, by not having to rely on pre-existing knowledge about a project (and thus having knowledge of a project with just a few select people), we promote synergy.
206
+ The 'lib' directory contains all your stacks, modules and project libraries.
207
+ This directory is the most important, as this is the directory where you work
208
+ in. I will go into more detail on the `lib` directory in the next chapter.
281
209
 
282
- You start a new project by typing `rubycfn` at the prompt. This will ask you a couple of questions about the project - such as the project’s name. The entire project is then generated for you. It then looks like this:
210
+ #### spec
283
211
 
284
- -rw-r--r-- 1 binx staff 166 Oct 17 16:06 .env
285
- -rw-r--r-- 1 binx staff 81 Oct 17 16:06 .env.test
286
- -rw-r--r-- 1 binx staff 246 Oct 17 16:06 Gemfile
287
- -rw-r--r-- 1 binx staff 346 Oct 17 16:06 Rakefile
288
- drwxr-xr-x 2 binx staff 64 Oct 17 16:06 build
289
- drwxr-xr-x 3 binx staff 96 Oct 17 16:06 config
290
- -rw-r--r-- 1 binx staff 15 Oct 17 16:06 format.vim
291
- drwxr-xr-x 6 binx staff 192 Oct 17 16:06 lib
292
- drwxr-xr-x 4 binx staff 128 Oct 17 16:06 spec
212
+ The `spec` directory contains all unit tests. They are executed with the
213
+ `rake spec` command.
293
214
 
294
- First the flat files:
215
+ ## The lib directory
295
216
 
296
- `.env` and `.env.test` are files where you store environment variables that you may want to use in your project code. The difference between the .env and the .env.test file is that the .env file is the “global” environment variable file, whereas the .env.test file is an environment-specific environment variable file, that can override values you’ve specified in your .env file (or add new environment variables, for that matter). You’d typically have a .env.test, .env.acceptance and a .env.production file for things like instance sizing.
217
+ As mentioned the `lib` directory is the most important directory. When you
218
+ create a Rubycfn project it will contain the following by default:
297
219
 
298
- Example:
299
- ```$ cat .env.test
300
- # ENV vars for test environment
301
- APPLICATION_INSTANCE_CLASS="t2.micro"
220
+ ```
221
+ total 8
222
+ drwxr-xr-x 7 dennis staff 224 Jul 15 20:43 .
223
+ drwxr-xr-x 16 dennis staff 512 Jul 15 21:08 ..
224
+ drwxr-xr-x 9 dennis staff 288 Jul 15 20:43 aws_helper
225
+ drwxr-xr-x 6 dennis staff 192 Jul 15 20:43 core
226
+ -rw-r--r-- 1 dennis staff 734 Jul 15 20:43 main.rb
227
+ drwxr-xr-x 5 dennis staff 160 Jul 15 20:43 shared_concerns
228
+ drwxr-xr-x 8 dennis staff 256 Jul 15 20:43 stacks
302
229
  ```
303
230
 
304
- To make use of - for example - .env.production as source, you can either override the ENVIRONMENT variable in the .env file, setting it to production, or you can invoke rake as: `ENVIRONMENT="production" rake`.
305
-
306
- The `Gemfile` is a collection of Ruby dependencies. By running `bundle` you install the dependencies.
231
+ The `aws_helper` and `core` directories and the `main.rb` file contains helper
232
+ function and a lot of glue to make Rubycfn code compile and deploy. You should
233
+ never need to touch those files. In this section I'll focus on the
234
+ `shared_concerns` directory and the `stacks` directory.
307
235
 
308
- The `Rakefile` contain the tasks that are performed when you type the `rake` command. It consists of a `compile` task and a `spec` task. You can invoke the tasks individually by typing `rake compile` or `rake spec`, but by default the `spec` task is ran first, and then the `compile` task. A “spec” is another word for unit test. It’s important to run the unit test first, so that if a test fails no template is generated. If you just run `rake` both tasks are executed sequentially, provided the specs throw no error.
236
+ To understand the purpose of the `shared_concerns` directory it's important to
237
+ understand that a stack consists of a parent stack file and modules. Lets say
238
+ you have a VPC stack: It will consist of a `vpc_stack.rb` file that includes
239
+ modules from the `vpc_stack/` directory. This modular approach keeps your
240
+ projects nice and tidy. By default, the shared_concerns directory contains a
241
+ global variables module, a shared methods module and a helper methods module.
309
242
 
310
- Onto the subdirectories:
243
+ The `shared_concerns/` directory also contain modules. The difference is that
244
+ these modules can be used in more than one stack. If you have resources or code
245
+ that you want to reuse cross stacks, create a shared concern.
311
246
 
312
- The `build` directory is where all the compiled templates end up.
247
+ The `stacks` folder, by default, contains the following:
313
248
 
314
- Example:
315
249
  ```
316
- $ ls -al build/
317
250
  total 24
318
- drwxr-xr-x 4 binx staff 128 Oct 17 16:27 .
319
- drwxr-xr-x 16 binx staff 512 Oct 17 16:27 ..
320
- -rw-r--r-- 1 binx staff 4152 Oct 17 16:27 test-aws-demostack.json
321
- ```
322
-
323
- The `spec` directory is where your unit tests live. These are not your application unit tests. A Rubycfn project lives in another universe than your application code. These unit tests test your expectations of the generated templates against reality. Such tests typically check for the existence and absence of particular resources, and values of properties. The tests are always executed when you run the `rake` command or the `rake spec` command. By default a project comes with unit tests that amongst other things check for the generation of the CI/CD pipeline and if it’s been configured correctly.
324
-
325
- Example:
251
+ drwxr-xr-x 8 dennis staff 256 Jul 15 20:43 .
252
+ drwxr-xr-x 7 dennis staff 224 Jul 15 20:43 ..
253
+ drwxr-xr-x 3 dennis staff 96 Jul 15 20:43 ecs_stack
254
+ -rw-r--r-- 1 dennis staff 254 Jul 15 20:43 ecs_stack.rb
255
+ drwxr-xr-x 3 dennis staff 96 Jul 15 20:43 parent_stack
256
+ -rw-r--r-- 1 dennis staff 259 Jul 15 20:43 parent_stack.rb
257
+ drwxr-xr-x 4 dennis staff 128 Jul 15 20:43 vpc_stack
258
+ -rw-r--r-- 1 dennis staff 248 Jul 15 20:43 vpc_stack.rb
326
259
  ```
327
- context "Codebuild Service Role" do
328
- let(:code_build_service_role) { resources["CodeBuildDemoServiceRole"] }
329
- subject { code_build_service_role }
330
260
 
331
- it { should have_key "Properties" }
261
+ The default project creates three CloudFormation templates: a VPC stack, an
262
+ ECS stack and a parent stack. The parent stack is a CloudFormation stack that
263
+ contains all other stacks. When you deploy a Rubycfn project these other stacks
264
+ show up as `nested stacks`. The parent stack acts not only as a container for
265
+ all other stacks, but is also responsible for passing outputs from stacks as
266
+ parameters to another. For example: The VPC Id that is created in the VPC stack
267
+ can easily be passed to the ECS stack as a parameter. This nested stack approach
268
+ has an additional benefit: A change of output in stack X can trigger an update
269
+ in stack Y.
332
270
 
333
- context "Code build service role properties" do
334
- let(:code_build_service_role_properties) { code_build_service_role["Properties"] }
335
- subject { code_build_service_role_properties }
271
+ The .rb files you see in the lib/stacks/ directory are the parent stack files.
272
+ Lets have a look at `vpc_stack.rb`:
336
273
 
337
- it { should have_key "AssumeRolePolicyDocument" }
338
- it { should have_key "Path" }
339
- it { should have_key "Policies" }
340
-
341
- context "Code build service role policy document" do
342
- let(:policy_document) { code_build_service_role_properties["Policies"][0]["PolicyDocument"] }
343
- subject { policy_document }
344
-
345
- it { should have_key "Statement" }
274
+ ```ruby
275
+ module VpcStack
276
+ extend ActiveSupport::Concern
277
+ include Rubycfn
346
278
 
347
- context "Code build service role actions" do
348
- let(:statement) { policy_document["Statement"][0]["Action"] }
349
- subject { statement }
279
+ included do
280
+ include Concerns::GlobalVariables
281
+ include Concerns::SharedMethods
282
+ include VpcStack::Main
350
283
 
351
- it { should eq %w(logs:CreateLogGroup logs:CreateLogStream logs:PutLogEvents) }
352
- end
353
- end
354
- end
355
- end
284
+ description generate_stack_description("VpcStack")
285
+ end
286
+ end
356
287
  ```
357
288
 
358
- The `config` directory contains your buildspec.yml, which contain all the instructions for the build pipeline. It’s also possible to source the buildspec.yml from another source, such as the application repository.
289
+ On the first line we define the module name. It is important that the module
290
+ name ends with 'Stack' to make the compiler magic work. The code between
291
+ `include do` and `end` loads in two of the shared concerns, and includes the
292
+ VpcStack::Main module. Finally the description of the stack is set.
359
293
 
360
- And finally, the `lib` directory contains all the relevant project code. The `lib` directory consists of several files and directories. The `lib/main.rb` is the bootstrapper that ties everything together. The `lib/compile.rb` is responsible for compiling the templates and writing them to the build directory.
294
+ The `lib/stacks/vpc_stack/` directory contains a `vpc.rb` file, which is the
295
+ implementation of the VpcStack::Main module:
361
296
 
362
- There are two directories under `lib`, namely `shared_concerns` and `stacks`. Concerns in this context simply mean Modules. Rubycfn relies on the ActiveConcern gem which makes modularisation of code very easy. The `shared_concerns` directory contains modules that can be used by several stacks. By default it has a `global_variables` module containing the following:
297
+ ```ruby
298
+ require_relative "subnets"
363
299
 
364
- ```
365
- module Concerns
366
- module GlobalVariables
300
+ module VpcStack
301
+ module Main
367
302
  extend ActiveSupport::Concern
368
303
 
369
304
  included do
370
- variable :environment,
371
- default: "test",
372
- global: true,
373
- value: ENV["ENVIRONMENT"]
305
+ # A lot of VPC code here
374
306
  end
375
307
  end
376
308
  end
377
309
  ```
378
- This module exposes a variable `environment`, which defaults to `test` if not set. It sources the value from the ENVIRONMENT environment variable. This variable can be used throughout your project at any place you see fit.
379
310
 
380
- The `stacks` directory is a container for all stacks that you want to generate. There is no limitation to the amount of stacks that it supports. By default, it comes with a single stack for your project:
311
+ The first line is identical to the parent stack file and defines this module is
312
+ part of `VpcStack`. The second line defines the name of the module, in this case
313
+ `Main`. The code beteen `included do` and `end` is the implementation of this
314
+ module.
381
315
 
382
- Example `lib/stacks/demo_stack.rb`:
383
- ```
384
- module DemoStack
385
- extend ActiveSupport::Concern
386
- include Rubycfn
316
+ ## AWS Intrinsic functions
387
317
 
388
- included do
389
- include DemoStack::Main
390
- include DemoStack::CICD
391
- end
392
- end
393
- ```
394
- Our stack file consists of two modules: Main and CICD. When compiling the code, the combined result of the Main and CICD module will be written to the DemoStack json file in the build/ directory. Modularising stacks allows for separation of code by cohesion or any other logic you deem appropriate.
318
+ You can Ref by postpending the .ref method to any string or hash, e.g. :foobar.ref
319
+ If you supply an argument to .ref it'll be rendered as Fn::GetAtt. Last but
320
+ not least, calling Fn::Join is achieved by postpending .fnjoin to an array.
321
+ You can provide it with an argument for the separator. By default its "".
395
322
 
396
- The stacks directory also contains a directory that is named the same, minus the .rb extension: `lib/stacks/demo_stack/`
323
+ You can use the following methods in the same fashion:
397
324
 
398
- All the stack modules live inside this directory. The modules that make up the stack are the actual implementation of the resources, parameters and outputs.
325
+ fnsplit, fnbase64, fnjoin and fnselect
399
326
 
400
- An example of such a module:
401
- ```
402
- module DemoStack
403
- module Main
404
- extend ActiveSupport::Concern
405
- included do
327
+ ## Outputting to YAML
406
328
 
407
- resource :api_gateway_rest_api,
408
- type: "AWS::ApiGateway::RestApi" do |r|
409
- r.property(:name) { "#{environment}-webhook" }
410
- end
411
- end
412
- end
413
- end
414
- ```
329
+ `brew install cfnflip`
330
+
331
+ or...
332
+
333
+ Paste the CloudFormation output in [cfnflip.com](https://cfnflip.com/) to
334
+ convert it to YAML format ;)
415
335
 
416
336
  ## Serverless Transforms
417
337
 
data/bin/rubycfn CHANGED
@@ -71,6 +71,7 @@ dotenv_rspec = render('.env.rspec', { name: project_name }, path)
71
71
  ecs_stack = render('ecs_stack.rb', {}, path)
72
72
  ecs_stack_concern = render('ecs_Stack_concern.rb', { name: project_name }, path)
73
73
  gemfile = render('Gemfile', { version: Rubycfn::VERSION }, path)
74
+ gitignore = render('.gitignore', {}, path)
74
75
  global_variables = render('global_variables.rb', { name: project_name.downcase }, path)
75
76
  helpers = render('helpers.rb', {}, path)
76
77
  helper_methods = render('helper_methods.rb', {}, path)
@@ -109,6 +110,7 @@ File.open("#{project_path}/.env.rspec", 'w') { |file| file.write(dotenv_rspec) }
109
110
  File.open("#{project_path}/lib/stacks/ecs_stack.rb", 'w') { |file| file.write(ecs_stack) }
110
111
  File.open("#{project_path}/lib/stacks/ecs_stack/ecs_cluster.rb", 'w') { |file| file.write(ecs_stack_concern) }
111
112
  File.open("#{project_path}/Gemfile", 'w') { |file| file.write(gemfile) }
113
+ File.open("#{project_path}/.gitignore", 'w') { |file| file.write(gitignore) }
112
114
  File.open("#{project_path}/lib/shared_concerns/global_variables.rb", 'w') { |file| file.write(global_variables) }
113
115
  File.open("#{project_path}/lib/aws_helper/helpers.rb", 'w') { |file| file.write(helpers) }
114
116
  File.open("#{project_path}/lib/shared_concerns/helper_methods.rb", 'w') { |file| file.write(helper_methods) }
@@ -1,4 +1,4 @@
1
1
  # Rubycfn version
2
2
  module Rubycfn
3
- VERSION = "0.3.6"
3
+ VERSION = "0.3.7"
4
4
  end
data/lib/rubycfn.rb CHANGED
@@ -26,6 +26,7 @@ class Symbol
26
26
  unless attr
27
27
  return { Ref: self.to_s.split("_").map{ |e| e.capitalize }.join }
28
28
  end
29
+ attr = attr.class == String ? attr : attr.to_s.split('_').map{|e| e.capitalize}.join
29
30
  return { "Fn::GetAtt": [ self.to_s.split("_").map{ |e| e.capitalize }.join, attr ] }
30
31
  end
31
32
  end
@@ -51,6 +52,7 @@ class String
51
52
  unless attr
52
53
  return { Ref: self }
53
54
  end
55
+ attr = attr.class == String ? attr : attr.to_s.split('_').map{|e| e.capitalize}.join
54
56
  return { "Fn::GetAtt": [ self, attr ] }
55
57
  end
56
58
 
@@ -80,3 +80,5 @@ fabric.properties
80
80
  test-reports
81
81
  temp/
82
82
  attic/
83
+ /venv
84
+ /Gemfile.lock
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubycfn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dennis Vink
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-14 00:00:00.000000000 Z
11
+ date: 2019-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: neatjson