Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Broadside.configure do |config|
scale: 1,
command: ['java', '-cp', '*:.', 'path.to.MyClass'],
# This target has a task_definition and service config which you use to bootstrap a new AWS Service
# along with an ELB configured with :load_balancer_config. The load balancer is optional.
load_balancer_config: { subnets: ['subnet-xyz', 'subnet-abc'] },
service_config: { deployment_configuration: { minimum_healthy_percent: 0.5 } },
task_definition_config: { container_definitions: [ { cpu: 1, memory: 2000, } ] }
}
Expand Down
27 changes: 26 additions & 1 deletion lib/broadside/ecs/ecs_deploy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,35 @@ def bootstrap

if EcsManager.service_exists?(cluster, family)
info("Service for #{family} already exists.")

# Verify that the requested ELB config matches what is running.
if @target.load_balancer_config && (elb_arn = EcsManager.get_load_balancer_arn_by_name(family))
elb = elb_client.describe_load_balancers(load_balancer_arns: [elb_arn]).load_balancers.first.to_h

@target.load_balancer_config.each do |k, v|
raise Error, "Running ELB.#{k} is '#{elb[k]}'; config says #{v}. Can't reconfigure." if elb[k] != v
end
end
else
raise ConfigurationError, "No :service_config for #{family}" unless @target.service_config
info "Service '#{family}' doesn't exist, creating..."
EcsManager.create_service(cluster, family, @target.service_config)

if @target.load_balancer_config
container_port = EcsManager.get_latest_task_definition(family)[:container_definitions].first[:port_mappings].first[:container_port]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/LineLength: Line is too long. [141/120]


# We cautiously default to an internal ELB; the AWS default is `internet-facing`
EcsManager.create_load_balancer({ scheme: 'internal', name: family }.merge(@target.load_balancer_config))

EcsManager.create_service(
cluster,
family,
@target.service_config.merge(
load_balancers: [container_name: family, container_port: container_port, load_balancer_name: family]
)
)
else
EcsManager.create_service(cluster, family, @target.service_config)
end
end
end

Expand Down
19 changes: 19 additions & 0 deletions lib/broadside/ecs/ecs_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ def current_service_scale(target)
EcsManager.ecs.describe_services(cluster: target.cluster, services: [target.family]).services.first[:desired_count]
end

# Returns the load balancer description hash
def create_load_balancer(load_balancer_config)
# Docs say tags are required but sandbox accepts ELB definitions without :tags
tags = [{ key: 'family', value: load_balancer_config[:name] }]
elb_client.create_load_balancer(load_balancer_config.merge(tags: tags)).load_balancers.first.to_h
end

def get_load_balancer_arn_by_name(name)
load_balancers = elb_client.describe_load_balancers.load_balancers
load_balancers.detect { |lb| lb.load_balancer_name == name }.try(:[], :load_balancer_arn)
end

private

def all_results(method, key, args = {})
Expand All @@ -169,6 +181,13 @@ def ec2_client
credentials: Broadside.config.aws.credentials
)
end

def elb_client
@elb_client ||= Aws::ElasticLoadBalancingV2::Client.new(
region: Broadside.config.aws.region,
credentials: Broadside.config.aws.credentials
)
end
end
end
end
8 changes: 8 additions & 0 deletions lib/broadside/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Target
:cluster,
:command,
:docker_image,
:load_balancer_config,
:name,
:predeploy_commands,
:scale,
Expand All @@ -30,6 +31,12 @@ class Target
record.errors.add(attr, 'is not a hash') unless val.is_a?(Hash)
end

validates_each(:load_balancer_config, allow_nil: true) do |record, attr, val|
record.errors.add(attr, 'is not a hash') unless val.is_a?(Hash)
record.errors.add(attr, ':load_balancer_name is set automatically') if val[:load_balancer_name]
record.errors.add(attr, ':subnets is required') unless val[:subnets] && val[:subnets].is_a?(Array)
end

validates_each(:task_definition_config, allow_nil: true) do |record, attr, val|
if val.is_a?(Hash)
if val[:container_definitions] && val[:container_definitions].size > 1
Expand All @@ -52,6 +59,7 @@ def initialize(name, options = {})
@cluster = config.delete(:cluster) || Broadside.config.aws.ecs_default_cluster
@command = config.delete(:command)
@docker_image = config.delete(:docker_image) || Broadside.config.default_docker_image
@load_balancer_config = config.delete(:load_balancer_config)
@predeploy_commands = config.delete(:predeploy_commands)
@scale = config.delete(:scale)
@service_config = config.delete(:service_config)
Expand Down
2 changes: 1 addition & 1 deletion lib/broadside/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Broadside
VERSION = '3.0.0-prerelease'.freeze
VERSION = '3.1.0-prerelease'.freeze
end
52 changes: 52 additions & 0 deletions spec/broadside/ecs/ecs_deploy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,58 @@
expect(Broadside::EcsManager).to receive(:create_service).with(cluster, deploy.family, service_config)
expect { deploy.bootstrap }.to_not raise_error
end

