putpaws 0.0.8 → 0.1.0
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/README.md +230 -0
- data/lib/Putpawsfile +6 -1
- data/lib/putpaws/ai/ai_task.rb +1 -0
- data/lib/putpaws/ai/guide.rb +119 -0
- data/lib/putpaws/ai/tasks/ai_task.rake +14 -0
- data/lib/putpaws/application_config.rb +23 -3
- data/lib/putpaws/code_build/build_task.rb +1 -0
- data/lib/putpaws/code_build/project_command.rb +57 -0
- data/lib/putpaws/code_build/tasks/build_task.rake +39 -0
- data/lib/putpaws/ecs/run_command.rb +115 -0
- data/lib/putpaws/ecs/task_command.rb +63 -9
- data/lib/putpaws/ecs/tasks/ecs_task.rake +113 -6
- data/lib/putpaws/iam/grant_command.rb +205 -0
- data/lib/putpaws/iam/iam_task.rb +1 -0
- data/lib/putpaws/iam/operator_config.rb +46 -0
- data/lib/putpaws/iam/tasks/iam_task.rake +65 -0
- data/lib/putpaws/info.rb +1 -0
- data/lib/putpaws/provision/all.rb +29 -0
- data/lib/putpaws/provision/aws_clients.rb +59 -0
- data/lib/putpaws/provision/config_writer.rb +88 -0
- data/lib/putpaws/provision/policy_generator.rb +221 -0
- data/lib/putpaws/provision/preset.rb +91 -0
- data/lib/putpaws/provision/presets/rails-nginx/preset.json +32 -0
- data/lib/putpaws/provision/presets/rails-nginx/templates/taskdef-app.json.erb +29 -0
- data/lib/putpaws/provision/presets/rails-nginx/templates/taskdef-web.json.erb +48 -0
- data/lib/putpaws/provision/provision_config.rb +156 -0
- data/lib/putpaws/provision/provision_task.rb +1 -0
- data/lib/putpaws/provision/resources/base.rb +49 -0
- data/lib/putpaws/provision/resources/cluster.rb +30 -0
- data/lib/putpaws/provision/resources/codebuild_project.rb +85 -0
- data/lib/putpaws/provision/resources/iam_role.rb +89 -0
- data/lib/putpaws/provision/resources/log_group.rb +50 -0
- data/lib/putpaws/provision/resources/security_group.rb +72 -0
- data/lib/putpaws/provision/resources/service.rb +71 -0
- data/lib/putpaws/provision/resources/task_definition.rb +119 -0
- data/lib/putpaws/provision/runner.rb +218 -0
- data/lib/putpaws/provision/state.rb +45 -0
- data/lib/putpaws/provision/tasks/provision.rake +129 -0
- data/lib/putpaws/provision/util.rb +50 -0
- data/lib/putpaws/tasks/info.rake +35 -0
- data/lib/putpaws/version.rb +1 -1
- metadata +80 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Putpaws
|
|
2
|
+
module Provision
|
|
3
|
+
module Resources
|
|
4
|
+
MANAGED_TAG = {key: 'managed-by', value: 'putpaws'}
|
|
5
|
+
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :config, :state, :clients
|
|
8
|
+
def initialize(config:, state:, clients:)
|
|
9
|
+
@config = config
|
|
10
|
+
@state = state
|
|
11
|
+
@clients = clients
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def kind
|
|
15
|
+
self.class.name.split('::').last
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Subclasses implement: name, current, create!, outputs(current)
|
|
19
|
+
# and optionally: changed?(current), update!(current)
|
|
20
|
+
|
|
21
|
+
def changed?(_current)
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def plan
|
|
26
|
+
c = current
|
|
27
|
+
action = c.nil? ? :create : (changed?(c) ? :update : :skip)
|
|
28
|
+
{action: action, kind: kind, name: name}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Idempotent apply. Always returns the outputs hash.
|
|
32
|
+
def ensure!
|
|
33
|
+
c = current
|
|
34
|
+
if c.nil?
|
|
35
|
+
create!
|
|
36
|
+
elsif changed?(c)
|
|
37
|
+
update!(c)
|
|
38
|
+
else
|
|
39
|
+
outputs(c)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def update!(_current)
|
|
44
|
+
raise NotImplementedError, "#{kind} does not support update"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'putpaws/provision/resources/base'
|
|
2
|
+
|
|
3
|
+
module Putpaws
|
|
4
|
+
module Provision
|
|
5
|
+
module Resources
|
|
6
|
+
class Cluster < Base
|
|
7
|
+
def name
|
|
8
|
+
config.cluster_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def current
|
|
12
|
+
res = clients.ecs.describe_clusters(clusters: [name])
|
|
13
|
+
res.clusters.detect{|c| c.status == 'ACTIVE'}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create!
|
|
17
|
+
res = clients.ecs.create_cluster(
|
|
18
|
+
cluster_name: name,
|
|
19
|
+
tags: [MANAGED_TAG]
|
|
20
|
+
)
|
|
21
|
+
{cluster_arn: res.cluster.cluster_arn, cluster_name: name}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def outputs(current)
|
|
25
|
+
{cluster_arn: current.cluster_arn, cluster_name: name}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'putpaws/provision/util'
|
|
2
|
+
require 'putpaws/provision/resources/base'
|
|
3
|
+
|
|
4
|
+
module Putpaws
|
|
5
|
+
module Provision
|
|
6
|
+
module Resources
|
|
7
|
+
class CodebuildProject < Base
|
|
8
|
+
def name
|
|
9
|
+
config.build_project_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def devops
|
|
13
|
+
config.devops_settings
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def security_group_ids
|
|
17
|
+
ids = state.resources[:security_group_ids]
|
|
18
|
+
raise "security_group_ids not found in state. Run the service step first." if ids.nil? || ids.empty?
|
|
19
|
+
ids
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def desired_params
|
|
23
|
+
{
|
|
24
|
+
name: name,
|
|
25
|
+
description: "Managed by putpaws for #{config.service_name}",
|
|
26
|
+
source: {
|
|
27
|
+
type: devops[:source_type] || 'GITHUB',
|
|
28
|
+
location: devops[:source_repository],
|
|
29
|
+
buildspec: devops[:buildspec] || 'buildspec.yml',
|
|
30
|
+
},
|
|
31
|
+
artifacts: {type: 'NO_ARTIFACTS'},
|
|
32
|
+
environment: {
|
|
33
|
+
type: 'LINUX_CONTAINER',
|
|
34
|
+
image: devops[:environment_image] || 'aws/codebuild/standard:7.0',
|
|
35
|
+
compute_type: devops[:compute_type] || 'BUILD_GENERAL1_SMALL',
|
|
36
|
+
privileged_mode: devops.fetch(:privileged_mode, true),
|
|
37
|
+
},
|
|
38
|
+
service_role: config.roles[:codebuild_role_arn],
|
|
39
|
+
logs_config: {
|
|
40
|
+
cloud_watch_logs: {status: 'ENABLED', group_name: config.build_log_group}
|
|
41
|
+
},
|
|
42
|
+
# CI needs VPC access (Ex: db:migrate against RDS). Reuse the
|
|
43
|
+
# service security group so the build reaches whatever the app can.
|
|
44
|
+
vpc_config: {
|
|
45
|
+
vpc_id: config.base[:vpc_id],
|
|
46
|
+
subnets: config.base[:subnets],
|
|
47
|
+
security_group_ids: security_group_ids,
|
|
48
|
+
},
|
|
49
|
+
tags: [MANAGED_TAG],
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current
|
|
54
|
+
res = clients.codebuild.batch_get_projects(names: [name])
|
|
55
|
+
res.projects.first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def changed?(current)
|
|
59
|
+
current.source&.type != desired_params[:source][:type] ||
|
|
60
|
+
current.source&.location != desired_params[:source][:location] ||
|
|
61
|
+
current.source&.buildspec != desired_params[:source][:buildspec] ||
|
|
62
|
+
current.service_role != desired_params[:service_role] ||
|
|
63
|
+
current.environment&.image != desired_params[:environment][:image] ||
|
|
64
|
+
current.environment&.compute_type != desired_params[:environment][:compute_type] ||
|
|
65
|
+
current.vpc_config&.subnets.to_a.sort != desired_params[:vpc_config][:subnets].sort ||
|
|
66
|
+
current.vpc_config&.security_group_ids.to_a.sort != desired_params[:vpc_config][:security_group_ids].sort
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def create!
|
|
70
|
+
res = clients.codebuild.create_project(**desired_params)
|
|
71
|
+
{codebuild_project: res.project.name}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update!(_current)
|
|
75
|
+
res = clients.codebuild.update_project(**desired_params)
|
|
76
|
+
{codebuild_project: res.project.name}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def outputs(current)
|
|
80
|
+
{codebuild_project: current.name}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'digest'
|
|
3
|
+
require 'putpaws/provision/resources/base'
|
|
4
|
+
|
|
5
|
+
module Putpaws
|
|
6
|
+
module Provision
|
|
7
|
+
module Resources
|
|
8
|
+
# Registers an IAM role from a reviewed draft file under policies/.
|
|
9
|
+
# This runs only after the user approved the drafts explicitly
|
|
10
|
+
# (roles.approve_drafts in provision.json). The draft content is applied
|
|
11
|
+
# as-is, including any edits the user made.
|
|
12
|
+
class IamRole < Base
|
|
13
|
+
attr_reader :roles_key, :draft_file
|
|
14
|
+
def initialize(roles_key:, draft_file:, **args)
|
|
15
|
+
super(**args)
|
|
16
|
+
@roles_key = roles_key
|
|
17
|
+
@draft_file = draft_file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def draft
|
|
21
|
+
@draft ||= begin
|
|
22
|
+
path = config.policies_dir.join(draft_file)
|
|
23
|
+
raise "Role draft not found: #{path}. Please run `putpaws ready` first." unless path.exist?
|
|
24
|
+
JSON.parse(File.read(path), symbolize_names: true)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name
|
|
29
|
+
draft[:SuggestedRoleName]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def policy_name
|
|
33
|
+
draft[:SuggestedPolicyName]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def digest
|
|
37
|
+
Digest::SHA256.hexdigest(JSON.generate(draft))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def digest_key
|
|
41
|
+
:"iam_#{roles_key}_digest"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current
|
|
45
|
+
clients.iam.get_role(role_name: name).role
|
|
46
|
+
rescue Aws::IAM::Errors::NoSuchEntityException
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Re-applied only when the draft content changed since the last apply,
|
|
51
|
+
# so that manual changes on the AWS side are left alone otherwise.
|
|
52
|
+
def changed?(_current)
|
|
53
|
+
state.resources[digest_key] != digest
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create!
|
|
57
|
+
res = clients.iam.create_role(
|
|
58
|
+
role_name: name,
|
|
59
|
+
assume_role_policy_document: JSON.generate(draft[:AssumeRolePolicyDocument]),
|
|
60
|
+
tags: [MANAGED_TAG]
|
|
61
|
+
)
|
|
62
|
+
put_policy!
|
|
63
|
+
{roles_key => res.role.arn, digest_key => digest}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def update!(current)
|
|
67
|
+
clients.iam.update_assume_role_policy(
|
|
68
|
+
role_name: name,
|
|
69
|
+
policy_document: JSON.generate(draft[:AssumeRolePolicyDocument])
|
|
70
|
+
)
|
|
71
|
+
put_policy!
|
|
72
|
+
{roles_key => current.arn, digest_key => digest}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def outputs(current)
|
|
76
|
+
{roles_key => current.arn, digest_key => digest}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def put_policy!
|
|
80
|
+
clients.iam.put_role_policy(
|
|
81
|
+
role_name: name,
|
|
82
|
+
policy_name: policy_name,
|
|
83
|
+
policy_document: JSON.generate(draft[:PolicyDocument])
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'putpaws/provision/resources/base'
|
|
2
|
+
|
|
3
|
+
module Putpaws
|
|
4
|
+
module Provision
|
|
5
|
+
module Resources
|
|
6
|
+
class LogGroup < Base
|
|
7
|
+
def initialize(log_group_name:, output_key:, **args)
|
|
8
|
+
super(**args)
|
|
9
|
+
@log_group_name = log_group_name
|
|
10
|
+
@output_key = output_key
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
@log_group_name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def retention_in_days
|
|
18
|
+
(config.settings[:log_retention_days] || 30).to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def current
|
|
22
|
+
res = clients.logs.describe_log_groups(log_group_name_prefix: name)
|
|
23
|
+
res.log_groups.detect{|g| g.log_group_name == name}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def changed?(current)
|
|
27
|
+
current.retention_in_days != retention_in_days
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create!
|
|
31
|
+
clients.logs.create_log_group(
|
|
32
|
+
log_group_name: name,
|
|
33
|
+
tags: {MANAGED_TAG[:key] => MANAGED_TAG[:value]}
|
|
34
|
+
)
|
|
35
|
+
clients.logs.put_retention_policy(log_group_name: name, retention_in_days: retention_in_days)
|
|
36
|
+
{@output_key => name}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update!(_current)
|
|
40
|
+
clients.logs.put_retention_policy(log_group_name: name, retention_in_days: retention_in_days)
|
|
41
|
+
{@output_key => name}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def outputs(_current)
|
|
45
|
+
{@output_key => name}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'putpaws/provision/util'
|
|
2
|
+
require 'putpaws/provision/resources/base'
|
|
3
|
+
|
|
4
|
+
module Putpaws
|
|
5
|
+
module Provision
|
|
6
|
+
module Resources
|
|
7
|
+
# When base.security_group_ids is set, the existing security groups are
|
|
8
|
+
# used as-is (putpaws never touches their rules). Otherwise a new one is
|
|
9
|
+
# created with no ingress rules (egress open by default).
|
|
10
|
+
# Ingress for ALB etc. is out of scope: add manually or by another command.
|
|
11
|
+
class SecurityGroup < Base
|
|
12
|
+
def specified_ids
|
|
13
|
+
ids = Array(config.base[:security_group_ids])
|
|
14
|
+
# backward compatible with the singular key
|
|
15
|
+
ids = [config.base[:security_group_id]] if ids.empty?
|
|
16
|
+
ids.reject{|x| Util.blank?(x)}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def specified?
|
|
20
|
+
specified_ids.any?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def name
|
|
24
|
+
specified? ? "#{specified_ids.join(', ')} (existing)" : config.sg_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def current
|
|
28
|
+
return current_specified if specified?
|
|
29
|
+
res = clients.ec2.describe_security_groups(filters: [
|
|
30
|
+
{name: 'group-name', values: [name]},
|
|
31
|
+
{name: 'vpc-id', values: [config.base[:vpc_id]]},
|
|
32
|
+
])
|
|
33
|
+
res.security_groups.first
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def current_specified
|
|
37
|
+
res = begin
|
|
38
|
+
clients.ec2.describe_security_groups(group_ids: specified_ids)
|
|
39
|
+
rescue Aws::EC2::Errors::InvalidGroupNotFound
|
|
40
|
+
raise "Specified security group not found: #{specified_ids.join(', ')}"
|
|
41
|
+
end
|
|
42
|
+
found = res.security_groups
|
|
43
|
+
missing = specified_ids - found.map(&:group_id)
|
|
44
|
+
raise "Specified security group not found: #{missing.join(', ')}" if missing.any?
|
|
45
|
+
wrong = found.reject{|sg| sg.vpc_id == config.base[:vpc_id]}
|
|
46
|
+
if wrong.any?
|
|
47
|
+
sg = wrong.first
|
|
48
|
+
raise "Security group #{sg.group_id} belongs to #{sg.vpc_id}, not #{config.base[:vpc_id]}"
|
|
49
|
+
end
|
|
50
|
+
found
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create!
|
|
54
|
+
res = clients.ec2.create_security_group(
|
|
55
|
+
group_name: name,
|
|
56
|
+
description: "Managed by putpaws for #{config.service_name}",
|
|
57
|
+
vpc_id: config.base[:vpc_id],
|
|
58
|
+
tag_specifications: [
|
|
59
|
+
{resource_type: 'security-group', tags: [MANAGED_TAG]}
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
{security_group_ids: [res.group_id]}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def outputs(current)
|
|
66
|
+
ids = current.is_a?(Array) ? current.map(&:group_id) : [current.group_id]
|
|
67
|
+
{security_group_ids: ids}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'putpaws/provision/resources/base'
|
|
2
|
+
|
|
3
|
+
module Putpaws
|
|
4
|
+
module Provision
|
|
5
|
+
module Resources
|
|
6
|
+
class Service < Base
|
|
7
|
+
def name
|
|
8
|
+
config.service_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def desired_count
|
|
12
|
+
(config.settings[:desired_count] || 1).to_i
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def task_definition_family
|
|
16
|
+
config.service_task_definition_family
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def security_group_ids
|
|
20
|
+
ids = state.resources[:security_group_ids]
|
|
21
|
+
raise "security_group_ids not found in state. Run the security group step first." if ids.nil? || ids.empty?
|
|
22
|
+
ids
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def current
|
|
26
|
+
res = clients.ecs.describe_services(cluster: config.cluster_name, services: [name])
|
|
27
|
+
res.services.detect{|s| s.status == 'ACTIVE'}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def changed?(current)
|
|
31
|
+
current_family = current.task_definition.to_s.split('/').last.to_s.split(':').first
|
|
32
|
+
current_family != task_definition_family || current.desired_count != desired_count
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create!
|
|
36
|
+
res = clients.ecs.create_service(
|
|
37
|
+
cluster: config.cluster_name,
|
|
38
|
+
service_name: name,
|
|
39
|
+
task_definition: task_definition_family,
|
|
40
|
+
desired_count: desired_count,
|
|
41
|
+
launch_type: 'FARGATE',
|
|
42
|
+
enable_execute_command: true,
|
|
43
|
+
network_configuration: {
|
|
44
|
+
awsvpc_configuration: {
|
|
45
|
+
subnets: config.base[:subnets],
|
|
46
|
+
security_groups: security_group_ids,
|
|
47
|
+
assign_public_ip: config.settings[:assign_public_ip] || 'DISABLED',
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
tags: [MANAGED_TAG]
|
|
51
|
+
)
|
|
52
|
+
{service_arn: res.service.service_arn, service_name: name}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def update!(_current)
|
|
56
|
+
res = clients.ecs.update_service(
|
|
57
|
+
cluster: config.cluster_name,
|
|
58
|
+
service: name,
|
|
59
|
+
task_definition: task_definition_family,
|
|
60
|
+
desired_count: desired_count
|
|
61
|
+
)
|
|
62
|
+
{service_arn: res.service.service_arn, service_name: name}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def outputs(current)
|
|
66
|
+
{service_arn: current.service_arn, service_name: name}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'putpaws/provision/util'
|
|
5
|
+
require 'putpaws/provision/resources/base'
|
|
6
|
+
|
|
7
|
+
module Putpaws
|
|
8
|
+
module Provision
|
|
9
|
+
module Resources
|
|
10
|
+
class TaskDefinition < Base
|
|
11
|
+
# Variables exposed to taskdef ERB templates.
|
|
12
|
+
class TemplateContext
|
|
13
|
+
attr_reader :service_name, :region, :account_id,
|
|
14
|
+
:cpu, :memory, :container_port, :image, :nginx_image,
|
|
15
|
+
:log_group, :execution_role_arn, :task_role_arn,
|
|
16
|
+
:env_json, :secrets_json
|
|
17
|
+
|
|
18
|
+
def initialize(config)
|
|
19
|
+
settings = config.settings
|
|
20
|
+
@service_name = config.service_name
|
|
21
|
+
@region = config.region
|
|
22
|
+
@account_id = config.account_id
|
|
23
|
+
@cpu = settings[:cpu].to_s
|
|
24
|
+
@memory = settings[:memory].to_s
|
|
25
|
+
@container_port = (settings[:container_port] || 3000).to_i
|
|
26
|
+
@image = config.ecr_image_uri
|
|
27
|
+
@nginx_image = settings[:nginx_image] || 'public.ecr.aws/nginx/nginx:stable'
|
|
28
|
+
@log_group = config.log_group
|
|
29
|
+
@execution_role_arn = config.roles[:task_execution_role_arn]
|
|
30
|
+
@task_role_arn = config.roles[:task_role_arn]
|
|
31
|
+
@env_json = JSON.generate(
|
|
32
|
+
(settings[:env] || {}).map{|k, v| {name: k.to_s, value: v.to_s}}
|
|
33
|
+
)
|
|
34
|
+
@secrets_json = JSON.generate(
|
|
35
|
+
config.resolved_secrets.map{|name, path|
|
|
36
|
+
{name: name, valueFrom: "arn:aws:ssm:#{config.region}:#{config.account_id}:parameter#{path}"}
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_binding
|
|
42
|
+
binding
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :entry
|
|
47
|
+
def initialize(entry:, **args)
|
|
48
|
+
super(**args)
|
|
49
|
+
@entry = entry
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def suffix
|
|
53
|
+
entry[:suffix]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def name
|
|
57
|
+
config.task_definition_family(suffix)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def desired_params
|
|
61
|
+
@desired_params ||= begin
|
|
62
|
+
template = File.read(config.preset.template_path(entry[:template]))
|
|
63
|
+
rendered = ERB.new(template, trim_mode: '-').result(TemplateContext.new(config).get_binding)
|
|
64
|
+
params = Util.deep_underscore_keys(JSON.parse(rendered))
|
|
65
|
+
params[:family] = name
|
|
66
|
+
params[:tags] = [MANAGED_TAG]
|
|
67
|
+
params
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def digest
|
|
72
|
+
Digest::SHA256.hexdigest(JSON.generate(desired_params))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def digest_key
|
|
76
|
+
:"taskdef_#{suffix}_digest"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def current
|
|
80
|
+
clients.ecs.describe_task_definition(task_definition: name).task_definition
|
|
81
|
+
rescue Aws::ECS::Errors::ClientException, Aws::ECS::Errors::ClientError
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# AWS-side normalization makes a field-by-field diff noisy, so
|
|
86
|
+
# compare against the digest of the params we registered last time.
|
|
87
|
+
def changed?(_current)
|
|
88
|
+
state.resources[digest_key] != digest
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create!
|
|
92
|
+
register!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def update!(_current)
|
|
96
|
+
register!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def register!
|
|
100
|
+
res = clients.ecs.register_task_definition(**desired_params)
|
|
101
|
+
td = res.task_definition
|
|
102
|
+
{
|
|
103
|
+
:"taskdef_#{suffix}" => name,
|
|
104
|
+
:"taskdef_#{suffix}_arn" => td.task_definition_arn,
|
|
105
|
+
digest_key => digest,
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def outputs(current)
|
|
110
|
+
{
|
|
111
|
+
:"taskdef_#{suffix}" => name,
|
|
112
|
+
:"taskdef_#{suffix}_arn" => current.task_definition_arn,
|
|
113
|
+
digest_key => digest,
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|