Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
/doc/
/log/*.log
/pkg/
/tmp/
/tmp/*
!/tmp/.gitkeep
/private/

# rspec failure tracking
Expand Down
4 changes: 4 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
include:
- local: .gitlab/ci/release-coordinator.canary.gitlab-ci.yml

stages:
- build
- components
- !reference [.release-coordinator:canary:stages]

default:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Expand Down
67 changes: 67 additions & 0 deletions .gitlab/ci/release-coordinator.canary.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.release-coordinator:canary:stages:
- release-coordinator:canary:build
- release-coordinator:canary:publish

.release-coordinator:canary:
rules:
- if: $RELEASE_COORDINATOR == "canary"

release-coordinator:canary:tmp-branch:
extends:
- .release-coordinator:canary
stage: release-coordinator:canary:build
script:
- bin/pyxis internal release_canary_tmp_branch --build-id-to-promote $BUILD_ID_TO_PROMOTE
variables:
DRY_RUN: "false"
artifacts:
reports:
dotenv: tmp/reticulum_variables.env

release-coordinator:canary:build:
extends:
- .release-coordinator:canary
stage: release-coordinator:canary:build
needs:
- release-coordinator:canary:tmp-branch
trigger:
project: code0-tech/development/reticulum
branch: pyxis/canary-build/$BUILD_ID_TO_PROMOTE
forward:
pipeline_variables: true
strategy: depend
variables:
RETICULUM_BUILD_TYPE: canary

release-coordinator:canary:tmp-branch-cleanup:
extends:
- .release-coordinator:canary
stage: release-coordinator:canary:build
needs:
- release-coordinator:canary:build
script:
- bin/pyxis internal release_canary_tmp_branch_cleanup --build-id-to-promote $BUILD_ID_TO_PROMOTE
variables:
DRY_RUN: "false"
when: always

release-coordinator:canary:publish:
extends:
- .release-coordinator:canary
stage: release-coordinator:canary:publish
needs:
- release-coordinator:canary:build
script:
- echo "Publishing approved"
when: manual

release-coordinator:canary:publish-containers:
extends:
- .release-coordinator:canary
stage: release-coordinator:canary:publish
needs:
- release-coordinator:canary:publish
script:
- bin/pyxis internal release_canary_publish_tags --coordinator-pipeline-id $CI_PIPELINE_ID
variables:
DRY_RUN: "false"
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ gem 'jwt', '~> 2.10'
gem 'octokit', '~> 10.0'
gem 'openssl', '~> 3.3'

gem 'semantic_logger', '~> 4.16'
gem 'semantic_logger', '~> 4.16', require: 'semantic_logger/sync'

gem 'json', '~> 2.12'

Expand Down
8 changes: 7 additions & 1 deletion lib/pyxis/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ class Cli < Thor
desc 'components', 'Commands managing projects under managed versioning'
subcommand 'components', Pyxis::Commands::Components

def self.exit_on_failure?
desc 'release', 'Commands managing the release process'
subcommand 'release', Pyxis::Commands::Release

desc 'internal', 'Internal commands for usage by the pipeline', hide: true
subcommand 'internal', Pyxis::Commands::Internal

def Thor.exit_on_failure?
true
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/pyxis/commands/components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def update
def list
result = 'Available components:'
Pyxis::Project.components.each do |project|
result += "\n- #{project.downcase}"
result += "\n- #{project}"
end
result
end
Expand Down
97 changes: 97 additions & 0 deletions lib/pyxis/commands/internal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

module Pyxis
module Commands
class Internal < Thor
include Thor::Actions

RETICULUM_CI_BUILDS_PREFIX = 'ghcr.io/code0-tech/reticulum/ci-builds/'
CONTAINER_RELEASE_PREFIX = 'registry.gitlab.com/code0-tech/packages/'

desc 'release_canary_tmp_branch', ''
method_option :build_id_to_promote, required: true, type: :numeric
def release_canary_tmp_branch
component_information = Pyxis::ManagedVersioning::ComponentInfo.new(
build_id: options[:build_id_to_promote]
).execute

raise 'Build not found' if component_information.nil?

GitlabClient.client.create_branch(
Project::Reticulum.api_gitlab_path,
"pyxis/canary-build/#{options[:build_id_to_promote]}",
component_information[:reticulum]
)

version_variables = component_information.map do |component, version|
next nil unless Project.components.include?(component)

["OVERRIDE_#{component}_VERSION", version]
end.compact

create_env_file(
'reticulum_variables',
version_variables + [['C0_GH_TOKEN', Pyxis::Environment.github_reticulum_publish_token]]
)
end

desc 'release_canary_tmp_branch_cleanup', ''
method_option :build_id_to_promote, required: true, type: :numeric
def release_canary_tmp_branch_cleanup
GitlabClient.client.delete_branch(
Project::Reticulum.api_gitlab_path,
"pyxis/canary-build/#{options[:build_id_to_promote]}"
)
end

desc 'release_canary_publish_tags', ''
method_option :coordinator_pipeline_id, required: true, type: :numeric
def release_canary_publish_tags
build_id = GitlabClient.client
.list_pipeline_bridges(Project::Pyxis.api_gitlab_path, options[:coordinator_pipeline_id])
.find { |bridge| bridge['name'] == 'release-coordinator:canary:build' }
.dig('downstream_pipeline', 'id')

info = ManagedVersioning::ComponentInfo.new(build_id: build_id)
container_tag = info.find_container_tag_for_build_id
container_tags = info.find_manifests.map do |manifest|
next nil unless Project.components.include?(manifest.first.to_sym)

next "#{manifest.first}:#{container_tag}" if manifest.length == 1

"#{manifest.first}:#{container_tag}-#{manifest.last}"
end.compact

File.write('tmp/gitlab_token', Pyxis::Environment.gitlab_release_tools_token)
run 'crane auth login -u code0-release-tools --password-stdin registry.gitlab.com < tmp/gitlab_token'

overall_success = true

original_pretend = options[:pretend]
options[:pretend] = Pyxis::GlobalStatus.dry_run?
container_tags.each do |tag|
success = run "crane copy #{RETICULUM_CI_BUILDS_PREFIX}#{tag} #{CONTAINER_RELEASE_PREFIX}#{tag}",
abort_on_failure: false
overall_success &&= success

logger.error('Failed to copy container image to release registry', image: tag) unless success
end
options[:pretend] = original_pretend

run 'crane auth logout registry.gitlab.com'
File.delete('tmp/gitlab_token')

abort unless overall_success || Pyxis::GlobalStatus.dry_run?
end

no_commands do
include SemanticLogger::Loggable

def create_env_file(name, variables)
path = File.absolute_path(File.join(__FILE__, "../../../../tmp/#{name}.env"))
File.write(path, variables.map { |k, v| "#{k}=#{v}" }.join("\n"))
end
end
end
end
end
47 changes: 47 additions & 0 deletions lib/pyxis/commands/release.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Pyxis
module Commands
class Release < Thor
include PermissionHelper

desc 'create_canary', 'Promote an experimental build to canary'
exclusive do
at_least_one do
method_option :build,
aliases: '-b',
desc: 'The build ID',
required: false,
type: :numeric
method_option :container_tag,
aliases: '-c',
desc: 'The container tag excluding variant modifiers',
required: false,
type: :string
end
end
def create_canary
assert_executed_by_delivery_team_member!

build_id = options[:build] || ManagedVersioning::ComponentInfo.new(
container_tag: options[:container_tag]
).find_build_id_for_container_tag

raise Pyxis::MessageError, 'This build does not exist' if build_id.nil?

pipeline = GitlabClient.client.create_pipeline(
Project::Pyxis.api_gitlab_path,
Project::Pyxis.default_branch,
variables: {
RELEASE_COORDINATOR: 'canary',
BUILD_ID_TO_PROMOTE: build_id.to_s,
}
)

raise Pyxis::MessageError, 'Failed to create pipeline' if pipeline.response.status != 201

"Created coordinator pipeline at #{pipeline.body.web_url}"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/pyxis/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def github_release_tools_approver_private_key
File.read(ENV.fetch('PYXIS_GH_RELEASE_TOOLS_APPROVER_PRIVATE_KEY'))
end

def github_reticulum_publish_token
File.read(ENV.fetch('PYXIS_GH_RETICULUM_PUBLISH_TOKEN'))
end

def gitlab_release_tools_token
File.read(ENV.fetch('PYXIS_GL_RELEASE_TOOLS_PRIVATE_TOKEN'))
end
Expand Down
2 changes: 2 additions & 0 deletions lib/pyxis/github_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Pyxis
class GithubClient
include SemanticLogger::Loggable

ORGANIZATION_NAME = 'code0-tech'

CLIENT_CONFIGS = {
release_tools: {
app_id: 857194,
Expand Down
38 changes: 38 additions & 0 deletions lib/pyxis/gitlab_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,33 @@ def initialize(faraday)
@faraday = faraday
end

# @param project_path_or_id Project path or id to create the branch in
# @param branch The name of the branch to create
# @param ref The branch name or commit sha to create the branch from
def create_branch(project_path_or_id, branch, ref)
post_json(
"/api/v4/projects/#{project_path_or_id}/repository/branches",
{
branch: branch,
ref: ref,
}
)
end

def delete_branch(project_path_or_id, branch)
delete("/api/v4/projects/#{project_path_or_id}/repository/branches/#{path_encode branch}")
end

def create_pipeline(project_path_or_id, ref, variables: nil)
if variables.is_a?(Hash)
variables = variables.map do |key, value|
{
key: key,
value: value,
}
end
end

post_json(
"/api/v4/projects/#{project_path_or_id}/pipeline",
{
Expand All @@ -68,6 +94,18 @@ def create_pipeline(project_path_or_id, ref, variables: nil)
}
)
end

def list_pipeline_bridges(project_path_or_id, pipeline_id)
paginate_json("/api/v4/projects/#{project_path_or_id}/pipelines/#{pipeline_id}/bridges")
end

def path_encode(content)
content.gsub('/', '%2F')
end

def paginate_json(url, options = {})
GitlabClient.paginate_json(faraday, url, options)
end
end

class PageLinks
Expand Down
Loading