ecs_deploy 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 932b0b421b5a352c6f5fd2a3f110baf393c6d0667fa951abfeeb97a0badbe074
4
- data.tar.gz: 53ddcbcea0662a0e154192be3e71c5161309ba042146e81f9a5f29794e25988f
3
+ metadata.gz: 7ada66f2f084f7ea0e30b0b6808d2635480db1071ae13acfc16f6f7d4cc02fef
4
+ data.tar.gz: 2188aa1ed3727d1c7719bca93006383b831a6f6379ffa28b08aee8a97d5281e5
5
5
  SHA512:
6
- metadata.gz: 88a77e68a9ef43fe818fe529af5f75b1dfaa4b9ccf0505fabe1c185f67eb051f041b6720239bda895fd812125aa960728585aff85f149b99a30331e16c47cef7
7
- data.tar.gz: 4dd7312beb91fc54635d79e44b8795c00866085d3b117f8583fb5c082970dc521fd53423ef3aa5c2c942d5adf6656296034b1952cb32e5b55087d8102173e79e
6
+ metadata.gz: a3e3f662af6586e71f002853a3599adb107c7db5b8b44c4d7ce3d8d13c6d37ae2210ec9a0a0749813e926f78c4879a3a72d851f6dfd2c466e8ec296a9900f244
7
+ data.tar.gz: f96818f250b0644a3908822fa741b26bba564095feff3d78e9a28acc30baa9da5b435200d3c2bc83edf4a2e3e7b73e48e5702ade9f1f3ac7c614be1a9402a067
@@ -6,18 +6,17 @@ on:
6
6
 
7
7
  jobs:
8
8
  test:
9
-
10
9
  runs-on: ubuntu-latest
11
10
  strategy:
12
11
  matrix:
13
- ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2']
12
+ ruby-version: ["3.2", "3.3", "3.4", "4.0"]
14
13
 
15
14
  steps:
16
- - uses: actions/checkout@v3
17
- - name: Set up Ruby
18
- uses: ruby/setup-ruby@v1
19
- with:
20
- ruby-version: ${{ matrix.ruby-version }}
21
- bundler-cache: true
22
- - name: Run tests
23
- run: bundle exec rake
15
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
18
+ with:
19
+ ruby-version: ${{ matrix.ruby-version }}
20
+ bundler-cache: true
21
+ - name: Run tests
22
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -10,3 +10,5 @@
10
10
 
11
11
  .rspec_status
12
12
  .envrc
13
+
14
+ .claude/settings.local.json
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ # v1.1
2
+
3
+ ## Unreleased
4
+
5
+ ### New feature
6
+
7
+ - Add `ecs:describe_deployment`, `ecs:continue_deployment[hook_id]`, `ecs:rollback_deployment[hook_id]`, `ecs:stop_deployment[service_deployment_arn]` capistrano tasks for operating ECS-managed blue/green deployments (lifecycle hook approval / rollback / abort).
8
+
9
+ ### Breaking change
10
+
11
+ - `ecs:deploy` capistrano task now passes the entire service hash through to `EcsDeploy::Service.new` instead of forwarding an explicit allow-list of keys. New SDK fields (e.g. `deployment_controller`, `platform_version`, `service_connect_configuration`, `volume_configurations`, `availability_zone_rebalancing`, `load_balancers[].advanced_configuration`) are supported automatically without gem updates. Any custom non-SDK keys previously placed in `set :ecs_services` entries will now reach the SDK and may cause errors — remove or rename them.
12
+
13
+ ### Enhancement
14
+
15
+ - Add `wait_strategy` option to `EcsDeploy::Service` for ECS-managed blue/green deployments (`DeploymentController.Type=ECS` with `BLUE_GREEN`/`LINEAR`/`CANARY`). `wait_all_running` now auto-detects ECS-managed deployments and skips polling so Capistrano sessions do not block on multi-day Pause Hooks.
16
+ - `EcsDeploy::Service#update_service` now forwards every field accepted by `Aws::ECS::Client#update_service` (aws-sdk-ecs 1.238+), including fields added after this gem release. Options that ECS cannot apply on an existing service (`launch_type`, `scheduling_strategy`, `role`, `client_token`, `deployment_controller`) are detected and logged as warnings when the user's value differs from the current service. `desired_count: 0` and `propagate_tags: "NONE"` are now honored (previously silently dropped by a truthiness check).
17
+ - Bump minimum `aws-sdk-ecs` to `1.238` for `continue_service_deployment`, `stop_service_deployment`, `LINEAR`/`CANARY` strategy, `lifecycle_hooks`, and `LoadBalancer.advanced_configuration`.
18
+
1
19
  # v1.0
2
20
 
3
21
  ## Release v1.0.7 - 2024/08/08
data/README.md CHANGED
@@ -186,6 +186,75 @@ And rollback
186
186
  | 6 | myapp:15 | myapp-service | deregister |
187
187
  | 7 | myapp:12 | myapp-service | current |
188
188
 
