rubycfn 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/Gemfile.lock +1 -1
- data/README.md +253 -11
- data/lib/compound/vpc.rb +5 -5
- data/lib/rubycfn/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0849cb139ac3748d7b632536c72f4cfdd24cbdb
|
4
|
+
data.tar.gz: a2dfc5d8d89ea55f7ca693ebd264f71f4b37efe7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d84760f33c8903f332e4b78c969768bf550a7b25ae50c4df22f0cdf0b9572ba7439c1cfc6ffa8b19bc93b9e4f647c841d2b5d783b46c41c772d263186b78f908
|
7
|
+
data.tar.gz: 281ed98ef84978bc640d4ab7ffd475fa2c9d30777e81007aca22ee3dded7ffe53e36804abb744fd68f9d7492a5cff7b20b1f1f0317408a9c9bec2b8d445b7c43
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,11 @@
|
|
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.2.
|
5
|
+
## 0.2.2 (Next Release)
|
6
|
+
|
7
|
+
## 0.2.1
|
8
|
+
* Fixed bug in VPC compound resource. Resource names are now camel cased -- [@dennisvink][@dennisvink]
|
9
|
+
* Updated README.md -- [@dennisvink][@dennisvink]
|
6
10
|
|
7
11
|
## 0.2.0
|
8
12
|
* Added support for GCP templates -- [@dennisvink][@dennisvink]
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,24 +1,62 @@
|
|
1
1
|
# RubyCfn
|
2
2
|
|
3
|
-
RubyCfn is a light-weight tiny CloudFormation DSL to make expressing
|
4
|
-
|
3
|
+
[RubyCfn](https://rubycfn.com/) is a light-weight tiny CloudFormation, Deployment Manager and ARM DSL to make expressing
|
4
|
+
cloud vendor templates as Ruby code a bit more pleasing to the eye.
|
5
5
|
|
6
|
-
##
|
6
|
+
## Quick start
|
7
7
|
|
8
|
-
|
8
|
+
Install Rubycfn:
|
9
|
+
`gem install rubycfn`
|
9
10
|
|
10
|
-
|
11
|
+
Starting a new Rubycfn project:
|
12
|
+
`rubycfn`
|
13
|
+
$ rubycfn
|
14
|
+
__________ ____ __________________.___._________ _____________________
|
15
|
+
\______ \ | \______ \__ | |\_ ___ \\_ _____/\______ \
|
16
|
+
| _/ | /| | _// | |/ \ \/ | __) | | _/
|
17
|
+
| | \ | / | | \\____ |\ \____| \ | | \
|
18
|
+
|____|_ /______/ |______ // ______| \______ /\___ / |______ /
|
19
|
+
\/ \/ \/ \/ \/ \/ [v0.2.1]
|
20
|
+
Project name? example
|
21
|
+
Account ID? 1234567890
|
22
|
+
Select region EU (Frankfurt)
|
11
23
|
|
12
|
-
|
13
|
-
|
24
|
+
Installing project dependencies:
|
25
|
+
`bundle`
|
14
26
|
|
15
|
-
|
27
|
+
Updating project dependencies:
|
28
|
+
`bundle update`
|
16
29
|
|
17
|
-
|
30
|
+
Compiling Rubycfn project:
|
31
|
+
`rake compile`
|
32
|
+
|
33
|
+
Running Rubycfn unit tests:
|
34
|
+
`rake spec`
|
35
|
+
|
36
|
+
Running tests and compiling:
|
37
|
+
`rake`
|
38
|
+
|
39
|
+
Converting CloudFormation JSON template to Rubycfn:
|
40
|
+
`./cfn2rubycfn /path/to/cloudformation_template.json`
|
41
|
+
|
42
|
+
## Philosophy
|
18
43
|
|
19
|
-
|
44
|
+
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.
|
20
45
|
|
21
|
-
|
46
|
+
## Overview of Rubycfn
|
47
|
+
|
48
|
+
RubyCfn is an abstraction layer around several Cloud templates such as CloudFormation (AWS), Deployment Manager (GCP) and ARM (Azure). 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 the respective cloud providers offer 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.
|
49
|
+
|
50
|
+
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.
|
51
|
+
|
52
|
+
Typically a commit to your application GIT repository triggers the build process and the following things happen:
|
53
|
+
- Application code is checked out
|
54
|
+
- Infrastructure as code (Rubycfn project) is checked out
|
55
|
+
- Rubycfn project is ‘built’, kicking off unit tests against the underlying code, and against the resulting build artifact (template(s))
|
56
|
+
- Application (unit) tests are ran
|
57
|
+
- The build artifact is stored (versioned), so you can use it as input for your delivery pipeline
|
58
|
+
- 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.
|
59
|
+
- The resulting artifact is the complete recipe to deploy the application and associated resources to AWS, GCP or Azure.
|
22
60
|
|
23
61
|
## Example code
|
24
62
|
|
@@ -199,6 +237,210 @@ or...
|
|
199
237
|
Paste the CloudFormation output in [cfnflip.com](https://cfnflip.com/) to
|
200
238
|
convert it to YAML format ;)
|
201
239
|
|
240
|
+
## The anatomy of a Rubycfn project
|
241
|
+
|
242
|
+
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.
|
243
|
+
|
244
|
+
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:
|
245
|
+
|
246
|
+
-rw-r--r-- 1 binx staff 166 Oct 17 16:06 .env
|
247
|
+
-rw-r--r-- 1 binx staff 81 Oct 17 16:06 .env.test
|
248
|
+
-rw-r--r-- 1 binx staff 246 Oct 17 16:06 Gemfile
|
249
|
+
-rw-r--r-- 1 binx staff 346 Oct 17 16:06 Rakefile
|
250
|
+
drwxr-xr-x 2 binx staff 64 Oct 17 16:06 build
|
251
|
+
-rwxrwxrwx 1 binx staff 3223 Oct 17 16:06 cfn2rubycfn
|
252
|
+
drwxr-xr-x 3 binx staff 96 Oct 17 16:06 config
|
253
|
+
-rw-r--r-- 1 binx staff 15 Oct 17 16:06 format.vim
|
254
|
+
drwxr-xr-x 6 binx staff 192 Oct 17 16:06 lib
|
255
|
+
drwxr-xr-x 4 binx staff 128 Oct 17 16:06 spec
|
256
|
+
|
257
|
+
First the flat files:
|
258
|
+
|
259
|
+
`.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.
|
260
|
+
|
261
|
+
Example:
|
262
|
+
$ cat .env.test
|
263
|
+
# ENV vars for test environment
|
264
|
+
APPLICATION_INSTANCE_CLASS="t2.micro"
|
265
|
+
|
266
|
+
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`.
|
267
|
+
|
268
|
+
The `Gemfile` is a collection of Ruby dependencies. By running `bundle` you install the dependencies.
|
269
|
+
|
270
|
+
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.
|
271
|
+
|
272
|
+
`cfn2rubycfn` is a small helper script that converts AWS CloudFormation scripts to Rubycfn code. This allows you to migrate your existing projects over to Rubycfn quickly. It exports the converted CloudFormation script to `generated.rb`, a ready to use module for your stacks.
|
273
|
+
|
274
|
+
Example:
|
275
|
+
$ cat sample.json
|
276
|
+
{
|
277
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
278
|
+
"Resources": {
|
279
|
+
"ApiGatewayRestApi": {
|
280
|
+
"Properties": {
|
281
|
+
"Name": "trigger-github-webhook"
|
282
|
+
},
|
283
|
+
"Type": "AWS::ApiGateway::RestApi"
|
284
|
+
}
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
288
|
+
$ ./cfn2rubycfn sample.json
|
289
|
+
Reformatting code...
|
290
|
+
|
291
|
+
$ cat generated.rb
|
292
|
+
module ConvertedStack
|
293
|
+
module Main
|
294
|
+
extend ActiveSupport::Concern
|
295
|
+
included do
|
296
|
+
resource :api_gateway_rest_api,
|
297
|
+
type: "AWS::ApiGateway::RestApi" do |r|
|
298
|
+
r.property(:name) { "trigger-github-webhook" }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
The `format.vim` file is used by the CloudFormation conversion script to reindent the file after conversion. You can make changes to the file to reflect your preferred style of code indentation.
|
305
|
+
|
306
|
+
Onto the subdirectories:
|
307
|
+
|
308
|
+
The `build` directory is where all the compiled templates end up.
|
309
|
+
|
310
|
+
Example:
|
311
|
+
$ ls -al build/
|
312
|
+
total 24
|
313
|
+
drwxr-xr-x 4 binx staff 128 Oct 17 16:27 .
|
314
|
+
drwxr-xr-x 16 binx staff 512 Oct 17 16:27 ..
|
315
|
+
-rw-r--r-- 1 binx staff 3197 Oct 17 16:27 test-gcp-demostack.json
|
316
|
+
-rw-r--r-- 1 binx staff 4152 Oct 17 16:27 test-aws-demostack.json
|
317
|
+
|
318
|
+
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.
|
319
|
+
|
320
|
+
Example:
|
321
|
+
context "Codebuild Service Role" do
|
322
|
+
let(:code_build_service_role) { resources["CodeBuildDemoServiceRole"] }
|
323
|
+
subject { code_build_service_role }
|
324
|
+
|
325
|
+
it { should have_key "Properties" }
|
326
|
+
|
327
|
+
context "Code build service role properties" do
|
328
|
+
let(:code_build_service_role_properties) { code_build_service_role["Properties"] }
|
329
|
+
subject { code_build_service_role_properties }
|
330
|
+
|
331
|
+
it { should have_key "AssumeRolePolicyDocument" }
|
332
|
+
it { should have_key "Path" }
|
333
|
+
it { should have_key "Policies" }
|
334
|
+
|
335
|
+
context "Code build service role policy document" do
|
336
|
+
let(:policy_document) { code_build_service_role_properties["Policies"][0]["PolicyDocument"] }
|
337
|
+
subject { policy_document }
|
338
|
+
|
339
|
+
it { should have_key "Statement" }
|
340
|
+
|
341
|
+
context "Code build service role actions" do
|
342
|
+
let(:statement) { policy_document["Statement"][0]["Action"] }
|
343
|
+
subject { statement }
|
344
|
+
|
345
|
+
it { should eq %w(logs:CreateLogGroup logs:CreateLogStream logs:PutLogEvents) }
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
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.
|
352
|
+
|
353
|
+
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.
|
354
|
+
|
355
|
+
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:
|
356
|
+
|
357
|
+
module Concerns
|
358
|
+
module GlobalVariables
|
359
|
+
extend ActiveSupport::Concern
|
360
|
+
|
361
|
+
included do
|
362
|
+
variable :environment,
|
363
|
+
default: "test",
|
364
|
+
global: true,
|
365
|
+
value: ENV["ENVIRONMENT"]
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
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.
|
371
|
+
|
372
|
+
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:
|
373
|
+
|
374
|
+
Example `lib/stacks/demo_stack.rb`:
|
375
|
+
module DemoStack
|
376
|
+
extend ActiveSupport::Concern
|
377
|
+
include Rubycfn
|
378
|
+
|
379
|
+
included do
|
380
|
+
include DemoStack::Main
|
381
|
+
include DemoStack::CICD
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
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.
|
386
|
+
|
387
|
+
The stacks directory also contains a directory that is named the same, minus the .rb extension: `lib/stacks/demo_stack/`
|
388
|
+
|
389
|
+
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.
|
390
|
+
|
391
|
+
An example of such a module:
|
392
|
+
module DemoStack
|
393
|
+
module Main
|
394
|
+
extend ActiveSupport::Concern
|
395
|
+
included do
|
396
|
+
import(
|
397
|
+
path: "cloudsql.jinja"
|
398
|
+
)
|
399
|
+
|
400
|
+
resource :api_gateway_rest_api,
|
401
|
+
type: "AWS::ApiGateway::RestApi" do |r|
|
402
|
+
r.property(:name) { "#{environment}-webhook" }
|
403
|
+
end
|
404
|
+
|
405
|
+
resource "cloudsql",
|
406
|
+
type: "GCP::cloudsql.jinja" do |r|
|
407
|
+
r.property("database") do
|
408
|
+
{
|
409
|
+
"name": "#{environment}"
|
410
|
+
}
|
411
|
+
end
|
412
|
+
r.property("dbUser") do
|
413
|
+
{
|
414
|
+
"password": "test123_"
|
415
|
+
}
|
416
|
+
end
|
417
|
+
r.property("failover") { true }
|
418
|
+
r.property("readReplicas") { 1 }
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
When compiling the project, Rubycfn will recognise the resources for the different Cloud providers and write them to separate template files. When building a cloud agnostic solution, you implement the resources for the respective vendors. To decide which resources are deployed to which cloud provider, and to be able to switch them quickly, you can wrap the resources with `if` statements, but a better solution is to utilise the `amount` property for resources:
|
425
|
+
|
426
|
+
variable :cloudsql_vendor,
|
427
|
+
default: "GCP",
|
428
|
+
value: ENV["CLOUDSQL_VENDOR"]
|
429
|
+
|
430
|
+
resource "cloudsql",
|
431
|
+
amount: cloudsql_vendor == "GCP" ? 1 : 0,
|
432
|
+
type: "GCP::cloudsql.jinja" do |r|
|
433
|
+
...
|
434
|
+
end
|
435
|
+
|
436
|
+
resource "cloudsql",
|
437
|
+
amount: cloudsql_vendor == "ARM" ? 1 : 0,
|
438
|
+
type: "ARM::MSSQL" do |r|
|
439
|
+
...
|
440
|
+
end
|
441
|
+
|
442
|
+
By implementing resources in the above way you can switch particular resources to another cloud vendor by simply updating the environment variable in your CI/CD pipeline, while still being able to use the same variables and the same ecosystem.
|
443
|
+
|
202
444
|
## License
|
203
445
|
|
204
446
|
MIT License
|
data/lib/compound/vpc.rb
CHANGED
@@ -32,7 +32,7 @@ module RubyCfn
|
|
32
32
|
|
33
33
|
yield self if block_given? # Variable overrides
|
34
34
|
|
35
|
-
resource "#{prefix}_vpc#{suffix}",
|
35
|
+
resource "#{prefix}_vpc#{suffix}".cfnize,
|
36
36
|
type: "AWS::EC2::VPC" do |r, index|
|
37
37
|
r.property(:cidr_block) { cidr_block }
|
38
38
|
r.property(:enable_dns_support) { enable_dns_support }
|
@@ -40,22 +40,22 @@ module RubyCfn
|
|
40
40
|
r.property(:instance_tenancy) { instance_tenancy } unless instance_tenancy.empty?
|
41
41
|
end
|
42
42
|
|
43
|
-
resource "#{prefix}_internet_gateway#{suffix}",
|
43
|
+
resource "#{prefix}_internet_gateway#{suffix}".cfnize,
|
44
44
|
type: "AWS::EC2::InternetGateway"
|
45
45
|
|
46
|
-
resource "#{prefix}_route#{suffix}",
|
46
|
+
resource "#{prefix}_route#{suffix}".cfnize,
|
47
47
|
type: "AWS::EC2::Route" do |r, index|
|
48
48
|
r.property(:destination_cidr_block) { "0.0.0.0/0" }
|
49
49
|
r.property(:gateway_id) { "#{prefix}_internet_gateway#{suffix}".cfnize.ref }
|
50
50
|
r.property(:route_table_id) { "#{prefix}_route_table#{suffix}".cfnize.ref }
|
51
51
|
end
|
52
52
|
|
53
|
-
resource "#{prefix}_route_table#{suffix}",
|
53
|
+
resource "#{prefix}_route_table#{suffix}.cfnize",
|
54
54
|
type: "AWS::EC2::RouteTable" do |r, index|
|
55
55
|
r.property(:vpc_id) { "#{prefix}_vpc#{suffix}".cfnize.ref }
|
56
56
|
end
|
57
57
|
|
58
|
-
resource "#{prefix}_vpc_gateway_attachment#{suffix}",
|
58
|
+
resource "#{prefix}_vpc_gateway_attachment#{suffix}".cfnize,
|
59
59
|
type: "AWS::EC2::VPCGatewayAttachment" do |r, index|
|
60
60
|
r.property(:internet_gateway_id) { "#{prefix}_internet_gateway#{suffix}".cfnize.ref }
|
61
61
|
r.property(:vpc_id) { "#{prefix}_vpc#{suffix}".cfnize.ref }
|
data/lib/rubycfn/version.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Vink
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: neatjson
|