capistrano-dockerbuild 0.2.2 → 1.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: 1478c1f08638b7fbd90429775a474e501920403a455dc222b43bb7c031d2c873
4
- data.tar.gz: 6d77122f43ed9fe79f9c672f4b2d209056c44396a4767efa24f0bd7886a94b45
3
+ metadata.gz: d7346efa239fc872e0194afb86f2e155aec196f8ef16490af28dfb16a8e1b726
4
+ data.tar.gz: 647ae21571209763324edb86400d78498bdbd5b5423784530cac71e01544e783
5
5
  SHA512:
6
- metadata.gz: db2802cde724287f42aadc207c9f195d6a14b9475196c49d3f8a476a6ef61babdf23b3013a1a5d18dfee0c3703beb7b8ace4d989c1af8bf977f4f178b4ed0fe4
7
- data.tar.gz: 759048b911f58047325cd50aae2f84937cee18834b78cb8596a4e185d88e0e8a241eb3c82019cec2e18b64bbc6942d179564bd8514af987bf59d5e7425476560
6
+ metadata.gz: 9e3b6d93525ef5baf7d376f9d893f5f0db6227bb4c8fbb04c11f7c92a126e6ec2481c1dfb7c61562004aa58f4391bca3b31f73217df843b0af062032e32514b4
7
+ data.tar.gz: 949edfc5295850a32f6dbef737127ba2d1e700214fb560666936b4a6e6c8f4be3f60846f7c635b394914088afb97ee93270c4bbb382287c4ac285936d2e007e5
data/README.md CHANGED
@@ -30,6 +30,48 @@ require 'capistrano/dockerbuild'
30
30
  install_plugin Capistrano::Dockerbuild