189
+ ## Native Blue/Green Deployment (ECS-managed)
190
+
191
+ `ecs_deploy` supports ECS-managed blue/green deployments where ECS itself drives the rollout (no CodeDeploy required). Set `deployment_controller`, `deployment_configuration`, lifecycle hooks, and `load_balancers[].advanced_configuration` directly on the service entry. The Capistrano `ecs:deploy` task forwards the entire hash through to `EcsDeploy::Service.new` and then to `Aws::ECS::Client#create_service` / `#update_service`, so any new SDK field is supported automatically.
192
+
193
+ When updating an existing service, `EcsDeploy::Service` forwards every field accepted by `Aws::ECS::Client#update_service`, including `deployment_configuration` and `load_balancers[].advanced_configuration`. Options that ECS treats as create-only (`launch_type`, `scheduling_strategy`, `role`, `client_token`, `deployment_controller`) are skipped and, if their value differs from the current service, logged as a warning. Unknown keys are forwarded verbatim and will surface as SDK errors — this is intentional so new SDK fields work without gem updates.
194
+
195
+ ```ruby
196
+ set :ecs_services, [
197
+ {
198
+ name: "myapp-#{fetch(:rails_env)}",
199
+ cluster: "myapp-cluster",
200
+ task_definition_name: "myapp-#{fetch(:rails_env)}",
201
+ launch_type: "FARGATE",
202
+ platform_version: "LATEST",
203
+ desired_count: 1,
204
+ network_configuration: { awsvpc_configuration: { subnets: %w[subnet-...], security_groups: %w[sg-...], assign_public_ip: "DISABLED" } },
205
+ deployment_controller: { type: "ECS" },
206
+ deployment_configuration: {
207
+ strategy: "LINEAR",
208
+ linear_configuration: { step_percent: 50.0, step_bake_time_in_minutes: 60 },
209
+ bake_time_in_minutes: 5,
210
+ deployment_circuit_breaker: { enable: true, rollback: true },
211
+ lifecycle_hooks: [
212
+ {
213
+ hook_target_arn: "arn:aws:lambda:ap-northeast-1:<account-id>:function:my-pause-hook",
214
+ role_arn: "arn:aws:iam::<account-id>:role/ecsLifecycleHookRole",
215
+ lifecycle_stages: ["POST_TEST_TRAFFIC_SHIFT"],
216
+ },
217
+ ],
218
+ },
219
+ load_balancers: [{
220
+ target_group_arn: "arn:aws:elasticloadbalancing:...:targetgroup/blue/...",
221
+ container_name: "app",
222
+ container_port: 8080,
223
+ advanced_configuration: {
224
+ alternate_target_group_arn: "arn:aws:elasticloadbalancing:...:targetgroup/green/...",
225
+ production_listener_rule: "arn:aws:elasticloadbalancing:...:listener-rule/...", # for NLB, pass the Listener ARN directly
226
+ test_listener_rule: "arn:aws:elasticloadbalancing:...:listener-rule/...",
227
+ role_arn: "arn:aws:iam::<account-id>:role/ecsInfrastructureRole",
228
+ },
229
+ }],
230
+ health_check_grace_period_seconds: 300,
231
+
232
+ # gem-internal option (not sent to the SDK):
233
+ wait_strategy: :none, # default for ECS-managed deployments is auto-detected as :none
234
+ },
235
+ ]
236
+ ```
237
+
238
+ | option | values | purpose |
239
+ |--------|--------|---------|
240
+ | `wait_strategy:` | `nil` (auto), `:legacy`, `:none`, `:service_deployment` | `nil` auto-detects ECS-managed deployments and skips waiting (multi-day Pause Hooks make blocking impractical). `:legacy` matches pre-1.1 behavior. `:service_deployment` polls `list_service_deployments` (not recommended for sessions). |
241
+
242
+ ### Operational tasks
243
+
244
+ ```sh
245
+ bundle exec cap <stage> ecs:describe_deployment # list in-flight deployments with lifecycle hook details
246
+ bundle exec cap <stage> ecs:continue_deployment[hook-id] # approve a paused lifecycle hook
247
+ bundle exec cap <stage> ecs:rollback_deployment[hook-id] # reject a paused lifecycle hook (ECS rolls back)
248
+ bundle exec cap <stage> ecs:stop_deployment[arn] # stop an in-progress deployment; STOP_TYPE=ABORT or ROLLBACK
249
+ ```
250
+
251
+ ### Caveats
252
+
253
+ - `deployment_controller` is immutable on an existing service. To switch an existing `CODE_DEPLOY` service to `ECS`, delete and re-create the service.
254
+ - The PAUSE lifecycle hook stages `TEST_TRAFFIC_SHIFT` and `PRODUCTION_TRAFFIC_SHIFT` are rejected by AWS (those stages are also entered during rollback). Use `PRE_*`/`POST_*` variants instead.
255
+ - For NLB, `advanced_configuration.production_listener_rule` should hold the Listener ARN directly (NLBs do not have Listener Rules).
256
+ - Pre-1.1 versions filtered the `set :ecs_services` hash to an allow-list of keys before calling `EcsDeploy::Service.new`. Starting with 1.1, the entire hash is forwarded. Any custom non-SDK keys previously placed there must be removed or renamed; otherwise the SDK will raise.
257
+
189
258
  ## Autoscaler
190
259
 
191
260
  The autoscaler of `ecs_deploy` supports auto scaling of ECS services and clusters.
data/ecs_deploy.gemspec CHANGED
@@ -22,12 +22,12 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency "aws-sdk-cloudwatch", "~> 1"
23
23
  spec.add_runtime_dependency "aws-sdk-cloudwatchevents", "~> 1"
24
24
  spec.add_runtime_dependency "aws-sdk-ec2", "~> 1"
25
- spec.add_runtime_dependency "aws-sdk-ecs", "~> 1"
25
+ spec.add_runtime_dependency "aws-sdk-ecs", ">= 1.238", "< 2"
26
26
  spec.add_runtime_dependency "aws-sdk-sqs", "~> 1"
27
27
  spec.add_runtime_dependency "terminal-table"
28
28
  spec.add_runtime_dependency "paint"
29
29
 
30
- spec.add_development_dependency "bundler", ">= 1.11", "< 3"
30
+ spec.add_development_dependency "bundler", ">= 1.11"
31
31
  spec.add_development_dependency "rake", ">= 10.0"
32
32
  spec.add_development_dependency "rspec", "~> 3.0"
33
33
  spec.add_development_dependency "rexml" # For aws-sdk-*
@@ -0,0 +1,13 @@
1
+ module EcsDeploy
2
+ module AutoScaler
3
+ class NullClusterResourceManager
4
+ def acquire(capacity, timeout: nil)
5
+ true
6
+ end
7
+
8
+ def release(capacity)
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ require "ecs_deploy/auto_scaler/config_base"
2
+ require "ecs_deploy/auto_scaler/null_cluster_resource_manager"
3
+
4
+ module EcsDeploy
5
+ module AutoScaler
6
+ SimpleScalingConfig = Struct.new(:name, :region, :cluster, :service_configs) do
7
+ include ConfigBase
8
+
9
+ def initialize(attributes = {}, logger)
10
+ attributes = attributes.dup
11
+ services = attributes.delete("services")
12
+ super(attributes, logger)
13
+ self.service_configs = services.map do |s|
14
+ ServiceConfig.new(s.merge("cluster" => cluster, "region" => region), logger)
15
+ end
16
+ end
17
+
18
+ def update_desired_capacity(required_capacity)
19
+ @logger.debug "#{log_prefix} Skipping infrastructure scaling (managed by capacity provider)"
20
+ end
21
+
22
+ def cluster_resource_manager
23
+ @cluster_resource_manager ||= NullClusterResourceManager.new
24
+ end
25
+
26
+ private
27
+
28
+ def log_prefix
29
+ "[#{self.class.to_s.sub(/\AEcsDeploy::AutoScaler::/, "")} #{name} #{region}]"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -5,6 +5,7 @@ require "yaml"
5
5
  require "ecs_deploy/auto_scaler/auto_scaling_group_config"
6
6
  require "ecs_deploy/auto_scaler/instance_drainer"
