cloudshaper 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.lock +27 -0
  6. data/LICENSE +23 -0
  7. data/README.md +162 -0
  8. data/Rakefile +9 -0
  9. data/TODO.md +7 -0
  10. data/examples/secretconfig/README.md +3 -0
  11. data/examples/secretconfig/atlas.json +5 -0
  12. data/examples/secretconfig/aws.json +6 -0
  13. data/examples/secretconfig/cloudflare.json +6 -0
  14. data/examples/secretconfig/cloudstack.json +7 -0
  15. data/examples/secretconfig/digitalocean.json +5 -0
  16. data/examples/secretconfig/dme.json +6 -0
  17. data/examples/secretconfig/heroku.json +6 -0
  18. data/examples/secretconfig/mailgun.json +5 -0
  19. data/examples/simple_app.rb +61 -0
  20. data/lib/tasks/tasks.rb +12 -0
  21. data/lib/tasks/terraform.rake +127 -0
  22. data/lib/terraform_dsl.rb +6 -0
  23. data/lib/terraform_dsl/aws/remote_s3.rb +20 -0
  24. data/lib/terraform_dsl/aws/tagging.rb +23 -0
  25. data/lib/terraform_dsl/command.rb +58 -0
  26. data/lib/terraform_dsl/module.rb +38 -0
  27. data/lib/terraform_dsl/output.rb +6 -0
  28. data/lib/terraform_dsl/provider.rb +13 -0
  29. data/lib/terraform_dsl/remote.rb +31 -0
  30. data/lib/terraform_dsl/resource.rb +44 -0
  31. data/lib/terraform_dsl/secrets.rb +29 -0
  32. data/lib/terraform_dsl/stack.rb +79 -0
  33. data/lib/terraform_dsl/stack_element.rb +62 -0
  34. data/lib/terraform_dsl/stack_module.rb +121 -0
  35. data/lib/terraform_dsl/stack_modules.rb +24 -0
  36. data/lib/terraform_dsl/stacks.rb +64 -0
  37. data/lib/terraform_dsl/variable.rb +6 -0
  38. data/lib/terraform_dsl/version.rb +3 -0
  39. data/terraform_dsl.gemspec +25 -0
  40. data/test/stack_module_test.rb +152 -0
  41. data/test/stack_test.rb +5 -0
  42. data/test/test_helper.rb +4 -0
  43. metadata +131 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 720390f8ef2c81bf799239154777e8b0177a7d4b
