rubycfn 0.5.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ef7317d69104c7137bc0010d44e5170dcd5dd41057be8b9bc6a0c146cba8e4b
4
- data.tar.gz: 404876907399ca2637709f6be230a90159ad3555ea68435e38a47558df994487
3
+ metadata.gz: 337ae0668503c4e97c9a3b8a3ed2693d5611ecb592ad2802b83e158f1a2b32f5
4
+ data.tar.gz: 25e6d98d9f4ecb0f80fb8a1c55bd907ff2af293ca04e8186774ffec5375948ec
5
5
  SHA512:
6
- metadata.gz: 3f928a0f477cce0be833e586571df27861e41a5ce37c251f919ae712638d3653a2d9d5a25fea3d5a8f01b25bc36d730b4d63ddc8bdce003a9b3cfdfea7299031
7
- data.tar.gz: e9a9d01201c7fd0cf73e136820044951e48629f2752626203ce98bc3a22a12e4f1ddb7c2623cd6e3e45ad2523501df93a9c3f2e59679f7f7a33061e279ec68d9
6
+ metadata.gz: 5de8efbd6b5b242f5f63a336f82602dcb96d6824b4bbf763c62bba0661bf1c811a1c453dfbcef7e20b6650cba46660758a5f99e271b70481e9850c51ba3bf9f5
7
+ data.tar.gz: '08fdaabf55a7e3ed1e6bf60b66b038f8729433d257c59fe74323042d178c6f0e0a1d52fd12aae13ea482b14ea986326614f79efb7a412cd2b1eea6932fc0fc83'
@@ -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.5.3 (Next Release)
5
+ ## 0.5.4 (Next Release)
6
+
7
+ ## 0.5.3
8
+
9
+ * Improved code quality in templated files -- [@dennisvink][@dennisvink]
6
10
 
7
11
  ## 0.5.2
8
12
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubycfn (0.5.2)
4
+ rubycfn (0.5.3)
5
5
  activesupport (~> 5.1.5)
6
6
  dotenv (~> 2.4.0)
7
7
  json (~> 2.1.0)
@@ -77,7 +77,7 @@ GEM
77
77
  rspec-mocks (~> 3.9.0)
78
78
  rspec-core (3.9.1)
79
79
  rspec-support (~> 3.9.1)
80
- rspec-expectations (3.9.0)
80
+ rspec-expectations (3.9.1)
81
81
  diff-lcs (>= 1.2.0, < 2.0)
82
82
  rspec-support (~> 3.9.0)
83
83
  rspec-given (3.8.0)
data/README.md CHANGED
@@ -61,7 +61,7 @@ __________ ____ __________________.___._________ _____________________
61
61
  | _/ | /| | _// | |/ \ \/ | __) | | _/
62
62
  | | \ | / | | \\____ |\ \____| \ | | \
63
63
  |____|_ /______/ |______ // ______| \______ /\___ / |______ /
64
- \/ \/ \/ \/ \/ \/ [v0.5.2]
64
+ \/ \/ \/ \/ \/ \/ [v0.5.3]
65
65
  Project name? example
66
66
  Account ID? 1234567890
67
67
  Select region EU (Frankfurt)
@@ -159,12 +159,11 @@ You can reuse these environment variables in your project code.
159
159
  The `.env.rspec` is used when running unit tests. It contains mock variables
160
160
  so that you can test the resulting CloudFormation templates properly.
161
161
 
162
- #### The missing .env.private file
162
+ #### The .env.private file
163
163
 
164
- There is one file that is not generated by default but does need mentioning:
165
- the `.env.private` file. This is a special file that allows you to override
166
- environment variables. An environment variable set in .env.private always takes
167
- precedence over environment variables set in other .env files.
164
+ This is a special file that allows you to override environment variables.
165
+ An environment variable set in .env.private always takes precedence over
166
+ environment variables set in other .env files.
168
167
 
169
168
  #### .rubocop.yml
170
169
 
@@ -1,4 +1,4 @@
1
1
  # Rubycfn version
2
2
  module Rubycfn
3
- VERSION = "0.5.2".freeze
3
+ VERSION = "0.5.3".freeze
4
4
  end
@@ -83,5 +83,9 @@ attic/
83
83
  /venv
84
84
  /Gemfile.lock
85
85
  .env.dependencies
86
+ .env.dependencies.development
87
+ .env.dependencies.test
88
+ .env.dependencies.acceptance
89
+ .env.dependencies.production
86
90
  CloudFormationResourceSpecification.json
87
91
  config.json
@@ -102,3 +102,6 @@ Metrics/PerceivedComplexity:
102
102
 
