Skip to content

Commit

Permalink
Backport of #10746 to v9 (#13197)
Browse files Browse the repository at this point in the history
* Backport of #10746 to v9

* Added prerelease check to new APT promotion pipeline
  • Loading branch information
fheinecke authored Jun 14, 2022
1 parent c44a882 commit a823d2d
Show file tree
Hide file tree
Showing 14 changed files with 1,974 additions and 21 deletions.
130 changes: 129 additions & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5000,6 +5000,134 @@ volumes:
- name: dockersock
temp: {}

---
################################################
# Generated using dronegen, do not edit by hand!
# Use 'make dronegen' to update.
# Generated at dronegen/misc.go:145
################################################

kind: pipeline
type: kubernetes
name: migrate-apt-new-repos
trigger:
event:
include:
- custom
repo:
include:
- non-existent-repository
branch:
include:
- non-existent-branch
clone:
disable: true
steps:
- name: Placeholder
image: alpine:latest
commands:
- echo "This command, step, and pipeline never runs"

---
################################################
# Generated using dronegen, do not edit by hand!
# Use 'make dronegen' to update.
# Generated at dronegen/misc.go:169
################################################

kind: pipeline
type: kubernetes
name: publish-apt-new-repos
trigger:
event:
include:
- promote
target:
include:
- production
repo:
include:
- gravitational/teleport
workspace:
path: /go
clone:
disable: true
steps:
- name: Verify build is tagged
image: alpine:latest
commands:
- '[ -n ${DRONE_TAG} ] || (echo ''DRONE_TAG is not set. Is the commit tagged?''
&& exit 1)'
- name: Check out code
image: alpine/git:latest
commands:
- mkdir -p "/go/src/github.com/gravitational/teleport"
- cd "/go/src/github.com/gravitational/teleport"
- git clone https://github.com/gravitational/${DRONE_REPO_NAME}.git .
- git checkout "${DRONE_TAG}"
- name: Check if tag is prerelease
image: golang:1.17-alpine
commands:
- cd "/go/src/github.com/gravitational/teleport/build.assets/tooling"
- go run ./cmd/check -tag ${DRONE_TAG} -check prerelease || (echo '---> This is
a prerelease, not publishing ${DRONE_TAG} packages to APT repos' && exit 78)
- name: Download artifacts for "${DRONE_TAG}"
image: amazon/aws-cli
commands:
- mkdir -pv "$ARTIFACT_PATH"
- aws s3 sync --no-progress --delete --exclude "*" --include "*.deb*" s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v}/
"$ARTIFACT_PATH"
environment:
ARTIFACT_PATH: /go/artifacts
AWS_ACCESS_KEY_ID:
from_secret: AWS_ACCESS_KEY_ID
AWS_S3_BUCKET:
from_secret: AWS_S3_BUCKET
AWS_SECRET_ACCESS_KEY:
from_secret: AWS_SECRET_ACCESS_KEY
- name: Publish debs to APT repos for "${DRONE_TAG}"
image: golang:1.18.1-bullseye
commands:
- mkdir -pv -m0700 $GNUPGHOME
- echo "$GPG_RPM_SIGNING_ARCHIVE" | base64 -d | tar -xzf - -C $GNUPGHOME
- chown -R root:root $GNUPGHOME
- apt update
- apt install aptly tree -y
- cd "/go/src/github.com/gravitational/teleport/build.assets/tooling"
- export VERSION="${DRONE_TAG}"
- export RELEASE_CHANNEL="stable"
- go run ./cmd/build-apt-repos -bucket "$APT_S3_BUCKET" -local-bucket-path "$BUCKET_CACHE_PATH"
-artifact-version "$VERSION" -release-channel "$RELEASE_CHANNEL" -aptly-root-dir
"$APTLY_ROOT_DIR" -artifact-path "$ARTIFACT_PATH" -log-level 4
- rm -rf "$BUCKET_CACHE_PATH"
- df -h "$APTLY_ROOT_DIR"
environment:
APT_S3_BUCKET:
from_secret: APT_REPO_NEW_AWS_S3_BUCKET
APTLY_ROOT_DIR: /mnt/aptly
ARTIFACT_PATH: /go/artifacts
AWS_ACCESS_KEY_ID:
from_secret: APT_REPO_NEW_AWS_ACCESS_KEY_ID
AWS_REGION: us-west-2
AWS_SECRET_ACCESS_KEY:
from_secret: APT_REPO_NEW_AWS_SECRET_ACCESS_KEY
BUCKET_CACHE_PATH: /tmp/bucket
GNUPGHOME: /tmpfs/gnupg
GPG_RPM_SIGNING_ARCHIVE:
from_secret: GPG_RPM_SIGNING_ARCHIVE
volumes:
- name: aptrepo
path: /mnt
- name: tmpfs
path: /tmpfs
volumes:
- name: aptrepo
claim:
name: drone-s3-aptrepo-pvc
- name: tmpfs
temp:
medium: memory

