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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c43cabdbb5f495f514c1ebc4a349558b5c9d8333
4
- data.tar.gz: d5440442930fa154a50f53499b29f33c7caeac69
3
+ metadata.gz: b0849cb139ac3748d7b632536c72f4cfdd24cbdb
4
+ data.tar.gz: a2dfc5d8d89ea55f7ca693ebd264f71f4b37efe7
5
5
  SHA512:
6
- metadata.gz: 47448e9fa021ecb199521f3550d1c4f5b505dc6c34700dea376518f5972416601852f73f716372010e90d19fa3e7afe71b222eadcdf14a3e63099bc12f39e786
7
- data.tar.gz: d27ca79b9e457f496e5435e659845df1870a9fb140806a4f777d991d88fae306d685b7c47a70acb92989d125b229df88abd1b6692fc8833e5fb288f760c2c838
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.1 (Next Release)
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubycfn (0.2.0)
4
+ rubycfn (0.2.1)
5
5
  activesupport (~> 5.1.5)
6
6
  dotenv (~> 2.4.0)
7
7
  json (~> 2.1.0)
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
- CloudFormation as Ruby code a bit more pleasing to the eye.
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
- ## Installation
6
+ ## Quick start
7
7
 
8
- Type: `gem install rubycfn`
8
+ Install Rubycfn:
9
+ `gem install rubycfn`
9
10
 
10
- or, create a Gemfile with this content:
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
- source "https://rubygems.org"
24
+ Installing project dependencies:
25
+ `bundle`
14
26
 
15
- gem "rubycfn", "~> 0.2.0"
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
- ## Starting a new project
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
- Typing `rubycfn` at the prompt and answering its questions generates a new sample project.
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 }
@@ -1,4 +1,4 @@
1
1
  # Rubycfn version
2
2
  module Rubycfn
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
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.0
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-17 00:00:00.000000000 Z
11
+ date: 2018-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: neatjson