Class: EcsDeploy::Service
- Inherits:
-
Object
- Object
- EcsDeploy::Service
- Defined in:
- lib/ecs_deploy/service.rb
Defined Under Namespace
Classes: TooManyAttemptsError
Constant Summary collapse
- CHECK_INTERVAL =
5- MAX_DESCRIBE_SERVICES =
10- CREATE_ONLY_KEYS =
Options that Aws::ECS::Client#update_service will not honor on an existing service:
- launch_type / scheduling_strategy: not in update_service's parameter list
- role / client_token: create-only, no update-side equivalent
- deployment_controller: accepted by update_service in aws-sdk-ecs 1.238+ but AWS rejects any change to the controller type at runtime
%i[launch_type scheduling_strategy role client_token deployment_controller].freeze
- VALID_WAIT_STRATEGIES =
%i[legacy none service_deployment].freeze
- ECS_NATIVE_BLUE_GREEN_STRATEGIES =
%w[BLUE_GREEN LINEAR CANARY].freeze
Instance Attribute Summary collapse
-
#cluster ⇒ Object
readonly
Returns the value of attribute cluster.
-
#delete ⇒ Object
readonly
Returns the value of attribute delete.
-
#deploy_started_at ⇒ Object
readonly
Returns the value of attribute deploy_started_at.
-
#region ⇒ Object
readonly
Returns the value of attribute region.
-
#service_name ⇒ Object
readonly
Returns the value of attribute service_name.
-
#wait_strategy ⇒ Object
readonly
Returns the value of attribute wait_strategy.
Class Method Summary collapse
- .legacy_threads(client, cluster, all_services, targets) ⇒ Object
- .service_deployment_threads(client, cluster, targets) ⇒ Object
- .wait_all_running(services) ⇒ Object
Instance Method Summary collapse
- #current_task_definition_arn ⇒ Object
- #delete_service ⇒ Object
- #deploy ⇒ Object
-
#initialize(cluster:, service_name:, region: nil, **options) ⇒ Service
constructor
A new instance of Service.
- #log_events(ecs_service) ⇒ Object
- #skip_wait? ⇒ Boolean
- #update_tags(service_name, tags) ⇒ Object
Constructor Details
#initialize(cluster:, service_name:, region: nil, **options) ⇒ Service
Returns a new instance of Service.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/ecs_deploy/service.rb', line 23 def initialize(cluster:, service_name:, region: nil, **) @cluster = cluster @service_name = service_name @options = .dup @task_definition_name = @options.delete(:task_definition_name) || service_name @revision = @options.delete(:revision) @delete = @options.delete(:delete) || false @wait_strategy = @options.delete(:wait_strategy) # Snapshot the keys the user actually passed in, so warnings only fire on # options the caller explicitly set (not on defaults injected below). @user_provided_keys = (.keys - %i[task_definition_name revision delete wait_strategy]).freeze if @wait_strategy && !VALID_WAIT_STRATEGIES.include?(@wait_strategy) raise ArgumentError, "Invalid wait_strategy #{@wait_strategy.inspect}, expected nil or one of #{VALID_WAIT_STRATEGIES.inspect}" end @options[:deployment_configuration] ||= {maximum_percent: 200, minimum_healthy_percent: 100} @options[:placement_constraints] ||= [] @options[:placement_strategy] ||= [] @options[:scheduling_strategy] ||= 'REPLICA' @options[:enable_execute_command] ||= false @response = nil region ||= EcsDeploy.config.default_region params ||= EcsDeploy.config.ecs_client_params @client = region ? Aws::ECS::Client.new(params.merge(region: region)) : Aws::ECS::Client.new(params) @region = @client.config.region end |
Instance Attribute Details
#cluster ⇒ Object (readonly)
Returns the value of attribute cluster.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def cluster @cluster end |
#delete ⇒ Object (readonly)
Returns the value of attribute delete.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def delete @delete end |
#deploy_started_at ⇒ Object (readonly)
Returns the value of attribute deploy_started_at.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def deploy_started_at @deploy_started_at end |
#region ⇒ Object (readonly)
Returns the value of attribute region.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def region @region end |
#service_name ⇒ Object (readonly)
Returns the value of attribute service_name.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def service_name @service_name end |
#wait_strategy ⇒ Object (readonly)
Returns the value of attribute wait_strategy.
10 11 12 |
# File 'lib/ecs_deploy/service.rb', line 10 def wait_strategy @wait_strategy end |
Class Method Details
.legacy_threads(client, cluster, all_services, targets) ⇒ Object
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/ecs_deploy/service.rb', line 244 def self.legacy_threads(client, cluster, all_services, targets) targets.map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES).map do |chunked_service_names| Thread.new do EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do EcsDeploy.logger.info "waiting for services to stabilize [#{chunked_service_names.join(", ")}] [#{cluster}]" resp = client.describe_services(cluster: cluster, services: chunked_service_names) resp.services.each do |s| # cf. https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-ecs/lib/aws-sdk-ecs/waiters.rb#L91-L96 if s.deployments.size == 1 && s.running_count == s.desired_count chunked_service_names.delete(s.service_name) end service = all_services.detect { |sc| sc.service_name == s.service_name } service&.log_events(s) end break if chunked_service_names.empty? sleep EcsDeploy.config.ecs_wait_until_services_stable_delay end raise TooManyAttemptsError unless chunked_service_names.empty? end end end |
.service_deployment_threads(client, cluster, targets) ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/ecs_deploy/service.rb', line 266 def self.service_deployment_threads(client, cluster, targets) targets.map do |service| Thread.new do pending = true EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do EcsDeploy.logger.info "waiting for service deployment to settle [#{service.service_name}] [#{cluster}]" arns = client.list_service_deployments( cluster: cluster, service: service.service_name, status: %w[IN_PROGRESS PENDING], ).service_deployments.map(&:service_deployment_arn) if arns.empty? pending = false break end sleep EcsDeploy.config.ecs_wait_until_services_stable_delay end raise TooManyAttemptsError if pending end end end |
.wait_all_running(services) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/ecs_deploy/service.rb', line 222 def self.wait_all_running(services) threads = services.group_by { |s| [s.cluster, s.region] }.flat_map do |(cl, region), ss| params = EcsDeploy.config.ecs_client_params client = Aws::ECS::Client.new(params.merge(region: region)) targets = ss.reject(&:delete).reject do |s| if s.skip_wait? EcsDeploy.logger.info "skip waiting for service [#{s.service_name}] [#{cl}]: ECS-managed deployment, monitor via ecs:describe_deployment" true else false end end legacy_targets = targets.reject { |s| s.wait_strategy == :service_deployment } sd_targets = targets.select { |s| s.wait_strategy == :service_deployment } legacy_threads(client, cl, ss, legacy_targets) + service_deployment_threads(client, cl, sd_targets) end threads.each(&:join) end |
Instance Method Details
#current_task_definition_arn ⇒ Object
51 52 53 54 |
# File 'lib/ecs_deploy/service.rb', line 51 def current_task_definition_arn res = @client.describe_services(cluster: @cluster, services: [@service_name]) res.services[0].task_definition end |
#delete_service ⇒ Object
163 164 165 166 167 168 169 170 |
# File 'lib/ecs_deploy/service.rb', line 163 def delete_service if @options[:scheduling_strategy] != 'DAEMON' @client.update_service(cluster: @cluster, service: @service_name, desired_count: 0) sleep 1 end @client.delete_service(cluster: @cluster, service: @service_name) EcsDeploy.logger.info "deleted service [#{@service_name}] [#{@cluster}] [#{@region}] [#{Paint['OK', :green]}]" end |
#deploy ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/ecs_deploy/service.rb', line 56 def deploy @deploy_started_at = Time.now res = @client.describe_services(cluster: @cluster, services: [@service_name]) if res.services.select{ |s| s.status == 'ACTIVE' }.empty? return if @delete create_service else return delete_service if @delete update_service(res.services[0]) end end |
#log_events(ecs_service) ⇒ Object
194 195 196 197 198 199 200 201 202 |
# File 'lib/ecs_deploy/service.rb', line 194 def log_events(ecs_service) ecs_service.events.sort_by(&:created_at).each do |e| next if e.created_at <= deploy_started_at next if @last_event && e.created_at <= @last_event.created_at EcsDeploy.logger.info e. @last_event = e end end |
#skip_wait? ⇒ Boolean
204 205 206 207 208 209 210 211 212 213 |
# File 'lib/ecs_deploy/service.rb', line 204 def skip_wait? case @wait_strategy when :none true when :legacy, :service_deployment false when nil ecs_native_blue_green? end end |
#update_tags(service_name, tags) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/ecs_deploy/service.rb', line 172 def (service_name, ) service_arn = @client.describe_services(cluster: @cluster, services: [service_name]).services.first.service_arn if service_arn.split('/').size == 2 if EcsDeploy.logger.warn "#{service_name} doesn't support tagging operations, so tags are ignored. Long arn format must be used for tagging operations." end return end ||= [] current_tag_keys = @client.(resource_arn: service_arn)..map(&:key) deleted_tag_keys = current_tag_keys - .map { |t| t[:key] } unless deleted_tag_keys.empty? @client.untag_resource(resource_arn: service_arn, tag_keys: deleted_tag_keys) end unless .empty? @client.tag_resource(resource_arn: service_arn, tags: ) end end |