class Broadside::EcsDeploy
Constants
- DEFAULT_CONTAINER_DEFINITION
Public Instance Methods
bootstrap()
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 26 def bootstrap if EcsManager.get_latest_task_definition_arn(family) info "Task definition for #{family} already exists." else raise ConfigurationError, "No :task_definition_config for #{family}" unless @target.task_definition_config raise ConfigurationError, 'Bootstrapping a task_definition requires a :tag for the image' unless @tag info "Creating an initial task definition for '#{family}' from the config..." EcsManager.ecs.register_task_definition( @target.task_definition_config.merge( family: family, container_definitions: [DEFAULT_CONTAINER_DEFINITION.merge(configured_container_definition)] ) ) end run_commands(@target.bootstrap_commands, started_by: 'bootstrap') if EcsManager.service_exists?(cluster, family) info("Service for #{family} already exists.") 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) end end
full()
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 18 def full info "Running predeploy commands for #{family}..." run_commands(@target.predeploy_commands, started_by: 'predeploy') info 'Predeploy complete.' deploy end
rollback(options = {})
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 53 def rollback(options = {}) count = options[:rollback] || 1 info "Rolling back #{count} release(s) for #{family}..." EcsManager.check_service_and_task_definition_state!(@target) begin EcsManager.deregister_last_n_tasks_definitions(family, count) update_service(options) rescue StandardError error 'Rollback failed to complete!' raise end info 'Rollback complete.' end
run_commands(commands, options = {})
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 75 def run_commands(commands, options = {}) return if commands.nil? || commands.empty? update_task_revision begin commands.each do |command| command_name = "'#{command.join(' ')}'" task_arn = EcsManager.run_task(cluster, family, command, options).tasks[0].task_arn info "Launched #{command_name} task #{task_arn}, waiting for completion..." EcsManager.ecs.wait_until(:tasks_stopped, cluster: cluster, tasks: [task_arn]) do |w| w.max_attempts = nil w.delay = Broadside.config.aws.ecs_poll_frequency w.before_attempt do |attempt| info "Attempt #{attempt}: waiting for #{command_name} to complete..." end end exit_status = EcsManager.get_task_exit_status(cluster, task_arn, family) raise EcsError, "#{command_name} failed to start:\n'#{exit_status[:reason]}'" if exit_status[:exit_code].nil? raise EcsError, "#{command_name} nonzero exit code: #{exit_status[:exit_code]}!" unless exit_status[:exit_code].zero? info "#{command_name} task container logs:\n#{get_container_logs(task_arn)}" info "#{command_name} task #{task_arn} complete" end ensure EcsManager.deregister_last_n_tasks_definitions(family, 1) end end
scale(options = {})
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 69 def scale(options = {}) info "Rescaling #{family} with scale=#{options[:scale] || @target.scale}..." update_service(options) info 'Rescaling complete.' end
short()
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 14 def short deploy end
Private Instance Methods
configured_container_definition()
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 202 def configured_container_definition (@target.task_definition_config.try(:[], :container_definitions).try(:first) || {}).merge( name: family, command: @target.command, environment: @target.ecs_env_vars, image: image_tag ) end
deploy()
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 107 def deploy current_scale = EcsManager.current_service_scale(@target) update_task_revision begin update_service rescue Interrupt, StandardError => e msg = e.is_a?(Interrupt) ? 'Caught interrupt signal' : "#{e.class}: #{e.message}" error "#{msg}, rolling back..." # In case of failure during deploy, rollback to the previously configured scale rollback(scale: current_scale) error 'Deployment did not finish successfully.' raise e end end
get_container_logs(task_arn)
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 181 def get_container_logs(task_arn) ip = EcsManager.get_running_instance_ips!(cluster, family, task_arn).first debug "Found IP of container instance: #{ip}" find_container_id_cmd = "#{Broadside.config.ssh_cmd(ip)} \"docker ps -aqf 'label=com.amazonaws.ecs.task-arn=#{task_arn}'\"" debug "Running command to find container id:\n#{find_container_id_cmd}" container_ids = `#{find_container_id_cmd}`.split logs = '' container_ids.each do |container_id| get_container_logs_cmd = "#{Broadside.config.ssh_cmd(ip)} \"docker logs #{container_id}\"" debug "Running command to get logs of container #{container_id}:\n#{get_container_logs_cmd}" Open3.popen3(get_container_logs_cmd) do |_, stdout, stderr, _| logs << "STDOUT (#{container_id}):\n--\n#{stdout.read}\nSTDERR (#{container_id}):\n--\n#{stderr.read}\n" end end logs end
update_service(options = {})
click to toggle source
# File lib/broadside/ecs/ecs_deploy.rb, line 145 def update_service(options = {}) scale = options[:scale] || @target.scale raise ArgumentError, ':scale not provided' unless scale EcsManager.check_service_and_task_definition_state!(target) task_definition_arn = EcsManager.get_latest_task_definition_arn(family) debug "Updating #{family} with scale=#{scale} using task_definition #{task_definition_arn}..." update_service_response = EcsManager.ecs.update_service({ cluster: cluster, desired_count: scale, service: family, task_definition: task_definition_arn }.deep_merge(@target.service_config_for_update || {})) unless update_service_response.successful? raise EcsError, "Failed to update service:\n#{update_service_response.pretty_inspect}" end EcsManager.ecs.wait_until(:services_stable, cluster: cluster, services: [family]) do |w| timeout = Broadside.config.timeout w.delay = Broadside.config.aws.ecs_poll_frequency w.max_attempts = timeout ? timeout / w.delay : nil seen_event_id = nil w.before_wait do |attempt, response| info "(#{attempt}/#{w.max_attempts || Float::INFINITY}) Polling ECS for events..." # Skip first event since it doesn't apply to current request if response.services[0].events.first && response.services[0].events.first.id != seen_event_id && attempt > 1 seen_event_id = response.services[0].events.first.id info response.services[0].events.first.message end end end end
update_task_revision()
click to toggle source
Creates a new task revision using current directory's env vars, provided tag, and @target.task_definition_config
# File lib/broadside/ecs/ecs_deploy.rb, line 124 def update_task_revision EcsManager.check_task_definition_state!(target) revision = EcsManager.get_latest_task_definition(family).except( :requires_attributes, :revision, :status, :task_definition_arn ) updatable_container_definitions = revision[:container_definitions].select { |c| c[:name] == family } raise Error, 'Can only update one container definition!' if updatable_container_definitions.size != 1 # Deep merge doesn't work well with arrays (e.g. container_definitions), so build the container first. updatable_container_definitions.first.merge!(configured_container_definition) revision.deep_merge!((@target.task_definition_config || {}).except(:container_definitions)) revision[:requires_compatibilities] = revision.delete(:compatibilities) if revision.key? :compatibilities debug "Registering updated task definition for #{revision[:family]}" task_definition = EcsManager.ecs.register_task_definition(revision).task_definition debug "Successfully created #{task_definition.task_definition_arn}" end