ecs_deploy 2.0.0 → 2.1.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: 7ada66f2f084f7ea0e30b0b6808d2635480db1071ae13acfc16f6f7d4cc02fef
4
- data.tar.gz: 2188aa1ed3727d1c7719bca93006383b831a6f6379ffa28b08aee8a97d5281e5
3
+ metadata.gz: ffd7f96436519805879cb2793cffbe33cf991a6f17109904f014cf35b5f51fe8
4
+ data.tar.gz: 8085faa308fe594cab25202e3cefbcddca7553775b0bd484f4c5ecdfc5db95f5
5
5
  SHA512:
6
- metadata.gz: a3e3f662af6586e71f002853a3599adb107c7db5b8b44c4d7ce3d8d13c6d37ae2210ec9a0a0749813e926f78c4879a3a72d851f6dfd2c466e8ec296a9900f244
7
- data.tar.gz: f96818f250b0644a3908822fa741b26bba564095feff3d78e9a28acc30baa9da5b435200d3c2bc83edf4a2e3e7b73e48e5702ade9f1f3ac7c614be1a9402a067
6
+ metadata.gz: dc6488543f3b0ca040de988a5a2e8df5241ed0c01e2f7e11ad52fce9450f0a9d967685202718c9db87ee4834062faad002d0e17adb7e0b6262a5710f1eeaecf7
7
+ data.tar.gz: b5a11acbafb0b6153961f91b4ef6237248ba484f055fea5092b03cf9f1f70f25519fca19629dd3e72cad44f8bcc349137bee6fcbb64bf0d52a6b5e91d74d5f56
@@ -14,7 +14,7 @@ jobs:
14
14
  steps:
15
15
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
16
  - name: Set up Ruby
17
- uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
17
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
18
18
  with:
19
19
  ruby-version: ${{ matrix.ruby-version }}
20
20
  bundler-cache: true
data/README.md CHANGED
@@ -30,10 +30,13 @@ set :ecs_deploy_wait_timeout, 600 # default: 300
30
30
  set :ecs_wait_until_services_stable_max_attempts, 40 # optional
31
31
  set :ecs_wait_until_services_stable_delay, 15 # optional
32
32
  set :ecs_client_params, { retry_mode: "standard", max_attempts: 10 } # default: {}
33
+ set :ecs_docker_buildx_env, { "DOCKER_CONFIG" => "/path/to/docker/config" } # optional. Env vars for the `docker buildx` command used by use_digest. Merged with each task's docker_buildx_env (the per-task one takes precedence). default: {}
33
34
 
