Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 67565e0

Browse files
committed
🆕 Creating a keypair if required, notify on missing IAM roles
1 parent 7bfca3c commit 67565e0

File tree

5 files changed

+74
-20
lines changed

5 files changed

+74
-20
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Changelog
22
All notable changes to this project made by Monade Team are documented in this file. For info refer to [email protected]
33

4+
## [UNRELEASED]
5+
### Added
6+
- Command `setup` now create the keypair if it's missing
7+
8+
### Fixed
9+
- Command `setup` now raises error when the IAM role `ecsInstanceRole` doesn't exist in your account
10+
- Command `setup` now considers inactive clusters and services as deleted
11+
412
## [0.4.0] - 2021-05-24
513
### Changed
614
- The command `ssh` now handles multiple container instances. You can now filter by task or service. If there are multiple options, it will be prompted.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ runner.update_services!
236236
- The ecsInstanceRole has to be created manually if missing: https://docs.aws.amazon.com/batch/latest/userguide/instance_IAM_role.html
237237

238238
## TODOs
239-
239+
- Creating the ecsInstanceRole automatically
240240
- Create scheduled tasks if not present?
241241
- Navigate through logs (or maybe not: https://github.com/jorgebastida/awslogs)
242242
- Recap cluster status

lib/ecs_deploy_cli/runners/base.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ def cf_client
9797
end
9898
end
9999

100+
def iam_client
101+
@iam_client ||= begin
102+
require 'aws-sdk-iam'
103+
Aws::IAM::Client.new(
104+
profile: ENV.fetch('AWS_PROFILE', 'default'),
105+
region: config[:aws_region]
106+
)
107+
end
108+
end
109+
100110
def config
101111
@parser.config
102112
end

lib/ecs_deploy_cli/runners/setup.rb

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33
module EcsDeployCli
44
module Runners
55
class Setup < Base
6+
REQUIRED_ECS_ROLES = {
7+
'ecsInstanceRole' => 'https://docs.aws.amazon.com/batch/latest/userguide/instance_IAM_role.html',
8+
'ecsTaskExecutionRole' => 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html'
9+
}.freeze
10+
class SetupError < StandardError; end
11+
612
def run!
713
services, resolved_tasks, _, cluster_options = @parser.resolve
814

15+
ensure_ecs_roles_exists!
16+
917
setup_cluster! cluster_options
1018
setup_services! services, resolved_tasks: resolved_tasks
19+
rescue SetupError => e
20+
EcsDeployCli.logger.info e.message
1121
end
1222

1323
private
@@ -18,22 +28,18 @@ def setup_cluster!(cluster_options)
1828
return
1929
end
2030

21-
unless ecs_instance_role_exists?
22-
EcsDeployCli.logger.info 'IAM Role ecsInstanceRole does not exist. Please create it: https://docs.aws.amazon.com/batch/latest/userguide/instance_IAM_role.html.'
23-
return
24-
end
25-
2631
EcsDeployCli.logger.info "Creating cluster #{config[:cluster]}..."
2732

33+
create_keypair_if_required! cluster_options
2834
params = create_params(cluster_options)
2935

3036
ecs_client.create_cluster(
3137
cluster_name: config[:cluster]
3238
)
39+
EcsDeployCli.logger.info "Cluster created, now running cloudformation..."
3340

3441
stack_name = "EC2ContainerService-#{config[:cluster]}"
3542

36-
3743
cf_client.create_stack(
3844
stack_name: stack_name,
3945
template_body: File.read(File.join(__dir__, '..', 'cloudformation', 'default.yml')),
@@ -42,12 +48,13 @@ def setup_cluster!(cluster_options)
4248
)
4349

4450
cf_client.wait_until(:stack_create_complete, { stack_name: stack_name }, delay: 30, max_attempts: 120)
45-
EcsDeployCli.logger.info "Cluster #{config[:cluster]} created!"
51+
EcsDeployCli.logger.info "Cluster #{config[:cluster]} created! 🎉"
4652
end
4753

4854
def setup_services!(services, resolved_tasks:)
4955
services.each do |service_name, service_definition|
50-
if ecs_client.describe_services(cluster: config[:cluster], services: [service_name]).to_h[:services].any?
56+
existing_services = ecs_client.describe_services(cluster: config[:cluster], services: [service_name]).to_h[:services].filter { |s| s[:status] != 'INACTIVE' }
57+
if existing_services.any?
5158
EcsDeployCli.logger.info "Service #{service_name} already created, skipping."
5259
next
5360
end
@@ -58,7 +65,7 @@ def setup_services!(services, resolved_tasks:)
5865

5966
ecs_client.create_service(
6067
cluster: config[:cluster],
61-
desired_count: 1,
68+
desired_count: 1, # FIXME: this should be a parameter
6269
load_balancers: service_definition.as_definition(task_definition)[:load_balancers],
6370
service_name: service_name,
6471
task_definition: task_name
@@ -117,14 +124,24 @@ def create_params(cluster_options)
117124
def cluster_exists?
118125
clusters = ecs_client.describe_clusters(clusters: [config[:cluster]]).to_h[:clusters]
119126

120-
clusters.length == 1
127+
clusters.filter { |c| c[:status] != 'INACTIVE' }.length == 1
128+
end
129+
130+
def ensure_ecs_roles_exists!
131+
REQUIRED_ECS_ROLES.each do |role_name, link|
132+
role = iam_client.get_role(role_name: role_name).to_h
133+
rescue Aws::IAM::Errors::NoSuchEntity
134+
raise SetupError, "IAM Role #{role_name} does not exist. Please create it: #{link}."
135+
end
121136
end
122137

123-
def ecs_instance_role_exists?
124-
role = iam_client.get_role(role_name: 'ecsInstanceRole').to_h
125-
true
126-
rescue Aws::IAM::Errors::NoSuchEntity
127-
false
138+
def create_keypair_if_required!(cluster_options)
139+
keypairs = ec2_client.describe_key_pairs(key_names: [cluster_options[:keypair_name]]).to_h[:key_pairs]
140+
rescue Aws::EC2::Errors::InvalidKeyPairNotFound
141+
EcsDeployCli.logger.info "Keypair \"#{cluster_options[:keypair_name]}\" not found, creating it..."
142+
key_material = ec2_client.create_key_pair(key_name: cluster_options[:keypair_name]).to_h[:key_material]
143+
File.write("#{cluster_options[:keypair_name]}.pem", key_material)
144+
EcsDeployCli.logger.info "Created PEM file at #{Dir.pwd}/#{cluster_options[:keypair_name]}.pem"
128145
end
129146

130147
def format_cloudformation_params(params)

spec/ecs_deploy_cli/runner_spec.rb

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,24 @@
9393
end
9494

9595
context '#setup!' do
96-
it 'setups the cluster correctly' do
96+
before do
9797
mock_ssm_client.stub_responses(:get_parameter, {
9898
parameter: {
9999
name: '/aws/service/ecs/optimized-ami/amazon-linux-2/recommended',
100100
type: 'String',
101101
value: '{"schema_version":1,"image_name":"amzn2-ami-ecs-hvm-2.0.20210331-x86_64-ebs","image_id":"ami-03bbf53329af34379","os":"Amazon Linux 2","ecs_runtime_version":"Docker version 19.03.13-ce","ecs_agent_version":"1.51.0"}'
102102
}
103103
})
104+
end
105+
106+
it 'setups the cluster correctly' do
107+
expect(mock_ec2_client).to receive(:describe_key_pairs).and_return(key_pairs: [{ key_id: 'some' }])
104108

105-
expect(mock_iam_client).to receive(:get_role).with({ role_name: 'ecsInstanceRole' }).and_return({ role: { arn: 'some' } })
109+
expect(mock_iam_client).to receive(:get_role).at_least(:once).and_return({ role: { arn: 'some' } })
106110
expect(mock_cf_client).to receive(:wait_until)
107111
expect(mock_ecs_client).to receive(:create_service)
108112

113+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ec2_client).at_least(:once).and_return(mock_ec2_client)
109114
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
110115
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
111116
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
@@ -120,11 +125,10 @@
120125
puts message
121126
end
122127

123-
expect(mock_iam_client).to receive(:get_role).with({ role_name: 'ecsInstanceRole' }) do
128+
expect(mock_iam_client).to receive(:get_role).at_least(:once) do
124129
raise Aws::IAM::Errors::NoSuchEntity.new(nil, 'some')
125130
end
126131

127-
128132
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
129133
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
130134

@@ -138,10 +142,25 @@
138142
puts message
139143
end
140144

145+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
141146
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
142147

143148
expect { subject.setup! }.to output(/Cluster already created, skipping./).to_stdout
144149
end
150+
151+
it 'creates the keypair if not there' do
152+
expect(mock_ec2_client).to receive(:describe_key_pairs) do
153+
raise Aws::EC2::Errors::InvalidKeyPairNotFound.new(nil, 'some')
154+
end
155+
156+
expect(mock_ec2_client).to receive(:create_key_pair) { raise 'created keypair' }
157+
158+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ec2_client).at_least(:once).and_return(mock_ec2_client)
159+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
160+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
161+
162+
expect { subject.setup! }.to raise_error('created keypair')
163+
end
145164
end
146165

147166
context '#ssh' do

0 commit comments

Comments
 (0)