7
7
  require "ecs_deploy/auto_scaler/service_config"
8
+ require "ecs_deploy/auto_scaler/simple_scaling_config"
8
9
  require "ecs_deploy/auto_scaler/spot_fleet_request_config"
9
10
 
10
11
  module EcsDeploy
@@ -23,7 +24,7 @@ module EcsDeploy
23
24
  STDERR.sync = true unless error_log_file
24
25
  load_config(yaml_path)
25
26
 
26
- ths = (auto_scaling_group_configs + spot_fleet_request_configs).map do |cluster_scaling_config|
27
+ ths = (auto_scaling_group_configs + spot_fleet_request_configs + simple_scaling_configs).map do |cluster_scaling_config|
27
28
  Thread.new(cluster_scaling_config, &method(:main_loop)).tap { |th| th.abort_on_exception = true }
28
29
  end
29
30
 
@@ -117,6 +118,16 @@ module EcsDeploy
117
118
  end.values.flat_map(&:values)
118
119
  end
119
120
 
121
+ def simple_scaling_configs
122
+ @simple_scaling_configs ||= (@config["simple_scaling"] || []).each.with_object({}) do |c, configs|
123
+ configs[c["name"]] ||= {}
124
+ if configs[c["name"]][c["region"]]
125
+ raise "Duplicate entry in simple_scaling (name: #{c["name"]}, region: #{c["region"]})"
126
+ end
127
+ configs[c["name"]][c["region"]] = SimpleScalingConfig.new(c, @logger)
128
+ end.values.flat_map(&:values)
129
+ end
130
+
120
131
  private
121
132
 
122
133
  def setup_signal_handlers
@@ -96,36 +96,72 @@ namespace :ecs do
96
96
  next unless fetch(:target_task_definition).include?(service[:task_definition_name])
97
97
  end
98
98
 
99
- service_options = {
100
- region: r,
101
- cluster: service[:cluster] || fetch(:ecs_default_cluster),
102
- service_name: service[:name],
103
- task_definition_name: service[:task_definition_name],
104
- load_balancers: service[:load_balancers],
105
- desired_count: service[:desired_count],
106
- launch_type: service[:launch_type],
107
- network_configuration: service[:network_configuration],
108
- health_check_grace_period_seconds: service[:health_check_grace_period_seconds],
109
- delete: service[:delete],
110
- enable_ecs_managed_tags: service[:enable_ecs_managed_tags],
111
- tags: service[:tags],
112
- propagate_tags: service[:propagate_tags],
113
- enable_execute_command: service[:enable_execute_command],
114
- }
115
- service_options[:deployment_configuration] = service[:deployment_configuration] if service[:deployment_configuration]
116
- service_options[:placement_constraints] = service[:placement_constraints] if service[:placement_constraints]
117
- service_options[:placement_strategy] = service[:placement_strategy] if service[:placement_strategy]
118
- service_options[:capacity_provider_strategy] = service[:capacity_provider_strategy] if service[:capacity_provider_strategy]
119
- service_options[:scheduling_strategy] = service[:scheduling_strategy] if service[:scheduling_strategy]
99
+ service_options = service.dup
100
+ service_options[:service_name] = service_options.delete(:name) if service_options.key?(:name)
101
+ service_options[:cluster] ||= fetch(:ecs_default_cluster)
102
+ service_options[:region] = r
103
+
120
104
  s = EcsDeploy::Service.new(**service_options)
121
105
  s.deploy
122
106
  s
123
- end
107
+ end.compact
124
108
  EcsDeploy::Service.wait_all_running(services)
125
109
  end
126
110
  end
127
111
  end
128
112
 
113
+ desc "Describe in-flight ECS service deployments for services in :ecs_services"
114
+ task describe_deployment: [:configure] do
115
+ if fetch(:ecs_services)
116
+ regions = Array(fetch(:ecs_region))
117
+ regions = [EcsDeploy.config.default_region] if regions.empty?
118
+ EcsDeploy::ServiceDeployment.describe(
119
+ services: fetch(:ecs_services),
120
+ regions: regions,
121
+ default_cluster: fetch(:ecs_default_cluster),
122
+ )
123
+ end
124
+ end
125
+
126
+ desc "Continue an ECS service deployment paused at a lifecycle hook"
127
+ task :continue_deployment, [:hook_id] => [:configure] do |_t, args|
128
+ raise "hook_id is required: cap ... ecs:continue_deployment[hook_id]" unless args[:hook_id]
129
+ regions = Array(fetch(:ecs_region))
130
+ regions = [EcsDeploy.config.default_region] if regions.empty?
131
+ EcsDeploy::ServiceDeployment.invoke_lifecycle_hook(
132
+ hook_id: args[:hook_id],
133
+ action: "CONTINUE",
134
+ services: fetch(:ecs_services),
135
+ regions: regions,
136
+ default_cluster: fetch(:ecs_default_cluster),
137
+ )
138
+ end
139
+
140
+ desc "Roll back an ECS service deployment paused at a lifecycle hook"
141
+ task :rollback_deployment, [:hook_id] => [:configure] do |_t, args|
142
+ raise "hook_id is required: cap ... ecs:rollback_deployment[hook_id]" unless args[:hook_id]
143
+ regions = Array(fetch(:ecs_region))
144
+ regions = [EcsDeploy.config.default_region] if regions.empty?
145
+ EcsDeploy::ServiceDeployment.invoke_lifecycle_hook(
146
+ hook_id: args[:hook_id],
147
+ action: "ROLLBACK",
148
+ services: fetch(:ecs_services),
149
+ regions: regions,
150
+ default_cluster: fetch(:ecs_default_cluster),
151
+ )
152
+ end
153
+
154
+ desc "Stop an in-progress ECS service deployment (STOP_TYPE env: ABORT or ROLLBACK)"
155
+ task :stop_deployment, [:service_deployment_arn] => [:configure] do |_t, args|
156
+ raise "service_deployment_arn is required: cap ... ecs:stop_deployment[arn]" unless args[:service_deployment_arn]
157
+ region = Array(fetch(:ecs_region)).first || EcsDeploy.config.default_region
158
+ EcsDeploy::ServiceDeployment.stop(
159
+ service_deployment_arn: args[:service_deployment_arn],
160
+ region: region,
161
+ stop_type: ENV["STOP_TYPE"],
162
+ )
163
+ end
164
+
129
165
  task rollback: [:configure] do
130
166
  if fetch(:ecs_services)
131
167
  regions = Array(fetch(:ecs_region))