---
kind: pipeline
type: kubernetes
Expand Down Expand Up @@ -5388,6 +5516,6 @@ volumes:
name: drone-s3-debrepo-pvc
---
kind: signature
hmac: 1b23c22f0b0eb99b4b1df555b1aeb10db2b27f7719076196c776ed9fec87460c
hmac: 5bc8cb78df6224ffc799331a7bb1668bbd738056e69bd42beafa920fe1b60a75

...
215 changes: 215 additions & 0 deletions build.assets/tooling/cmd/build-apt-repos/apt_repo_tool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"io/fs"
"path/filepath"
"strings"
"time"

"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
"golang.org/x/mod/semver"
)

type AptRepoTool struct {
config *Config
aptly *Aptly
s3Manager *S3manager
supportedOSs map[string][]string
}

// Instantiates a new apt repo tool instance and performs any required setup/config.
func NewAptRepoTool(config *Config, supportedOSs map[string][]string) (*AptRepoTool, error) {
art := &AptRepoTool{
config: config,
s3Manager: NewS3Manager(config.bucketName),
supportedOSs: supportedOSs,
}

aptly, err := NewAptly(config.aptlyPath)
if err != nil {
return nil, trace.Wrap(err, "failed to create a new aptly instance")
}

art.aptly = aptly

return art, nil
}

// Runs the tool, creating and updating APT repos based upon the current configuration.
func (art *AptRepoTool) Run() error {
start := time.Now()
logrus.Infoln("Starting APT repo build process...")

isFirstRun, err := art.aptly.IsFirstRun()
if err != nil {
return trace.Wrap(err, "failed to check if Aptly needs (re)built")
}

if isFirstRun {
logrus.Warningln("First run or disaster recovery detected, attempting to rebuild existing repos from APT repository...")

err = art.s3Manager.DownloadExistingRepo(art.config.localBucketPath)
if err != nil {
return trace.Wrap(err, "failed to sync existing repo from S3 bucket")
}

_, err = art.recreateExistingRepos(art.config.localBucketPath)
if err != nil {
return trace.Wrap(err, "failed to recreate existing repos")
}
}

// Note: this logic will only push the artifact into the `art.supportedOSs` repos.
// This behavior is intended to allow deprecating old OS versions in the future
// without removing the associated repos entirely.
artifactRepos, err := art.getArtifactRepos()
if err != nil {
return trace.Wrap(err, "failed to create repos")
}

err = art.importNewDebs(artifactRepos)
if err != nil {
return trace.Wrap(err, "failed to import new debs")
}

err = art.publishRepos()
if err != nil {
return trace.Wrap(err, "failed to publish repos")
}

err = art.s3Manager.UploadBuiltRepo(filepath.Join(art.aptly.rootDir, "public"))
if err != nil {
return trace.Wrap(err, "failed to sync changes to S3 bucket")
}

logrus.Infof("APT repo build process completed in %s", time.Since(start).Round(time.Millisecond))
return nil
}