103
103
  Style/EvalWithLocation:
104
104
  Enabled: false
105
+
106
+ Style/PerlBackrefs:
107
+ Enabled: false
@@ -21,7 +21,7 @@ __________ ____ __________________.___._________ _____________________
21
21
  `rake compile` - Compile the code into CloudFormation templates
22
22
  `rake spec` - Run unit tests
23
23
  `rake upload` - Upload the CloudFormation templates to s3
24
- `rake update` - Save required outputs from DependencyStack to .env.dependencies
24
+ `rake update` - Save required outputs from DependencyStack to .env.dependencies.<ENVIRONMENT>
25
25
  `rake apply` - Deploy the CloudFormation templates
26
26
 
27
27
  ## Stack configuration
@@ -36,7 +36,7 @@ task :clean do
36
36
  end
37
37
  end
38
38
 
39
- desc "Store dependencies of DependencyStack in .env.dependencies"
39
+ desc "Store dependencies of DependencyStack in .env.dependencies.<ENVIRONMENT>"
40
40
  task :dependencies do
41
41
  require_relative "lib/core/dependencies.rb"
42
42
  end
@@ -51,8 +51,7 @@ RuboCop::RakeTask.new(:rubocop) do |t|
51
51
  t.options = ["--display-cop-names"]
52
52
  end
53
53
 
54
- task apply: %i(dependencies apply_stack)
55
- task compile: %i(compile_stack)
56
54
  task default: %i(dependencies compile_stack spec)
57
- task update: %i(dependencies)
55
+ task compile: %i(dependencies compile_stack)
58
56
  task upload: %i(dependencies upload_stack)
57
+ task apply: %i(dependencies apply_stack)
@@ -52,6 +52,9 @@ applications:
52
52
  env:
53
53
  SOME_ENV_VAR: Exposed
54
54
  SOME_OTHER_VAR: desopxE
55
+ # SOME_REFFED_VAR: :foobar.ef
56
+ # SOME_OTHER_STACK_VAR: :vpc_stack.ref("Outputs.VpcId")
57
+ # SOME_ENV_VAR: ${MY_ENV_VAR}
55
58
  hello-world2:
56
59
  image: tutum/hello-world
57
60
  container_port: 80
@@ -2,7 +2,7 @@ require "git-revision"
2
2
 
3
3
  def update_references(contents, environment, _artifact_bucket)
4
4
  Dotenv.load(".env.private")
5
- Dotenv.load(".env.dependencies")
5
+ Dotenv.load(".env.dependencies.#{ENV["ENVIRONMENT"]}")
6
6
  contents["Resources"].map do |resource|
7
7
  resource_name = resource.shift
8
8
  resource_values = resource.shift
@@ -1,6 +1,14 @@
1
+ def infra_config
2
+ config = YAML.safe_load(File.read("config.yaml"), [Symbol])
3
+ config["applications"] ||= {}
4
+ config["environments"] ||= {}
5
+ config["subnets"] ||= {}
6
+ config
7
+ end
8
+
1
9
  def load_env_vars
2
10
  Dotenv.load(".env.private")
3
- Dotenv.load(".env.dependencies")
11
+ Dotenv.load(".env.dependencies.#{ENV["ENVIRONMENT"]}")
4
12
  Dotenv.load(".env")
5
13
  Dotenv.load(".env.#{ENV["ENVIRONMENT"]}")
6
14
 
@@ -12,7 +20,7 @@ def load_env_vars
12
20
  aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
13
21
  cloudformation_bucket: ENV["CLOUDFORMATIONBUCKET"],
14
22
  environment: ENV["ENVIRONMENT"],
15
- stack_name: ENV["STACK_NAME"]
23
+ stack_name: infra_config["environments"][ENV["ENVIRONMENT"]]["stack_name"]
16
24
  }
17
25
  end
18
26
 
@@ -22,6 +30,6 @@ def check_dependencies
22
30
  raise "CLOUDFORMATIONBUCKET not set. Run `rake init` and `rake update` first!" unless ENV["CLOUDFORMATIONBUCKET"]
23
31
  raise "ARTIFACTBUCKET not set. Run `rake init` and `rake update` first!" unless ENV["ARTIFACTBUCKET"]
24
32
  raise "ENVIRONMENT not set." unless ENV["ENVIRONMENT"]
25
- raise "STACK_NAME not set" unless ENV["STACK_NAME"]
33
+ raise "`stack_name` not configured in config.yaml" unless infra_config["environments"][ENV["ENVIRONMENT"]]["stack_name"]
26
34
  raise "AWS CREDENTIALS NOT SET" unless ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