31
31
  ```
32
32
 
33
+ ```ruby
34
+ # deploy.rb
35
+
36
+ set :application, :sample_project
37
+
38
+ set :repo_url, 'git@github.com:example/example.git'
39
+
40
+ set :git_sha1, `git rev-parse HEAD`.chomp
41
+
42
+ set :branch, fetch(:git_sha1)
43
+
44
+ set :ssh_user, ENV["SSH_USER"] || ENV["USER"] || Etc.getlogin
45
+
46
+ set :ssh_options, {
47
+ user: fetch(:ssh_user),
48
+ port: 22,
49
+ use_agent: true,
50
+ }
51
+
52
+ # add :docker_build role to server definition
53
+ # add :arch property to server definition
54
+ server "docker-build-amd64.example.com", roles: [:docker_build], ssh_options: fetch(:ssh_options), arch: "amd64"
55
+ server "docker-build-arm64.example.com", roles: [:docker_build], ssh_options: fetch(:ssh_options), arch: "arm64"
56
+
57
+ set :docker_registry, "ghcr.io"
58
+ set :docker_build_base_dir, "/home/#{fetch(:ssh_user)}/#{fetch(:application)}"
59
+
60
+ # if docker_build_cmd is proc and has more than 1 arity, pass host object.
61
+ set :docker_build_cmd, ->(host) {
62
+ [:docker, "build", "-f", "Dockerfile", "-t", fetch(:docker_tag_with_arch).call(host), "--build-arg", "host=#{host}", "."]
63
+ }
64
+ set :docker_tag, "ghcr.io/NAMESPACE/IMAGE_NAME:#{fetch(:git_sha1)}"
65
+ ```
66
+
67
+ If any servers have `arch` property, this plugin enables multi architecture mode.
68
+
69
+ The behavior of multi architecture mode is following.
70
+
71
+ 1. build image with a arch suffix like `-amd64`
72
+ 1. push the image
73
+ 1. create a manifest list of pushed images and push it on first server
74
+
33
75
  ## Variables
34
76
 
35
77
  #### Common Variables
@@ -39,19 +81,15 @@ Use common variables
39
81
 
40
82
  | name | required | default | desc |
41
83
  | ---- | ---- | ---- | ---- |
42
- | docker_build_server_host | yes | nil | Build server hostname or SSH::Host object |
43
84
  | docker_build_base_dir | yes | nil | Repository clone to here, and execute build command here |
44
- | docker_registry | no | nil | Docker registry hostname. if use DockerHub, keep nil |
45
- | docker_build_cmd | no | `-> { [:docker, "build", "-t", fetch(:docker_tag_full), "."] }` | Execute command for image building |
46
- | docker_repository_name | no | `-> { fetch(:application) }` | Use by `docker tag {{docker_repository_name}}:tag` |
47
- | docker_tag | no | `-> { fetch(:branch) }` | Use by `docker tag repository:{{docker_tag}}` |
48
- | docker_tag_full | no | `-> { #{fetch(:docker_repository_name)}:#{fetch(:docker_tag)}" }` | Use by `docker tag {{docker_tag_full}}` |
49
- | docker_remote_repository_name | no | `-> { fetch(:docker_repository_name) }` | Use by `docker push docker_registry/{{docker_remote_repository_name}}:docker_remote_tag` |
50
- | docker_remote_tag | no | `-> { fetch(:docker_tag) }` | Use by `docker push docker_registry/docker_remote_repository_name:{{docker_remote_tag}}` |
51
- | docker_remote_tag_full | no | `-> { "#{fetch(:docker_registry) &.+ "/"}#{fetch(:docker_remote_repository_name)}:#{fetch(:docker_remote_tag)}" }` | Use by `docker push {{docker_remote_tag_full}}` |
85
+ | docker_build_cmd | no | `->(host) { [:docker, :build, "-t", fetch(:docker_tag_with_arch).call(host), "."] }` | Execute command for image building |
86
+ | docker_tag | no | `-> { fetch(:application) + ":" + fetch(:branch) }` | Use by `docker tag repository:{{docker_tag}}` |
87
+ | docker_latest_tag | no | false | Add latest tag to building image |
52
88
  | keep_docker_image_count | no | 10 | |
53
89
  | git_http_username | no | nil | See below |
54
90
  | git_http_password | no | nil | See below |
91
+ | git_auth_token | no | nil | See below |
92
+ | update_git_submodule | no | false | Run `git submodule update --init --recursive` after checkout |
55
93
 
56
94
  If you want to use GitHub Apps installation access token or something to authorize repository access using HTTPS protocol. You can set variables in your config/deploy.rb:
57
95
 
@@ -69,8 +107,32 @@ end
69
107
 
70
108
  Update remote URL always if you set proper value to all of `repo_url`, `git_http_username`, and `git_http_password`.
71
109
 
110
+ Alternatively, you can authenticate via an HTTP `Authorization` header using `:git_auth_token`. This is useful when the build server has no SSH key access to the repository (e.g. using a GitHub Apps installation access token or a personal access token).
111
+
112
+ ```ruby
113
+ set :git_auth_token, -> { ENV["GIT_AUTH_TOKEN"] }
114
+ set :repo_url, "git@github.com:owner/repo.git" # SSH or HTTPS both work
115
+ ```
116
+
117
+ When `:git_auth_token` is set, `docker:setup` creates a temporary `.gitconfig` on each remote build host containing:
118
+
119
+ - `http.extraheader = Authorization: Basic <token>` — injects the token into every HTTPS git request
120
+ - `url."https://<host>/".insteadOf` rules — rewrites SSH-style URLs (`git@host:` and `ssh://git@host/`) to HTTPS so the token is used regardless of how `repo_url` is written
121
+
122
+ The host is derived automatically from `:repo_url`, so this works with GitHub, GitLab, Bitbucket, or any self-hosted git server.
123
+
124
+ The temporary directory is removed automatically by `docker:clean` after `docker:build`.
125
+
72
126
  ## Tasks
73
127
 
128
+ #### docker:setup
129
+ - Create a temporary `.gitconfig` on each remote build host when `:git_auth_token` is set
130
+ - Automatically invoked before `docker:check`
131
+
132
+ #### docker:clean
133
+ - Remove the temporary directory created by `docker:setup`
134
+ - Automatically invoked after `docker:build`
135
+
74
136
  #### docker:check
75
137
  - Ensure `#{docker_build_base_dir}`
76
138
  - Ensure git reachable
@@ -87,8 +149,7 @@ Update remote URL always if you set proper value to all of `repo_url`, `git_http
87
149
  - Clear worktree
88
150
 
89
151
  #### docker:push
90
- - docker tag `#{docker_tag_full}` `#{docker_remote_tag_full}`
91
- - docker push `#{docker_remote_tag_full}`
152
+ - docker push `#{docker_tag}`
92
153
 
93
154
  #### docker:cleanup_local_images
94
155
  - Remove docker images
@@ -97,7 +158,9 @@ Update remote URL always if you set proper value to all of `repo_url`, `git_http
97
158
 
98
159
  ## Task Dependency
99
160
 
100
- docker:push => docker:build => docker:update_mirror => docker:clone => docker:check
161
+ docker:push => docker:build => docker:update_mirror => docker:clone => docker:check => docker:setup
162
+
163
+ docker:clean is triggered automatically after docker:build
101
164
 
102
165
  ## Development
103
166
 
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "capistrano-dockerbuild"
7
- spec.version = "0.2.2"
7
+ spec.version = "1.1.0"
8
8
  spec.authors = ["joker1007"]
9
9
  spec.email = ["kakyoin.hierophant@gmail.com"]
10
10
 
@@ -1,19 +1,22 @@
1
1
  require "cgi"
2
2
  require "uri"
3
+ require "securerandom"
3
4
 
4
5
  class Capistrano::Dockerbuild < Capistrano::Plugin
5
6
  def set_defaults
6
- set_if_empty :docker_build_cmd, -> { [:docker, "build", "-t", fetch(:docker_tag_full), "."] }
7
- set_if_empty :docker_repository_name, -> { fetch(:application) }
8
- set_if_empty :docker_tag, -> { fetch(:branch) }
9
- set_if_empty :docker_tag_full, -> { "#{fetch(:docker_repository_name)}:#{fetch(:docker_tag)}" }
10
- set_if_empty :docker_remote_repository_name, -> { fetch(:docker_repository_name) }
11
- set_if_empty :docker_remote_tag, -> { fetch(:docker_tag) }
12
- set_if_empty :docker_remote_tag_full, -> { "#{fetch(:docker_registry) &.+ "/"}#{fetch(:docker_remote_repository_name)}:#{fetch(:docker_remote_tag)}" }
7
+ set_if_empty :docker_tag, -> { fetch(:application) + ":" + fetch(:branch) }
8
+ set :docker_tag_with_arch, ->(host) do
9
+ arch_suffix = host.properties.arch ? "-#{host.properties.arch}" : ""
10
+ fetch(:docker_tag) + arch_suffix
11
+ end
12
+ set_if_empty :docker_build_cmd, ->(host) do
13
+ [:docker, :build, "-t", fetch(:docker_tag_with_arch).call(host), "."]
14
+ end
13
15
  set_if_empty :docker_latest_tag, false
14
16
  set_if_empty :keep_docker_image_count, 10
15
17
  set_if_empty :git_gc_prune_date, "3.days.ago"
16
18
  set_if_empty :docker_build_no_worktree, false
19
+ set_if_empty :update_git_submodule, false
17
20
  end
18
21
 
19
22
  def define_tasks
@@ -39,4 +42,20 @@ class Capistrano::Dockerbuild < Capistrano::Plugin
39
42
  repo_url
40
43
  end
41
44
  end
45
+
46
+ def git_repo_host
47
+ URI.parse(repo_url.sub(/^git@.*/) {|m| "https://#{m}" }).host
48
+ end
49
+
50
+ def tmp_home(host)
51
+ @tmp_host ||= {}
52
+ @tmp_host[host.properties.arch] ||= "/tmp/capistrano-dockerbuild-#{SecureRandom.hex(8)}"
53
+ end
54
+
55
+ def git_env(host)
56
+ return {} if fetch(:git_auth_token).to_s.empty?
57
+
58
+ @git_env ||= {}
59
+ @git_env[host.properties.arch] ||= { HOME: tmp_home(host), GIT_CONFIG_NOSYSTEM: "1" }
60
+ end
42
61
  end
@@ -1,23 +1,51 @@
1
1
  dockerbuild_plugin = self
2
2
 
3
3
  namespace :docker do
4
+ desc "setup temporary gitconfig for HTTP authentication"
5
+ task :setup do
6
+ on roles(:docker_build) do |host|
7
+ unless fetch(:git_auth_token).to_s.empty?
8
+ tmp_home = dockerbuild_plugin.tmp_home(host)
9
+ git_host = dockerbuild_plugin.git_repo_host
10
+ execute :mkdir, "-p", tmp_home
11
+ execute :git, :config, "--file", "#{tmp_home}/.gitconfig", "http.extraheader", "'Authorization: Basic #{fetch(:git_auth_token)}'"
12
+ execute :git, :config, "--file", "#{tmp_home}/.gitconfig", %Q(url."https://#{git_host}/".insteadOf), "git@#{git_host}:"
13
+ execute :git, :config, "--file", "#{tmp_home}/.gitconfig", "--add", %Q(url."https://#{git_host}/".insteadOf), "ssh://git@#{git_host}/"
14
+ end
15
+ end
16
+ end
17
+
18
+ desc "clean up temporary gitconfig"
19
+ task :clean do
20
+ on roles(:docker_build) do |host|
21
+ unless fetch(:git_auth_token).to_s.empty?
22
+ tmp_home = dockerbuild_plugin.tmp_home(host)
23
+ execute :rm, "-rf", tmp_home
24
+ end
25
+ end
26
+ end
27
+
4
28
  desc "check directory exist"
5
- task :check do
6
- on fetch(:docker_build_server_host) do
7
- execute :mkdir, "-p", dockerbuild_plugin.docker_build_base_path.dirname.to_s
8
- execute :git, :'ls-remote', dockerbuild_plugin.git_repo_url, "HEAD"
29
+ task check: [:"docker:setup"] do
30
+ on roles(:docker_build) do |host|
31
+ with dockerbuild_plugin.git_env(host) do
32
+ execute :mkdir, "-p", dockerbuild_plugin.docker_build_base_path.dirname.to_s
33
+ execute :git, :'ls-remote', dockerbuild_plugin.git_repo_url, "HEAD"
34
+ end
9
35
  end
10
36
  end
11
37
 
12
38
  desc "Clone the repo to docker build base directory"
13
39
  task :clone => [:'docker:check'] do
14
- on fetch(:docker_build_server_host) do
40
+ on roles(:docker_build) do |host|
15
41
  if fetch(:docker_build_no_worktree)
16
42
  if test " [ -f #{dockerbuild_plugin.docker_build_base_path}/.git/HEAD ] "
17
43
  info "The repository is at #{dockerbuild_plugin.docker_build_base_path}"
18
44
  else
19
45
  within dockerbuild_plugin.docker_build_base_path.dirname do
20
- execute :git, :clone, dockerbuild_plugin.git_repo_url, dockerbuild_plugin.docker_build_base_path.to_s
46
+ with dockerbuild_plugin.git_env(host) do
47
+ execute :git, :clone, dockerbuild_plugin.git_repo_url, dockerbuild_plugin.docker_build_base_path.to_s
48
+ end
21
49
  end
22
50
  end
23
51
  else
@@ -25,7 +53,9 @@ namespace :docker do
25
53
  info t(:mirror_exists, at: dockerbuild_plugin.docker_build_base_path.to_s)
26
54
  else
27
55
  within dockerbuild_plugin.docker_build_base_path.dirname do
28
- execute :git, :clone, "--mirror", dockerbuild_plugin.git_repo_url, dockerbuild_plugin.docker_build_base_path.to_s
56
+ with dockerbuild_plugin.git_env(host) do
57
+ execute :git, :clone, "--mirror", dockerbuild_plugin.git_repo_url, dockerbuild_plugin.docker_build_base_path.to_s
58
+ end
29
59
  end
30
60
  end
31
61
  end
@@ -34,21 +64,34 @@ namespace :docker do
34
64
 
35
65
  desc "Update the repo mirror to reflect the origin state"
36
66
  task update_mirror: :'docker:clone' do
37
- on fetch(:docker_build_server_host) do
67
+ on roles(:docker_build) do |host|
38
68
  within dockerbuild_plugin.docker_build_base_path do
39
- execute :git, :remote, "set-url", :origin, dockerbuild_plugin.git_repo_url
40
- execute :git, :remote, :update, "--prune"
69
+ with dockerbuild_plugin.git_env(host) do
70
+ execute :git, :remote, "set-url", :origin, dockerbuild_plugin.git_repo_url
71
+ execute :git, :remote, :update, "--prune"
72
+ end
41
73
  end
42
74
  end
43
75
  end
44
76
 
45
- desc "build and push docker image on remote host"
77
+ desc "build docker image on remote host"
46
78
  task :build => [:'docker:update_mirror'] do
47
- on fetch(:docker_build_server_host) do
79
+ on roles(:docker_build) do |host|
80
+ build_cmd = fetch(:docker_build_cmd)
81
+ if build_cmd.is_a?(Proc)
82
+ if build_cmd.arity != 0
83
+ build_cmd = build_cmd.call(host)
84
+ else
85
+ build_cmd = build_cmd.call
86
+ end
87
+ end
48
88
  within dockerbuild_plugin.docker_build_base_path do
49
89
  if fetch(:docker_build_no_worktree)
50
- commands = "sha1=$(git rev-parse #{fetch(:branch)}); git reset --hard ${sha1}; #{fetch(:docker_build_cmd).map {|c| c.to_s.shellescape }.join(" ")}"
51
- execute(:flock, "capistrano_dockerbuild.lock", "-c", "'#{commands}'")
90
+ submodule_cmd = fetch(:update_git_submodule) ? "; git submodule update --init --recursive" : ""
91
+ commands = "sha1=$(git rev-parse #{fetch(:branch)}); git reset --hard ${sha1}#{submodule_cmd}; #{build_cmd.map {|c| c.to_s.shellescape }.join(" ")}"
92
+ with dockerbuild_plugin.git_env(host) do
93
+ execute(:flock, "capistrano_dockerbuild.lock", "-c", "'#{commands}'")
94
+ end
52
95
  else
53
96
  timestamp = Time.now.to_i
54
97
  git_sha1 = `git rev-parse #{fetch(:branch)}`.chomp
@@ -58,7 +101,12 @@ namespace :docker do
58
101
 
59
102
  begin
60
103
  within worktree_dir_name do
61
- execute(*fetch(:docker_build_cmd))
104
+ if fetch(:update_git_submodule)
105
+ with dockerbuild_plugin.git_env(host) do
106
+ execute :git, :submodule, :update, "--init", "--recursive"
107
+ end
108
+ end
109
+ execute(*build_cmd)
62
110
  end
63
111
  ensure
64
112
  execute(:rm, "-rf", worktree_dir_name)
@@ -71,32 +119,52 @@ namespace :docker do
71
119
  end
72
120
  end
73
121
 
122
+ desc "push docker image on remote host"
74
123
  task :push => [:'docker:build'] do
75
- on fetch(:docker_build_server_host) do
76
- execute(:docker, :tag, fetch(:docker_tag_full), fetch(:docker_remote_tag_full))
77
- execute(:docker, :push, fetch(:docker_remote_tag_full))
124
+ on roles(:docker_build) do |host|
125
+ docker_tag_with_arch = fetch(:docker_tag_with_arch).call(host)
126
+ execute(:docker, :push, docker_tag_with_arch)
78
127
  if fetch(:docker_latest_tag)
79
- latest_tag = "#{fetch(:docker_registry) &.+ "/"}#{fetch(:docker_remote_repository_name)}:latest"
80
- execute(:docker, :tag, fetch(:docker_tag_full), latest_tag)
128
+ arch_suffix = host.properties.arch ? "-#{host.properties.arch}" : ""
129
+ latest_tag = docker_tag_with_arch.split(":").first + ":latest" + arch_suffix
130
+ execute(:docker, :tag, docker_tag_with_arch, latest_tag)
81
131
  execute(:docker, :push, latest_tag)
82
132
  end
83
133
  end
134
+
135
+ archs = roles(:docker_build).map { |host| host.properties.arch }.compact.uniq
136
+ unless archs.empty?
137
+ on roles(:docker_build).take(1) do |host|
138
+ manifest_tags = archs.map do |arch|
139
+ arch_suffix = "-#{arch}"
140
+ fetch(:docker_tag) + arch_suffix
141
+ end
142
+ execute(:docker, :manifest, :create, "--amend", fetch(:docker_tag), *manifest_tags)
143
+ execute(:docker, :manifest, :push, "--purge", fetch(:docker_tag))
144
+
145
+ if fetch(:docker_latest_tag)
146
+ latest_tag = fetch(:docker_tag).split(":").first + ":latest"
147
+ execute(:docker, :manifest, :create, "--amend", latest_tag, *manifest_tags)
148
+ execute(:docker, :manifest, :push, "--purge", latest_tag)
149
+ end
150
+ end
151
+ end
84
152
  end
85
153
 
86
154
  task :push_unless_exists => [:'docker:build'] do
87
- on fetch(:docker_build_server_host) do
88
- unless test "docker manifest inspect #{fetch(:docker_remote_tag_full)}"
155
+ on roles(:docker_build).take(1) do
156
+ unless test "docker manifest inspect #{fetch(:docker_tag)}"
89
157
  invoke "docker:push"
90
158
  end
91
159
  end
92
160
  end
93
161
 
94
162
  task :cleanup_local_images do
95
- on fetch(:docker_build_server_host) do
163
+ on roles(:docker_build) do |host|
96
164
  local_images = []
97
165
  capture(:docker, "images --format='{{.ID}}\t{{.Repository}}\t{{.CreatedAt}}'").split("\n").map do |line|
98
166
  id, repository, created_at = line.split("\t")
99
- if repository == fetch(:docker_repository_name)
167
+ if repository == fetch(:docker_tag).split(":").first
100
168
  local_images << { id: id, created_at: created_at }
101
169
  end
102
170
  end
@@ -108,3 +176,5 @@ namespace :docker do
108
176
  end
109
177
  end
110
178
  end
179
+
180
+ after "docker:build", "docker:clean"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-dockerbuild
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - joker1007
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-13 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: capistrano
@@ -73,7 +72,6 @@ homepage: https://github.com/reproio/capistrano-dockerbuild
73
72
  licenses:
74
73
  - MIT
75
74
  metadata: {}
76
- post_install_message:
77
75
  rdoc_options: []
78
76
  require_paths:
79
77
  - lib
@@ -88,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
86
  - !ruby/object:Gem::Version
89
87
  version: '0'
90
88
  requirements: []
91
- rubygems_version: 3.5.11
92
- signing_key:
89
+ rubygems_version: 4.0.10
93
90
  specification_version: 4
94
91
  summary: Capistrano task definition for `docker build`
95
92
  test_files: []