func (art *AptRepoTool) publishRepos() error {
// Pull in all Aptly repos, not just the latest ones to ensure they all get built into APT repos correctly
repos, err := art.aptly.GetAllRepos()
if err != nil {
return trace.Wrap(err, "failed to get all Aptly repos")
}

// Build a map keyed by os info with value of all repos that support the os in the key
// This will be used to structure the publish command
logrus.Debugf("Categorizing repos according to OS info: %v", RepoNames(repos))
categorizedRepos := make(map[string][]*Repo)
for _, r := range repos {
if osRepos, ok := categorizedRepos[r.OSInfo()]; ok {
categorizedRepos[r.OSInfo()] = append(osRepos, r)
} else {
categorizedRepos[r.OSInfo()] = []*Repo{r}
}
}
logrus.Debugf("Categorized repos: %v", categorizedRepos)

for osInfo, osRepoList := range categorizedRepos {
if len(osRepoList) < 1 {
continue
}

err := art.aptly.PublishRepos(osRepoList, osRepoList[0].os, osRepoList[0].osVersion)
if err != nil {
return trace.Wrap(err, "failed to publish for os %q", osInfo)
}
}

return nil
}

func (art *AptRepoTool) recreateExistingRepos(localPublishedPath string) ([]*Repo, error) {
logrus.Infoln("Recreating previously published repos...")
createdRepos, err := art.aptly.CreateReposFromPublishedPath(localPublishedPath)
if err != nil {
return nil, trace.Wrap(err, "failed to recreate existing repos")
}

for _, repo := range createdRepos {
err := art.aptly.ImportDebsFromExistingRepo(repo)
if err != nil {
return nil, trace.Wrap(err, "failed to import debs from existing repo %q", repo.Name())
}
}

logrus.Infof("Recreated and imported pre-existing artifacts for %d repos", len(createdRepos))
return createdRepos, nil
}

func (art *AptRepoTool) getArtifactRepos() ([]*Repo, error) {
logrus.Infoln("Creating or getting Aptly repos for artifact requirements...")

artifactRepos, err := art.aptly.CreateReposFromArtifactRequirements(art.supportedOSs,
art.config.releaseChannel, semver.Major(art.config.artifactVersion))
if err != nil {
return nil, trace.Wrap(err, "failed to create or get repos from artifact requirements")
}

logrus.Infof("Created or got %d artifact Aptly repos", len(artifactRepos))
return artifactRepos, nil
}

func (art *AptRepoTool) importNewDebs(repos []*Repo) error {
logrus.Debugf("Importing new debs into %d repos: %q", len(repos), strings.Join(RepoNames(repos), "\", \""))
err := filepath.WalkDir(art.config.artifactPath,
func(debPath string, d fs.DirEntry, err error) error {
return art.importNewDebsWalker(debPath, d, err, repos)
},
)
if err != nil {
return trace.Wrap(err, "failed to find and import debs")
}

return nil
}

// This should not be used outside of importNewDebs
func (art *AptRepoTool) importNewDebsWalker(debPath string, d fs.DirEntry, err error, repos []*Repo) error {
if err != nil {
return trace.Wrap(err, "failure while searching %s for debs", debPath)
}

if d.IsDir() {
return nil
}

fileName := d.Name()
if filepath.Ext(fileName) != ".deb" {
return nil
}

// Import new artifacts into all repos that match the artifact's requirements
for _, repo := range repos {
// Other checks could be added here to ensure that a given deb gets added to the correct repo
// such as name or parent directory, facilitating os-specific artifacts
if repo.majorVersion != semver.Major(art.config.artifactVersion) || repo.releaseChannel != art.config.releaseChannel {
continue
}

err = art.aptly.ImportDeb(repo.Name(), debPath)
if err != nil {
return trace.Wrap(err, "failed to import deb from %s", debPath)
}
}

return nil
}
Loading

0 comments on commit a823d2d

Please sign in to comment.