34
35
  set :ecs_tasks, [
35
36
  {
36
37
  name: "myapp-#{fetch(:rails_env)}",
38
+ use_digest: true, # optional. When true, each container_definitions[].image is rewritten to a digest reference (registry/repo@sha256:...) resolved via `docker buildx imagetools inspect` at registration time. Requires the `docker` CLI with buildx on the deploy host; resolution failure aborts the deploy.
39
+ docker_buildx_env: { "DOCKER_CONFIG" => "/path/to/docker/config" }, # optional. Env vars for the `docker buildx` command. Merged on top of :ecs_docker_buildx_env.
37
40
  container_definitions: [
38
41
  {
39
42
  name: "myapp",
@@ -11,6 +11,7 @@ namespace :ecs do
11
11
  c.ecs_wait_until_services_stable_max_attempts = fetch(:ecs_wait_until_services_stable_max_attempts) if fetch(:ecs_wait_until_services_stable_max_attempts)
12
12
  c.ecs_wait_until_services_stable_delay = fetch(:ecs_wait_until_services_stable_delay) if fetch(:ecs_wait_until_services_stable_delay)
13
13
  c.ecs_client_params = fetch(:ecs_client_params) if fetch(:ecs_client_params)
14
+ c.docker_buildx_env = fetch(:ecs_docker_buildx_env) if fetch(:ecs_docker_buildx_env)
14
15
  end
15
16
 
16
17
  if ENV["TARGET_CLUSTER"]
@@ -32,6 +33,8 @@ namespace :ecs do
32
33
  task_definition = EcsDeploy::TaskDefinition.new(
33
34
  region: region,
34
35
  task_definition_name: t[:name],
36
+ use_digest: t[:use_digest],
37
+ docker_buildx_env: t[:docker_buildx_env],
35
38
  container_definitions: t[:container_definitions],
36
39
  task_role_arn: t[:task_role_arn],
37
40
  execution_role_arn: t[:execution_role_arn],
@@ -9,7 +9,8 @@ module EcsDeploy
9
9
  :ecs_service_role,
10
10
  :ecs_wait_until_services_stable_max_attempts,
11
11
  :ecs_wait_until_services_stable_delay,
12
- :ecs_client_params
12
+ :ecs_client_params,
13
+ :docker_buildx_env
13
14
 
14
15
  def initialize
15
16
  @log_level = :info
@@ -18,6 +19,7 @@ module EcsDeploy
18
19
  @ecs_wait_until_services_stable_max_attempts = 40
19
20
  @ecs_wait_until_services_stable_delay = 15
20
21
  @ecs_client_params = {}
22
+ @docker_buildx_env = {}
21
23
  end
22
24
  end
23
25
  end
@@ -1,5 +1,18 @@
1
+ require "open3"
2
+
1
3
  module EcsDeploy
2
4
  class TaskDefinition
5
+ DIGEST_SUFFIX = /@sha256:[0-9a-f]{64}\z/
6
+ DIGEST_FORMAT = /\Asha256:[0-9a-f]{64}\z/
7
+
8
+ # Process-wide cache of resolved digests keyed by image reference,
9
+ # so the same image is only inspected via docker once per process.
10
+ @digest_cache = {}
11
+
12
+ class << self
13
+ attr_reader :digest_cache
14
+ end
15
+
3
16
  def self.deregister(arn, region: nil)
4
17
  region ||= EcsDeploy.config.default_region
5
18
  params ||= EcsDeploy.config.ecs_client_params
@@ -10,8 +23,12 @@ module EcsDeploy
10
23
  EcsDeploy.logger.info "deregistered task definition [#{arn}] [#{client.config.region}] [#{Paint['OK', :green]}]"
11
24
  end
12
25
 
13
- def initialize(task_definition_name:, region: nil, **options)
26
+ def initialize(task_definition_name:, region: nil, use_digest: false, docker_buildx_env: nil, **options)
14
27
  @task_definition_name = task_definition_name
28
+ @use_digest = use_digest
29
+ @docker_buildx_env = (EcsDeploy.config.docker_buildx_env || {})
30
+ .merge(docker_buildx_env || {})
31
+ .map { |k, v| [k.to_s, v&.to_s] }.to_h
15
32
  region ||= EcsDeploy.config.default_region
16
33
  params ||= EcsDeploy.config.ecs_client_params
17
34
 
@@ -49,11 +66,61 @@ module EcsDeploy
49
66
  end
50
67
 
51
68
  def register
52
- res = @client.register_task_definition(
53
- @options.merge(family: @task_definition_name)
54
- )
69
+ options = @options.merge(family: @task_definition_name)
70
+ options[:container_definitions] = apply_digests(options[:container_definitions]) if @use_digest
71
+ res = @client.register_task_definition(options)
55
72
  EcsDeploy.logger.info "registered task definition [#{@task_definition_name}] [#{@region}] [#{Paint['OK', :green]}]"
56
73
  res.task_definition
57
74
  end
75
+
76
+ private
77
+
78
+ def apply_digests(container_definitions)
79
+ (container_definitions || []).map do |cd|
80
+ image = cd[:image]
81
+ next cd if image.nil? || image.empty?
82
+
83
+ resolved = resolve_image_with_digest(image)
84
+ EcsDeploy.logger.info "resolved image [#{image}] -> [#{resolved}]" if resolved != image
85
+ cd.merge(image: resolved)
86
+ end
87
+ end
88
+
89
+ def resolve_image_with_digest(image)
90
+ return image if image =~ DIGEST_SUFFIX
91
+
92
+ "#{repository_without_tag(image)}@#{fetch_manifest_digest(image)}"
93
+ end
94
+
95
+ # "registry:5000/ns/repo:tag" -> "registry:5000/ns/repo"
96
+ # "registry:5000/ns/repo" -> "registry:5000/ns/repo"
97
+ # "repo:tag" -> "repo"
98
+ def repository_without_tag(image)
99
+ name_start = (idx = image.rindex("/")) ? idx + 1 : 0
100
+ colon = image.index(":", name_start)
101
+ colon ? image[0...colon] : image
102
+ end
103
+
104
+ def fetch_manifest_digest(image)
105
+ cache = self.class.digest_cache
106
+ if (cached = cache[image])
107
+ EcsDeploy.logger.debug "using cached digest for #{image}: #{cached}"
108
+ return cached
109
+ end
110
+
111
+ EcsDeploy.logger.debug "docker buildx imagetools inspect --format '{{.Manifest.Digest}}' #{image}"
112
+ stdout, stderr, status =
113
+ Open3.capture3(@docker_buildx_env, "docker", "buildx", "imagetools", "inspect", "--format", "{{.Manifest.Digest}}", image)
114
+ unless status.success?
115
+ raise EcsDeploy::Error, "docker buildx imagetools inspect failed for '#{image}': #{stderr.strip}"
116
+ end
117
+ digest = stdout.strip
118
+ unless digest =~ DIGEST_FORMAT
119
+ raise EcsDeploy::Error, "Unexpected digest for '#{image}': #{digest.inspect}"
120
+ end
121
+ cache[image] = digest
122
+ rescue Errno::ENOENT => e
123
+ raise EcsDeploy::Error, "docker command not found: #{e.message}"
124
+ end
58
125
  end
59
126
  end
@@ -1,3 +1,3 @@
1
1
  module EcsDeploy
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
data/lib/ecs_deploy.rb CHANGED
@@ -7,6 +7,8 @@ require 'terminal-table'
7
7
  require 'paint'
8
8
 
9
9
  module EcsDeploy
10
+ class Error < StandardError; end
11
+
10
12
  def self.logger
11
13
  @logger ||= Logger.new(STDOUT).tap do |l|
12
14
  l.level = Logger.const_get(config.log_level.to_s.upcase)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecs_deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007