Skip to content

Commit eaad060

Browse files
committed
Start implementation of canary release
1 parent 88da879 commit eaad060

17 files changed

Lines changed: 355 additions & 27 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
/doc/
33
/log/*.log
44
/pkg/
5-
/tmp/
5+
/tmp/*
6+
!/tmp/.gitkeep
67
/private/
78

89
# rspec failure tracking

.gitlab-ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
include:
2+
- local: .gitlab/ci/release-coordinator.canary.gitlab-ci.yml
3+
14
stages:
25
- build
36
- components
7+
- !reference [.release-coordinator:canary:stages]
48

59
default:
610
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.release-coordinator:canary:stages:
2+
- release-coordinator:canary:build
3+
- release-coordinator:canary:publish
4+
5+
.release-coordinator:canary:
6+
rules:
7+
- if: $RELEASE_COORDINATOR == "canary"
8+
9+
release-coordinator:canary:tmp-branch:
10+
extends:
11+
- .release-coordinator:canary
12+
stage: release-coordinator:canary:build
13+
script:
14+
- bin/pyxis internal release_canary_tmp_branch --build-id-to-promote $BUILD_ID_TO_PROMOTE
15+
variables:
16+
DRY_RUN: "false"
17+
artifacts:
18+
reports:
19+
dotenv: tmp/reticulum_variables.env
20+
21+
release-coordinator:canary:build:
22+
extends:
23+
- .release-coordinator:canary
24+
stage: release-coordinator:canary:build
25+
needs:
26+
- release-coordinator:canary:tmp-branch
27+
trigger:
28+
project: code0-tech/development/reticulum
29+
branch: pyxis/canary-build/$BUILD_ID_TO_PROMOTE
30+
forward:
31+
pipeline_variables: true
32+
strategy: depend
33+
variables:
34+
RETICULUM_BUILD_TYPE: canary
35+
36+
release-coordinator:canary:tmp-branch-cleanup:
37+
extends:
38+
- .release-coordinator:canary
39+
stage: release-coordinator:canary:build
40+
needs:
41+
- release-coordinator:canary:build
42+
script:
43+
- bin/pyxis internal release_canary_tmp_branch_cleanup --build-id-to-promote $BUILD_ID_TO_PROMOTE
44+
variables:
45+
DRY_RUN: "false"
46+
when: always
47+
48+
release-coordinator:canary:publish:
49+
extends:
50+
- .release-coordinator:canary
51+
stage: release-coordinator:canary:publish
52+
needs:
53+
- release-coordinator:canary:build
54+
script:
55+
- echo "Publishing approved"
56+
when: manual
57+
58+
release-coordinator:canary:publish-containers:
59+
extends:
60+
- .release-coordinator:canary
61+
stage: release-coordinator:canary:publish
62+
needs:
63+
- release-coordinator:canary:publish
64+
script:
65+
- bin/pyxis internal release_canary_publish_tags --coordinator-pipeline-id $CI_PIPELINE_ID
66+
variables:
67+
DRY_RUN: "false"

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ gem 'jwt', '~> 2.10'
1313
gem 'octokit', '~> 10.0'
1414
gem 'openssl', '~> 3.3'
1515

16-
gem 'semantic_logger', '~> 4.16'
16+
gem 'semantic_logger', '~> 4.16', require: 'semantic_logger/sync'
1717

1818
gem 'json', '~> 2.12'
1919

lib/pyxis/cli.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ class Cli < Thor
55
desc 'components', 'Commands managing projects under managed versioning'
66
subcommand 'components', Pyxis::Commands::Components
77

8-
def self.exit_on_failure?
8+
desc 'release', 'Commands managing the release process'
9+
subcommand 'release', Pyxis::Commands::Release
10+
11+
desc 'internal', 'Internal commands for usage by the pipeline', hide: true
12+
subcommand 'internal', Pyxis::Commands::Internal
13+
14+
def Thor.exit_on_failure?
915
true
1016
end
1117
end

lib/pyxis/commands/components.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def update
7171
def list
7272
result = 'Available components:'
7373
Pyxis::Project.components.each do |project|
74-
result += "\n- #{project.downcase}"
74+
result += "\n- #{project}"
7575
end
7676
result
7777
end

lib/pyxis/commands/internal.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# frozen_string_literal: true
2+
3+
module Pyxis
4+
module Commands
5+
class Internal < Thor
6+
include Thor::Actions
7+
8+
RETICULUM_CI_BUILDS_PREFIX = 'ghcr.io/code0-tech/reticulum/ci-builds/'
9+
CONTAINER_RELEASE_PREFIX = 'registry.gitlab.com/code0-tech/packages/'
10+
11+
desc 'release_canary_tmp_branch', ''
12+
method_option :build_id_to_promote, required: true, type: :numeric
13+
def release_canary_tmp_branch
14+
component_information = Pyxis::ManagedVersioning::ComponentInfo.new(
15+
build_id: options[:build_id_to_promote]
16+
).execute
17+
18+
raise 'Build not found' if component_information.nil?
19+
20+
GitlabClient.client.create_branch(
21+
Project::Reticulum.api_gitlab_path,
22+
"pyxis/canary-build/#{options[:build_id_to_promote]}",
23+
component_information[:reticulum]
24+
)
25+
26+
version_variables = component_information.map do |component, version|
27+
next nil unless Project.components.include?(component)
28+
29+
["OVERRIDE_#{component}_VERSION", version]
30+
end.compact
31+
32+
create_env_file(
33+
'reticulum_variables',
34+
version_variables + [['C0_GH_TOKEN', Pyxis::Environment.github_reticulum_publish_token]]
35+
)
36+
end
37+
38+
desc 'release_canary_tmp_branch_cleanup', ''
39+
method_option :build_id_to_promote, required: true, type: :numeric
40+
def release_canary_tmp_branch_cleanup
41+
GitlabClient.client.delete_branch(
42+
Project::Reticulum.api_gitlab_path,
43+
"pyxis/canary-build/#{options[:build_id_to_promote]}"
44+
)
45+
end
46+
47+
desc 'release_canary_publish_tags', ''
48+
method_option :coordinator_pipeline_id, required: true, type: :numeric
49+
def release_canary_publish_tags
50+
build_id = GitlabClient.client
51+
.list_pipeline_bridges(Project::Pyxis.api_gitlab_path, options[:coordinator_pipeline_id])
52+
.find { |bridge| bridge['name'] == 'release-coordinator:canary:build' }
53+
.dig('downstream_pipeline', 'id')
54+
55+
info = ManagedVersioning::ComponentInfo.new(build_id: build_id)
56+
container_tag = info.find_container_tag_for_build_id
57+
container_tags = info.find_manifests.map do |manifest|
58+
next nil unless Project.components.include?(manifest.first.to_sym)
59+
60+
next "#{manifest.first}:#{container_tag}" if manifest.length == 1
61+
62+
"#{manifest.first}:#{container_tag}-#{manifest.last}"
63+
end.compact
64+
65+
File.write('tmp/gitlab_token', Pyxis::Environment.gitlab_release_tools_token)
66+
run 'crane auth login -u code0-release-tools --password-stdin registry.gitlab.com < tmp/gitlab_token'
67+
68+
overall_success = true
69+
70+
original_pretend = options[:pretend]
71+
options[:pretend] = Pyxis::GlobalStatus.dry_run?
72+
container_tags.each do |tag|
73+
success = run "crane copy #{RETICULUM_CI_BUILDS_PREFIX}#{tag} #{CONTAINER_RELEASE_PREFIX}#{tag}",
74+
abort_on_failure: false
75+
overall_success &&= success
76+
77+
logger.error('Failed to copy container image to release registry', image: tag) unless success
78+
end
79+
options[:pretend] = original_pretend
80+
81+
run 'crane auth logout registry.gitlab.com'
82+
File.delete('tmp/gitlab_token')
83+
84+
abort unless overall_success || Pyxis::GlobalStatus.dry_run?
85+
end
86+
87+
no_commands do
88+
include SemanticLogger::Loggable
89+
90+
def create_env_file(name, variables)
91+
path = File.absolute_path(File.join(__FILE__, "../../../../tmp/#{name}.env"))
92+
File.write(path, variables.map { |k, v| "#{k}=#{v}" }.join("\n"))
93+
end
94+
end
95+
end
96+
end
97+
end

lib/pyxis/commands/release.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module Pyxis
4+
module Commands
5+
class Release < Thor
6+
include PermissionHelper
7+
8+
desc 'create_canary', 'Promote an experimental build to canary'
9+
exclusive do
10+
at_least_one do
11+
method_option :build,
12+
aliases: '-b',
13+
desc: 'The build ID',
14+
required: false,
15+
type: :numeric
16+
method_option :container_tag,
17+
aliases: '-c',
18+
desc: 'The container tag excluding variant modifiers',
19+
required: false,
20+
type: :string
21+
end
22+
end
23+
def create_canary
24+
assert_executed_by_delivery_team_member!
25+
26+
build_id = options[:build] || ManagedVersioning::ComponentInfo.new(
27+
container_tag: options[:container_tag]
28+
).find_build_id_for_container_tag
29+
30+
raise Pyxis::MessageError, 'This build does not exist' if build_id.nil?
31+
32+
pipeline = GitlabClient.client.create_pipeline(
33+
Project::Pyxis.api_gitlab_path,
34+
Project::Pyxis.default_branch,
35+
variables: {
36+
RELEASE_COORDINATOR: 'canary',
37+
BUILD_ID_TO_PROMOTE: build_id.to_s,
38+
}
39+
)
40+
41+
raise Pyxis::MessageError, 'Failed to create pipeline' if pipeline.response.status != 201
42+
43+
"Created coordinator pipeline at #{pipeline.body.web_url}"
44+
end
45+
end
46+
end
47+
end

lib/pyxis/environment.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def github_release_tools_approver_private_key
1616
File.read(ENV.fetch('PYXIS_GH_RELEASE_TOOLS_APPROVER_PRIVATE_KEY'))
1717
end
1818

19+
def github_reticulum_publish_token
20+
File.read(ENV.fetch('PYXIS_GH_RETICULUM_PUBLISH_TOKEN'))
21+
end
22+
1923
def gitlab_release_tools_token
2024
File.read(ENV.fetch('PYXIS_GL_RELEASE_TOOLS_PRIVATE_TOKEN'))
2125
end

lib/pyxis/github_client.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module Pyxis
44
class GithubClient
55
include SemanticLogger::Loggable
66

7+
ORGANIZATION_NAME = 'code0-tech'
8+
79
CLIENT_CONFIGS = {
810
release_tools: {
911
app_id: 857194,

0 commit comments

Comments
 (0)