rubycfn 0.3.2 → 0.3.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/Gemfile.lock +1 -1
- data/README.md +13 -40
- data/bin/rubycfn +56 -20
- data/lib/cli_methods.rb +5 -1
- data/lib/rubycfn/version.rb +1 -1
- data/lib/rubycfn.rb +0 -1
- data/templates/.env.erb +0 -1
- data/templates/.env.production.erb +6 -0
- data/templates/.env.rspec.erb +6 -0
- data/templates/.env.test.erb +5 -1
- data/templates/.gitignore.erb +82 -0
- data/templates/.gitlab-ci.yml.erb +75 -0
- data/templates/.rubocop.yml.erb +91 -0
- data/templates/Gemfile.erb +13 -5
- data/templates/Rakefile.erb +34 -1
- data/templates/aws_sdk.rb.erb +18 -0
- data/templates/compiler.rb.erb +61 -0
- data/templates/core_compile.rb.erb +6 -0
- data/templates/core_deploy.rb.erb +115 -0
- data/templates/core_diff.rb.erb +59 -0
- data/templates/core_upload.rb.erb +3 -0
- data/templates/dependencies.rb.erb +23 -0
- data/templates/deploy.rb.erb +53 -0
- data/templates/ecs_stack.rb.erb +12 -0
- data/templates/ecs_stack_concern.rb.erb +20 -0
- data/templates/global_variables.rb.erb +4 -0
- data/templates/helper_methods.rb.erb +3 -0
- data/templates/helpers.rb.erb +7 -0
- data/templates/main.rb.erb +4 -4
- data/templates/main_aws_helper.rb.erb +16 -0
- data/templates/parent_stack_spec.rb.erb +38 -0
- data/templates/project_concern.rb.erb +16 -50
- data/templates/project_stack.rb.erb +5 -2
- data/templates/shared_methods.rb.erb +38 -0
- data/templates/spec_helper.rb.erb +3 -1
- data/templates/subnets.rb.erb +18 -0
- data/templates/upload_stack.rb.erb +27 -0
- data/templates/vpc_concerns.rb.erb +87 -0
- data/templates/vpc_spec.rb.erb +40 -0
- data/templates/vpc_stack.rb.erb +12 -0
- metadata +30 -13
- data/lib/compound/resources.rb +0 -1
- data/lib/compound/vpc.rb +0 -90
- data/lib/compound.rb +0 -1
- data/templates/buildspec.yml.erb +0 -21
- data/templates/cfn2rubycfn.erb +0 -127
- data/templates/cicd.rb.erb +0 -91
- data/templates/compile.rb.erb +0 -18
- data/templates/example_stack_spec.rb.erb +0 -101
- data/templates/format.vim.erb +0 -3
@@ -0,0 +1,61 @@
|
|
1
|
+
require "git-revision"
|
2
|
+
|
3
|
+
def update_references(contents, environment, artifact_bucket)
|
4
|
+
contents["Resources"].map do |resource|
|
5
|
+
resource_name = resource.shift
|
6
|
+
resource_values = resource.shift
|
7
|
+
if resource_values["Type"] == "AWS::CloudFormation::Stack"
|
8
|
+
template_hash = @stack_hashes[resource_name.to_sym]
|
9
|
+
s3_url = "https://s3.amazonaws.com/#{artifact_bucket}/#{environment}-" \
|
10
|
+
"#{resource_name.downcase}-#{template_hash}.json"
|
11
|
+
resource_values["Properties"]["TemplateURL"] = s3_url
|
12
|
+
end
|
13
|
+
end
|
14
|
+
JSON.pretty_generate(JSON.parse(contents.to_json))
|
15
|
+
end
|
16
|
+
|
17
|
+
def inject_dummy_resource(stack)
|
18
|
+
hack_stack = JSON.parse(stack)
|
19
|
+
hack_stack["Resources"] = {} if hack_stack["Resources"].nil?
|
20
|
+
hack_stack["Resources"]["CloudFormationDummyResource"] = {
|
21
|
+
"Type": "AWS::CloudFormation::WaitConditionHandle",
|
22
|
+
"Metadata": {
|
23
|
+
"Comment": "Resource to update stack even if there are no changes",
|
24
|
+
"GitCommitHash": Git::Revision.commit
|
25
|
+
}
|
26
|
+
}
|
27
|
+
hack_stack.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def compile_stacks(skip_creation = false)
|
31
|
+
stacks = {}
|
32
|
+
FileUtils.mkdir_p "build" unless skip_creation
|
33
|
+
Module.constants.select do |mod|
|
34
|
+
if mod =~ /Stack$/
|
35
|
+
send("include", Object.const_get("SharedConcerns"))
|
36
|
+
stacks[mod.to_sym] = send("include", Object.const_get(mod)).render_template("AWS")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
stacks.each do |stack_name, stack|
|
41
|
+
stack = inject_dummy_resource(stack)
|
42
|
+
next if JSON.parse(stack)["Resources"].nil?
|
43
|
+
stack_to_md5(stack_name, stack)
|
44
|
+
unless skip_creation
|
45
|
+
puts "- Saved #{stack_name} to build/#{ENV["ENVIRONMENT"]}-#{stack_name.downcase}.json"
|
46
|
+
File.open("build/#{ENV["ENVIRONMENT"]}-#{stack_name.downcase}.json", "w") { |f| f.write(JSON.pretty_generate(JSON.parse(stack))) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
stacks.each do |stack_name, stack|
|
51
|
+
stack = inject_dummy_resource(stack)
|
52
|
+
next if JSON.parse(stack)["Resources"].nil?
|
53
|
+
stack = update_references(JSON.parse(stack), ENV["ENVIRONMENT"], ENV["ARTIFACT_BUCKET"])
|
54
|
+
stacks[stack_name] = stack
|
55
|
+
stack_to_md5(stack_name, stack)
|
56
|
+
unless skip_creation
|
57
|
+
File.open("build/#{ENV["ENVIRONMENT"]}-#{stack_name.downcase}.json", "w") { |f| f.write(JSON.pretty_generate(JSON.parse(stack))) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
stacks
|
61
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "colorize"
|
2
|
+
require "digest/md5"
|
3
|
+
require "dotenv"
|
4
|
+
require "aws-sdk-s3"
|
5
|
+
require "aws-sdk"
|
6
|
+
|
7
|
+
require_relative "../aws_helper/main"
|
8
|
+
|
9
|
+
env_vars = load_env_vars
|
10
|
+
|
11
|
+
set_aws_credentials(
|
12
|
+
env_vars[:aws_region],
|
13
|
+
env_vars[:aws_access_key_id],
|
14
|
+
env_vars[:aws_secret_access_key]
|
15
|
+
)
|
16
|
+
|
17
|
+
s3_filename = get_parent_stack_s3_location(
|
18
|
+
env_vars[:artifact_bucket],
|
19
|
+
env_vars[:environment]
|
20
|
+
)
|
21
|
+
|
22
|
+
client = Aws::CloudFormation::Client.new
|
23
|
+
|
24
|
+
# Store previous CloudFormation events in an array, so that we don't output
|
25
|
+
# events from previous deploys.
|
26
|
+
|
27
|
+
stack_exists = false
|
28
|
+
previous_statuses = []
|
29
|
+
80.times do
|
30
|
+
previous_events = get_prior_events(client, env_vars[:stack_name])
|
31
|
+
previous_statuses = previous_events.map(&:event_id)
|
32
|
+
stack_exists = previous_events.size.to_i.positive? ? true : false
|
33
|
+
break unless stack_exists
|
34
|
+
|
35
|
+
events_last_deploy = get_events_last_deploy(previous_events)
|
36
|
+
last_event = events_last_deploy.shift
|
37
|
+
break if last_event \
|
38
|
+
&& (last_event.logical_resource_id == env_vars[:stack_name]) \
|
39
|
+
&& (last_event.stack_name == env_vars[:stack_name]) \
|
40
|
+
&& (DEPLOYABLE_STATES.include? last_event.resource_status)
|
41
|
+
puts "Stack is currently in #{last_event.resource_status} mode. Waiting for it to finish..." if last_event
|
42
|
+
sleep 15
|
43
|
+
end
|
44
|
+
|
45
|
+
if stack_exists
|
46
|
+
client.update_stack(
|
47
|
+
stack_name: env_vars[:stack_name],
|
48
|
+
template_url: s3_filename,
|
49
|
+
capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND),
|
50
|
+
# stack_policy_body: "StackPolicyBody",
|
51
|
+
# stack_policy_url: "StackPolicyURL",
|
52
|
+
tags: [
|
53
|
+
{
|
54
|
+
key: "team",
|
55
|
+
value: "<%= name.downcase %>"
|
56
|
+
}
|
57
|
+
],
|
58
|
+
# client_request_token: "ClientRequestToken",
|
59
|
+
)
|
60
|
+
else
|
61
|
+
client.create_stack(
|
62
|
+
stack_name: env_vars[:stack_name],
|
63
|
+
template_url: s3_filename,
|
64
|
+
timeout_in_minutes: 60,
|
65
|
+
capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND),
|
66
|
+
on_failure: "ROLLBACK",
|
67
|
+
# stack_policy_body: "StackPolicyBody",
|
68
|
+
# stack_policy_url: "StackPolicyURL",
|
69
|
+
tags: [
|
70
|
+
{
|
71
|
+
key: "team",
|
72
|
+
value: "<%= name.downcase %>"
|
73
|
+
}
|
74
|
+
],
|
75
|
+
# client_request_token: "ClientRequestToken",
|
76
|
+
enable_termination_protection: false
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
## Rudimentary feedback for CI/CD
|
81
|
+
shown_log_lines = {}
|
82
|
+
|
83
|
+
360.times do
|
84
|
+
resp = client.describe_stack_events(
|
85
|
+
stack_name: env_vars[:stack_name]
|
86
|
+
)
|
87
|
+
resp.stack_events.to_a.reverse.each_with_index do |event, index|
|
88
|
+
next if previous_statuses.include? event.event_id
|
89
|
+
@completed = false
|
90
|
+
@stack_name = event.stack_name
|
91
|
+
@logical_resource_id = event.logical_resource_id
|
92
|
+
@resource_type = event.resource_type
|
93
|
+
@timestamp = event.timestamp
|
94
|
+
@resource_status = event.resource_status
|
95
|
+
@resource_status_reason = event.resource_status_reason
|
96
|
+
log_line = "[#{@logical_resource_id.green}] #{@timestamp.to_s.blue}: " \
|
97
|
+
"#{@resource_status.white} #{@resource_status_reason.to_s.red}"
|
98
|
+
puts log_line unless shown_log_lines[log_line]
|
99
|
+
shown_log_lines[log_line] = true
|
100
|
+
if (@stack_name == env_vars[:stack_name]) \
|
101
|
+
&& (@logical_resource_id == env_vars[:stack_name]) \
|
102
|
+
&& (END_STATES.include? @resource_status) \
|
103
|
+
&& (index + 1 == resp.stack_events.to_a.size)
|
104
|
+
@completed = true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if @completed
|
108
|
+
puts "==================================="
|
109
|
+
puts " STATUS: #{@resource_status} #{@resource_status_reason}"
|
110
|
+
puts "==================================="
|
111
|
+
raise "#{@resource_status} #{@resource_status_reason}" if FAILURE_STATES.include? @resource_status
|
112
|
+
break
|
113
|
+
end
|
114
|
+
sleep(10)
|
115
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "diffy"
|
2
|
+
require_relative "../aws_helper/main"
|
3
|
+
|
4
|
+
env_vars = load_env_vars
|
5
|
+
|
6
|
+
set_aws_credentials(
|
7
|
+
env_vars[:aws_region],
|
8
|
+
env_vars[:aws_access_key_id],
|
9
|
+
env_vars[:aws_secret_access_key]
|
10
|
+
)
|
11
|
+
|
12
|
+
client = Aws::CloudFormation::Client.new
|
13
|
+
template = client.get_template(
|
14
|
+
stack_name: "#{ENV["ENVIRONMENT"]}-#{ENV["PROJECT_NAME"]}"
|
15
|
+
)
|
16
|
+
|
17
|
+
s3 = Aws::S3::Resource.new(region: env_vars[:aws_region])
|
18
|
+
orig_template = {}
|
19
|
+
|
20
|
+
template = JSON.parse(template.template_body)
|
21
|
+
template["Resources"].each do |resource_name, content|
|
22
|
+
if content["Type"] == "AWS::CloudFormation::Stack"
|
23
|
+
stack_name = "#{ENV["PROJECT_NAME"].capitalize}Stack"
|
24
|
+
orig_template[stack_name] = JSON.pretty_generate(
|
25
|
+
JSON.parse(
|
26
|
+
template.to_json
|
27
|
+
)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
next unless content["Type"] == "AWS::CloudFormation::Stack"
|
31
|
+
s3_filename = content["Properties"]["TemplateURL"].split("/").last
|
32
|
+
orig_template[resource_name] = JSON.pretty_generate(
|
33
|
+
JSON.parse(
|
34
|
+
s3.client.get_object(
|
35
|
+
bucket: env_vars[:artifact_bucket],
|
36
|
+
key: s3_filename
|
37
|
+
).body.read
|
38
|
+
)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
stacks = compile_stacks(true)
|
43
|
+
@stack_hashes.each do |stack_name, _hash|
|
44
|
+
new_template = JSON.pretty_generate(
|
45
|
+
JSON.parse(
|
46
|
+
stacks[stack_name]
|
47
|
+
)
|
48
|
+
)
|
49
|
+
diff = Diffy::Diff.new(
|
50
|
+
orig_template[stack_name.to_s], new_template
|
51
|
+
).to_s(:color)
|
52
|
+
|
53
|
+
if diff.strip.empty?
|
54
|
+
puts "No difference between local #{stack_name} and remote #{stack_name}"
|
55
|
+
else
|
56
|
+
puts "Orig #{stack_name} vs #{stack_name}:"
|
57
|
+
puts diff
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
def load_env_vars
|
2
|
+
Dotenv.load(".env")
|
3
|
+
Dotenv.load(".env.#{ENV["ENVIRONMENT"]}")
|
4
|
+
Dotenv.load(".env.private")
|
5
|
+
check_dependencies
|
6
|
+
{
|
7
|
+
aws_region: ENV["AWS_REGION"],
|
8
|
+
aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"],
|
9
|
+
aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
|
10
|
+
artifact_bucket: ENV["ARTIFACT_BUCKET"],
|
11
|
+
environment: ENV["ENVIRONMENT"],
|
12
|
+
stack_name: ENV["STACK_NAME"]
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_dependencies
|
17
|
+
ENV["AWS_REGION"] ||= ENV["AWS_DEFAULT_REGION"]
|
18
|
+
raise "AWS_REGION not set" unless ENV["AWS_REGION"]
|
19
|
+
raise "ARTIFACT_BUCKET not set" unless ENV["ARTIFACT_BUCKET"]
|
20
|
+
raise "ENVIRONMENT not set" unless ENV["ENVIRONMENT"]
|
21
|
+
raise "STACK_NAME not set" unless ENV["STACK_NAME"]
|
22
|
+
raise "AWS CREDENTIALS NOT SET" unless ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
WAITING_STATES = %w(
|
2
|
+
CREATE_IN_PROGRESS DELETE_IN_PROGRESS ROLLBACK_IN_PROGRESS
|
3
|
+
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_IN_PROGRESS
|
4
|
+
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_IN_PROGRESS
|
5
|
+
).freeze
|
6
|
+
SUCCESS_STATES = %w(CREATE_COMPLETE UPDATE_COMPLETE).freeze
|
7
|
+
FAILURE_STATES = %w(
|
8
|
+
CREATE_FAILED DELETE_FAILED UPDATE_ROLLBACK_FAILED
|
9
|
+
ROLLBACK_FAILED ROLLBACK_COMPLETE ROLLBACK_FAILED
|
10
|
+
UPDATE_ROLLBACK_COMPLETE UPDATE_ROLLBACK_FAILED
|
11
|
+
).freeze
|
12
|
+
END_STATES = SUCCESS_STATES + FAILURE_STATES
|
13
|
+
DEPLOYABLE_STATES = %w(
|
14
|
+
CREATE_COMPLETE UPDATE_COMPLETE ROLLBACK_COMPLETE
|
15
|
+
UPDATE_ROLLBACK_COMPLETE
|
16
|
+
).freeze
|
17
|
+
|
18
|
+
# Method to retrieve paste CF events.
|
19
|
+
# Arguments: Aws::Client and stack name
|
20
|
+
def get_prior_events(client, stack_name)
|
21
|
+
client.describe_stack_events(
|
22
|
+
stack_name: stack_name
|
23
|
+
).stack_events.to_a
|
24
|
+
rescue => exception
|
25
|
+
raise exception unless exception.class == Aws::CloudFormation::Errors::ValidationError
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_events_last_deploy(previous_events)
|
30
|
+
initiated = false
|
31
|
+
previous_events.map do |event|
|
32
|
+
initiated = true if event.resource_status_reason == "User Initiated"
|
33
|
+
initiated ? nil : event
|
34
|
+
end.to_a.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
# Method to predict location of the s3 CloudFormation artifact
|
38
|
+
def get_parent_stack_s3_location(bucket, environment)
|
39
|
+
stacks = compile_stacks(true)
|
40
|
+
parent_stack = nil
|
41
|
+
|
42
|
+
stacks.each do |stack_name, stack|
|
43
|
+
next if JSON.parse(stack)["Resources"].nil?
|
44
|
+
JSON.parse(stack)["Resources"].each do |_resource, payload|
|
45
|
+
if payload["Type"] == "AWS::CloudFormation::Stack"
|
46
|
+
parent_stack = stack_name
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
"https://s3.amazonaws.com/#{bucket}/#{environment}-#{parent_stack.downcase}-#{@stack_hashes[parent_stack.to_sym]}.json"
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EcsStack
|
2
|
+
module EcsCluster
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
transform
|
7
|
+
parameter :vpc,
|
8
|
+
description: "VPC ID"
|
9
|
+
|
10
|
+
# Create an empty ECS Cluster to launch fargate bastions (or other things) in
|
11
|
+
resource :<%= name.downcase %>_ecs_cluster,
|
12
|
+
type: "AWS::ECS::Cluster"
|
13
|
+
|
14
|
+
output :<%= name.downcase %>_ecs_cluster,
|
15
|
+
value: "<%= name %>EcsCluster".ref
|
16
|
+
output :<%= name.downcase %>_ecs_cluster_arn,
|
17
|
+
value: "<%= name.downcase %>EcsCluster".ref("Arn")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/templates/main.rb.erb
CHANGED
@@ -6,7 +6,7 @@ Dotenv.load(".env.private")
|
|
6
6
|
Dotenv.load(".env.#{ENV["ENVIRONMENT"]}")
|
7
7
|
|
8
8
|
# Include all group concerns
|
9
|
-
Dir[File.expand_path(
|
9
|
+
Dir[File.expand_path("../shared_concerns/", __FILE__) + "/**/*.rb"].sort.each do |file|
|
10
10
|
require file
|
11
11
|
end
|
12
12
|
|
@@ -21,9 +21,9 @@ module SharedConcerns
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Include all stack concerns
|
24
|
-
Dir[File.expand_path(
|
25
|
-
subdir = File.basename(file, ".rb")
|
26
|
-
Dir[File.expand_path(
|
24
|
+
Dir[File.expand_path("../stacks/", __FILE__) + "/**/*.rb"].sort.each do |file|
|
25
|
+
subdir = File.basename(file, ".rb")
|
26
|
+
Dir[File.expand_path("../stacks/", __dir__) + "/#{subdir}/**/*.rb"].sort.each do |subfile|
|
27
27
|
require subfile
|
28
28
|
end
|
29
29
|
require file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "aws-sdk-s3"
|
2
|
+
require "aws-sdk"
|
3
|
+
require "colorize"
|
4
|
+
require "digest/md5"
|
5
|
+
require "dotenv"
|
6
|
+
require "fileutils"
|
7
|
+
require "json"
|
8
|
+
|
9
|
+
require_relative "aws_sdk"
|
10
|
+
require_relative "compiler"
|
11
|
+
require_relative "dependencies"
|
12
|
+
require_relative "deploy"
|
13
|
+
require_relative "helpers"
|
14
|
+
require_relative "upload_stack"
|
15
|
+
|
16
|
+
@stack_hashes = {}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "rubycfn"
|
4
|
+
require "active_support/concern"
|
5
|
+
require_relative "../../lib/main.rb"
|
6
|
+
|
7
|
+
module ParentSpec
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include Rubycfn
|
10
|
+
|
11
|
+
included do
|
12
|
+
description "<%= name %> RSpec"
|
13
|
+
include Concerns::GlobalVariables
|
14
|
+
include Concerns::SharedMethods
|
15
|
+
include <%= name %>Stack::Parent
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ParentSpecCfn = include ParentSpec
|
20
|
+
|
21
|
+
describe ParentSpec do
|
22
|
+
RspecParentSpec = ParentSpecCfn.render_template
|
23
|
+
let(:template) { JSON.parse(RspecParentSpec) }
|
24
|
+
|
25
|
+
context "Renders template" do
|
26
|
+
subject { template }
|
27
|
+
|
28
|
+
it { should have_key "Resources" }
|
29
|
+
|
30
|
+
context "Has Required Resources" do
|
31
|
+
let(:resources) { template["Resources"] }
|
32
|
+
subject { resources }
|
33
|
+
|
34
|
+
it { should have_key "VpcStack" }
|
35
|
+
it { should have_key "EcsStack" }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,59 +1,25 @@
|
|
1
|
+
require_relative "../vpc_stack/subnets"
|
2
|
+
|
1
3
|
module <%= name %>Stack
|
2
|
-
module
|
4
|
+
module Parent
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# Example variables
|
11
|
-
variable :example_variable,
|
12
|
-
required: false,
|
13
|
-
value: ENV["SOME_ENV_VAR"],
|
14
|
-
default: "default_value"
|
15
|
-
|
16
|
-
variable :cidr_block,
|
17
|
-
default: "10.0.0.0/16"
|
18
|
-
variable :enable_dns_support,
|
19
|
-
default: true
|
20
|
-
variable :enable_dns_hostnames,
|
21
|
-
default: true
|
22
|
-
|
23
|
-
# Example resource, custom name
|
24
|
-
resource :my_example_s3_bucket,
|
25
|
-
type: "AWS::S3::Bucket" do |r|
|
26
|
-
#r.depends_on "SomeLogicalResourceName"
|
27
|
-
r.property(:bucket_name) { example_variable }
|
8
|
+
resource :vpc_stack,
|
9
|
+
type: "AWS::CloudFormation::Stack" do |r|
|
10
|
+
r.property(:template_u_r_l) { "vpcstack" }
|
11
|
+
r.property(:timeout_in_minutes) { "5" }
|
28
12
|
end
|
29
13
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
# Example resource, dynamically generated name
|
42
|
-
resource :my_example_s3_bucket,
|
43
|
-
type: "AWS::S3::Bucket"
|
44
|
-
|
45
|
-
# Multiple resources of the same type
|
46
|
-
resource :my_sqs_queue,
|
47
|
-
amount: 3,
|
48
|
-
type: "AWS::SQS::Queue"
|
49
|
-
|
50
|
-
queue_names = %w(MyFirstQueue MySecondQueue)
|
51
|
-
|
52
|
-
# Multiple resources with index, starts at 0
|
53
|
-
resource :sqs_with_index,
|
54
|
-
amount: 2,
|
55
|
-
type: "AWS::SQS::Queue" do |r, index|
|
56
|
-
r.property(:queue_name) { queue_names[index] }
|
14
|
+
resource :ecs_stack,
|
15
|
+
type: "AWS::CloudFormation::Stack" do |r|
|
16
|
+
r.depends_on %w(VpcStack)
|
17
|
+
r.property(:template_u_r_l) { "ecsstack" }
|
18
|
+
r.property(:parameters) do
|
19
|
+
{
|
20
|
+
"Vpc": "VpcStack".ref("Outputs.<%= name %>Vpc")
|
21
|
+
}
|
22
|
+
end
|
57
23
|
end
|
58
24
|
end
|
59
25
|
end
|
@@ -3,7 +3,10 @@ module <%= name %>Stack
|
|
3
3
|
include Rubycfn
|
4
4
|
|
5
5
|
included do
|
6
|
-
include
|
7
|
-
include
|
6
|
+
include Concerns::GlobalVariables
|
7
|
+
include Concerns::SharedMethods
|
8
|
+
include <%= name %>Stack::Parent
|
9
|
+
|
10
|
+
description generate_stack_description("ParentStack")
|
8
11
|
end
|
9
12
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "git-revision"
|
2
|
+
|
3
|
+
module Git
|
4
|
+
class Revision
|
5
|
+
class << self
|
6
|
+
def dirty?
|
7
|
+
!`git diff --numstat | wc -l`.strip.to_i.zero?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Concerns
|
14
|
+
module SharedMethods
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
included do
|
17
|
+
# Convert array
|
18
|
+
def recipients_to_array(val)
|
19
|
+
val.split(",").map do |email|
|
20
|
+
{
|
21
|
+
"Endpoint": email,
|
22
|
+
"Protocol": "email-json"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns 1 if enviroments[] match the environment, otherwise 0
|
28
|
+
def get_resources_amount(environments = %w(production rspec))
|
29
|
+
((environments.include? ENV["ENVIRONMENT"]) && 1) || 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_stack_description(stack_name)
|
33
|
+
"#{stack_name}-#{Git::Revision.commit}" \
|
34
|
+
"#{Git::Revision.dirty? ? "-dirty" : ""}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -9,6 +9,8 @@ require "rspec"
|
|
9
9
|
require "rspec/its"
|
10
10
|
require "rspec/given"
|
11
11
|
|
12
|
+
ENV["ENVIRONMENT"] = "rspec"
|
13
|
+
|
12
14
|
RSpec.configure do |config|
|
13
15
|
config.color = true
|
14
16
|
config.formatter = "documentation"
|
@@ -23,4 +25,4 @@ RSpec.configure do |config|
|
|
23
25
|
config.run_all_when_everything_filtered = true
|
24
26
|
config.filter_run_excluding :slow unless ENV["SLOW_SPECS"]
|
25
27
|
config.filter_run_excluding :debug unless ENV["DEBUG_SPECS"]
|
26
|
-
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
def upload_stacks
|
2
|
+
env_vars = load_env_vars
|
3
|
+
|
4
|
+
set_aws_credentials(
|
5
|
+
env_vars[:aws_region],
|
6
|
+
env_vars[:aws_access_key_id],
|
7
|
+
env_vars[:aws_secret_access_key]
|
8
|
+
)
|
9
|
+
|
10
|
+
s3 = create_bucket_if_not_exists(
|
11
|
+
env_vars[:aws_region],
|
12
|
+
env_vars[:artifact_bucket]
|
13
|
+
)
|
14
|
+
|
15
|
+
stacks = compile_stacks(true)
|
16
|
+
stacks.each do |stack_name, stack|
|
17
|
+
next if JSON.parse(stack)["Resources"].nil?
|
18
|
+
hash = @stack_hashes[stack_name.to_sym]
|
19
|
+
local_file = "#{env_vars[:environment]}-#{stack_name.downcase}.json"
|
20
|
+
s3_filename = "#{env_vars[:environment]}-#{stack_name.downcase}-#{hash}.json"
|
21
|
+
# content = JSON.parse(stack).to_json
|
22
|
+
obj = s3.bucket(env_vars[:artifact_bucket]).object(s3_filename)
|
23
|
+
content = File.open("build/#{local_file}").read
|
24
|
+
obj.put(body: content)
|
25
|
+
puts "Uploaded #{stack_name} to s3://#{env_vars[:artifact_bucket]}/#{s3_filename}"
|
26
|
+
end
|
27
|
+
end
|