rake_cloudspin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ module RakeCloudspin
2
+ module Tasks
3
+ class All < TaskLib
4
+
5
+ parameter :deployment_stacks
6
+ parameter :delivery_stacks
7
+ parameter :account_stacks
8
+ parameter :configuration
9
+
10
+ def define
11
+ @configuration = Confidante.configuration(
12
+ :hiera => Hiera.new(config: hiera_file)
13
+ )
14
+ @deployment_statebucket_required = false
15
+
16
+ discover_deployment_stacks
17
+ discover_delivery_stacks
18
+ discover_account_stacks
19
+
20
+ define_terraform_installation_tasks
21
+ define_deployment_stacks_tasks
22
+ define_delivery_stacks_tasks
23
+ define_account_stacks_tasks
24
+
25
+ define_statebucket_tasks
26
+
27
+ define_top_level_deployment_tasks
28
+ define_top_level_delivery_tasks
29
+ define_top_level_account_tasks
30
+ end
31
+
32
+ def hiera_file
33
+ File.expand_path(File.join(File.dirname(__FILE__), 'hiera.yaml'))
34
+ end
35
+
36
+ def discover_deployment_stacks
37
+ @deployment_stacks = discover_stacks('deployment')
38
+ end
39
+
40
+ def discover_delivery_stacks
41
+ @delivery_stacks = discover_stacks('delivery')
42
+ end
43
+
44
+ def discover_account_stacks
45
+ @account_stacks = discover_stacks('account')
46
+ end
47
+
48
+ def discover_stacks(stack_type)
49
+ if Dir.exist?(stack_type)
50
+ Dir.entries(stack_type).select { |stack|
51
+ File.directory? File.join(stack_type, stack) and File.exists?("#{stack_type}/#{stack}/stack.yaml")
52
+ }
53
+ else
54
+ []
55
+ end
56
+ end
57
+
58
+ def define_terraform_installation_tasks
59
+ RakeTerraform.define_installation_tasks(
60
+ path: File.join(Dir.pwd, 'vendor', 'terraform'),
61
+ version: '0.11.7'
62
+ )
63
+ end
64
+
65
+ def define_deployment_stacks_tasks
66
+ define_stack_tasks('deployment', @deployment_stacks)
67
+ end
68
+
69
+ def define_delivery_stacks_tasks
70
+ define_stack_tasks('delivery', @delivery_stacks)
71
+ end
72
+
73
+ def define_account_stacks_tasks
74
+ define_stack_tasks('account', @account_stacks)
75
+ end
76
+
77
+ def define_stack_tasks(stack_type, stacks)
78
+ namespace stack_type do
79
+ stacks.each { |stack_name|
80
+ namespace stack_name do
81
+ StackTask.new do |t|
82
+ t.stack_name = stack_name
83
+ t.stack_type = stack_type
84
+ t.configuration = configuration
85
+ end
86
+ if deployment_statebucket_required?(stack_type, stack_name)
87
+ @deployment_statebucket_required = true
88
+ end
89
+ if stack_needs_ssh_keys?(stack_type, stack_name)
90
+ SshKeyTask.new do |t|
91
+ t.stack_name = stack_name
92
+ t.stack_type = stack_type
93
+ t.configuration = configuration
94
+ end
95
+ end
96
+ StackTestTask.new do |t|
97
+ t.stack_name = stack_name
98
+ t.stack_type = stack_type
99
+ t.configuration = configuration
100
+ end
101
+ end
102
+ }
103
+ end
104
+ end
105
+
106
+ def define_statebucket_tasks
107
+ if @deployment_statebucket_required
108
+
109
+ namespace 'deployment' do
110
+ namespace 'statebucket' do
111
+ DeploymentStatebucketTask.new do |t|
112
+ t.configuration = configuration
113
+ end
114
+ end
115
+
116
+ @deployment_stacks.each {|stack_name|
117
+ task "#{stack_name}:plan" => [ 'statebucket:plan' ]
118
+ task "#{stack_name}:provision" => [ 'statebucket:provision' ]
119
+ task "#{stack_name}:vars" => [ 'statebucket:vars' ]
120
+ }
121
+
122
+ end
123
+ end
124
+ end
125
+
126
+ def define_top_level_deployment_tasks
127
+ ['plan', 'provision', 'destroy', 'test', 'vars'].each { |action|
128
+ desc "#{action} for all deployment stacks"
129
+ task action => @deployment_stacks.map { |stack|
130
+ :"deployment:#{stack}:#{action}"
131
+ }
132
+ }
133
+ end
134
+
135
+ def define_top_level_delivery_tasks
136
+ ['plan', 'provision', 'destroy', 'test', 'vars'].each { |action|
137
+ desc "#{action} for all delivery stacks"
138
+ task "delivery_#{action}" => @delivery_stacks.map { |stack|
139
+ :"delivery:#{stack}:#{action}"
140
+ }
141
+ }
142
+ end
143
+
144
+ def define_top_level_account_tasks
145
+ ['plan', 'provision', 'destroy', 'test', 'vars'].each { |action|
146
+ desc "#{action} for all account stacks"
147
+ task "delivery_#{action}" => @delivery_stacks.map { |stack|
148
+ :"account:#{stack}:#{action}"
149
+ }
150
+ }
151
+ end
152
+
153
+ def stack_needs_ssh_keys?(stack_type, stack)
154
+ ! configuration
155
+ .for_scope(stack_type => stack).ssh_keys.nil?
156
+ end
157
+
158
+ def stack_uses_remote_state?(stack_type, stack_name)
159
+ state_config = configuration
160
+ .for_scope(stack_type => stack_name).state
161
+ ! state_config.nil? && state_config['type'] == 's3'
162
+ end
163
+
164
+ def deployment_statebucket_required?(stack_type, stack_name)
165
+ state_config = configuration
166
+ .for_scope(stack_type => stack_name).state
167
+
168
+ ! state_config.nil? &&
169
+ state_config['type'] == 's3' &&
170
+ state_config['scope'] == 'deployment'
171
+ end
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module RakeCloudspin
3
+ module Tasks
4
+ class BaseTask < TaskLib
5
+
6
+ parameter :configuration, :required => true
7
+ parameter :stack_name, :required => true
8
+ parameter :stack_type, :required => true
9
+
10
+ def stack_config(args = {})
11
+ configuration
12
+ .for_overrides(args)
13
+ .for_scope(stack_type => stack_name)
14
+ end
15
+
16
+ def spin_user_variables(args)
17
+ user_variables_hash = {
18
+ 'spin_api_users' => []
19
+ }
20
+ api_users = stack_config(args).api_users
21
+ api_users.each { |user_name, user_configuration|
22
+ user_variables_hash['spin_api_users'] << user_name
23
+ if user_configuration.key?('roles')
24
+ user_configuration['roles'].each { |role_name|
25
+ unless user_variables_hash.key?(role_user_variable_name(role_name))
26
+ user_variables_hash[role_user_variable_name(role_name)] = []
27
+ end
28
+ user_variables_hash[role_user_variable_name(role_name)] << user_name
29
+ }
30
+ end
31
+ }
32
+ user_variables_hash.each { |var_name, value_list|
33
+ user_variables_hash[var_name].uniq!
34
+ }
35
+ user_variables_hash
36
+ end
37
+
38
+ def role_user_variable_name(role_name)
39
+ "#{role_name}-users"
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,96 @@
1
+
2
+ module RakeCloudspin
3
+ module Tasks
4
+ class DeploymentStatebucketTask < TaskLib
5
+
6
+ parameter :configuration, :required => true
7
+
8
+ def define
9
+ define_terraform_tasks
10
+ define_vars_task
11
+ end
12
+
13
+ def define_terraform_tasks
14
+ RakeTerraform.define_command_tasks do |t|
15
+ t.configuration_name = "deployment-statebucket"
16
+ t.source_directory = source_directory
17
+ t.work_directory = 'work'
18
+ t.vars = terraform_vars_builder
19
+ t.state_file = local_state_path_builder
20
+ end
21
+ end
22
+
23
+ def source_directory
24
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'statebucket', 'infra'))
25
+ end
26
+
27
+ def define_vars_task
28
+ desc "Show terraform variables for deployment statebucket'"
29
+ task :vars do |t, args|
30
+ puts "Terraform variables for statebucket"
31
+ puts "---------------------------------------"
32
+ puts "#{terraform_vars_builder.call(args).to_yaml}"
33
+ puts "---------------------------------------"
34
+ puts "Local statefile path:"
35
+ puts "---------------------------------------"
36
+ puts "#{local_state_path_builder.call(args)}"
37
+ puts "---------------------------------------"
38
+ end
39
+ end
40
+
41
+ def terraform_vars_builder
42
+ lambda do |args|
43
+ {
44
+ 'region' => configuration.region,
45
+ 'state_bucket_name' => Statebucket.build_bucket_name(
46
+ estate: config(args).estate,
47
+ deployment_identifier: config(args).deployment_identifier,
48
+ component: config(args).component
49
+ ),
50
+ 'component' => config(args).component,
51
+ 'estate' => config(args).estate,
52
+ 'deployment_identifier' => config(args).deployment_identifier,
53
+ 'assume_role_arn' => assume_role_arn_var(args)
54
+ }
55
+ end
56
+ end
57
+
58
+ def assume_role_arn_var(args)
59
+ if assume_role?(args)
60
+ stack_manager_role_arn(args)
61
+ else
62
+ ''
63
+ end
64
+ end
65
+
66
+ def stack_manager_role_arn(args)
67
+ if assume_role?(args)
68
+ "arn:aws:iam::#{config(args).aws_account_id}:role/stack_manager-#{config(args).component}-#{config(args).estate}"
69
+ else
70
+ raise "Don't use 'stack_manager_role_arn' if assume_role? is false"
71
+ end
72
+ end
73
+
74
+ def assume_role?(args)
75
+ config(args).assume_role == true
76
+ end
77
+
78
+ def local_state_path_builder
79
+ lambda do |args|
80
+ Paths.from_project_root_directory(
81
+ 'state',
82
+ config(args).deployment_identifier || 'delivery',
83
+ config(args).component,
84
+ 'deployment',
85
+ "statebucket.tfstate"
86
+ )
87
+ end
88
+ end
89
+
90
+ def config(args)
91
+ configuration.for_overrides(args)
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ ---
2
+ :backends:
3
+ - "overrides"
4
+ - "env"
5
+ - "yaml"
6
+ :logger: "noop"
7
+ :yaml:
8
+ :datadir: "."
9
+ :hierarchy:
10
+ - "account/%{account}/stack"
11
+ - "deployment/%{deployment}/stack"
12
+ - "delivery/%{delivery}/stack"
13
+ - "users-local"
14
+ - "users"
15
+ - "component-local"
16
+ - "component"
@@ -0,0 +1,46 @@
1
+ require 'aws_ssh_key'
2
+
3
+ module RakeCloudspin
4
+ module Tasks
5
+ class SshKeyTask < BaseTask
6
+
7
+ def define
8
+ desc "Ensure ssh keys for #{stack_name}"
9
+ task :ssh_keys do
10
+ ssh_keys_config = stack_config.ssh_keys
11
+
12
+ role_param = maybe_role_parameter
13
+
14
+ ssh_keys_config.each { |ssh_key_name|
15
+ key = AwsSshKey::Key.new(
16
+ key_path: "/#{stack_config.estate}/#{stack_config.component}/#{stack_name}/#{stack_config.deployment_identifier}/ssh_key",
17
+ key_name: ssh_key_name,
18
+ aws_region: stack_config.region,
19
+ tags: {
20
+ :Estate => stack_config.estate,
21
+ :Component => stack_config.component,
22
+ :Service => stack_name,
23
+ :DeploymentIdentifier => stack_config.deployment_identifier
24
+ },
25
+ **role_param
26
+ )
27
+ key.load
28
+ key.write("work/#{stack_type}/#{stack_name}/ssh_keys/")
29
+ }
30
+ end
31
+
32
+ task :plan => [ :ssh_keys ]
33
+ task :provision => [ :ssh_keys ]
34
+ end
35
+
36
+ def maybe_role_parameter
37
+ if assume_role?
38
+ { role: stack_manager_role_arn }
39
+ else
40
+ {}
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,108 @@
1
+
2
+ module RakeCloudspin
3
+ module Tasks
4
+ class StackTask < BaseTask
5
+
6
+ def define
7
+ define_terraform_tasks
8
+ define_vars_task
9
+ end
10
+
11
+ def define_terraform_tasks
12
+ RakeTerraform.define_command_tasks do |t, args|
13
+ t.configuration_name = "#{stack_type}-#{stack_name}"
14
+ t.source_directory = "#{stack_type}/#{stack_name}/infra"
15
+ t.work_directory = 'work'
16
+ t.vars = terraform_vars_builder
17
+ if uses_local_state?
18
+ t.state_file = local_state_path_builder
19
+ elsif uses_remote_state?
20
+ t.backend_config = backend_config_builder
21
+ else
22
+ raise "ERROR: state_type '#{state_type}' not supported"
23
+ end
24
+ end
25
+ end
26
+
27
+ def define_vars_task
28
+ desc "Show terraform variables for stack '#{stack_name}'"
29
+ task :vars do |t, args|
30
+ puts "Terraform variables for stack '#{stack_name}'"
31
+ puts "---------------------------------------"
32
+ puts "#{terraform_vars_builder.call(args).to_yaml}"
33
+ puts "---------------------------------------"
34
+ if uses_local_state?
35
+ puts "Local statefile path:"
36
+ puts "---------------------------------------"
37
+ puts "#{local_state_path_builder.call(args)}"
38
+ elsif uses_remote_state?
39
+ puts "Backend configuration for stack '#{stack_name}':"
40
+ puts "---------------------------------------"
41
+ puts "#{backend_config_builder.call(args).to_yaml}"
42
+ else
43
+ puts "Unknown state configuration type ('#{state_type}')"
44
+ end
45
+ puts "---------------------------------------"
46
+ end
47
+ end
48
+
49
+ def uses_local_state?
50
+ state_configuration.nil? ||
51
+ state_configuration['type'].to_s.empty? ||
52
+ state_configuration['type'] == 'local'
53
+ end
54
+
55
+ def uses_remote_state?
56
+ ! state_configuration.nil? &&
57
+ ! state_configuration['type'].to_s.empty? &&
58
+ state_configuration['type'] == 's3'
59
+ end
60
+
61
+ def state_configuration
62
+ stack_config.state
63
+ end
64
+
65
+ def terraform_vars_builder
66
+ lambda do |args|
67
+ stack_config(args).vars.merge(spin_user_variables(args))
68
+ end
69
+ end
70
+
71
+ def local_state_path_builder
72
+ lambda do |args|
73
+ Paths.from_project_root_directory(
74
+ 'state',
75
+ stack_config(args).deployment_identifier || 'delivery',
76
+ stack_config(args).component,
77
+ stack_type,
78
+ "#{stack_name}.tfstate")
79
+ end
80
+ end
81
+
82
+ def backend_config_builder
83
+ lambda do |args|
84
+ {
85
+ 'region' => stack_config.region,
86
+ 'bucket' => Statebucket.build_bucket_name(
87
+ estate: stack_config(args).estate,
88
+ deployment_identifier: stack_config(args).deployment_identifier,
89
+ component: stack_config(args).component
90
+ ),
91
+ 'key' => state_key(args),
92
+ 'encrypt' => true
93
+ }
94
+ end
95
+ end
96
+
97
+ def state_key(args)
98
+ [
99
+ stack_config(args).deployment_identifier || 'delivery',
100
+ stack_config(args).component,
101
+ stack_type,
102
+ "#{stack_name}.tfstate"
103
+ ].join('/')
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,133 @@
1
+
2
+ module RakeCloudspin
3
+ module Tasks
4
+ class StackTestTask < BaseTask
5
+
6
+ parameter :stack_name
7
+ parameter :stack_type
8
+
9
+ def define
10
+ define_inspec_task
11
+ define_aws_configuration_task
12
+ end
13
+
14
+ def define_inspec_task
15
+ desc 'Run inspec tests'
16
+ task :test do |t, args|
17
+ if has_inspec_tests?
18
+ create_inspec_attributes.call(args)
19
+ run_inspec_profile.call(args)
20
+ else
21
+ puts "NO TESTS FOR STACK ('#{stack_name}')"
22
+ end
23
+ end
24
+ end
25
+
26
+ def has_inspec_tests?
27
+ Dir.exist? ("#{stack_type}/#{stack_name}/tests/inspec")
28
+ end
29
+
30
+ def create_inspec_attributes
31
+ lambda do |args|
32
+ mkpath "work/tests/inspec"
33
+ attributes_file_path = "work/tests/inspec/attributes-#{stack_type}-#{stack_name}.yml"
34
+ puts "INFO: Writing inspec attributes to file: #{attributes_file_path}"
35
+ File.open(attributes_file_path, 'w') {|f|
36
+ f.write(test_attributes(args).to_yaml)
37
+ }
38
+ end
39
+ end
40
+
41
+ def test_attributes(args)
42
+ stack_config(args).vars.merge(test_vars(args)).merge(configured_api_users(args))
43
+ end
44
+
45
+ def test_vars(args)
46
+ stack_config(args).test_vars || {}
47
+ end
48
+
49
+ def configured_api_users(args)
50
+ build_user_configuration_hash(stack_config(args).api_users)
51
+ end
52
+
53
+ def build_user_configuration_hash(api_user_hash = {})
54
+ { 'configured_api_users' => api_user_hash }
55
+ end
56
+
57
+ def define_aws_configuration_task
58
+ task :aws_configuration do |t, args|
59
+ make_aws_configuration_file.call(args)
60
+ end
61
+ end
62
+
63
+ def make_aws_configuration_file
64
+ lambda do |args|
65
+ mkpath aws_configuration_folder
66
+ puts "INFO: Writing AWS configuration file: #{aws_configuration_file_path}"
67
+ File.open(aws_configuration_file_path, 'w') {|f|
68
+ f.write(aws_configuration_contents(args))
69
+ }
70
+ end
71
+ end
72
+
73
+ def aws_configuration_folder
74
+ "work/#{stack_type}/#{stack_name}/aws"
75
+ end
76
+
77
+ def aws_configuration_file_path
78
+ "#{aws_configuration_folder}/config"
79
+ end
80
+
81
+ def aws_configuration_contents(args)
82
+ <<~END_AWS_CONFIG
83
+ [profile #{assume_role_profile(args)}]
84
+ role_arn = #{stack_config(args).vars['assume_role_arn']}
85
+ source_profile = #{stack_config(args).vars['aws_profile']}
86
+ END_AWS_CONFIG
87
+ end
88
+
89
+ def assume_role_profile(args)
90
+ "assume-spin_account_manager-#{stack_config(args).component}"
91
+ end
92
+
93
+ def run_inspec_profile
94
+ lambda do |args|
95
+ inspec_profiles = Dir.entries("#{stack_type}/#{stack_name}/tests/inspec").select { |inspec_profile|
96
+ inspec_profile != '..' &&
97
+ File.exists?("#{stack_type}/#{stack_name}/tests/inspec/#{inspec_profile}/inspec.yml")
98
+ }.each { |inspec_profile|
99
+ inspec_profile_name = make_inspec_profile_name(inspec_profile)
100
+ puts "INSPEC (inspec_profile '#{inspec_profile_name}'): #{inspec_cmd(
101
+ inspec_profile: inspec_profile,
102
+ inspec_profile_name: inspec_profile_name,
103
+ aws_profile: assume_role_profile(args),
104
+ aws_region: stack_config(args).region
105
+ )}"
106
+ system(
107
+ inspec_cmd(
108
+ inspec_profile: inspec_profile,
109
+ inspec_profile_name: inspec_profile_name,
110
+ aws_profile: assume_role_profile(args),
111
+ aws_region: stack_config(args).region
112
+ )
113
+ )
114
+ }
115
+ end
116
+ end
117
+
118
+ def make_inspec_profile_name(inspec_profile)
119
+ inspec_profile != '.' ? inspec_profile : '__root__'
120
+ end
121
+
122
+ def inspec_cmd(inspec_profile:, inspec_profile_name:, aws_profile:, aws_region:)
123
+ "inspec exec " \
124
+ "#{stack_type}/#{stack_name}/tests/inspec/#{inspec_profile} " \
125
+ "-t aws://#{aws_region}/#{aws_profile} " \
126
+ "--reporter json-rspec:work/tests/inspec/results-#{stack_type}-#{stack_name}-#{inspec_profile_name}.json " \
127
+ "cli " \
128
+ "--attrs work/tests/inspec/attributes-#{stack_type}-#{stack_name}.yml"
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module RakeCloudspin
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,19 @@
1
+ require 'confidante'
2
+ require 'rake_terraform'
3
+ require 'rake/tasklib'
4
+ require 'rake_cloudspin/paths'
5
+ require 'rake_cloudspin/version'
6
+ require 'rake_cloudspin/tasklib'
7
+ require 'rake_cloudspin/tasks/all'
8
+ require 'rake_cloudspin/tasks/base_task'
9
+ require 'rake_cloudspin/tasks/stack_task'
10
+ require 'rake_cloudspin/tasks/deployment_statebucket_task'
11
+ require 'rake_cloudspin/tasks/ssh_key_task'
12
+ require 'rake_cloudspin/tasks/stack_test_task'
13
+ require 'rake_cloudspin/statebucket/deployment_statebucket'
14
+
15
+ module RakeCloudspin
16
+ def self.define_tasks
17
+ RakeCloudspin::Tasks::All.new
18
+ end
19
+ end
@@ -0,0 +1 @@
1
+ # Local
@@ -0,0 +1,3 @@
1
+ output "state_bucket_name" {
2
+ value = "${var.state_bucket_name}"
3
+ }
@@ -0,0 +1,8 @@
1
+
2
+ provider "aws" {
3
+ region = "${var.region}"
4
+ assume_role {
5
+ role_arn = "${var.assume_role_arn}"
6
+ session_name = "session-${var.component}-${var.estate}"
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ module "state_bucket" {
2
+ source = "infrablocks/encrypted-bucket/aws"
3
+ version = "~> 0.1.12"
4
+
5
+ bucket_name = "${var.state_bucket_name}"
6
+
7
+ tags = {
8
+ Component = "${var.component}"
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ variable "region" {}
2
+ variable "state_bucket_name" {}
3
+ variable "component" {}
4
+ variable "estate" {}
5
+ variable "deployment_identifier" {}
6
+ variable "assume_role_arn" {}