domed-city 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7216036ceb6858cda42ec4ca107ef3ac98cab6be
4
+ data.tar.gz: c7ba0c37d0871bad6e1822133488a6186fe45abd
5
+ SHA512:
6
+ metadata.gz: 9c5b0bd930972a77768a9605a3d5f4875bd6c98689d876f8e58fad3b424663e9af76b87aef7fbd32f79e79f1cd6bd2e937b22c4ad35ee73d61d525cbb12944df
7
+ data.tar.gz: a9cc15593eca39c0b7fa8b3acefca0361d1539ad07c68934d9d04df30df9e170bb3cb389a4de39308c300912c112507310a50aa211c0882c9ccf4b357da5f5ff
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dome.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 ITV
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,14 @@
1
+ # dome
2
+ Simple Terraform API wrapper in Ruby.
3
+
4
+ ## Purpose
5
+
6
+ To consolidate, improve and enforce standards around ITV's use of Terraform (via Rake) across product teams.
7
+
8
+ ## Naming
9
+
10
+ From [Wikipedia](https://en.wikipedia.org/wiki/Domed_city):
11
+
12
+ ```
13
+ ...the dome is airtight and pressurized, creating a habitat that can be controlled for air temperature, composition and quality, typically due to an external atmosphere (or lack thereof) that is inimical to habitation for one or more reasons.
14
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require 'trollop'
5
+ require "dome"
6
+
7
+ # desc "creates the S3 bucket relevant for the environment; if the S3 state exists it pulls the remote S3 state locally"
8
+ # task :bootstrap_s3_state do
9
+ #
10
+ # desc "creates the TF plan in a local file"
11
+ # task :plan do
12
+ #
13
+ # desc "creates the TF plan, to destroy resources, in a local file"
14
+ # task :plandestroy do
15
+ #
16
+ # desc "applies a TF plan"
17
+ # task :apply do
18
+ #
19
+ # desc "applies a destructive TF immediately"
20
+ # task :destroy do
21
+ #
22
+ # desc "updates the TF binary dependencies"
23
+ # task :update do
24
+
25
+ opts = Trollop::options do
26
+ version Dome::VERSION
27
+ banner <<-EOS
28
+ Dome wraps the Terraform API and performs useful stuff.
29
+
30
+ Usage:
31
+ dome [command]
32
+ where [commands] are:
33
+ EOS
34
+
35
+ opt :plan, "Creates the Terraform plan in a local file"
36
+ opt :apply, "Applies the Terraform plan"
37
+ opt :destroy, "Applies a destructive Terraform plan"
38
+ opt :plan_destroy, "TODO"
39
+ opt :update, "TODO (maybe?)"
40
+ end
41
+
42
+ if opts[:plan]
43
+ Dome::Environment.validate_environment
44
+ puts 'plan called'
45
+ elsif opts[:apply]
46
+ puts 'apply called'
47
+ else
48
+ Trollop::educate
49
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dome/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "domed-city"
8
+ spec.version = Dome::VERSION
9
+ spec.authors = ["Ben Snape"]
10
+ spec.email = ["ben.snape@itv.com"]
11
+
12
+ spec.summary = %q{A simple Terraform API wrapper and helpers for ITV.}
13
+ spec.homepage = "https://github.com/ITV/dome"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.9"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+
24
+ spec.add_dependency 'trollop'
25
+ spec.add_dependency 'aws-profile_parser'
26
+ spec.add_dependency 'aws-sdk'
27
+ spec.add_dependency 'colorize'
28
+ end
@@ -0,0 +1,8 @@
1
+ require 'dome/version'
2
+ require 'dome/environment'
3
+ require 'dome/terraform'
4
+
5
+ require 'aws-sdk'
6
+ require 'colorize'
7
+ require 'aws/profile_parser'
8
+ require 'fileutils'
@@ -0,0 +1,132 @@
1
+ module Dome
2
+ class Environment
3
+
4
+ def self.validate_environment
5
+ current_dir = File.absolute_path(ENV['PWD'])
6
+ environment = current_dir.to_s.split('/')[-1]
7
+ account = current_dir.to_s.split('/')[-2]
8
+
9
+ valid_accounts = ['deirdre-dev', 'deirdre-prd']
10
+ valid_env_nonprod = ['infradev', 'sit', 'qa', 'stg']
11
+ valid_env_prod = ['infraprd', 'prd']
12
+
13
+ if account
14
+ if is_valid_account?(account)
15
+ puts "found valid account #{account}, moving on ...".colorize(:green)
16
+ else
17
+ invalid_account_notification
18
+ end
19
+ else
20
+ fail "\n#{account} is no a valid account\n\n".colorize(:red)
21
+ end
22
+
23
+ if environment
24
+ if is_valid_env?(environment)
25
+ puts "found valid environment #{environment}, moving on ...".colorize(:green)
26
+ else
27
+ invalid_environment_notification
28
+ end
29
+ else
30
+ fail "\n #{environment} is not a valid environment for the account: #{account}\n\n".colorize(:red)
31
+ end
32
+
33
+ current_env_dir = "#{account}/#{environment}"
34
+ @varfile = "-var-file=params/env.tfvars"
35
+ end
36
+
37
+ def self.cd_to_tf_dir
38
+ Dir.chdir(current_env_dir) if Dir.pwd != current_env_dir
39
+ end
40
+
41
+ def self.purge_terraform
42
+ FileUtils.rm_rf ".terraform/"
43
+ end
44
+
45
+ def self.set_env
46
+ fail "Unable to set an account!" if account.nil?
47
+ set_creds
48
+ end
49
+
50
+ def self.set_creds
51
+ accounts = AWS::ProfileParser.new
52
+ begin
53
+ @aws_creds = accounts.get(account)
54
+ rescue StandardError
55
+ raise "No credentials found for #{account}"
56
+ end
57
+ ENV['AWS_ACCESS_KEY_ID'] = @aws_creds[:access_key_id]
58
+ ENV['AWS_SECRET_ACCESS_KEY'] = @aws_creds[:secret_access_key]
59
+ ENV['AWS_DEFAULT_REGION'] = @aws_creds[:region]
60
+ end
61
+
62
+ def self.is_valid_account?(account)
63
+ valid_accounts.include?(account)
64
+ end
65
+
66
+ def self.is_valid_env?(environment)
67
+ if valid_accounts[valid_accounts.index(account)] == 'deirdre-dev'
68
+ valid_env_nonprod.include?(environment)
69
+ elsif valid_accounts[valid_accounts.index(account)] == 'deirdre-prd'
70
+ valid_env_prod.include?(environment)
71
+ end
72
+ end
73
+
74
+ def self.invalid_account_notification
75
+ puts "\n#{account} is not a valid account\n\n".colorize(:red)
76
+ puts "valid accounts are: "
77
+ p valid_accounts
78
+ puts "please set your .aws/config to one of the valid accounts described above!"
79
+ puts "if you've correctly set your .aws/config then make sure you've cd into the correct directory matching the env name from .aws/config"
80
+ exit 1
81
+ end
82
+
83
+ def self.invalid_environment_notification
84
+ puts "\n#{environment} is not a valid environment\n\n".colorize(:red)
85
+ puts "valid environments are:"
86
+ if account == 'deirdre-dev'
87
+ p valid_env_nonprod
88
+ elsif account == 'deirdre-prd'
89
+ p valid_env_prod
90
+ end
91
+ exit 1
92
+ end
93
+
94
+ def self.s3_bucket_exists?(tfstate_bucket)
95
+ s3_client = Aws::S3::Client.new(@aws_creds)
96
+ resp = s3_client.list_buckets
97
+ resp.buckets.each { |bucket| return true if bucket.name == tfstate_bucket }
98
+ false
99
+ end
100
+
101
+ def self.s3_tf_create_remote_state_bucket(tfstate_bucket, tfstate_s3_obj)
102
+ puts "initial boostrap of the S3 bucket".colorize(:green)
103
+ s3_client = Aws::S3::Client.new(@aws_creds)
104
+ begin
105
+ s3_client.create_bucket({
106
+ bucket: tfstate_bucket,
107
+ acl: "private"
108
+ })
109
+ rescue Aws::S3::Errors::BucketAlreadyExists => e
110
+ puts "type of exception #{e.class}".colorize(:red)
111
+ puts "backtrace for this exception:".colorize(:red)
112
+ puts e.backtrace
113
+ puts "\nmake sure the bucket name is unique per whole AWS S3 service, see here for docs on uniqueness https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html\n\n".colorize(:red)
114
+ exit 1
115
+ end
116
+ puts "enabling versioning on the S3 bucket - http://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html".colorize(:green)
117
+ s3_client.put_bucket_versioning({
118
+ bucket: tfstate_bucket,
119
+ versioning_configuration: {
120
+ mfa_delete: "Disabled",
121
+ status: "Enabled"
122
+ },
123
+ })
124
+ puts "creating an empty S3 object".colorize(:green)
125
+ s3_client.put_object({
126
+ bucket: tfstate_bucket,
127
+ key: tfstate_s3_obj,
128
+ body: ""
129
+ })
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,96 @@
1
+ module Dome
2
+ class Terraform
3
+ def self.plan
4
+ puts "--- running task :plan".colorize(:light_cyan)
5
+ set_env
6
+ Dir.chdir(CURRENT_ENV_DIR)
7
+ puts "purging older terraform module cache dir ...".colorize(:green)
8
+ purge_terraform
9
+ puts "purging older terraform plan ...".colorize(:green)
10
+ FileUtils.rm_f(PLAN)
11
+ puts "updating terraform external modules ...".colorize(:green)
12
+ Rake::Task['tf:update'].invoke
13
+
14
+ cmd = "terraform remote config"\
15
+ " -backend=S3"\
16
+ " -backend-config='bucket=#{tfstate_bucket}' -backend-config='key=#{tfstate_s3_obj}'"
17
+ puts "Command to execute: #{cmd}"
18
+ bool = system(cmd)
19
+ fail "something went wrong when fetching the S3 state" unless bool
20
+ cmd = "terraform plan -module-depth=1 -refresh=true -out=#{PLAN} #{@varfile}"
21
+ puts "\nCommand to execute: \n #{cmd}\n\n"
22
+ bool = system(cmd)
23
+ fail "something went wrong when creating the TF plan" unless bool
24
+ end
25
+
26
+ def self.apply
27
+ puts "--- running task :apply".colorize(:light_cyan)
28
+ set_env
29
+ cd_to_tf_dir
30
+ set_env
31
+ cmd = "terraform apply #{PLAN}"
32
+ puts "\n Command to execute: #{cmd}\n\n"
33
+ bool = system(cmd)
34
+ fail "something went wrong when applying the TF plan" unless bool
35
+ end
36
+
37
+ def self.plan_destroy
38
+ puts "--- running task :plandestroy".colorize(:light_cyan)
39
+ set_env
40
+ Dir.chdir(CURRENT_ENV_DIR)
41
+ puts "purging older terraform module cache dir ...".colorize(:green)
42
+ purge_terraform
43
+ puts "purging older terraform plan ...".colorize(:green)
44
+ FileUtils.rm_f(PLAN)
45
+ puts "updating terraform external modules ...".colorize(:green)
46
+ Rake::Task['tf:update'].invoke
47
+ p PLAN
48
+ cmd = "terraform plan -destroy -module-depth=1 -out=#{PLAN} #{@varfile}"
49
+ puts "\nCommand to execute: \n #{cmd}\n\n"
50
+ bool = system(cmd)
51
+ fail "something went wrong when creating the TF plan" unless bool
52
+ end
53
+
54
+ def self.destroy
55
+ puts "--- running task :destroy".colorize(:light_cyan)
56
+ puts "here is the destroy plan that terraform will carry out"
57
+ plan_destroy
58
+ apply
59
+ end
60
+
61
+ def self.update
62
+ puts "--- running task :update".colorize(:light_cyan)
63
+ cmd = "terraform get -update=true"
64
+ puts "\nCommand to execute: \n #{cmd}\n\n"
65
+ bool = system(cmd)
66
+ fail "something went wrong when pulling remote TF modules" unless bool
67
+ end
68
+
69
+ def self.bootstrap_s3_state
70
+ set_env
71
+ if s3_bucket_exists?(tfstate_bucket)
72
+ puts "Bootstrap attempted, but config for account: #{ACCOUNT.colorize(:green)} and environment: #{ENVIRONMENT.colorize(:green)} already exists in S3 bucket: #{tfstate_bucket.colorize(:green)}"
73
+ puts "synchronising the remote S3 state ..."
74
+ cd_to_tf_dir
75
+ cmd = "terraform remote config"\
76
+ " -backend=S3"\
77
+ " -backend-config='bucket=#{tfstate_bucket}' -backend-config='key=#{tfstate_s3_obj}'"\
78
+ " -state=#{STATE_FILE_DIR}/#{REMOTE_STATE_FILE}"
79
+ # still not clear for me if the -state in the above cmd matters
80
+ puts "Command to execute: #{cmd}"
81
+ bool = system(cmd)
82
+ fail "something went wrong when creating the S3 state" unless bool
83
+ else
84
+ s3_tf_create_remote_state_bucket(tfstate_bucket, tfstate_s3_obj)
85
+ puts "\nsetting up the initial terraform S3 state in the S3 bucket: #{tfstate_bucket.colorize(:green)} for account:#{ACCOUNT.colorize(:green)} and environment:#{ENVIRONMENT.colorize(:green)} ..."
86
+ cd_to_tf_dir
87
+ cmd = "terraform remote config"\
88
+ " -backend=S3"\
89
+ " -backend-config='bucket=#{tfstate_bucket}' -backend-config='key=#{tfstate_s3_obj}'"
90
+ puts "Command to execute: #{cmd}"
91
+ bool = system(cmd)
92
+ fail "something went wrong when creating the S3 state" unless bool
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module Dome
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: domed-city
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Snape
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: trollop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-profile_parser
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: aws-sdk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: colorize
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - ben.snape@itv.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE
123
+ - README.md
124
+ - Rakefile
125
+ - bin/dome
126
+ - dome.gemspec
127
+ - lib/dome.rb
128
+ - lib/dome/environment.rb
129
+ - lib/dome/terraform.rb
130
+ - lib/dome/version.rb
131
+ homepage: https://github.com/ITV/dome
132
+ licenses: []
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.4.6
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: A simple Terraform API wrapper and helpers for ITV.
154
+ test_files: []