context 'with a load_balancer_config' do
let(:elb_config) { { subnets: ['subnet-xyz', 'subnet-abc'] } }
let(:local_target_config) { { service_config: service_config, load_balancer_config: elb_config } }
let(:load_balancer_response) do
{
load_balancers: [
{
availability_zones: [{ subnet_id: 'notorious-subnet', zone_name: 'zone' }],
canonical_hosted_zone_id: 'EXAMPLE',
created_time: Time.now,
dns_name: 'dns',
load_balancer_arn: 'arn:aws:elasticloadbalancing:arnslength',
load_balancer_name: family,
scheme: 'internal',
security_groups: [ 'security' ],
state: { code: 'provisioning' },
type: 'application',
vpc_id: 'vpc',
}
]
}
end

let(:container_port) { 80 }
let(:service_config_args) do
service_config.merge(
cluster: cluster,
load_balancers: [{ container_name: family, container_port: container_port, load_balancer_name: family }],
service_name: family,
task_definition: family
)
end

before do
elb_stub.stub_responses(:create_load_balancer, load_balancer_response)
end

it 'sets up the ELB' do
expect(Broadside::EcsManager).to receive(:get_latest_task_definition).and_return(
container_definitions: [port_mappings: [container_port: container_port]]
)

expect(elb_stub).to receive(:create_load_balancer).with(
elb_config.merge(name: family, scheme: 'internal', tags: [{ key: 'family', value: family }])
).and_call_original

expect(ecs_stub).to receive(:create_service).with(service_config_args).and_call_original

expect { deploy.bootstrap }.to_not raise_error
end
end
end

context 'with an existing service' do
Expand Down
21 changes: 21 additions & 0 deletions spec/broadside/ecs/ecs_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,25 @@
expect(described_class.get_task_definition_arns('task')).to eq(task_definition_arns)
end
end

context 'load balancers' do
let(:elb_name) { 'my-load-balancer' }
let(:elb_config) { { name: elb_name, subnets: ['subnet-xyz', 'subnet-abc'] } }
let(:elb_arn) { 'elb_arn' }
let(:elb_response) { { load_balancer_arn: elb_arn, load_balancer_name: elb_config[:name] } }

it 'can create an ELB' do
described_class.create_load_balancer(elb_config)
end

describe '#get_load_balancer_arn_by_name' do
before do
elb_stub.stub_responses(:describe_load_balancers, load_balancers: [elb_response])
end

it 'should find the relevant elb config' do
expect(described_class.get_load_balancer_arn_by_name(elb_name)).to eq(elb_arn)
end
end
end
end
12 changes: 11 additions & 1 deletion spec/broadside/target_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,17 @@
it_behaves_like 'valid_configuration?', true, predeploy_commands: [%w(do something)]
it_behaves_like 'valid_configuration?', true, predeploy_commands: [%w(do something), %w(other command)]

it_behaves_like 'valid_configuration?', false, task_definition_config: { container_definitions: %w(a b) }
it_behaves_like 'valid_configuration?', false, task_definition_config: { container_definitions: %w(a b) }

it_behaves_like 'valid_configuration?', true, { service_config: { load_balancers: [load_balancer_name: 'x'] } }
it_behaves_like 'valid_configuration?', true, {
service_config: {
load_balancers: [load_balancer_name: 'x']
},
load_balancer_config: {
subnets: %w(abc xyz)
}
}
end

describe '#ecs_env_vars' do
Expand Down
2 changes: 2 additions & 0 deletions spec/support/ecs_shared_contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
let(:api_request_methods) { api_request_log.map(&:keys).flatten }
let(:ecs_stub) { build_stub_aws_client(Aws::ECS::Client, api_request_log) }
let(:ec2_stub) { build_stub_aws_client(Aws::EC2::Client, api_request_log) }
let(:elb_stub) { build_stub_aws_client(Aws::ElasticLoadBalancingV2::Client, api_request_log) }

before(:each) do
Broadside::EcsManager.instance_variable_set(:@ecs_client, ecs_stub)
Broadside::EcsManager.instance_variable_set(:@ec2_client, ec2_stub)
Broadside::EcsManager.instance_variable_set(:@elb_client, elb_stub)
end
end

Expand Down