27
35
  end
@@ -2,7 +2,7 @@ require_relative "../core/git"
2
2
 
3
3
  def upload_stacks
4
4
  Dotenv.load(".env.private")
5
- Dotenv.load(".env.dependencies")
5
+ Dotenv.load(".env.dependencies.#{ENV["ENVIRONMENT"]}")
6
6
  env_vars = load_env_vars
7
7
 
8
8
  set_aws_credentials(
@@ -11,17 +11,10 @@ def upload_stacks
11
11
  env_vars[:aws_secret_access_key]
12
12
  )
13
13
 
14
- begin
15
- s3 = create_bucket_if_not_exists(
16
- env_vars[:aws_region],
17
- env_vars[:artifact_bucket]
18
- )
19
- rescue => e
20
- puts "Exception create_bucket_if_not_exists #{e}"
21
- end
14
+ s3 = Aws::S3::Resource.new(region: env_vars[:aws_region])
22
15
 
23
16
  stacks = compile_stacks(true)
24
- raise "CLOUDFORMATIONBUCKET not found in DependencyStack outputs" unless ENV["CLOUDFORMATIONBUCKET"]
17
+ raise "CLOUDFORMATIONBUCKET not found in <%= project_name %>#{ENV["ENVIRONMENT"].capitalize}DependencyStack outputs" unless ENV["CLOUDFORMATIONBUCKET"]
25
18
  stacks.each do |stack_name, stack|
26
19
  next if JSON.parse(stack)["Resources"].nil?
27
20
  hash = @stack_hashes[stack_name.to_sym]
@@ -1,44 +1,97 @@
1
+ def to_bool(val)
2
+ return false unless val.nil? || val.empty?
3
+ return false unless val != "true"
4
+ true
5
+ end
6
+
7
+ def env_vars_to_array(val)
8
+ if val.nil? || val.empty?
9
+ return [
10
+ {
11
+ Name: "WITHOUT_ENV",
12
+ Value: "true"
13
+ }
14
+ ]
15
+ end
16
+ vars = []
17
+
18
+ # Create an array of variables to pass to this application.
19
+ # Resolve any references to its intrinsic function.
20
+ val.keys.each_with_index do |object, index|
21
+ value = val.values[index].class == Symbol && Kernel.class_eval(":#{val.values[index]}") || val.values[index]
22
+
23
+ # Support ${ENV_VAR} in config_yaml
24
+ if value =~ /\$\{([^\}]*)\}/
25
+ capture = $1
26
+ value = ENV[capture]
27
+ end
28
+
29
+ vars.push(
30
+ Name: object,
31
+ Value: value
32
+ )
33
+ end
34
+ vars
35
+ end
36
+
37
+ # Return the mandatory Stack parameters and add all defined ENV vars from config.yaml
38
+ def parent_parameters(env_vars)
39
+ params = {
40
+ "Vpc": :vpc_stack.ref("Outputs.Vpc"),
41
+ "Cluster": :ecs_stack.ref("Outputs.EcsCluster"),
42
+ "Listener": :ecs_stack.ref("Outputs.EcsLoadBalancerListener"),
43
+ "EcsServiceAutoScalingRoleArn": :ecs_stack.ref("Outputs.EcsAutoScalingRoleArn"),
44
+ "HostedZoneId": "HOSTEDZONEID".ref,
45
+ "HostedZoneName": "HOSTEDZONENAME".ref,
46
+ "LoadBalancerDnsName": :ecs_stack.ref("Outputs.EcsLoadBalancerUrl"),
47
+ "CanonicalHostedZoneId": :ecs_stack.ref("Outputs.EcsLoadBalancerHostedZoneId"),
48
+ "CertificateProviderFunctionArn": :acm_stack.ref("Outputs.CertificateProviderFunctionArn"),
49
+ "ApplicationDeploymentFailureRollbackFunctionArn": :ecs_stack.ref("Outputs.ApplicationDeploymentFailureRollbackFunctionArn")
50
+ }
51
+
52
+ env_vars.each do |var|
53
+ params[var[:Name].cfnize] = var[:Value]
54
+ end
55
+ params
56
+ end
57
+
58
+ def application_parameters(env_vars)
59
+ vars = []
60
+
61
+ env_vars.each do |var|
62
+ vars.push(
63
+ Name: var[:Name],
64
+ Value: var[:Name].cfnize.ref
65
+ )
66
+ end
67
+ vars
68
+ end
69
+
1
70
  # Generate nested stacks for each defined application, and create module dynamically.
2
71
  def create_applications