@@ -7,28 +7,21 @@ module EcsDeploy
7
7
 
8
8
  attr_reader :cluster, :region, :schedule_rule_name
9
9
 
10
- def initialize(
11
- cluster:, rule_name:, schedule_expression:, enabled: true, description: nil, target_id: nil,
12
- task_definition_name:, revision: nil, task_count: nil, role_arn:, network_configuration: nil, launch_type: nil, platform_version: nil, group: nil,
13
- region: nil, container_overrides: nil
14
- )
10
+ def initialize(cluster:, rule_name:, schedule_expression:, task_definition_name:, role_arn:, region: nil, **options)
15
11
  @cluster = cluster
16
12
  @rule_name = rule_name
17
13
  @schedule_expression = schedule_expression
18
- @enabled = enabled
19
- @description = description
20
- @target_id = target_id || task_definition_name
21
14
  @task_definition_name = task_definition_name
22
- @task_count = task_count || 1
23
- @revision = revision
24
15
  @role_arn = role_arn
25
- @network_configuration = network_configuration
26
- @launch_type = launch_type || "EC2"
27
- @platform_version = platform_version
28
- @group = group
16
+
17
+ @options = options.dup
18
+ @options[:enabled] = @options.fetch(:enabled, true)
19
+ @options[:target_id] ||= task_definition_name
20
+ @options[:task_count] ||= 1
21
+ @options[:launch_type] ||= "EC2"
22
+
29
23
  region ||= EcsDeploy.config.default_region
30
24
  params ||= EcsDeploy.config.ecs_client_params
31
- @container_overrides = container_overrides
32
25
 
33
26
  @client = region ? Aws::ECS::Client.new(params.merge(region: region)) : Aws::ECS::Client.new(params)
34
27
  @region = @client.config.region
@@ -50,7 +43,7 @@ module EcsDeploy
50
43
  end
51
44
 
52
45
  def task_definition_arn
53
- suffix = @revision ? ":#{@revision}" : ""
46
+ suffix = @options[:revision] ? ":#{@options[:revision]}" : ""
54
47
  name = "#{@task_definition_name}#{suffix}"
55
48
  @client.describe_task_definition(task_definition: name).task_definition.task_definition_arn
56
49
  end
@@ -59,30 +52,25 @@ module EcsDeploy
59
52
  res = @cloud_watch_events.put_rule(
60
53
  name: @rule_name,
61
54
  schedule_expression: @schedule_expression,
62
- state: @enabled ? "ENABLED" : "DISABLED",
63
- description: @description,
55
+ state: @options[:enabled] ? "ENABLED" : "DISABLED",
56
+ description: @options[:description],
64
57
  )
65
58
  EcsDeploy.logger.info "created cloudwatch event rule [#{res.rule_arn}] [#{@region}] [#{Paint['OK', :green]}]"
66
59
  end
67
60
 
68
61
  def put_targets
69
62
  target = {
70
- id: @target_id,
63
+ id: @options[:target_id],
71
64
  arn: cluster_arn,
72
65
  role_arn: @role_arn,
73
- ecs_parameters: {
66
+ ecs_parameters: @options.except(:enabled, :description, :target_id, :revision, :container_overrides).merge(
74
67
  task_definition_arn: task_definition_arn,
75
- task_count: @task_count,
76
- network_configuration: @network_configuration,
77
- launch_type: @launch_type,
78
- platform_version: @platform_version,
79
- group: @group,
80
- },
68
+ ),
81
69
  }
82
70
  target[:ecs_parameters].compact!
83
71
 
84
- if @container_overrides
85
- target.merge!(input: { containerOverrides: @container_overrides }.to_json)
72
+ if @options[:container_overrides]
73
+ target.merge!(input: { containerOverrides: @options[:container_overrides] }.to_json)
86
74
  end
87
75
 
88
76
  res = @cloud_watch_events.put_targets(
@@ -90,7 +78,7 @@ module EcsDeploy
90
78
  targets: [target]
91
79
  )
92
80
  if res.failed_entry_count.zero?
93
- EcsDeploy.logger.info "created cloudwatch event target [#{@target_id}] [#{@region}] [#{Paint['OK', :green]}]"
81
+ EcsDeploy.logger.info "created cloudwatch event target [#{@options[:target_id]}] [#{@region}] [#{Paint['OK', :green]}]"
94
82
  else
95
83
  res.failed_entries.each do |entry|
96
84
  EcsDeploy.logger.error "failed to create cloudwatch event target [#{@region}] target_id=#{entry.target_id} error_code=#{entry.error_code} error_message=#{entry.error_message}"
@@ -7,44 +7,38 @@ module EcsDeploy
7
7
 
8
8
  class TooManyAttemptsError < StandardError; end
9
9
 
10
- attr_reader :cluster, :region, :service_name, :delete, :deploy_started_at
11
-
12
- def initialize(
13
- cluster:, service_name:, task_definition_name: nil, revision: nil,
14
- load_balancers: nil,
15
- desired_count: nil, deployment_configuration: {maximum_percent: 200, minimum_healthy_percent: 100},
16
- launch_type: nil,
17
- placement_constraints: [],
18
- placement_strategy: [],
19
- capacity_provider_strategy: nil,
20
- network_configuration: nil,
21
- health_check_grace_period_seconds: nil,
22
- scheduling_strategy: 'REPLICA',
23
- enable_ecs_managed_tags: nil,
24
- tags: nil,
25
- propagate_tags: nil,
26
- region: nil,
27
- delete: false,
28
- enable_execute_command: false
29
- )
10
+ attr_reader :cluster, :region, :service_name, :delete, :deploy_started_at, :wait_strategy
11
+
12
+ # Options that Aws::ECS::Client#update_service will not honor on an existing
13
+ # service:
14
+ # - launch_type / scheduling_strategy: not in update_service's parameter list
15
+ # - role / client_token: create-only, no update-side equivalent
16
+ # - deployment_controller: accepted by update_service in aws-sdk-ecs 1.238+
17
+ # but AWS rejects any change to the controller type at runtime
18
+ CREATE_ONLY_KEYS = %i[launch_type scheduling_strategy role client_token deployment_controller].freeze
19
+
20
+ VALID_WAIT_STRATEGIES = %i[legacy none service_deployment].freeze
21
+ ECS_NATIVE_BLUE_GREEN_STRATEGIES = %w[BLUE_GREEN LINEAR CANARY].freeze
22
+
23
+ def initialize(cluster:, service_name:, region: nil, **options)
30
24
  @cluster = cluster
31
25
  @service_name = service_name
