-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFD 58: Package Distribution #10746
RFD 58: Package Distribution #10746
Changes from all commits
5f0a933
f2456a9
62d53b0
286a482
88518e1
80b3a4b
13d9859
b160bd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5092,6 +5092,128 @@ volumes: | |
- name: dockersock | ||
temp: {} | ||
|
||
--- | ||
################################################ | ||
# Generated using dronegen, do not edit by hand! | ||
# Use 'make dronegen' to update. | ||
# Generated at dronegen/misc.go:133 | ||
################################################ | ||
|
||
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:157 | ||
################################################ | ||
|
||
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: 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need post-build cleanup? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can certainly add |
||
- 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 | ||
|
@@ -5480,6 +5602,6 @@ volumes: | |
name: drone-s3-debrepo-pvc | ||
--- | ||
kind: signature | ||
hmac: e83f39ac80fa38122a8cf34a6202f36a6d08163e8a31c30ce2e7599222f8b103 | ||
hmac: 0f665f13b0e591f35ceb28f503ac0e81e219011914af7d27bcb1ced907ebd397 | ||
|
||
... |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When writing this I thought that would be a great idea, but ultimately decided against it as it has several downsides:
Not pulling in the |
||
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 { | ||
fheinecke marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be a part of the drone pipeline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about this a lot while building this part out. Putting it in the pipeline is by far the easiest way to implement migrations for older versions. In 99.9% of cases this pipeline will never be ran, but when we need to add previous versions this will save significant time. This section along with this new dronegen function takes another migration process like I did last week and turns it into a 5m change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this particular pipeline is here, though. I get
publish-apt-new-repos
, but not this one. Is it just marking that you intend to implement the migration of legacy here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pipeline is the implementation of old artifact migration. It's just not enabled/ran unless dronegen is configured to migrate specific versions. When specific versions are added to this function then running
make dronegen
will add migration steps to this pipeline for Drone to run. When there are not any functions listed there, runningmake dronegen
will replace the pipeline with a "NOP" pipeline to prevent repeated migrations.