72
+ return if infra_config["environments"][environment]["cluster_size"].nil? || infra_config["environments"][environment]["cluster_size"].to_i.zero?
3
73
  resource :applications_stack,
4
74
  amount: infra_config["applications"].count,
5
75
  type: "AWS::CloudFormation::Stack" do |r, index|
76
+ env_vars = env_vars_to_array(infra_config["applications"].values[index]["env"])
77
+ application_vars = application_parameters(env_vars) # rubocop:disable Lint/UselessAssignment
6
78
  name = infra_config["applications"].keys[index]
7
79
  resource_name = name.tr("-", "_").cfnize
8
80
  simple_name = resource_name.downcase
9
81
  app_config = infra_config["applications"].values[index]
10
- warn "App config is empty" unless app_config # Rubocop didn't understand that the variable is actually used.
11
- # request_certificate("Gateway", gateway_domains[environment], :asellion_com_hosted_zone_id.ref, "us-east-1")
82
+ is_essential = to_bool(app_config["essential"].to_s)
12
83
 
13
84
  r._id("#{resource_name}Stack")
14
85
  r.property(:template_url) { "#{simple_name}stack" }
15
- r.property(:parameters) do
16
- {
17
- "Vpc": :vpc_stack.ref("Outputs.SfsVpc"),
18
- "Cluster": :ecs_stack.ref("Outputs.SfsEcsCluster"),
19
- "Listener": :ecs_stack.ref("Outputs.EcsLoadBalancerListener"),
20
- "EcsServiceAutoScalingRoleArn": :ecs_stack.ref("Outputs.SfsEcsAutoScalingRoleArn"),
21
- "HostedZoneId": :route53_stack.ref("Outputs.HostedZoneId"),
22
- "HostedZoneName": :route53_stack.ref("Outputs.HostedZoneName"),
23
- "LoadBalancerDnsName": :ecs_stack.ref("Outputs.EcsLoadBalancerUrl"),
24
- "CanonicalHostedZoneId": :ecs_stack.ref("Outputs.EcsLoadBalancerHostedZoneId"),
25
- "CertificateProviderFunctionArn": :acm_stack.ref("Outputs.CertificateProviderFunctionArn")
26
- }
27
- end
86
+ r.property(:parameters) { parent_parameters(env_vars) }
28
87
  Object.const_set("#{resource_name}Stack", Module.new).class_eval <<-RUBY
29
88
  extend ActiveSupport::Concern
30
89
  include Rubycfn
31
90
 
32
91
  included do
33
- def env_vars_to_array(val)
34
- vars = []
35
- val.keys.each_with_index do |object, index|
36
- vars.push(
37
- Name: object,
38
- Value: val.values[index]
39
- )
40
- end
41
- vars
92
+
93
+ application_vars.each do |var|
94
+ Object.class_eval("parameter var[:Name].to_sym, description: 'Value for ENV var \#{var[:Name]}'")
42
95
  end
43
96
 
44
97
  parameter :vpc,
@@ -77,6 +130,10 @@ def create_applications
77
130
  description: "ARN of certificate provider",
78
131
  type: "String"
79
132
 
133
+ parameter :application_deployment_failure_rollback_function_arn,
134
+ description: "ARN of ECS deployment failure Lambda",
135
+ type: "String"
136
+
80
137
  variable :min,
81
138
  value: app_config["min"].to_s
82
139
 
@@ -103,7 +160,7 @@ def create_applications
103
160
  description generate_stack_description("#{resource_name}Stack")
104
161
  resource :service,
105
162
  type: "AWS::ECS::Service" do |r|
106
-
163
+ r.property(:service_name) { "#{environment}-<%= project_name %>-#{simple_name}" }
107
164
  r.property(:cluster) { :cluster.ref }
108
165
  r.property(:role) { :service_role.ref }
109
166
  r.property(:desired_count) { min.to_i }
@@ -117,6 +174,14 @@ def create_applications
117
174
  end
118
175
  end
119
176
 
177
+ resource :application_deployment_health,
178
+ type: "Custom::EcsDeploymentCheck" do |r|
179
+ r.property(:service_token) { :application_deployment_failure_rollback_function_arn.ref }
180
+ r.property("AWSRegion") { "${AWS::Region}".fnsub }
181
+ r.property(:service) { "#{environment}-<%= project_name %>-#{simple_name}" }
182
+ r.property(:cluster) { :cluster.ref }
183
+ end
184
+
120
185
  resource :task_definition,
121
186
  type: "AWS::ECS::TaskDefinition" do |r|
122
187
  r.property(:family) { "#{simple_name}-service" }