32
- @task_definition_name = task_definition_name || service_name
33
- @load_balancers = load_balancers
34
- @desired_count = desired_count
35
- @deployment_configuration = deployment_configuration
36
- @launch_type = launch_type
37
- @placement_constraints = placement_constraints
38
- @placement_strategy = placement_strategy
39
- @capacity_provider_strategy = capacity_provider_strategy
40
- @network_configuration = network_configuration
41
- @health_check_grace_period_seconds = health_check_grace_period_seconds
42
- @scheduling_strategy = scheduling_strategy
43
- @revision = revision
44
- @enable_ecs_managed_tags = enable_ecs_managed_tags
45
- @tags = tags
46
- @propagate_tags = propagate_tags
47
- @enable_execute_command = enable_execute_command
26
+ @options = options.dup
27
+ @task_definition_name = @options.delete(:task_definition_name) || service_name
28
+ @revision = @options.delete(:revision)
29
+ @delete = @options.delete(:delete) || false
30
+ @wait_strategy = @options.delete(:wait_strategy)
31
+ # Snapshot the keys the user actually passed in, so warnings only fire on
32
+ # options the caller explicitly set (not on defaults injected below).
33
+ @user_provided_keys = (options.keys - %i[task_definition_name revision delete wait_strategy]).freeze
34
+ if @wait_strategy && !VALID_WAIT_STRATEGIES.include?(@wait_strategy)
35
+ raise ArgumentError, "Invalid wait_strategy #{@wait_strategy.inspect}, expected nil or one of #{VALID_WAIT_STRATEGIES.inspect}"
36
+ end
37
+ @options[:deployment_configuration] ||= {maximum_percent: 200, minimum_healthy_percent: 100}
38
+ @options[:placement_constraints] ||= []
39
+ @options[:placement_strategy] ||= []
40
+ @options[:scheduling_strategy] ||= 'REPLICA'
41
+ @options[:enable_execute_command] ||= false
48
42
 
49
43
  @response = nil
50
44
 
@@ -52,8 +46,6 @@ module EcsDeploy
52
46
  params ||= EcsDeploy.config.ecs_client_params
53
47
  @client = region ? Aws::ECS::Client.new(params.merge(region: region)) : Aws::ECS::Client.new(params)
54
48
  @region = @client.config.region
55
-
56
- @delete = delete
57
49
  end
58
50
 
59
51
  def current_task_definition_arn
@@ -64,75 +56,100 @@ module EcsDeploy
64
56
  def deploy
65
57
  @deploy_started_at = Time.now
66
58
  res = @client.describe_services(cluster: @cluster, services: [@service_name])
67
- service_options = {
59
+
60
+ if res.services.select{ |s| s.status == 'ACTIVE' }.empty?
61
+ return if @delete
62
+ create_service
63
+ else
64
+ return delete_service if @delete
65
+ update_service(res.services[0])
66
+ end
67
+ end
68
+
69
+ private def create_service
70
+ service_options = @options.merge(
68
71
  cluster: @cluster,
72
+ service_name: @service_name,
69
73
  task_definition: task_definition_name_with_revision,
70
- deployment_configuration: @deployment_configuration,
71
- network_configuration: @network_configuration,
72
- health_check_grace_period_seconds: @health_check_grace_period_seconds,
73
- capacity_provider_strategy: @capacity_provider_strategy,
74
- enable_execute_command: @enable_execute_command,
75
- enable_ecs_managed_tags: @enable_ecs_managed_tags,
76
- placement_constraints: @placement_constraints,
77
- placement_strategy: @placement_strategy,
78
- }
74
+ )
75
+ service_options[:desired_count] = service_options[:desired_count].to_i
79
76
 
80
- if @load_balancers && EcsDeploy.config.ecs_service_role
81
- service_options.merge!({
82
- role: EcsDeploy.config.ecs_service_role,
83
- })
77
+ if service_options[:load_balancers] && EcsDeploy.config.ecs_service_role
78
+ service_options[:role] = EcsDeploy.config.ecs_service_role
84
79
  end
85
80
 
86
- if @load_balancers
87
- service_options.merge!({
88
- load_balancers: @load_balancers,
89
- })
81
+ if service_options[:scheduling_strategy] == 'DAEMON'
82
+ service_options.delete(:desired_count)
83
+ service_options.delete(:placement_strategy)
90
84
  end
91
85
 
92
- if res.services.select{ |s| s.status == 'ACTIVE' }.empty?
93
- return if @delete
86
+ @response = @client.create_service(service_options)
87
+ EcsDeploy.logger.info "created service [#{@service_name}] [#{@cluster}] [#{@region}] [#{Paint['OK', :green]}]"
88
+ end
94
89
 
95
- service_options.merge!({
96
- service_name: @service_name,
97
- desired_count: @desired_count.to_i,
98
- launch_type: @launch_type,
99
- tags: @tags,
100
- propagate_tags: @propagate_tags,
101
- })
102
-
103
- if @scheduling_strategy == 'DAEMON'
104
- service_options[:scheduling_strategy] = @scheduling_strategy
105
- service_options.delete(:desired_count)
106
- service_options.delete(:placement_strategy)
107
- end
108
- @response = @client.create_service(service_options)
109
- EcsDeploy.logger.info "created service [#{@service_name}] [#{@cluster}] [#{@region}] [#{Paint['OK', :green]}]"
110
- else
111
- return delete_service if @delete
90
+ private def update_service(current_service)
91
+ warn_on_ignored_options(current_service)
112
92
 
113
- service_options.merge!({service: @service_name})
114
- service_options.merge!({desired_count: @desired_count}) if @desired_count
115
- service_options.merge!({propagate_tags: @propagate_tags}) if @propagate_tags
93
+ service_options = @options.except(*CREATE_ONLY_KEYS, :tags).merge(
94
+ cluster: @cluster,
95
+ service: @service_name,
96
+ task_definition: task_definition_name_with_revision,
97
+ )
98
+ # If the user did not set these explicitly, leave them out so ECS keeps
99
+ # its current values (desired_count is often managed by autoscaling;
100
+ # propagate_tags reflects an existing policy).
101
+ service_options.delete(:desired_count) unless @options.key?(:desired_count)
102
+ service_options.delete(:propagate_tags) unless @options.key?(:propagate_tags)
103
+ service_options[:force_new_deployment] = true if need_force_new_deployment?(current_service)
104
+ service_options.delete(:placement_strategy) if @options[:scheduling_strategy] == 'DAEMON'
116
105
 
117
- current_service = res.services[0]
118
- service_options.merge!({force_new_deployment: true}) if need_force_new_deployment?(current_service)
106
+ update_tags(@service_name, @options[:tags])
119
107
 