4
+ data.tar.gz: 952a846501c2d3d5e32b34800cd1d70fd9b7e81d
5
+ SHA512:
6
+ metadata.gz: 610e4fc75444a2e2721aaccf6a0cfc76e8c56a979848a65d500f0d87934661d91f81cc06e2d91744450d742a357a4067d1f2352eb4efae53e8e462d40d76e5a1
7
+ data.tar.gz: 8def0b2aef1901b0e1b84fba5c3d061aa9bdab0062dca156200efe18f402a2d4bfe3f3c448e8a8b297b44bffaff74170874dfc78f42ebedf1855e148bc8c2608
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.swp
2
+ *.swo
3
+ coverage
4
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'simplecov', '=0.10.0'
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cloudshaper (0.0.4)
5
+ rake (~> 10.4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ docile (1.1.5)
11
+ json (1.8.2)
12
+ minitest (5.6.1)
13
+ rake (10.4.2)
14
+ simplecov (0.10.0)
15
+ docile (~> 1.1.0)
16
+ json (~> 1.8)
17
+ simplecov-html (~> 0.10.0)
18
+ simplecov-html (0.10.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bundler
25
+ cloudshaper!
26
+ minitest (~> 5.6)
27
+ simplecov (= 0.10.0)
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) [year] [fullname]
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ [![Build Status](https://travis-ci.org/dalehamel/terraform_dsl.svg)](https://travis-ci.org/dalehamel/terraform_dsl)
2
+
3
+ # Terraform DSL
4
+
5
+ This is a simple DSL for wrapping hashicorp's [terraform configuration](https://terraform.io/docs/configuration/index.html).
6
+
7
+ Terraform is an infrastructure-as-code tool for managing infrastructure by defining stack element in a DSL.
8
+
9
+ This ruby DSL It supports almost identical syntax, and generates JSON that the terraform go application will understand.
10
+
11
+ ## Differences from terraform's HCL configuration
12
+
13
+ There are a few ways to access stack element variables and attributes for different purposes:
14
+
15
+ * get(:var) - This gets the actual value of a variable at generation time. This is needed when passing variables to submodules
16
+ * var(:var) - This references the terraform interpolation syntax of a variable.
17
+ * value\_of(:var) - This references the value of a variable for a stack element by type and name using interpolation syntax.
18
+ * id\_of(:var) - This references the id of a stack element by type and name using interpolation syntax.
19
+
20
+ Since 'module' is a keyword in ruby, and even though it is technically OK to use it as a function name, we use 'submodule' instead of module, to make editors happy and avoid using the keyword.
21
+ * submodules are terraform modules under the hood - you can use normal terraform module syntax if you wish
22
+ * If you reference your source as 'module\_MODULENAME', you can reference another ruby module!
23
+ * This module will be resolved at runtime, not compile time
24
+ * Modules nested most deeply are resolved first. The root module is resolved last.
25
+
26
+ We allow variables to have array values. This is very useful as a flow-control technique to DRY up modules, and leverage the full power of the ruby DSL.
27
+ However, since terraform does not allow variables to have array values, we automatically flatten them to a comma-separated string. This variable may or may not be used by terraform, if you reference it explicity using one of the interpolated accessors mentioned above. If it is not used, it doesn't matter and will simply be ignored.
28
+
29
+ Keep in mind that when specifying a hash as a value within the DSL, you must use brackets around the braces ( call as ({key: value}) instead of {key: value}, this is an unfortunate but necessary syntax).
30
+
31
+ # Usage
32
+
33
+ To use this gem, you will need to:
34
+
35
+ * define some stack modules
36
+ * configure secrets for your app
37
+ * define those stacks using your modules in yaml
38
+ * create a simple rake file
39
+
40
+ Once you've done this, you can run various rake tasks to manage your stacks.
41
+
42
+ If you get lost, take a look at the [sample app](https://github.com/dalehamel/terraform_dsl_sample)
43
+
44
+ ## Stack modules
45
+
46
+ In terraform, everything is defined in terms of 'modules'. Even if you don't use the 'module' (submodule in our case) keyword, you're implicitly working within the 'root' module.
47
+
48
+ Create a stack module, like one of our [examples](examples), such as our [simple app](examples/simple_app.rb)
49
+
50
+ Generally, you need to do:
51
+
52
+ ```
53
+ require 'terraform_dsl'
54
+ ```
55
+
56
+ And then subclass Terraform::StackModule
57
+
58
+ ```
59
+ class MyAwesomeStackModule < Terraform::StackModule
60
+ ```
61
+
62
+ Within that class, define resources using a similar syntax to [terraform's configuration](https://terraform.io/docs/configuration/index.html).
63
+
64
+ ## Submodules
65
+
66
+ You may even reference other stack modules that you've defined! Just call the module by using "module\_MODULENAME" as the 'source' value.
67
+
68
+ Using submodules are a fantastic way to DRY up your infrastructure! Since they are just terraform modules under the hood, you can specify variables as input (to configure the module) and specify outputs of the module, so that other modules may interpolate them for their own resources.
69
+
70
+ ## Configuration
71
+
72
+ ### Secrets
73
+
74
+ Create a file at config/secrets.json that contains secrets needed for you providers.
75
+
76
+ Specify the secrets as a JSON hash like so:
77
+
78
+ ```
79
+ {
80
+ "aws": {
81
+ "AWS_ACCESS_KEY_ID": "ACCESS_KEY",
82
+ "AWS_SECRET_ACCESS_KEY": "SECRET_KEY"
83
+ }
84
+ }
85
+ ```
86
+
87
+ For other providers, see [example configs](examples/secretconfig)
88
+
89
+ **Note** do not commit plaintext secrets.json to your repository. We recommend you use [ejson](https://github.com/Shopify/ejson) to store your secrets, and [capistrano-ejson](https://github.com/Shopify/capistrano-ejson) to decrypt them in production.
90
+ **Note** Secrets are never written to module files, as a safeguared to prevent them from accidentally being committed. Instead, they are passed as environment vairables.
91
+
92
+ ### YAML
93
+
94
+ After setting up your rakefile, as below, just run:
95
+
96
+ ```
97
+ bundle exec rake terraform:init
98
+ ```
99
+
100
+ This will set up your stacks.yml with an initial config template, which you can customize for your chosen stack module
101
+
102
+ ```
103
+ common:
104
+ remote:
105
+ s3:
106
+ bucket: quartermaster-terraform
107
+ region: us-east-1
108
+ stacks:
109
+ - name: teststack
110
+ uuid: 1433296428_0safh8-Y # must be unique
111
+ description: just a test stack
112
+ root: simpleapp
113
+ variables:
114
+ flavor: t1.micro
115
+ key: SOMESSHKEY
116
+ ```
117
+
118
+ You may also specify a 'common' block, that will be merged into all stacks.
119
+
120
+ Terraform stacks need somewhere to store their state. By default, this will be the local filesystem.
121
+
122
+ It's highly recommended that you use a [remote backend](https://www.terraform.io/docs/commands/remote-config.html) instead, so that you can share your stacks.
123
+
124
+ ### Tasks
125
+
126
+ Create a rake file that loads your modules, and calls Terraform::Tasks.loadall.
127
+
128
+ ```
129
+ ## Loads terraform tasks and modules
130
+ require 'terraform_dsl'
131
+ Terraform::Tasks.loadall
132
+
133
+ ```
134
+
135
+ This will add some terraform tasks for managing your terraform stacks:
136
+
137
+ ```
138
+ rake terraform:apply[name] # Apply pending changes for a stack
139
+ rake terraform:apply_all # Apply all pending stack changes
140
+ rake terraform:destroy[name] # Destroy a stack
141
+ rake terraform:get[name] # Fetch modules for a stack
142
+ rake terraform:get_all # Fetch modules for all stacks
143
+ rake terraform:init # Initialize stacks.yml if it does not exist
144
+ rake terraform:list # List all available stacks
145
+ rake terraform:load # Loads available stack modules
146
+ rake terraform:plan[name] # Show pending changes for a stack
147
+ rake terraform:pull[name] # Pulls stack state from remote location
148
+ rake terraform:pull_all # Pulls stack states from remote location
149
+ rake terraform:push[name] # Push stack state to remote location
150
+ rake terraform:push_all # Push stack states to remote location
151
+ rake terraform:remote_config[name] # Sets up remote config for a stack
152
+ rake terraform:remote_config_all # Sets up remote config for all stacks that support it
153
+ rake terraform:show[name] # Show details about a stack by name
154
+ rake terraform:show_all # Show all pending stack changes
155
+ rake terraform:uuid # Generate a UUID for a stack, so stacks do not clobber each other
156
+ ```
157
+
158
+ # Credits
159
+
160
+ Inspired by [terraframe](https://github.com/eropple/terraframe), a very similar but less generic and complete terraform DSL.
161
+
162
+ [license](LICENSE)
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new('test') do |t|
5
+ t.libs << 'lib' << 'test'
6
+ t.test_files = FileList['test/*.rb']
7
+ end
8
+
9
+ task default: :test
data/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ * Allow variables to include arrays natively
2
+ * Detect arrays, and flatten into strings (terraform doesn't support arrays for variable values :sad\_panda:)
3
+ + Improve test coverage
4
+ + Come up with a way to lock S3 states
5
+ + Improve documentation
6
+ + Strategy to ship modules around
7
+ + Gems?
@@ -0,0 +1,3 @@
1
+ These are example JSON blobs for configuring various providers.
2
+
3
+ You will need to define one of these for each provider you wish to use that requires secrets.
@@ -0,0 +1,5 @@
1
+ {
2
+ "atlas": {
3
+ "ATLAS_TOKEN": "TOKEN"
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "aws": {
3
+ "AWS_ACCESS_KEY_ID": "ACCESS_KEY",
4
+ "AWS_SECRET_ACCESS_KEY": "SECRET_KEY"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "cloudflare": {
3
+ "CLOUDFLARE_EMAIL": "EMAIL",
4
+ "CLOUDFLARE_TOKEN": "TOKEN"
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "cloudstack": {
3
+ "CLOUDSTACK_API_URL": "API_URL",
4
+ "CLOUDSTACK_API_KEY": "API_KEY",
5
+ "CLOUDSTACK_SECRET_KEY": "SECRET_KEY"
6
+ }
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "digitalocean": {
3
+ "DIGITALOCEAN_TOKEN": "TOKEN"
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "dme": {
3
+ "DME_AKEY": "API_KEY",
4
+ "DME_SKEY": "SECRET_KEY"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "heroku": {
3
+ "HEROKU_EMAIL": "EMAIL",
4
+ "HEROKU_API_KEY": "API_KEY"
5
+ }
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "mailgun": {
3
+ "MAILGUN_API_KEY": "API_KEY"
4
+ }
5
+ }
@@ -0,0 +1,61 @@
1
+ class SimpleApp < StackTemplate
2
+ variable(:flavor) { default 'm1.small' }
3
+ variable(:ami) { default 'ami-50948838' }
4
+ variable(:region) { default 'us-east-1' }
5
+ variable(:availability_zone) { default 'us-east-1b' }
6
+ variable(:cidr) { default '10.1.2.0/24' }
7
+ variable(:name) { default 'default' }
8
+ variable(:key) {}
9
+
10
+ provider 'aws' do
11
+ region var(:region)
12
+ end
13
+
14
+ resource 'aws_security_group', :basic_web do
15
+ name 'simple_app_web'
16
+ description 'Basic security group with port 22, 80, 443 open to the world.'
17
+
18
+ ingress do
19
+ from_port 22
20
+ to_port 22
21
+ protocol 'tcp'
22
+ cidr_blocks ['0.0.0.0/0']
23
+ end
24
+
25
+ ingress do
26
+ from_port 80
27
+ to_port 80
28
+ protocol 'tcp'
29
+ cidr_blocks ['0.0.0.0/0']
30
+ end
31
+
32
+ ingress do
33
+ from_port 443
34
+ to_port 443
35
+ protocol 'tcp'
36
+ cidr_blocks ['0.0.0.0/0']
37
+ end
38
+ end
39
+
40
+ resource 'aws_vpc', :simple_vpc do
41
+ cidr_block var(:cidr)
42
+ tags ({
43
+ 'Name' => var(:name)
44
+ })
45
+ end
46
+
47
+ resource 'aws_subnet', :simple_subnet do
48
+ vpc_id id_of('aws_vpc', :simple_vpc)
49
+ cidr_block var(:cidr)
50
+ end
51
+
52
+ resource 'aws_instance', :simple_app do
53
+ security_groups [value_of('aws_security_group', :basic_web, :name)]
54
+ instance_type var(:flavor)
55
+ ami var(:ami)
56
+ key_name var(:key)
57
+ tags ({
58
+ 'Name' => "#{var(:name)}.ec2.shopify.com"
59
+ })
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+
3
+ module Terraform
4
+ # Loads all rake tasks when terraform_dsl is included by a rake script
5
+ class Tasks
6
+ def self.loadall
7
+ Dir.glob("#{File.join(File.dirname(__dir__), 'tasks')}/*.rake").each { |r| load r }
8
+ template_path = ENV['TERRAFORM_TEMPLATE_PATH'] || 'templates'
9
+ Dir.glob("#{File.join(Dir.pwd, template_path)}/*.rb").each { |t| require_relative t }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,127 @@
1
+ require 'terraform_dsl'
2
+
3
+ namespace 'terraform' do
4
+ desc 'Loads available stack modules'
5
+ task :load do
6
+ Terraform::Stacks.load
7
+ end
8
+
9
+ desc 'Initialize stacks.yml if it does not exist'
10
+ task :init do
11
+ Terraform::Stacks.init
12
+ end
13
+
14
+ desc 'Fetch modules for a stack'
15
+ task :get, [:name] => :load do |_t, args|
16
+ stack = Terraform::Stacks.stacks[args[:name]]
17
+ stack.get
18
+ end
19
+
20
+ desc 'Fetch modules for all stacks'
21
+ task get_all: :load do
22
+ Terraform::Stacks.stacks.each do |_name, stack|
23
+ stack.get
24
+ end
25
+ end
26
+
27
+ desc 'List all available stacks'
28
+ task list: :load do
29
+ Terraform::Stacks.stacks.each do |name, _stack|
30
+ puts name
31
+ end
32
+ end
33
+
34
+ desc 'Show details about a stack by name'
35
+ task :show, [:name] => :load do |_t, args|
36
+ fail 'Specify a name' unless args[:name]
37
+ stack = Terraform::Stacks.stacks[args[:name]]
38
+ puts stack
39
+ stack.plan
40
+ end
41
+
42
+ desc 'Show all pending stack changes'
43
+ task show_all: [:load, :get_all] do
44
+ Terraform::Stacks.stacks.each do |_name, stack|
45
+ puts stack
46
+ stack.plan
47
+ end
48
+ end
49
+
50
+ desc 'Show pending changes for a stack'
51
+ task :plan, [:name] => :load do |_t, args|
52
+ fail 'Specify a name' unless args[:name]
53
+ stack = Terraform::Stacks.stacks[args[:name]]
54
+ stack.plan
55
+ end
56
+
57
+ desc 'Apply pending changes for a stack'
58
+ task :apply, [:name] => :load do |_t, args|
59
+ fail 'Specify a name' unless args[:name]
60
+ stack = Terraform::Stacks.stacks[args[:name]]
61
+ stack.apply
62
+ end
63
+
64
+ desc 'Apply all pending stack changes'
65
+ task apply_all: :load do
66
+ Terraform::Stacks.stacks.each do |_name, stack|
67
+ puts stack
68
+ stack.apply
69
+ end
70
+ end
71
+
72
+ desc 'Destroy a stack'
73
+ task :destroy, [:name] => :load do |_t, args|
74
+ fail 'Specify a name' unless args[:name]
75
+ stack = Terraform::Stacks.stacks[args[:name]]
76
+ stack.destroy
77
+ end
78
+
79
+ desc 'Push stack state to remote location'
80
+ task :push, [:name] => [:load, :remote_config] do |_t, args|
81
+ stack = Terraform::Stacks.stacks[args[:name]]
82
+ stack.push
83
+ end
84
+
85
+ desc 'Push stack states to remote location'
86
+ task push_all: [:load, :remote_config_all] do
87
+ Terraform::Stacks.stacks.each do |_name, stack|
88
+ puts stack
89
+ stack.push
90
+ end
91
+ end
92
+
93
+ desc 'Pulls stack state from remote location'
94
+ task :pull, [:name] => [:load, :remote_config] do |_t, args|
95
+ stack = Terraform::Stacks.stacks[args[:name]]
96
+ stack.pull
97
+ end
98
+
99
+ desc 'Pulls stack states from remote location'
100
+ task pull_all: [:load, :remote_config_all] do
101
+ Terraform::Stacks.stacks.each do |_name, stack|
102
+ puts stack
103
+ stack.pull
104
+ end
105
+ end
106
+
107
+ desc 'Sets up remote config for a stack'
108
+ task :remote_config, [:name] => [:load] do |_t, args|
109
+ stack = Terraform::Stacks.stacks[args[:name]]
110
+ stack.remote_config
111
+ end
112
+
113
+ desc 'Sets up remote config for all stacks that support it'
114
+ task remote_config_all: :load do
115
+ Terraform::Stacks.stacks.each do |_name, stack|
116
+ puts stack
117
+ stack.remote_config
118
+ end
119
+ end
120
+
121
+ desc 'Generate a UUID for a stack, so stacks do not clobber each other'
122
+ task :uuid do
123
+ uuid = Terraform::Stacks.uuid
124
+ puts "uuid: #{uuid}"
125
+ puts 'Add this as a field to a new stack to prevent clobbering stacks with the same name'
126
+ end
127
+ end