@@ -124,10 +189,10 @@ def create_applications
124
189
  [
125
190
  {
126
191
  "Name": "#{simple_name}-service",
127
- "Essential": true,
192
+ "Essential": #{is_essential},
128
193
  "Image": image,
129
194
  "Memory": memory.to_i,
130
- "Environment": env_vars,
195
+ "Environment": application_vars,
131
196
  "PortMappings": [
132
197
  {
133
198
  "ContainerPort": container_port.to_i
@@ -235,6 +300,27 @@ def create_applications
235
300
  "elasticloadbalancing:RegisterTargets"
236
301
  ],
237
302
  "Resource": "*"
303
+ },
304
+ {
305
+ "Effect": "Allow",
306
+ "Action": [
307
+ "ec2:DescribeTags",
308
+ "ecs:CreateCluster",
309
+ "ecs:DeregisterContainerInstance",
310
+ "ecs:DiscoverPollEndpoint",
311
+ "ecs:Poll",
312
+ "ecs:RegisterContainerInstance",
313
+ "ecs:StartTelemetrySession",
314
+ "ecs:UpdateContainerInstancesState",
315
+ "ecs:Submit*",
316
+ "ecr:GetAuthorizationToken",
317
+ "ecr:BatchCheckLayerAvailability",
318
+ "ecr:GetDownloadUrlForLayer",
319
+ "ecr:BatchGetImage",
320
+ "logs:CreateLogStream",
321
+ "logs:PutLogEvents"
322
+ ],
323
+ "Resource": "*"
238
324
  }
239
325
  ]
240
326
  }
@@ -402,7 +488,10 @@ def create_applications
402
488
  end
403
489
 
404
490
  resource :cloudfront_distribution,
405
- depends_on: :ecs_application_issued_certificate,
491
+ depends_on: [
492
+ :ecs_application_issued_certificate,
493
+ :application_deployment_health
494
+ ],
406
495
  type: "AWS::CloudFront::Distribution" do |r|
407
496
  r.property(:distribution_config) do
408
497
  {
@@ -13,15 +13,15 @@ client = Aws::CloudFormation::Client.new(region: ENV["AWS_REGION"])
13
13
 
14
14
  begin
15
15
  res = client.describe_stacks(
16
- stack_name: "DependencyStack"
16
+ stack_name: "<%= project_name %>#{ENV["ENVIRONMENT"].capitalize}DependencyStack"
17
17
  )
18
18
  rescue Aws::CloudFormation::Errors::InvalidClientTokenId, Aws::CloudFormation::Errors::ValidationError => e
19
19
  puts "E: #{e.class}"
20
20
  raise "ERROR: Your AWS credentials are not set or invalid." if e.class == Aws::CloudFormation::Errors::InvalidClientTokenId
21
- raise "ERROR: DependencyStack does not exist. Run `rake init` first!" if e.class == Aws::CloudFormation::Errors::ValidationError
21
+ raise "ERROR: <%= project_name %>#{ENV["ENVIRONMENT"].capitalize}DependencyStack does not exist. Run `rake init` first!" if e.class == Aws::CloudFormation::Errors::ValidationError
22
22
  end
23
23
 
24
- dep_file = File.open(".env.dependencies", "w")
24
+ dep_file = File.open(".env.dependencies.#{ENV["ENVIRONMENT"]}", "w")
25
25
  res[:stacks].each do |stack|
26
26
  stack[:outputs].each do |output|
27
27
  dep_file.puts "#{output[:output_key].upcase}=#{output[:output_value]}"
@@ -7,7 +7,7 @@ require "aws-sdk"
7
7
  require_relative "../aws_helper/main"
8
8
 
9
9
  Dotenv.load(".env.private")
10
- Dotenv.load(".env.dependencies")
10
+ Dotenv.load(".env.dependencies.#{ENV["ENVIRONMENT"]}")
11
11
  raise "CLOUDFORMATIONBUCKET not set. Run `rake init` and `rake update` first!" unless ENV["CLOUDFORMATIONBUCKET"]
12
12
 
13
13
  env_vars = load_env_vars
@@ -26,7 +26,7 @@ s3_filename = get_parent_stack_s3_location(
26
26
  client = Aws::CloudFormation::Client.new
27
27
 
28
28
  parent_parameters = []
29
- File.open(".env.dependencies").read.each_line do |line|
29
+ File.open(".env.dependencies.#{ENV["ENVIRONMENT"]}").read.each_line do |line|
30
30
  line.strip!
31
31
  param, value = line.split("=")
32
32
  parent_parameters.push(