120
- update_tags(@service_name, @tags)
121
- if @scheduling_strategy == 'DAEMON'
122
- service_options.delete(:placement_strategy)
123
- end
124
- @response = @client.update_service(service_options)
125
- EcsDeploy.logger.info "updated service [#{@service_name}] [#{@cluster}] [#{@region}] [#{Paint['OK', :green]}]"
108
+ @response = @client.update_service(service_options)
109
+ EcsDeploy.logger.info "updated service [#{@service_name}] [#{@cluster}] [#{@region}] [#{Paint['OK', :green]}]"
110
+ end
111
+
112
+ # Log a warning for user-supplied options that update_service cannot apply.
113
+ # Silently drop keys whose value matches the current service (harmless
114
+ # re-declaration of the current state); warn only when the user's value
115
+ # would actually change something.
116
+ private def warn_on_ignored_options(current_service)
117
+ CREATE_ONLY_KEYS.each do |key|
118
+ next unless @user_provided_keys.include?(key)
119
+ next if create_only_matches_current?(key, current_service)
120
+ EcsDeploy.logger.warn "[#{@service_name}] option #{key.inspect} cannot be applied by update_service (current: #{create_only_current_display(current_service, key).inspect}, requested: #{@options[key].inspect}), skipping"
121
+ end
122
+ end
123
+
124
+ private def create_only_matches_current?(key, current_service)
125
+ case key
126
+ when :launch_type
127
+ @options[key].to_s == current_service.launch_type.to_s
128
+ when :scheduling_strategy
129
+ @options[key].to_s == current_service.scheduling_strategy.to_s
130
+ when :deployment_controller
131
+ Hash(@options[key])[:type].to_s == current_service.deployment_controller&.type.to_s
132
+ else
133
+ # role / client_token have no meaningful "current" comparison; always warn.
134
+ false
135
+ end
136
+ end
137
+
138
+ private def create_only_current_display(current_service, key)
139
+ case key
140
+ when :launch_type then current_service.launch_type
141
+ when :scheduling_strategy then current_service.scheduling_strategy
142
+ when :deployment_controller then current_service.deployment_controller&.type
126
143
  end
127
144
  end
128
145
 
129
146
  private def need_force_new_deployment?(service)
130
- return false unless @capacity_provider_strategy
147
+ return false unless @options[:capacity_provider_strategy]
131
148
  return true unless service.capacity_provider_strategy
132
149
 
133
- return true if @capacity_provider_strategy.size != service.capacity_provider_strategy.size
150
+ return true if @options[:capacity_provider_strategy].size != service.capacity_provider_strategy.size
134
151
 
135
- match_array = @capacity_provider_strategy.all? do |strategy|
152
+ match_array = @options[:capacity_provider_strategy].all? do |strategy|
136
153
  service.capacity_provider_strategy.find do |current_strategy|
137
154
  strategy[:capacity_provider] == current_strategy.capacity_provider &&
138
155
  strategy[:weight] == current_strategy.weight &&
@@ -144,7 +161,7 @@ module EcsDeploy
144
161
  end
145
162
 
146
163
  def delete_service
147
- if @scheduling_strategy != 'DAEMON'
164
+ if @options[:scheduling_strategy] != 'DAEMON'
148
165
  @client.update_service(cluster: @cluster, service: @service_name, desired_count: 0)
149
166
  sleep 1
150
167
  end
@@ -184,30 +201,88 @@ module EcsDeploy
184
201
  end
185
202
  end
186
203
 
204
+ def skip_wait?
205
+ case @wait_strategy
206
+ when :none
207
+ true
208
+ when :legacy, :service_deployment
209
+ false
210
+ when nil
211
+ ecs_native_blue_green?
212
+ end
213
+ end
214
+
215
+ private def ecs_native_blue_green?
216
+ svc = @response&.service
217
+ return false unless svc&.deployment_controller&.type == "ECS"
218
+ strategy = svc.deployment_configuration&.strategy.to_s
219
+ ECS_NATIVE_BLUE_GREEN_STRATEGIES.include?(strategy)
220
+ end
221
+
187
222
  def self.wait_all_running(services)
188
- services.group_by { |s| [s.cluster, s.region] }.flat_map do |(cl, region), ss|
189
- params ||= EcsDeploy.config.ecs_client_params
223
+ threads = services.group_by { |s| [s.cluster, s.region] }.flat_map do |(cl, region), ss|
224
+ params = EcsDeploy.config.ecs_client_params
190
225
  client = Aws::ECS::Client.new(params.merge(region: region))
191
- ss.reject(&:delete).map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES).map do |chunked_service_names|
192
- Thread.new do
193
- EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do
194
- EcsDeploy.logger.info "waiting for services to stabilize [#{chunked_service_names.join(", ")}] [#{cl}]"
195
- resp = client.describe_services(cluster: cl, services: chunked_service_names)
196
- resp.services.each do |s|
197
- # cf. https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-ecs/lib/aws-sdk-ecs/waiters.rb#L91-L96
198
- if s.deployments.size == 1 && s.running_count == s.desired_count
199
- chunked_service_names.delete(s.service_name)
200
- end
201
- service = ss.detect {|sc| sc.service_name == s.service_name }
202
- service.log_events(s)
226
+
227
+ targets = ss.reject(&:delete).reject do |s|
228
+ if s.skip_wait?
229
+ EcsDeploy.logger.info "skip waiting for service [#{s.service_name}] [#{cl}]: ECS-managed deployment, monitor via ecs:describe_deployment"
230
+ true
231
+ else
232
+ false
233
+ end
234
+ end
235
+
236
+ legacy_targets = targets.reject { |s| s.wait_strategy == :service_deployment }
237
+ sd_targets = targets.select { |s| s.wait_strategy == :service_deployment }
238
+
239
+ legacy_threads(client, cl, ss, legacy_targets) + service_deployment_threads(client, cl, sd_targets)
240
+ end
241
+ threads.each(&:join)
242
+ end
243
+
244
+ def self.legacy_threads(client, cluster, all_services, targets)
245
+ targets.map(&:service_name).each_slice(MAX_DESCRIBE_SERVICES).map do |chunked_service_names|
246
+ Thread.new do
247
+ EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do
248
+ EcsDeploy.logger.info "waiting for services to stabilize [#{chunked_service_names.join(", ")}] [#{cluster}]"
249
+ resp = client.describe_services(cluster: cluster, services: chunked_service_names)
250
+ resp.services.each do |s|
251
+ # cf. https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-ecs/lib/aws-sdk-ecs/waiters.rb#L91-L96
252
+ if s.deployments.size == 1 && s.running_count == s.desired_count
253
+ chunked_service_names.delete(s.service_name)
203
254
  end
