class Moonshot::DeploymentMechanism::CodeDeploy
This mechanism is used to deploy software to an auto-scaling group within a stack. It currently only works with the S3Bucket ArtifactRepository.
Usage: class MyApp < Moonshot::CLI
self.artifact_repository = S3Bucket.new('foobucket') self.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
end
Public Class Methods
new( asg: [], role: 'CodeDeployRole', app_name: nil, group_name: nil, config_name: 'CodeDeployDefault.OneAtATime')
click to toggle source
@param asg [Array, String]
The logical name of the AutoScalingGroup to create and manage a Deployment Group for in CodeDeploy.
@param role [String]
IAM role with AWSCodeDeployRole policy. CodeDeployRole is considered as default role if its not specified.
@param app_name
[String, nil] (nil)
The name of the CodeDeploy Application. By default, this is the same as the stack name, and probably what you want. If you have multiple deployments in a single Stack, they must have unique names.
@param group_name
[String, nil] (nil)
The name of the CodeDeploy Deployment Group. By default, this is the same as app_name.
@param config_name [String]
Name of the Deployment Config to use for CodeDeploy, By default we use CodeDeployDefault.OneAtATime.
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 32 def initialize( asg: [], role: 'CodeDeployRole', app_name: nil, group_name: nil, config_name: 'CodeDeployDefault.OneAtATime') @asg_logical_ids = asg.is_a?(Array) ? asg : [asg] @app_name = app_name @group_name = group_name @codedeploy_role = role @codedeploy_config = config_name end
Public Instance Methods
deploy_hook(artifact_repo, version_name)
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 68 def deploy_hook(artifact_repo, version_name) success = true deployment_id = nil ilog.start_threaded 'Creating Deployment' do |s| res = cd_client.create_deployment( application_name: app_name, deployment_group_name: group_name, revision: revision_for_artifact_repo(artifact_repo, version_name), deployment_config_name: @codedeploy_config, description: "Deploying version #{version_name}" ) deployment_id = res.deployment_id s.continue "Created Deployment #{deployment_id.blue}." success = wait_for_deployment(deployment_id, s) end handle_deployment_failure(deployment_id) unless success end
post_create_hook()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 45 def post_create_hook create_application_if_needed create_deployment_group_if_needed wait_for_asg_capacity end
post_delete_hook()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 88 def post_delete_hook ilog.start 'Cleaning up CodeDeploy Application' do |s| if application_exists? cd_client.delete_application(application_name: app_name) s.success "Deleted CodeDeploy Application '#{app_name}'." else s.success "CodeDeploy Application '#{app_name}' does not exist." end end end
post_update_hook()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 52 def post_update_hook post_create_hook unless deployment_group_ok? # rubocop:disable GuardClause delete_deployment_group create_deployment_group_if_needed end end
status_hook()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 61 def status_hook t = Moonshot::UnicodeTable.new('') application = t.add_leaf("CodeDeploy Application: #{app_name}") application.add_line(code_deploy_status_msg) t.draw_children end
Private Instance Methods
app_name()
click to toggle source
By default, use the stack name as the application name, unless one has been provided.
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 103 def app_name @app_name || stack.name end
application_exists?()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 189 def application_exists? cd_client.get_application(application_name: app_name) true rescue Aws::CodeDeploy::Errors::ApplicationDoesNotExistException false end
asg_names()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 181 def asg_names names = [] auto_scaling_groups.each do |auto_scaling_group| names.push(auto_scaling_group.auto_scaling_group_name) end names end
auto_scaling_groups()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 158 def auto_scaling_groups @auto_scaling_groups ||= load_auto_scaling_groups end
code_deploy_status_msg()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 143 def code_deploy_status_msg case [application_exists?, deployment_group_exists?, deployment_group_ok?] when [true, true, true] 'Application and Deployment Group are configured correctly.'.green when [true, true, false] 'Deployment Group exists, but not associated with the correct '\ "Auto-Scaling Group, try running #{'update'.yellow}." when [true, false, false] "Deployment Group does not exist, try running #{'create'.yellow}." when [false, false, false] 'Application and Deployment Group do not exist, try running'\ " #{'create'.yellow}." end end
create_application_if_needed()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 121 def create_application_if_needed ilog.start "Creating #{pretty_app_name}." do |s| if application_exists? s.success "#{pretty_app_name} already exists." else cd_client.create_application(application_name: app_name) s.success "Created #{pretty_app_name}." end end end
create_deployment_group()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 239 def create_deployment_group cd_client.create_deployment_group( application_name: app_name, deployment_group_name: group_name, service_role_arn: role.arn, auto_scaling_groups: asg_names) end
create_deployment_group_if_needed()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 132 def create_deployment_group_if_needed ilog.start "Creating #{pretty_deploy_group}." do |s| if deployment_group_exists? s.success "CodeDeploy #{pretty_deploy_group} already exists." else create_deployment_group s.success "Created #{pretty_deploy_group}." end end end
delete_deployment_group()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 230 def delete_deployment_group ilog.start "Deleting #{pretty_deploy_group}." do |s| cd_client.delete_deployment_group( application_name: app_name, deployment_group_name: group_name) s.success end end
deployment_group()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 196 def deployment_group cd_client.get_deployment_group( application_name: app_name, deployment_group_name: group_name) .deployment_group_info end
deployment_group_exists?()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 202 def deployment_group_exists? cd_client.get_deployment_group( application_name: app_name, deployment_group_name: group_name) true rescue Aws::CodeDeploy::Errors::ApplicationDoesNotExistException, Aws::CodeDeploy::Errors::DeploymentGroupDoesNotExistException false end
deployment_group_ok?()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 211 def deployment_group_ok? return false unless deployment_group_exists? asgs = deployment_group.auto_scaling_groups return false unless asgs return false unless asgs.count == auto_scaling_groups.count asgs.each do |asg| if (auto_scaling_groups.find_index { |a| a.auto_scaling_group_name == asg.name }).nil? return false end end true end
doctor_check_auto_scaling_resource_defined()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 349 def doctor_check_auto_scaling_resource_defined @asg_logical_ids.each do |asg_logical_id| if stack.template.resource_names.include?(asg_logical_id) success("Resource '#{asg_logical_id}' exists in the CloudFormation template.") # rubocop:disable LineLength else critical("Resource '#{asg_logical_id}' does not exist in the CloudFormation template!") # rubocop:disable LineLength end end end
doctor_check_code_deploy_role()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 336 def doctor_check_code_deploy_role iam_client.get_role(role_name: @codedeploy_role).role success("#{@codedeploy_role} exists.") rescue => e help = <<-EOF Error: #{e.message} For information on provisioning an account for use with CodeDeploy, see: http://docs.aws.amazon.com/codedeploy/latest/userguide/how-to-create-service-role.html EOF critical("Could not find #{@codedeploy_role}, ", help) end
group_name()
click to toggle source
By default, use the stack name as the deployment group name, unless one has been provided.
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 109 def group_name @group_name || stack.name end
handle_deployment_failure(deployment_id)
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 291 def handle_deployment_failure(deployment_id) instances = cd_client.list_deployment_instances(deployment_id: deployment_id) .instances_list.map do |instance_id| cd_client.get_deployment_instance(deployment_id: deployment_id, instance_id: instance_id) end instances.map(&:instance_summary).each do |inst_summary| next unless inst_summary.status == 'Failed' inst_summary.lifecycle_events.each do |event| next unless event.status == 'Failed' ilog.error(event.diagnostics.message) event.diagnostics.log_tail.each_line do |line| ilog.error(line) end end end raise 'Deployment was unsuccessful!' end
load_auto_scaling_groups()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 162 def load_auto_scaling_groups autoscaling_groups = [] @asg_logical_ids.each do |asg_logical_id| asg_name = stack.physical_id_for(asg_logical_id) unless asg_name raise "Could not find #{asg_logical_id} resource in Stack." end groups = as_client.describe_auto_scaling_groups( auto_scaling_group_names: [asg_name]) if groups.auto_scaling_groups.empty? raise "Could not find ASG #{asg_name}." end autoscaling_groups.push(groups.auto_scaling_groups.first) end autoscaling_groups end
pretty_app_name()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 113 def pretty_app_name "CodeDeploy Application #{app_name.blue}" end
pretty_deploy_group()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 117 def pretty_deploy_group "CodeDeploy Deployment Group #{app_name.blue}" end
revision_for_artifact_repo(artifact_repo, version_name)
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 314 def revision_for_artifact_repo(artifact_repo, version_name) case artifact_repo when Moonshot::ArtifactRepository::S3Bucket s3_revision_for(artifact_repo, version_name) when NilClass raise 'Must specify an ArtifactRepository with CodeDeploy. Take a look at the S3Bucket example.' # rubocop:disable LineLength else raise "Cannot use #{artifact_repo.class} to deploy with CodeDeploy." end end
role()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 224 def role iam_client.get_role(role_name: @codedeploy_role).role rescue Aws::IAM::Errors::NoSuchEntity raise "Did not find an IAM Role: #{@codedeploy_role}" end
s3_revision_for(artifact_repo, version_name)
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 325 def s3_revision_for(artifact_repo, version_name) { revision_type: 'S3', s3_location: { bucket: artifact_repo.bucket_name, key: artifact_repo.filename_for_version(version_name), bundle_type: 'tgz' } } end
wait_for_asg_capacity()
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 247 def wait_for_asg_capacity ilog.start_threaded 'Waiting for AutoScaling Group(s) to reach capacity...' do |s| loop do asgs_at_capacity = 0 asgs = load_auto_scaling_groups asgs.each do |asg| count = asg.instances.count { |i| i.lifecycle_state == 'InService' } if asg.desired_capacity == count asgs_at_capacity += 1 s.continue "#{asg.auto_scaling_group_name} DesiredCapacity is #{asg.desired_capacity}, currently #{count} instance(s) are InService." # rubocop:disable LineLength end end break if asgs.count == asgs_at_capacity sleep 5 end s.success 'AutoScaling Group(s) up to capacity!' end end
wait_for_deployment(id, step)
click to toggle source
# File lib/moonshot/deployment_mechanism/code_deploy.rb, line 267 def wait_for_deployment(id, step) success = true loop do sleep 5 info = cd_client.get_deployment(deployment_id: id).deployment_info status = info.status case status when 'Created', 'Queued', 'InProgress' step.continue "Waiting for Deployment #{id.blue} to complete, current status is '#{status}'." # rubocop:disable LineLength when 'Succeeded' step.success "Deployment #{id.blue} completed successfully!" break when 'Failed', 'Stopped' step.failure "Deployment #{id.blue} failed with status '#{status}'" success = false break end end success end