204
- break if chunked_service_names.empty?
205
- sleep EcsDeploy.config.ecs_wait_until_services_stable_delay
255
+ service = all_services.detect { |sc| sc.service_name == s.service_name }
256
+ service&.log_events(s)
257
+ end
258
+ break if chunked_service_names.empty?
259
+ sleep EcsDeploy.config.ecs_wait_until_services_stable_delay
260
+ end
261
+ raise TooManyAttemptsError unless chunked_service_names.empty?
262
+ end
263
+ end
264
+ end
265
+
266
+ def self.service_deployment_threads(client, cluster, targets)
267
+ targets.map do |service|
268
+ Thread.new do
269
+ pending = true
270
+ EcsDeploy.config.ecs_wait_until_services_stable_max_attempts.times do
271
+ EcsDeploy.logger.info "waiting for service deployment to settle [#{service.service_name}] [#{cluster}]"
272
+ arns = client.list_service_deployments(
273
+ cluster: cluster,
274
+ service: service.service_name,
275
+ status: %w[IN_PROGRESS PENDING],
276
+ ).service_deployments.map(&:service_deployment_arn)
277
+ if arns.empty?
278
+ pending = false
279
+ break
206
280
  end
207
- raise TooManyAttemptsError unless chunked_service_names.empty?
281
+ sleep EcsDeploy.config.ecs_wait_until_services_stable_delay
208
282
  end
283
+ raise TooManyAttemptsError if pending
209
284
  end
210
- end.each(&:join)
285
+ end
211
286
  end
212
287
 
213
288
  private
@@ -0,0 +1,71 @@
1
+ module EcsDeploy
2
+ module ServiceDeployment
3
+ IN_FLIGHT_STATUSES = %w[IN_PROGRESS PENDING].freeze
4
+ DESCRIBE_STATUSES = %w[IN_PROGRESS PENDING STOPPED].freeze
5
+
6
+ class HookNotFoundError < StandardError; end
7
+
8
+ module_function
9
+
10
+ def describe(services:, regions:, default_cluster:)
11
+ each_target(services: services, regions: regions, default_cluster: default_cluster) do |client, cluster, svc|
12
+ deployments = list_deployments(client, cluster, svc[:name], statuses: DESCRIBE_STATUSES)
13
+ next if deployments.empty?
14
+ deployments.each { |d| log_deployment(d) }
15
+ end
16
+ end
17
+
18
+ def invoke_lifecycle_hook(hook_id:, action:, services:, regions:, default_cluster:)
19
+ found = false
20
+ each_target(services: services, regions: regions, default_cluster: default_cluster) do |client, cluster, svc|
21
+ deployments = list_deployments(client, cluster, svc[:name], statuses: IN_FLIGHT_STATUSES)
22
+ deployments.each do |d|
23
+ next unless Array(d.lifecycle_hook_details).any? { |h| h.hook_id == hook_id }
24
+ client.continue_service_deployment(
25
+ service_deployment_arn: d.service_deployment_arn,
26
+ hook_id: hook_id,
27
+ action: action,
28
+ )
29
+ EcsDeploy.logger.info "#{action.downcase}d lifecycle_hook=#{hook_id} service_deployment_arn=#{d.service_deployment_arn}"
30
+ found = true
31
+ end
32
+ end
33
+ raise HookNotFoundError, "Lifecycle hook #{hook_id} not found in any in-progress service deployment" unless found
34
+ end
35
+
36
+ def stop(service_deployment_arn:, region:, stop_type: nil)
37
+ client = Aws::ECS::Client.new(EcsDeploy.config.ecs_client_params.merge(region: region))
38
+ params = { service_deployment_arn: service_deployment_arn }
39
+ params[:stop_type] = stop_type if stop_type
40
+ client.stop_service_deployment(params)
41
+ EcsDeploy.logger.info "stopped service_deployment_arn=#{service_deployment_arn}#{stop_type ? " stop_type=#{stop_type}" : ""}"
42
+ end
43
+
44
+ def each_target(services:, regions:, default_cluster:)
45
+ services.each do |svc|
46
+ cluster = svc[:cluster] || default_cluster
47
+ regions.each do |r|
48
+ client = Aws::ECS::Client.new(EcsDeploy.config.ecs_client_params.merge(region: r))
49
+ yield client, cluster, svc
50
+ end
51
+ end
52
+ end
53
+
54
+ def list_deployments(client, cluster, service_name, statuses:)
55
+ arns = client.list_service_deployments(
56
+ cluster: cluster,
57
+ service: service_name,
58
+ status: statuses,
59
+ ).service_deployments.map(&:service_deployment_arn)
60
+ return [] if arns.empty?
61
+ client.describe_service_deployments(service_deployment_arns: arns).service_deployments
62
+ end
63
+
64
+ def log_deployment(d)
65
+ EcsDeploy.logger.info "service_deployment_arn=#{d.service_deployment_arn} status=#{d.status} lifecycle_stage=#{d.lifecycle_stage}"
66
+ Array(d.lifecycle_hook_details).each do |hook|
67
+ EcsDeploy.logger.info " hook_id=#{hook.hook_id} target=#{hook.target_type} status=#{hook.status} expires_at=#{hook.expires_at} timeout_action=#{hook.timeout_action}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -10,23 +10,19 @@ module EcsDeploy
10
10
  EcsDeploy.logger.info "deregistered task definition [#{arn}] [#{client.config.region}] [#{Paint['OK', :green]}]"
11
11
  end
12
12
 
13
- def initialize(
14
- task_definition_name:, region: nil,
15
- network_mode: "bridge", volumes: [], container_definitions: [], placement_constraints: [],
16
- task_role_arn: nil,
17
- execution_role_arn: nil,
18
- requires_compatibilities: nil,
19
- cpu: nil, memory: nil,
20
- tags: nil,
21
- runtime_platform: {}
22
- )
13
+ def initialize(task_definition_name:, region: nil, **options)
23
14
  @task_definition_name = task_definition_name
24
- @task_role_arn = task_role_arn
25
- @execution_role_arn = execution_role_arn
26
15
  region ||= EcsDeploy.config.default_region
27
16
  params ||= EcsDeploy.config.ecs_client_params
28
17
 
29
- @container_definitions = container_definitions.map do |cd|
18
+ @options = options.dup
19
+ @options[:network_mode] ||= "bridge"
20
+ @options[:volumes] ||= []
21
+ @options[:container_definitions] ||= []
22
+ @options[:placement_constraints] ||= []
23
+ @options[:runtime_platform] ||= {}
24
+
25
+ @options[:container_definitions] = @options[:container_definitions].map do |cd|
30
26
  if cd[:docker_labels]
31
27
  cd[:docker_labels] = cd[:docker_labels].map { |k, v| [k.to_s, v] }.to_h
32
28
  end
@@ -35,16 +31,11 @@ module EcsDeploy
35
31
  end
36
32
  cd
37
33
  end
38
- @volumes = volumes
39
- @network_mode = network_mode
40
- @placement_constraints = placement_constraints
41
- @requires_compatibilities = requires_compatibilities
42
- @cpu = cpu&.to_s
43
- @memory = memory&.to_s
44
- @tags = tags
34
+ @options[:cpu] = @options[:cpu]&.to_s
35
+ @options[:memory] = @options[:memory]&.to_s
36
+
45
37
  @client = region ? Aws::ECS::Client.new(params.merge(region: region)) : Aws::ECS::Client.new(params)
46
38
  @region = @client.config.region
47
- @runtime_platform = runtime_platform
48
39
  end
49
40
 
50
41
  def recent_task_definition_arns
@@ -58,19 +49,9 @@ module EcsDeploy
58
49
  end
59
50
 
60
51
  def register
61
- res = @client.register_task_definition({
62
- family: @task_definition_name,
63
- network_mode: @network_mode,
64
- container_definitions: @container_definitions,
65
- volumes: @volumes,
66
- placement_constraints: @placement_constraints,
67
- task_role_arn: @task_role_arn,
68
- execution_role_arn: @execution_role_arn,
69
- requires_compatibilities: @requires_compatibilities,
70
- cpu: @cpu, memory: @memory,
71
- tags: @tags,
72
- runtime_platform: @runtime_platform
73
- })
52
+ res = @client.register_task_definition(
53
+ @options.merge(family: @task_definition_name)
54
+ )
74
55
  EcsDeploy.logger.info "registered task definition [#{@task_definition_name}] [#{@region}] [#{Paint['OK', :green]}]"
75
56
  res.task_definition
76
57
  end
@@ -1,3 +1,3 @@
1
1
  module EcsDeploy
2
- VERSION = "1.0.7"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/ecs_deploy.rb CHANGED
@@ -27,4 +27,5 @@ end
27
27
 
28
28
  require "ecs_deploy/task_definition"
29
29
  require "ecs_deploy/service"
30
+ require "ecs_deploy/service_deployment"
30
31
  require "ecs_deploy/scheduled_task"
data/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ]
6
+ }
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecs_deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-08-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: aws-sdk-autoscaling
@@ -70,16 +69,22 @@ dependencies:
70
69
  name: aws-sdk-ecs
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
- - - "~>"
72
+ - - ">="
74
73
  - !ruby/object:Gem::Version
75
- version: '1'
74
+ version: '1.238'
75
+ - - "<"
76
+ - !ruby/object:Gem::Version
77
+ version: '2'
76
78
  type: :runtime
77
79
  prerelease: false
78
80
  version_requirements: !ruby/object:Gem::Requirement
79
81
  requirements:
80
- - - "~>"
82
+ - - ">="
81
83
  - !ruby/object:Gem::Version
82
- version: '1'
84
+ version: '1.238'
85
+ - - "<"
86
+ - !ruby/object:Gem::Version
87
+ version: '2'
83
88
  - !ruby/object:Gem::Dependency
84
89
  name: aws-sdk-sqs
85
90
  requirement: !ruby/object:Gem::Requirement
@@ -129,9 +134,6 @@ dependencies:
129
134
  - - ">="
130
135
  - !ruby/object:Gem::Version
131
136
  version: '1.11'
132
- - - "<"
133
- - !ruby/object:Gem::Version
134
- version: '3'
135
137
  type: :development
136
138
  prerelease: false
137
139
  version_requirements: !ruby/object:Gem::Requirement
@@ -139,9 +141,6 @@ dependencies:
139
141
  - - ">="
140
142
  - !ruby/object:Gem::Version
141
143
  version: '1.11'
142
- - - "<"
143
- - !ruby/object:Gem::Version
144
- version: '3'
145
144
  - !ruby/object:Gem::Dependency
146
145
  name: rake
147
146
  requirement: !ruby/object:Gem::Requirement
@@ -194,6 +193,7 @@ extra_rdoc_files: []
194
193
  files:
195
194
  - ".github/workflows/test.yml"
196
195
  - ".gitignore"
196
+ - ".rspec"
197
197
  - CHANGELOG.md
198
198
  - Gemfile
199
199
  - README.md
@@ -208,7 +208,9 @@ files:
208
208
  - lib/ecs_deploy/auto_scaler/cluster_resource_manager.rb
209
209
  - lib/ecs_deploy/auto_scaler/config_base.rb
210
210
  - lib/ecs_deploy/auto_scaler/instance_drainer.rb
211
+ - lib/ecs_deploy/auto_scaler/null_cluster_resource_manager.rb
211
212
  - lib/ecs_deploy/auto_scaler/service_config.rb
213
+ - lib/ecs_deploy/auto_scaler/simple_scaling_config.rb
212
214
  - lib/ecs_deploy/auto_scaler/spot_fleet_request_config.rb
213
215
  - lib/ecs_deploy/auto_scaler/trigger_config.rb
214
216
  - lib/ecs_deploy/capistrano.rb
@@ -216,12 +218,13 @@ files:
216
218
  - lib/ecs_deploy/instance_fluctuation_manager.rb
217
219
  - lib/ecs_deploy/scheduled_task.rb
218
220
  - lib/ecs_deploy/service.rb
221
+ - lib/ecs_deploy/service_deployment.rb
219
222
  - lib/ecs_deploy/task_definition.rb
220
223
  - lib/ecs_deploy/version.rb
224
+ - renovate.json
221
225
  homepage: https://github.com/reproio/ecs_deploy
222
226
  licenses: []
223
227
  metadata: {}
224
- post_install_message:
225
228
  rdoc_options: []
226
229
  require_paths:
227
230
  - lib
@@ -236,8 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
239
  - !ruby/object:Gem::Version
237
240
  version: '0'
238
241
  requirements: []
239
- rubygems_version: 3.5.6
240
- signing_key:
242
+ rubygems_version: 4.0.6
241
243
  specification_version: 4
242
244
  summary: AWS ECS deploy helper
243
245
  test_files: []