mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 00:33:50 +00:00
Added multiarch build support for teleport-operator (#16688)
* Added multiarch build support for teleport oss, ent, and fips * Exported image/imageTag types * Resigned dronegen * Removed remainder of testing changes * Removed changes to submodules * Reverted dockerfile-fips change * FIxed docs wording * Un-exported most constants * Removed teleport.e makefile deb call * Moved "sed | cut magic" to files * Re-added `mkdir -pv /go/cache` to push.go * Command deterministic order fix * Added staging-only tag pipeline * Moved PR to teleport operator to minimize potential issue impact * Updated promote to pull and push without build * Made cron triggers not affect canonical tags * Added check for pre-existing tags on immutable CRs * Added immutability check to manifests * Updated staging ecr to only apply $TIMESTAMP tag on cron triggers * Updated triggerinfo struct to use a triggerflag struct * Fixed makefile after git mistake * Makefile fix * PR fixes * Moved internal tools Go version to constant * Separated container images gofile into multiple files * Moved testing comment * Added licenses * Reorganized and added docs for container images * Moved const to correct file * Tag trigger logic test * Testing specific fix * Moved testing to v10.3.2 * Make semver dirs * Refactored local registry name/socket * Merged previous dockerfile changes * Added TARGETOS TARGETARCH args * Updatd tag to testing tag * Promotion logic test * Promotion fixes * Testing specific fix * Removed prerelease check for testing * Added staging login commands to promote * Fixed missing credentials on promotion pull * Rerun tag test with new "full" semver * Made staging builds only publish full semver * Added semver logging command * Empty commit to trigger Drone * Promotion test * Fixed preceeding v on promote pull * Empty commit to trigger Drone * Re-enabled verify not prerelease step on promote * Cron trigger test * Testing fix * Testing fix 2 * Added sleep timer on docker buildx build * Testing cleanup
This commit is contained in:
parent
5621f42bef
commit
633b9582e7
3008
.drone.yml
3008
.drone.yml
File diff suppressed because it is too large
Load diff
18
Makefile
18
Makefile
|
@ -16,7 +16,6 @@ VERSION=12.0.0-dev
|
|||
DOCKER_IMAGE_QUAY ?= quay.io/gravitational/teleport
|
||||
DOCKER_IMAGE_ECR ?= public.ecr.aws/gravitational/teleport
|
||||
DOCKER_IMAGE_STAGING ?= 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport
|
||||
DOCKER_IMAGE_OPERATOR_STAGING ?= 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator
|
||||
|
||||
|
||||
GOPATH ?= $(shell go env GOPATH)
|
||||
|
@ -1035,23 +1034,6 @@ publish-ci: image-ci
|
|||
fi
|
||||
if [ -f e/Makefile ]; then $(MAKE) -C e publish-ci; fi
|
||||
|
||||
# Docker image build for Teleport Operator
|
||||
.PHONY: image-operator-ci
|
||||
image-operator-ci:
|
||||
make -C operator docker-build IMG="$(DOCKER_IMAGE_OPERATOR_STAGING):$(VERSION)"
|
||||
|
||||
# DOCKER_CLI_EXPERIMENTAL=enabled is set to allow inspecting the manifest for present images.
|
||||
# https://docs.docker.com/engine/reference/commandline/cli/#experimental-features
|
||||
# The internal staging images use amazon ECR's immutable repository settings. This makes overwrites impossible currently.
|
||||
# This can cause issues when drone tagging pipelines must be re-run due to failures.
|
||||
# Currently the work around for this is to not attempt to push to the image when it already exists.
|
||||
.PHONY: publish-operator-ci
|
||||
publish-operator-ci: image-operator-ci
|
||||
@if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "$(DOCKER_IMAGE_OPERATOR_STAGING):$(VERSION)" >/dev/null 2>&1; then \
|
||||
echo "$(DOCKER_IMAGE_OPERATOR_STAGING):$(VERSION) already exists. "; \
|
||||
else \
|
||||
docker push "$(DOCKER_IMAGE_OPERATOR_STAGING):$(VERSION)"; \
|
||||
fi
|
||||
|
||||
.PHONY: print-version
|
||||
print-version:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Those variables are extracted from build.assets/Makefile so they can be imported
|
||||
# by other Makefiles
|
||||
# These values may need to be updated in `dronegen/container_image_products.go` if
|
||||
# they change here
|
||||
BUILDBOX_VERSION ?= teleport11
|
||||
|
||||
BUILDBOX=public.ecr.aws/gravitational/teleport-buildbox:$(BUILDBOX_VERSION)
|
||||
|
|
|
@ -33,6 +33,16 @@ const (
|
|||
// ProductionRegistryQuay is the production image registry that hosts images on quay.io. Will be deprecated in the future.
|
||||
// See RFD 73 - https://github.com/gravitational/teleport/blob/c18c09f5d562dd46a509154eab4295ad39decc3c/rfd/0073-public-image-registry.md
|
||||
ProductionRegistryQuay = "quay.io"
|
||||
|
||||
// Go version used by internal tools
|
||||
GoVersion = "1.18"
|
||||
|
||||
// The name of this service must match k8s.io/apimachinery/pkg/util/validation `IsDNS1123Subdomain`
|
||||
// so that it is resolvable
|
||||
// See https://github.com/drone-runners/drone-runner-kube/blob/master/engine/compiler/compiler.go#L398
|
||||
// for details
|
||||
LocalRegistryHostname string = "drone-docker-registry"
|
||||
LocalRegistrySocket string = LocalRegistryHostname + ":5000"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -105,12 +115,20 @@ func pushTriggerForBranch(branches ...string) trigger {
|
|||
return t
|
||||
}
|
||||
|
||||
func cronTrigger(cronJobNames []string) trigger {
|
||||
return trigger{
|
||||
Cron: triggerRef{Include: cronJobNames},
|
||||
Repo: triggerRef{Include: []string{"gravitational/teleport"}},
|
||||
}
|
||||
}
|
||||
|
||||
func cloneRepoCommands(cloneDirectory, commit string) []string {
|
||||
return []string{
|
||||
fmt.Sprintf("mkdir -pv %q", cloneDirectory),
|
||||
fmt.Sprintf("cd %q", cloneDirectory),
|
||||
`git init && git remote add origin ${DRONE_REMOTE_URL}`,
|
||||
`git fetch origin --tags`,
|
||||
"git init",
|
||||
"git remote add origin ${DRONE_REMOTE_URL}",
|
||||
"git fetch origin --tags",
|
||||
fmt.Sprintf("git checkout -qf %q", commit),
|
||||
}
|
||||
}
|
||||
|
@ -215,6 +233,27 @@ func dockerService(v ...volumeRef) service {
|
|||
}
|
||||
}
|
||||
|
||||
// Starts a container registry service at `LocalRegistrySocket`
|
||||
// This can be pushed/pulled to via `docker push/pull <LocalRegistrySocket>:5000/image:tag`
|
||||
func dockerRegistryService() service {
|
||||
return service{
|
||||
Name: LocalRegistryHostname,
|
||||
Image: "registry:2",
|
||||
}
|
||||
}
|
||||
|
||||
// dockerVolumes returns a slice of volumes
|
||||
// It includes the Docker socket volume by default, plus any extra volumes passed in
|
||||
func dockerVolumes(v ...volume) []volume {
|
||||
return append(v, volumeDocker)
|
||||
}
|
||||
|
||||
// dockerVolumeRefs returns a slice of volumeRefs
|
||||
// It includes the Docker socket volumeRef as a default, plus any extra volumeRefs passed in
|
||||
func dockerVolumeRefs(v ...volumeRef) []volumeRef {
|
||||
return append(v, volumeRefDocker)
|
||||
}
|
||||
|
||||
// releaseMakefileTarget gets the correct Makefile target for a given arch/fips/centos combo
|
||||
func releaseMakefileTarget(b buildType) string {
|
||||
makefileTarget := fmt.Sprintf("release-%s", b.arch)
|
||||
|
@ -251,17 +290,23 @@ func waitForDockerStep() step {
|
|||
}
|
||||
}
|
||||
|
||||
func verifyValidPromoteRunSteps(checkoutPath, commit string, isParallelismEnabled bool) []step {
|
||||
tagStep := verifyTaggedStep()
|
||||
cloneStep := cloneRepoStep(checkoutPath, commit)
|
||||
verifyStep := verifyNotPrereleaseStep(checkoutPath)
|
||||
|
||||
if isParallelismEnabled {
|
||||
cloneStep.DependsOn = []string{tagStep.Name}
|
||||
verifyStep.DependsOn = []string{cloneStep.Name}
|
||||
// waitForDockerStep returns a step which checks that the Docker registry is ready
|
||||
func waitForDockerRegistryStep() step {
|
||||
return step{
|
||||
Name: "Wait for docker registry",
|
||||
Image: "alpine",
|
||||
Commands: []string{
|
||||
"apk add curl",
|
||||
fmt.Sprintf(`timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %%{http_code} http://%s/)" != "200" ]; do sleep 1; done'`, LocalRegistrySocket),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return []step{tagStep, cloneStep, verifyStep}
|
||||
func verifyValidPromoteRunSteps() []step {
|
||||
tagStep := verifyTaggedStep()
|
||||
verifyStep := verifyNotPrereleaseStep()
|
||||
|
||||
return []step{tagStep, verifyStep}
|
||||
}
|
||||
|
||||
func verifyTaggedStep() step {
|
||||
|
@ -283,13 +328,20 @@ func cloneRepoStep(clonePath, commit string) step {
|
|||
}
|
||||
}
|
||||
|
||||
func verifyNotPrereleaseStep(checkoutPath string) step {
|
||||
func verifyNotPrereleaseStep() step {
|
||||
clonePath := "/tmp/repo"
|
||||
commands := []string{
|
||||
"apk add git",
|
||||
}
|
||||
commands = append(commands, cloneRepoCommands(clonePath, "${DRONE_TAG}")...)
|
||||
commands = append(commands,
|
||||
fmt.Sprintf("cd %q", path.Join(clonePath, "build.assets", "tooling")),
|
||||
"go run ./cmd/check -tag ${DRONE_TAG} -check prerelease || (echo '---> This is a prerelease, not continuing promotion for ${DRONE_TAG}' && exit 78)",
|
||||
)
|
||||
|
||||
return step{
|
||||
Name: "Check if tag is prerelease",
|
||||
Image: "golang:1.18-alpine",
|
||||
Commands: []string{
|
||||
fmt.Sprintf("cd %q", path.Join(checkoutPath, "build.assets", "tooling")),
|
||||
"go run ./cmd/check -tag ${DRONE_TAG} -check prerelease || (echo '---> This is a prerelease, not continuing promotion for ${DRONE_TAG}' && exit 78)",
|
||||
},
|
||||
Name: "Check if tag is prerelease",
|
||||
Image: fmt.Sprintf("golang:%s-alpine", GoVersion),
|
||||
Commands: commands,
|
||||
}
|
||||
}
|
||||
|
|
241
dronegen/container_image_products.go
Normal file
241
dronegen/container_image_products.go
Normal file
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Describes a Gravitational "product", where a "product" is a piece of software
|
||||
// that we provide to our customers via container repositories.
|
||||
type Product struct {
|
||||
Name string
|
||||
DockerfilePath string
|
||||
WorkingDirectory string // Working directory to use for "docker build".
|
||||
DockerfileTarget string // Optional. Defines a dockerfile target to stop at on build.
|
||||
SupportedArchs []string // ISAs that the builder should produce
|
||||
SetupSteps []step // Product-specific steps that must be ran before building an image.
|
||||
DockerfileArgBuilder func(arch string) []string // Generator that returns "docker build --arg" strings
|
||||
ImageBuilder func(repo *ContainerRepo, tag *ImageTag) *Image // Generator that returns an Image struct that defines what "docker build" should produce
|
||||
GetRequiredStepNames func(arch string) []string // Generator that returns the name of the steps that "docker build" should wait for
|
||||
}
|
||||
|
||||
func NewTeleportOperatorProduct(cloneDirectory string) *Product {
|
||||
name := "teleport-operator"
|
||||
return &Product{
|
||||
Name: name,
|
||||
DockerfilePath: path.Join(cloneDirectory, "operator", "Dockerfile"),
|
||||
WorkingDirectory: cloneDirectory,
|
||||
SupportedArchs: []string{"amd64", "arm", "arm64"},
|
||||
ImageBuilder: func(repo *ContainerRepo, tag *ImageTag) *Image {
|
||||
return &Image{
|
||||
Repo: repo,
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
}
|
||||
},
|
||||
DockerfileArgBuilder: func(arch string) []string {
|
||||
buildboxName := fmt.Sprintf("%s/gravitational/teleport-buildbox", ProductionRegistry)
|
||||
compilerName := ""
|
||||
switch arch {
|
||||
case "x86_64", "amd64":
|
||||
compilerName = "x86_64-linux-gnu-gcc"
|
||||
case "i686", "i386":
|
||||
compilerName = "i686-linux-gnu-gcc"
|
||||
case "arm64", "aarch64":
|
||||
buildboxName += "-arm"
|
||||
compilerName = "aarch64-linux-gnu-gcc"
|
||||
// We may want to add additional arm ISAs in the future to support devices without hardware FPUs
|
||||
case "armhf":
|
||||
case "arm":
|
||||
buildboxName += "-arm"
|
||||
compilerName = "arm-linux-gnueabihf-gcc"
|
||||
}
|
||||
|
||||
buildboxName += ":teleport11"
|
||||
|
||||
return []string{
|
||||
fmt.Sprintf("BUILDBOX=%s", buildboxName),
|
||||
fmt.Sprintf("COMPILER_NAME=%s", compilerName),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Product) getBaseImage(arch string, version *ReleaseVersion) *Image {
|
||||
return &Image{
|
||||
Name: p.Name,
|
||||
Tag: &ImageTag{
|
||||
ShellBaseValue: version.GetFullSemver().GetSemverValue(),
|
||||
DisplayBaseValue: version.MajorVersion,
|
||||
Arch: arch,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Product) GetLocalRegistryImage(arch string, version *ReleaseVersion) *Image {
|
||||
image := p.getBaseImage(arch, version)
|
||||
image.Repo = NewLocalContainerRepo()
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func (p *Product) GetStagingRegistryImage(arch string, version *ReleaseVersion, stagingRepo *ContainerRepo) *Image {
|
||||
image := p.getBaseImage(arch, version)
|
||||
image.Repo = stagingRepo
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func (p *Product) buildSteps(version *ReleaseVersion, setupStepNames []string, flags *TriggerFlags) []step {
|
||||
steps := make([]step, 0)
|
||||
|
||||
stagingRepo := GetStagingContainerRepo(flags.UseUniqueStagingTag)
|
||||
productionRepos := GetProductionContainerRepos()
|
||||
|
||||
for _, setupStep := range p.SetupSteps {
|
||||
setupStep.DependsOn = append(setupStep.DependsOn, setupStepNames...)
|
||||
steps = append(steps, setupStep)
|
||||
setupStepNames = append(setupStepNames, setupStep.Name)
|
||||
}
|
||||
|
||||
archBuildStepDetails := make([]*buildStepOutput, 0, len(p.SupportedArchs))
|
||||
|
||||
for i, supportedArch := range p.SupportedArchs {
|
||||
// Include steps for building images from scratch
|
||||
if flags.ShouldBuildNewImages {
|
||||
archBuildStep, archBuildStepDetail := p.createBuildStep(supportedArch, version, i)
|
||||
|
||||
archBuildStep.DependsOn = append(archBuildStep.DependsOn, setupStepNames...)
|
||||
if p.GetRequiredStepNames != nil {
|
||||
archBuildStep.DependsOn = append(archBuildStep.DependsOn, p.GetRequiredStepNames(supportedArch)...)
|
||||
}
|
||||
|
||||
steps = append(steps, archBuildStep)
|
||||
archBuildStepDetails = append(archBuildStepDetails, archBuildStepDetail)
|
||||
} else {
|
||||
stagingImage := p.GetStagingRegistryImage(supportedArch, version, stagingRepo)
|
||||
pullStagingImageStep, locallyPushedImage := stagingRepo.pullPushStep(stagingImage, setupStepNames)
|
||||
steps = append(steps, pullStagingImageStep)
|
||||
|
||||
// Generate build details that point to the pulled staging images
|
||||
archBuildStepDetails = append(archBuildStepDetails, &buildStepOutput{
|
||||
StepName: pullStagingImageStep.Name,
|
||||
BuiltImage: locallyPushedImage,
|
||||
Version: version,
|
||||
Product: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerRepo := range getReposToPublishTo(productionRepos, stagingRepo, flags) {
|
||||
steps = append(steps, containerRepo.buildSteps(archBuildStepDetails, flags)...)
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
func getReposToPublishTo(productionRepos []*ContainerRepo, stagingRepo *ContainerRepo, flags *TriggerFlags) []*ContainerRepo {
|
||||
stagingRepos := []*ContainerRepo{stagingRepo}
|
||||
|
||||
if flags.ShouldAffectProductionImages {
|
||||
if !flags.ShouldBuildNewImages {
|
||||
// In this case the images will be pulled from staging and therefor should not be re-published
|
||||
// to staging
|
||||
return productionRepos
|
||||
}
|
||||
|
||||
return append(stagingRepos, productionRepos...)
|
||||
}
|
||||
|
||||
return stagingRepos
|
||||
}
|
||||
|
||||
func (p *Product) GetBuildStepName(arch string, version *ReleaseVersion) string {
|
||||
localImageName := p.GetLocalRegistryImage(arch, version)
|
||||
return fmt.Sprintf("Build %s image %q", p.Name, localImageName.GetDisplayName())
|
||||
}
|
||||
|
||||
func cleanBuilderName(builderName string) string {
|
||||
var invalidBuildxCharExpression = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
||||
return invalidBuildxCharExpression.ReplaceAllString(builderName, "-")
|
||||
}
|
||||
|
||||
func (p *Product) createBuildStep(arch string, version *ReleaseVersion, delay int) (step, *buildStepOutput) {
|
||||
localRegistryImage := p.GetLocalRegistryImage(arch, version)
|
||||
builderName := cleanBuilderName(fmt.Sprintf("%s-builder", localRegistryImage.GetDisplayName()))
|
||||
|
||||
buildxConfigFileDir := path.Join("/tmp", builderName)
|
||||
buildxConfigFilePath := path.Join(buildxConfigFileDir, "buildkitd.toml")
|
||||
|
||||
buildxCreateCommand := "docker buildx create"
|
||||
buildxCreateCommand += fmt.Sprintf(" --driver %q", "docker-container")
|
||||
// This is set so that buildx can reach the local registry
|
||||
buildxCreateCommand += fmt.Sprintf(" --driver-opt %q", "network=host")
|
||||
buildxCreateCommand += fmt.Sprintf(" --name %q", builderName)
|
||||
buildxCreateCommand += fmt.Sprintf(" --config %q", buildxConfigFilePath)
|
||||
|
||||
buildCommand := "docker buildx build"
|
||||
buildCommand += " --push"
|
||||
buildCommand += fmt.Sprintf(" --builder %q", builderName)
|
||||
if p.DockerfileTarget != "" {
|
||||
buildCommand += fmt.Sprintf(" --target %q", p.DockerfileTarget)
|
||||
}
|
||||
buildCommand += fmt.Sprintf(" --platform %q", "linux/"+arch)
|
||||
buildCommand += fmt.Sprintf(" --tag %s", localRegistryImage.GetShellName())
|
||||
buildCommand += fmt.Sprintf(" --file %q", p.DockerfilePath)
|
||||
if p.DockerfileArgBuilder != nil {
|
||||
for _, buildArg := range p.DockerfileArgBuilder(arch) {
|
||||
buildCommand += fmt.Sprintf(" --build-arg %q", buildArg)
|
||||
}
|
||||
}
|
||||
buildCommand += " " + p.WorkingDirectory
|
||||
|
||||
delayTime := delay * 5
|
||||
|
||||
step := step{
|
||||
Name: p.GetBuildStepName(arch, version),
|
||||
Image: "docker",
|
||||
Volumes: dockerVolumeRefs(),
|
||||
Environment: map[string]value{
|
||||
"DOCKER_BUILDKIT": {
|
||||
raw: "1",
|
||||
},
|
||||
},
|
||||
Commands: []string{
|
||||
// Without a delay buildx can occasionally try to pull base images faster than container registries will allow,
|
||||
// triggering a rate limit.
|
||||
fmt.Sprintf("echo 'Sleeping %ds to avoid registry pull rate limits' && sleep %d", delayTime, delayTime),
|
||||
"docker run --privileged --rm tonistiigi/binfmt --install all",
|
||||
fmt.Sprintf("mkdir -pv %q && cd %q", p.WorkingDirectory, p.WorkingDirectory),
|
||||
fmt.Sprintf("mkdir -pv %q", buildxConfigFileDir),
|
||||
fmt.Sprintf("echo '[registry.%q]' > %q", LocalRegistrySocket, buildxConfigFilePath),
|
||||
fmt.Sprintf("echo ' http = true' >> %q", buildxConfigFilePath),
|
||||
buildxCreateCommand,
|
||||
buildCommand,
|
||||
fmt.Sprintf("docker buildx rm %q", builderName),
|
||||
fmt.Sprintf("rm -rf %q", buildxConfigFileDir),
|
||||
},
|
||||
}
|
||||
|
||||
return step, &buildStepOutput{
|
||||
StepName: step.Name,
|
||||
BuiltImage: localRegistryImage,
|
||||
Version: version,
|
||||
Product: p,
|
||||
}
|
||||
}
|
163
dronegen/container_image_triggers.go
Normal file
163
dronegen/container_image_triggers.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Describes a Drone trigger as it pertains to container image building.
|
||||
type TriggerInfo struct {
|
||||
Trigger trigger
|
||||
Name string
|
||||
Flags *TriggerFlags
|
||||
SupportedVersions []*ReleaseVersion
|
||||
SetupSteps []step
|
||||
}
|
||||
|
||||
// This type is mainly used to make passing these vars around cleaner
|
||||
type TriggerFlags struct {
|
||||
ShouldAffectProductionImages bool
|
||||
ShouldBuildNewImages bool
|
||||
UseUniqueStagingTag bool
|
||||
ShouldOnlyPublishFullSemver bool
|
||||
}
|
||||
|
||||
func NewTagTrigger(branchMajorVersion string) *TriggerInfo {
|
||||
tagTrigger := triggerTag
|
||||
|
||||
return &TriggerInfo{
|
||||
Trigger: tagTrigger,
|
||||
Name: "tag",
|
||||
Flags: &TriggerFlags{
|
||||
ShouldAffectProductionImages: false,
|
||||
ShouldBuildNewImages: true,
|
||||
UseUniqueStagingTag: false,
|
||||
ShouldOnlyPublishFullSemver: true,
|
||||
},
|
||||
SupportedVersions: []*ReleaseVersion{
|
||||
{
|
||||
MajorVersion: branchMajorVersion,
|
||||
ShellVersion: "$DRONE_TAG",
|
||||
RelativeVersionName: "branch",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewPromoteTrigger(branchMajorVersion string) *TriggerInfo {
|
||||
promoteTrigger := triggerPromote
|
||||
promoteTrigger.Target.Include = append(promoteTrigger.Target.Include, "promote-docker")
|
||||
|
||||
return &TriggerInfo{
|
||||
Trigger: promoteTrigger,
|
||||
Name: "promote",
|
||||
Flags: &TriggerFlags{
|
||||
ShouldAffectProductionImages: true,
|
||||
ShouldBuildNewImages: false,
|
||||
UseUniqueStagingTag: false,
|
||||
ShouldOnlyPublishFullSemver: false,
|
||||
},
|
||||
SupportedVersions: []*ReleaseVersion{
|
||||
{
|
||||
MajorVersion: branchMajorVersion,
|
||||
ShellVersion: "$DRONE_TAG",
|
||||
RelativeVersionName: "branch",
|
||||
},
|
||||
},
|
||||
SetupSteps: verifyValidPromoteRunSteps(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewCronTrigger(latestMajorVersions []string) *TriggerInfo {
|
||||
if len(latestMajorVersions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
majorVersionVarDirectory := "/go/vars/full-version"
|
||||
|
||||
supportedVersions := make([]*ReleaseVersion, 0, len(latestMajorVersions))
|
||||
if len(latestMajorVersions) > 0 {
|
||||
latestMajorVersion := latestMajorVersions[0]
|
||||
supportedVersions = append(supportedVersions, &ReleaseVersion{
|
||||
MajorVersion: latestMajorVersion,
|
||||
ShellVersion: readCronShellVersionCommand(majorVersionVarDirectory, latestMajorVersion),
|
||||
RelativeVersionName: "current-version",
|
||||
SetupSteps: []step{getLatestSemverStep(latestMajorVersion, majorVersionVarDirectory)},
|
||||
})
|
||||
|
||||
if len(latestMajorVersions) > 1 {
|
||||
for i, majorVersion := range latestMajorVersions[1:] {
|
||||
supportedVersions = append(supportedVersions, &ReleaseVersion{
|
||||
MajorVersion: majorVersion,
|
||||
ShellVersion: readCronShellVersionCommand(majorVersionVarDirectory, majorVersion),
|
||||
RelativeVersionName: fmt.Sprintf("previous-version-%d", i+1),
|
||||
SetupSteps: []step{getLatestSemverStep(majorVersion, majorVersionVarDirectory)},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &TriggerInfo{
|
||||
Trigger: cronTrigger([]string{"teleport-container-images-cron"}),
|
||||
Name: "cron",
|
||||
Flags: &TriggerFlags{
|
||||
ShouldAffectProductionImages: true,
|
||||
ShouldBuildNewImages: true,
|
||||
UseUniqueStagingTag: true,
|
||||
ShouldOnlyPublishFullSemver: false,
|
||||
},
|
||||
SupportedVersions: supportedVersions,
|
||||
}
|
||||
}
|
||||
|
||||
func getLatestSemverStep(majorVersion string, majorVersionVarDirectory string) step {
|
||||
// We don't use "/go/src/github.com/gravitational/teleport" here as a later stage
|
||||
// may need to clone a different version, and "/go" persists between steps
|
||||
cloneDirectory := "/tmp/teleport"
|
||||
majorVersionVarPath := path.Join(majorVersionVarDirectory, majorVersion)
|
||||
return step{
|
||||
Name: fmt.Sprintf("Find the latest available semver for %s", majorVersion),
|
||||
Image: fmt.Sprintf("golang:%s", GoVersion),
|
||||
Commands: append(
|
||||
cloneRepoCommands(cloneDirectory, fmt.Sprintf("branch/%s", majorVersion)),
|
||||
fmt.Sprintf("mkdir -pv %q", majorVersionVarDirectory),
|
||||
fmt.Sprintf("cd %q", path.Join(cloneDirectory, "build.assets", "tooling", "cmd", "query-latest")),
|
||||
fmt.Sprintf("go run . %q > %q", majorVersion, majorVersionVarPath),
|
||||
fmt.Sprintf("echo Found full semver \"$(cat %q)\" for major version %q", majorVersionVarPath, majorVersion),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func readCronShellVersionCommand(majorVersionDirectory, majorVersion string) string {
|
||||
return fmt.Sprintf("$(cat '%s')", path.Join(majorVersionDirectory, majorVersion))
|
||||
}
|
||||
|
||||
// Drone triggers must all evaluate to "true" for a pipeline to be executed.
|
||||
// As a result these pipelines are duplicated for each trigger.
|
||||
// See https://docs.drone.io/pipeline/triggers/ for details.
|
||||
func (ti *TriggerInfo) buildPipelines() []pipeline {
|
||||
pipelines := make([]pipeline, 0, len(ti.SupportedVersions))
|
||||
for _, teleportVersion := range ti.SupportedVersions {
|
||||
pipeline := teleportVersion.buildVersionPipeline(ti.SetupSteps, ti.Flags)
|
||||
pipeline.Name += "-" + ti.Name
|
||||
pipeline.Trigger = ti.Trigger
|
||||
|
||||
pipelines = append(pipelines, pipeline)
|
||||
}
|
||||
|
||||
return pipelines
|
||||
}
|
115
dronegen/container_images.go
Normal file
115
dronegen/container_images.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func buildContainerImagePipelines() []pipeline {
|
||||
// *************************************************************
|
||||
// ****** These need to be updated on each major release. ******
|
||||
// ****** After updating, "make dronegen" must be reran. ******
|
||||
// *************************************************************
|
||||
latestMajorVersions := []string{"v11", "v10", "v9"}
|
||||
branchMajorVersion := "v11"
|
||||
|
||||
triggers := []*TriggerInfo{
|
||||
NewTagTrigger(branchMajorVersion),
|
||||
NewPromoteTrigger(branchMajorVersion),
|
||||
NewCronTrigger(latestMajorVersions),
|
||||
}
|
||||
|
||||
if configureForPRTestingOnly {
|
||||
triggers = append(triggers, NewTestTrigger(prBranch, branchMajorVersion))
|
||||
}
|
||||
|
||||
pipelines := make([]pipeline, 0, len(triggers))
|
||||
for _, trigger := range triggers {
|
||||
pipelines = append(pipelines, trigger.buildPipelines()...)
|
||||
}
|
||||
|
||||
return pipelines
|
||||
}
|
||||
|
||||
// Describes a container image. Used for both local and remove images.
|
||||
type Image struct {
|
||||
Repo *ContainerRepo
|
||||
Name string
|
||||
Tag *ImageTag
|
||||
}
|
||||
|
||||
func (i *Image) GetShellName() string {
|
||||
repo := strings.TrimSuffix(i.Repo.RegistryDomain, "/")
|
||||
if i.Repo.RegistryOrg != "" {
|
||||
repo = fmt.Sprintf("%s/%s", repo, i.Repo.RegistryOrg)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s:%s", repo, i.Name, i.Tag.GetShellValue())
|
||||
}
|
||||
|
||||
func (i *Image) GetDisplayName() string {
|
||||
return fmt.Sprintf("%s:%s", i.Name, i.Tag.GetDisplayValue())
|
||||
}
|
||||
|
||||
// Contains information about the tag portion of an image.
|
||||
type ImageTag struct {
|
||||
ShellBaseValue string // Should evaluate in a shell context to the tag's value
|
||||
DisplayBaseValue string // Should be set to a human-readable version of ShellTag
|
||||
Arch string
|
||||
IsImmutable bool
|
||||
}
|
||||
|
||||
func NewLatestTag() *ImageTag {
|
||||
return &ImageTag{
|
||||
ShellBaseValue: "latest",
|
||||
DisplayBaseValue: "latest",
|
||||
}
|
||||
}
|
||||
|
||||
func (it *ImageTag) AppendString(s string) {
|
||||
it.ShellBaseValue += fmt.Sprintf("-%s", s)
|
||||
it.DisplayBaseValue += fmt.Sprintf("-%s", s)
|
||||
}
|
||||
|
||||
func (it *ImageTag) IsMultArch() bool {
|
||||
return it.Arch != ""
|
||||
}
|
||||
|
||||
func (it *ImageTag) GetShellValue() string {
|
||||
return it.getValue(it.ShellBaseValue)
|
||||
}
|
||||
|
||||
func (it *ImageTag) GetDisplayValue() string {
|
||||
return it.getValue(it.DisplayBaseValue)
|
||||
}
|
||||
|
||||
func (it *ImageTag) getValue(baseValue string) string {
|
||||
if it.Arch == "" {
|
||||
return baseValue
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s", baseValue, it.Arch)
|
||||
}
|
||||
|
||||
// The `step` struct doesn't contain enough information to setup
|
||||
// dependent steps so we add that via this struct
|
||||
// This is used internally to pass information around
|
||||
type buildStepOutput struct {
|
||||
StepName string
|
||||
BuiltImage *Image
|
||||
Version *ReleaseVersion
|
||||
Product *Product
|
||||
}
|
230
dronegen/container_images_release_version.go
Normal file
230
dronegen/container_images_release_version.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
varDirectory = "/go/var"
|
||||
)
|
||||
|
||||
// Describes a Teleport/repo release version. All product releases are tied to Teleport's release cycle
|
||||
// via this struct.
|
||||
type ReleaseVersion struct {
|
||||
MajorVersion string // This is the major version of a given build. `SearchVersion` should match this when evaluated.
|
||||
ShellVersion string // This value will be evaluated by the shell in the context of a Drone step
|
||||
RelativeVersionName string // The set of values for this should not change between major releases
|
||||
SetupSteps []step // Version-specific steps that must be ran before executing build and push steps
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) buildVersionPipeline(triggerSetupSteps []step, flags *TriggerFlags) pipeline {
|
||||
pipelineName := fmt.Sprintf("teleport-container-images-%s", rv.RelativeVersionName)
|
||||
|
||||
setupSteps, dependentStepNames := rv.getSetupStepInformation(triggerSetupSteps)
|
||||
|
||||
pipeline := newKubePipeline(pipelineName)
|
||||
pipeline.Workspace = workspace{Path: "/go"}
|
||||
pipeline.Services = []service{
|
||||
dockerService(),
|
||||
dockerRegistryService(),
|
||||
}
|
||||
pipeline.Volumes = dockerVolumes()
|
||||
pipeline.Environment = map[string]value{
|
||||
"DEBIAN_FRONTEND": {
|
||||
raw: "noninteractive",
|
||||
},
|
||||
}
|
||||
pipeline.Steps = append(setupSteps, rv.buildSteps(dependentStepNames, flags)...)
|
||||
|
||||
return pipeline
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) getSetupStepInformation(triggerSetupSteps []step) ([]step, []string) {
|
||||
triggerSetupStepNames := make([]string, 0, len(triggerSetupSteps))
|
||||
for _, triggerSetupStep := range triggerSetupSteps {
|
||||
triggerSetupStepNames = append(triggerSetupStepNames, triggerSetupStep.Name)
|
||||
}
|
||||
|
||||
nextStageSetupStepNames := triggerSetupStepNames
|
||||
if len(rv.SetupSteps) > 0 {
|
||||
versionSetupStepNames := make([]string, 0, len(rv.SetupSteps))
|
||||
for _, versionSetupStep := range rv.SetupSteps {
|
||||
versionSetupStep.DependsOn = append(versionSetupStep.DependsOn, triggerSetupStepNames...)
|
||||
versionSetupStepNames = append(versionSetupStepNames, versionSetupStep.Name)
|
||||
}
|
||||
|
||||
nextStageSetupStepNames = versionSetupStepNames
|
||||
}
|
||||
|
||||
setupSteps := append(triggerSetupSteps, rv.SetupSteps...)
|
||||
|
||||
return setupSteps, nextStageSetupStepNames
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) buildSteps(setupStepNames []string, flags *TriggerFlags) []step {
|
||||
clonedRepoPath := "/go/src/github.com/gravitational/teleport"
|
||||
steps := make([]step, 0)
|
||||
|
||||
setupSteps := []step{
|
||||
waitForDockerStep(),
|
||||
waitForDockerRegistryStep(),
|
||||
cloneRepoStep(clonedRepoPath, rv.ShellVersion),
|
||||
rv.buildSplitSemverSteps(flags.ShouldOnlyPublishFullSemver),
|
||||
}
|
||||
for _, setupStep := range setupSteps {
|
||||
setupStep.DependsOn = append(setupStep.DependsOn, setupStepNames...)
|
||||
steps = append(steps, setupStep)
|
||||
setupStepNames = append(setupStepNames, setupStep.Name)
|
||||
}
|
||||
|
||||
for _, product := range rv.getProducts(clonedRepoPath) {
|
||||
steps = append(steps, product.buildSteps(rv, setupStepNames, flags)...)
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
type Semver struct {
|
||||
Name string // Human-readable name for the information contained in the semver, i.e. "major"
|
||||
FilePath string // The path under the working dir where the information can be read from
|
||||
FieldCount int // The number of significant version fields available in the semver i.e. "v11" -> 1
|
||||
IsImmutable bool
|
||||
IsFull bool
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) GetSemvers() []*Semver {
|
||||
return []*Semver{
|
||||
{
|
||||
Name: "major",
|
||||
FilePath: path.Join(varDirectory, "major-version"),
|
||||
FieldCount: 1,
|
||||
IsImmutable: false,
|
||||
},
|
||||
{
|
||||
Name: "minor",
|
||||
FilePath: path.Join(varDirectory, "minor-version"),
|
||||
FieldCount: 2,
|
||||
IsImmutable: false,
|
||||
},
|
||||
rv.GetFullSemver(),
|
||||
}
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) GetFullSemver() *Semver {
|
||||
return &Semver{
|
||||
// For releases this is the "canonical" semver.
|
||||
// For prereleases this is canonical + metadata.
|
||||
// This is done to keep prereleases pushed to staging
|
||||
// from overwriting release versions.
|
||||
Name: "full",
|
||||
FilePath: path.Join(varDirectory, "full-version"),
|
||||
IsImmutable: true,
|
||||
IsFull: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Semver) GetSemverValue() string {
|
||||
return fmt.Sprintf("$(cat %q)", s.FilePath)
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) buildSplitSemverSteps(onlyBuildFullSemver bool) step {
|
||||
semvers := rv.GetSemvers()
|
||||
|
||||
// Build the commands that generate the semvers
|
||||
commands := make([]string, 0, len(semvers))
|
||||
stepNameVersions := make([]string, 0, len(semvers))
|
||||
for _, semver := range semvers {
|
||||
if onlyBuildFullSemver && !semver.IsFull {
|
||||
continue
|
||||
}
|
||||
|
||||
commands = append(commands, fmt.Sprintf("mkdir -pv $(dirname %q)", semver.FilePath))
|
||||
if semver.IsFull {
|
||||
// Special case for full semver where only the "v" should be trimmed
|
||||
commands = append(commands, fmt.Sprintf("echo %s | sed 's/v//' > %q", rv.ShellVersion, semver.FilePath))
|
||||
} else {
|
||||
// Trim the semver metadata and some digits
|
||||
// Ex: semver.FieldCount = 3, cutFieldString = "1,2,3"
|
||||
cutFieldStrings := make([]string, 0, semver.FieldCount)
|
||||
for i := 1; i <= semver.FieldCount; i++ {
|
||||
cutFieldStrings = append(cutFieldStrings, strconv.Itoa(i))
|
||||
}
|
||||
cutFieldString := strings.Join(cutFieldStrings, ",")
|
||||
|
||||
commands = append(commands, fmt.Sprintf("echo %s | sed 's/v//' | cut -d'.' -f %q > %q",
|
||||
rv.ShellVersion, cutFieldString, semver.FilePath))
|
||||
}
|
||||
// For debugging
|
||||
commands = append(commands, fmt.Sprintf("echo %s", semver.GetSemverValue()))
|
||||
|
||||
stepNameVersions = append(stepNameVersions, semver.Name)
|
||||
}
|
||||
|
||||
// Build the formatted, human-readable step name
|
||||
concatStepNameVersions := "Build"
|
||||
for i, stepNameVersion := range stepNameVersions {
|
||||
if i+1 < len(stepNameVersions) {
|
||||
// If not the last version name
|
||||
concatStepNameVersions = fmt.Sprintf("%s %s,", concatStepNameVersions, stepNameVersion)
|
||||
} else {
|
||||
if len(stepNameVersions) > 1 {
|
||||
concatStepNameVersions = fmt.Sprintf("%s and", concatStepNameVersions)
|
||||
}
|
||||
|
||||
concatStepNameVersions = fmt.Sprintf("%s %s semver", concatStepNameVersions, stepNameVersion)
|
||||
if len(stepNameVersions) > 1 {
|
||||
concatStepNameVersions = fmt.Sprintf("%ss", concatStepNameVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return step{
|
||||
Name: concatStepNameVersions,
|
||||
Image: "alpine",
|
||||
Commands: commands,
|
||||
}
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) getProducts(clonedRepoPath string) []*Product {
|
||||
teleportOperatorProduct := NewTeleportOperatorProduct(clonedRepoPath)
|
||||
|
||||
products := make([]*Product, 0, 1)
|
||||
products = append(products, teleportOperatorProduct)
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func (rv *ReleaseVersion) getTagsForVersion(onlyBuildFullSemver bool) []*ImageTag {
|
||||
semvers := rv.GetSemvers()
|
||||
imageTags := make([]*ImageTag, 0, len(semvers))
|
||||
for _, semver := range semvers {
|
||||
if onlyBuildFullSemver && !semver.IsFull {
|
||||
continue
|
||||
}
|
||||
|
||||
imageTags = append(imageTags, &ImageTag{
|
||||
ShellBaseValue: semver.GetSemverValue(),
|
||||
DisplayBaseValue: semver.Name,
|
||||
IsImmutable: semver.IsImmutable,
|
||||
})
|
||||
}
|
||||
|
||||
return imageTags
|
||||
}
|
320
dronegen/container_images_repos.go
Normal file
320
dronegen/container_images_repos.go
Normal file
|
@ -0,0 +1,320 @@
|
|||
// Copyright 2021 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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// Describes a registry and repo that images are to be published to.
|
||||
type ContainerRepo struct {
|
||||
Name string
|
||||
IsProductionRepo bool
|
||||
IsImmutable bool
|
||||
EnvironmentVars map[string]value
|
||||
RegistryDomain string
|
||||
RegistryOrg string
|
||||
LoginCommands []string
|
||||
TagBuilder func(baseTag *ImageTag) *ImageTag // Postprocessor for tags that append CR-specific suffixes
|
||||
}
|
||||
|
||||
func NewEcrContainerRepo(accessKeyIDSecret, secretAccessKeySecret, domain string, isProduction, isImmutable, guaranteeUnique bool) *ContainerRepo {
|
||||
nameSuffix := "staging"
|
||||
ecrRegion := StagingEcrRegion
|
||||
loginSubcommand := "ecr"
|
||||
if isProduction {
|
||||
nameSuffix = "production"
|
||||
ecrRegion = PublicEcrRegion
|
||||
loginSubcommand = "ecr-public"
|
||||
}
|
||||
|
||||
registryOrg := ProductionRegistryOrg
|
||||
if configureForPRTestingOnly {
|
||||
accessKeyIDSecret = testingSecretPrefix + accessKeyIDSecret
|
||||
secretAccessKeySecret = testingSecretPrefix + secretAccessKeySecret
|
||||
registryOrg = testingECRRegistryOrg
|
||||
|
||||
if !isProduction {
|
||||
domain = testingECRDomain
|
||||
ecrRegion = testingECRRegion
|
||||
}
|
||||
}
|
||||
|
||||
loginCommands := []string{
|
||||
"apk add --no-cache aws-cli",
|
||||
fmt.Sprintf("aws %s get-login-password --region=%s | docker login -u=\"AWS\" --password-stdin %s", loginSubcommand, ecrRegion, domain),
|
||||
}
|
||||
|
||||
if guaranteeUnique {
|
||||
loginCommands = append(loginCommands, "TIMESTAMP=$(date -d @\"$DRONE_BUILD_CREATED\" '+%Y%m%d%H%M')")
|
||||
}
|
||||
|
||||
return &ContainerRepo{
|
||||
Name: fmt.Sprintf("ECR - %s", nameSuffix),
|
||||
IsProductionRepo: isProduction,
|
||||
IsImmutable: isImmutable,
|
||||
EnvironmentVars: map[string]value{
|
||||
"AWS_ACCESS_KEY_ID": {
|
||||
fromSecret: accessKeyIDSecret,
|
||||
},
|
||||
"AWS_SECRET_ACCESS_KEY": {
|
||||
fromSecret: secretAccessKeySecret,
|
||||
},
|
||||
},
|
||||
RegistryDomain: domain,
|
||||
RegistryOrg: registryOrg,
|
||||
LoginCommands: loginCommands,
|
||||
TagBuilder: func(tag *ImageTag) *ImageTag {
|
||||
if guaranteeUnique {
|
||||
tag.AppendString("$TIMESTAMP")
|
||||
}
|
||||
|
||||
return tag
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewQuayContainerRepo(dockerUsername, dockerPassword string) *ContainerRepo {
|
||||
registryOrg := ProductionRegistryOrg
|
||||
if configureForPRTestingOnly {
|
||||
dockerUsername = testingSecretPrefix + dockerUsername
|
||||
dockerPassword = testingSecretPrefix + dockerPassword
|
||||
registryOrg = testingQuayRegistryOrg
|
||||
}
|
||||
|
||||
return &ContainerRepo{
|
||||
Name: "Quay",
|
||||
IsProductionRepo: true,
|
||||
IsImmutable: false,
|
||||
EnvironmentVars: map[string]value{
|
||||
"QUAY_USERNAME": {
|
||||
fromSecret: dockerUsername,
|
||||
},
|
||||
"QUAY_PASSWORD": {
|
||||
fromSecret: dockerPassword,
|
||||
},
|
||||
},
|
||||
RegistryDomain: ProductionRegistryQuay,
|
||||
RegistryOrg: registryOrg,
|
||||
LoginCommands: []string{
|
||||
fmt.Sprintf("docker login -u=\"$QUAY_USERNAME\" -p=\"$QUAY_PASSWORD\" %q", ProductionRegistryQuay),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewLocalContainerRepo() *ContainerRepo {
|
||||
return &ContainerRepo{
|
||||
Name: "Local Registry",
|
||||
IsProductionRepo: false,
|
||||
IsImmutable: false,
|
||||
RegistryDomain: LocalRegistrySocket,
|
||||
}
|
||||
}
|
||||
|
||||
func GetLocalContainerRepo() *ContainerRepo {
|
||||
return NewLocalContainerRepo()
|
||||
}
|
||||
|
||||
func GetStagingContainerRepo(uniqueStagingTag bool) *ContainerRepo {
|
||||
return NewEcrContainerRepo("STAGING_TELEPORT_DRONE_USER_ECR_KEY", "STAGING_TELEPORT_DRONE_USER_ECR_SECRET", StagingRegistry, false, true, uniqueStagingTag)
|
||||
}
|
||||
|
||||
func GetProductionContainerRepos() []*ContainerRepo {
|
||||
return []*ContainerRepo{
|
||||
NewQuayContainerRepo("PRODUCTION_QUAYIO_DOCKER_USERNAME", "PRODUCTION_QUAYIO_DOCKER_PASSWORD"),
|
||||
NewEcrContainerRepo("PRODUCTION_TELEPORT_DRONE_USER_ECR_KEY", "PRODUCTION_TELEPORT_DRONE_USER_ECR_SECRET", ProductionRegistry, true, false, false),
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) buildSteps(buildStepDetails []*buildStepOutput, flags *TriggerFlags) []step {
|
||||
if len(buildStepDetails) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
steps := make([]step, 0)
|
||||
|
||||
// Tag and push, collecting the names of the tag/push steps and the images pushed.
|
||||
imageTags := cr.BuildImageTags(buildStepDetails[0].Version, flags)
|
||||
pushedImages := make(map[*ImageTag][]*Image, len(imageTags))
|
||||
pushStepNames := make([]string, 0, len(buildStepDetails))
|
||||
for _, buildStepDetail := range buildStepDetails {
|
||||
pushStep, pushedArchImages := cr.tagAndPushStep(buildStepDetail, imageTags)
|
||||
pushStepNames = append(pushStepNames, pushStep.Name)
|
||||
for _, imageTag := range imageTags {
|
||||
pushedImages[imageTag] = append(pushedImages[imageTag], pushedArchImages[imageTag])
|
||||
}
|
||||
|
||||
steps = append(steps, pushStep)
|
||||
}
|
||||
|
||||
// Create and push a manifest for each tag, referencing multiple architectures in the manifest
|
||||
for _, imageTag := range imageTags {
|
||||
multiarchImageTag := *imageTag
|
||||
multiarchImageTag.Arch = ""
|
||||
manifestImage := buildStepDetails[0].Product.ImageBuilder(cr, &multiarchImageTag)
|
||||
manifestStepName := cr.createAndPushManifestStep(manifestImage, pushStepNames, pushedImages[imageTag])
|
||||
steps = append(steps, manifestStepName)
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) logoutCommand() string {
|
||||
return fmt.Sprintf("docker logout %q", cr.RegistryDomain)
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) buildCommandsWithLogin(wrappedCommands []string) []string {
|
||||
if cr.LoginCommands == nil || len(cr.LoginCommands) == 0 {
|
||||
return wrappedCommands
|
||||
}
|
||||
|
||||
commands := make([]string, 0)
|
||||
commands = append(commands, cr.LoginCommands...)
|
||||
commands = append(commands, wrappedCommands...)
|
||||
commands = append(commands, cr.logoutCommand())
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) BuildImageRepo() string {
|
||||
return fmt.Sprintf("%s/%s/", cr.RegistryDomain, cr.RegistryOrg)
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) BuildImageTags(version *ReleaseVersion, flags *TriggerFlags) []*ImageTag {
|
||||
tags := version.getTagsForVersion(flags.ShouldOnlyPublishFullSemver)
|
||||
|
||||
if cr.TagBuilder != nil {
|
||||
for i, tag := range tags {
|
||||
tags[i] = cr.TagBuilder(tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
// Pulls an image with authentication pushes it to the local repo.
|
||||
// Does not generate additional tags.
|
||||
// Returns an *Image struct describing the locally pushed image.
|
||||
func (cr *ContainerRepo) pullPushStep(image *Image, dependencySteps []string) (step, *Image) {
|
||||
localRepo := GetLocalContainerRepo()
|
||||
localRepoImage := *image
|
||||
localRepoImage.Repo = localRepo
|
||||
|
||||
commands := image.Repo.buildCommandsWithLogin([]string{fmt.Sprintf("docker pull %s", image.GetShellName())})
|
||||
commands = append(commands,
|
||||
fmt.Sprintf("docker tag %s %s", image.GetShellName(), localRepoImage.GetShellName()),
|
||||
fmt.Sprintf("docker push %s", localRepoImage.GetShellName()),
|
||||
)
|
||||
|
||||
return step{
|
||||
Name: fmt.Sprintf("Pull %s and push it to %s", image.GetDisplayName(), localRepo.Name),
|
||||
Image: "docker",
|
||||
Volumes: dockerVolumeRefs(),
|
||||
Environment: cr.EnvironmentVars,
|
||||
Commands: commands,
|
||||
DependsOn: dependencySteps,
|
||||
}, &localRepoImage
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) tagAndPushStep(buildStepDetails *buildStepOutput, imageTags []*ImageTag) (step, map[*ImageTag]*Image) {
|
||||
archImageMap := make(map[*ImageTag]*Image, len(imageTags))
|
||||
for _, imageTag := range imageTags {
|
||||
archTag := *imageTag
|
||||
archTag.Arch = buildStepDetails.BuiltImage.Tag.Arch
|
||||
archImage := buildStepDetails.Product.ImageBuilder(cr, &archTag)
|
||||
archImageMap[imageTag] = archImage
|
||||
}
|
||||
|
||||
// This is tracked separately as maps in golang have a non-deterministic order when iterated over.
|
||||
// As a result, .drone.yml will be updated every time `make dronegen` is ran regardless of if there
|
||||
// is a change to the map or not
|
||||
// The order/comparator does not matter here as long as it is deterministic between dronegen runs
|
||||
archImageKeys := maps.Keys(archImageMap)
|
||||
sort.SliceStable(archImageKeys, func(i, j int) bool { return archImageKeys[i].GetDisplayValue() < archImageKeys[j].GetDisplayValue() })
|
||||
|
||||
pullCommands := []string{
|
||||
fmt.Sprintf("docker pull %s", buildStepDetails.BuiltImage.GetShellName()),
|
||||
}
|
||||
|
||||
tagAndPushCommands := make([]string, 0)
|
||||
for _, archImageKey := range archImageKeys {
|
||||
archImage := archImageMap[archImageKey]
|
||||
|
||||
// Skip pushing images if the tag or container registry is immutable
|
||||
tagAndPushCommands = append(tagAndPushCommands, buildImmutableSafeCommands(archImageKey.IsImmutable || cr.IsImmutable, archImage.GetShellName(), []string{
|
||||
fmt.Sprintf("docker tag %s %s", buildStepDetails.BuiltImage.GetShellName(), archImage.GetShellName()),
|
||||
fmt.Sprintf("docker push %s", archImage.GetShellName()),
|
||||
})...)
|
||||
}
|
||||
tagAndPushCommands = cr.buildCommandsWithLogin(tagAndPushCommands)
|
||||
|
||||
commands := append(pullCommands, tagAndPushCommands...)
|
||||
|
||||
dependencySteps := []string{}
|
||||
if buildStepDetails.StepName != "" {
|
||||
dependencySteps = append(dependencySteps, buildStepDetails.StepName)
|
||||
}
|
||||
|
||||
step := step{
|
||||
Name: fmt.Sprintf("Tag and push image %q to %s", buildStepDetails.BuiltImage.GetDisplayName(), cr.Name),
|
||||
Image: "docker",
|
||||
Volumes: dockerVolumeRefs(),
|
||||
Environment: cr.EnvironmentVars,
|
||||
Commands: commands,
|
||||
DependsOn: dependencySteps,
|
||||
}
|
||||
|
||||
return step, archImageMap
|
||||
}
|
||||
|
||||
func (cr *ContainerRepo) createAndPushManifestStep(manifestImage *Image, pushStepNames []string, pushedImages []*Image) step {
|
||||
if len(pushStepNames) == 0 {
|
||||
return step{}
|
||||
}
|
||||
|
||||
manifestCommandArgs := make([]string, 0, len(pushedImages))
|
||||
for _, pushedImage := range pushedImages {
|
||||
manifestCommandArgs = append(manifestCommandArgs, fmt.Sprintf("--amend %s", pushedImage.GetShellName()))
|
||||
}
|
||||
|
||||
// Skip pushing manifest if the tag or container registry is immutable
|
||||
commands := buildImmutableSafeCommands(manifestImage.Tag.IsImmutable || cr.IsImmutable, manifestImage.GetShellName(), []string{
|
||||
fmt.Sprintf("docker manifest create %s %s", manifestImage.GetShellName(), strings.Join(manifestCommandArgs, " ")),
|
||||
fmt.Sprintf("docker manifest push %s", manifestImage.GetShellName()),
|
||||
})
|
||||
|
||||
return step{
|
||||
Name: fmt.Sprintf("Create manifest and push %q to %s", manifestImage.GetDisplayName(), cr.Name),
|
||||
Image: "docker",
|
||||
Volumes: dockerVolumeRefs(),
|
||||
Environment: cr.EnvironmentVars,
|
||||
Commands: cr.buildCommandsWithLogin(commands),
|
||||
DependsOn: pushStepNames,
|
||||
}
|
||||
}
|
||||
|
||||
func buildImmutableSafeCommands(isImmutable bool, imageToCheck string, commandsToRun []string) []string {
|
||||
if !isImmutable {
|
||||
return commandsToRun
|
||||
}
|
||||
|
||||
conditionalCommand := fmt.Sprintf("docker manifest inspect %s > /dev/null 2>&1", imageToCheck)
|
||||
commandToRun := strings.Join(commandsToRun, " && ")
|
||||
return []string{fmt.Sprintf("%s && echo 'Found existing image, skipping' || (%s)", conditionalCommand, commandToRun)}
|
||||
}
|
85
dronegen/container_images_testing.go
Normal file
85
dronegen/container_images_testing.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2021 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
|
||||
|
||||
// This file contains variables and functions to make testing of the container image build process
|
||||
// more simple and easier.
|
||||
|
||||
// To run one of these pipelines locally:
|
||||
// # Drone requires certain variables to be set
|
||||
// export DRONE_REMOTE_URL="https://github.com/gravitational/teleport"
|
||||
// export DRONE_SOURCE_BRANCH="$(git branch --show-current)"
|
||||
// # `drone exec` does not support `exec` or `kubernetes` pipelines
|
||||
// sed -i '' 's/type\: kubernetes/type\: docker/' .drone.yml && sed -i '' 's/type\: exec/type\: docker/' .drone.yml
|
||||
// # Drone has a bug where "workspace" is appended to "/drone/src". This fixes that by updating references
|
||||
// sed -i '' 's~/go/~/drone/src/go/~g' .drone.yml
|
||||
// # Pull the current branch instead of v11
|
||||
// sed -i '' "s~git checkout -qf \"\$(cat '/go/vars/full-version/v11')\"~git checkout -qf \"${DRONE_SOURCE_BRANCH}\"~" .drone.yml
|
||||
// # `drone exec` does not properly map the workspace path. This creates a volume to be shared between steps
|
||||
// # at the correct path
|
||||
// DOCKER_VOLUME_NAME="go"
|
||||
// docker volume create "$DOCKER_VOLUME_NAME"
|
||||
// drone exec --trusted --pipeline teleport-container-images-current-version-cron --clone=false --volume "${DOCKER_VOLUME_NAME}:/go"
|
||||
// # Cleanup
|
||||
// docker volume rm "$DOCKER_VOLUME_NAME"
|
||||
|
||||
// If you are working on a PR/testing changes to this file you should configure the following for Drone testing:
|
||||
// 1. Publish the branch you're working on
|
||||
// 2. Set `prBranch` to the name of the branch in (1)
|
||||
// 3. Set `configureForPRTestingOnly` to true
|
||||
// 4. Create a public and private ECR, Quay repos for "teleport", "teleport-ent", "teleport-operator", "teleport-lab"
|
||||
// 5. Set `testingQuayRegistryOrg` and `testingECRRegistryOrg` to the org name(s) used in (4)
|
||||
// 6. Set the `ECRTestingDomain` to the domain used for the private ECR repos
|
||||
// 7. Create two separate IAM users, each with full access to either the public ECR repo OR the private ECR repo
|
||||
// 8. Create a Quay "robot account" with write permissions for the created Quay repos
|
||||
// 9. Set the Drone secrets for the secret names listed in "GetContainerRepos" to the credentials in (7, 8), prefixed by the value of `testingSecretPrefix`
|
||||
//
|
||||
// On each commit, after running `make dronegen``, run the following commands and resign the file:
|
||||
// # Pull the current branch instead of v11 so the appropriate dockerfile gets loaded
|
||||
// sed -i '' "s~git checkout -qf \"\$(cat '/go/vars/full-version/v11')\"~git checkout -qf \"${DRONE_SOURCE_BRANCH}\"~" .drone.yml
|
||||
//
|
||||
// When finishing up your PR check the following:
|
||||
// * The testing secrets added to Drone have been removed
|
||||
// * `configureForPRTestingOnly` has been set to false, and `make dronegen` has been reran afterwords
|
||||
|
||||
const (
|
||||
configureForPRTestingOnly bool = false
|
||||
testingSecretPrefix string = "TEST_"
|
||||
testingQuayRegistryOrg string = "" //"fred_heinecke"
|
||||
testingECRRegistryOrg string = "u8j2q1d9"
|
||||
testingECRRegion string = "us-east-2"
|
||||
prBranch string = "" //"fred/multiarch-teleport-container-images"
|
||||
testingECRDomain string = "278576220453.dkr.ecr.us-east-2.amazonaws.com"
|
||||
)
|
||||
|
||||
const (
|
||||
ProductionRegistryOrg string = "gravitational"
|
||||
PublicEcrRegion string = "us-east-1"
|
||||
StagingEcrRegion string = "us-west-2"
|
||||
)
|
||||
|
||||
func NewTestTrigger(triggerBranch, testMajorVersion string) *TriggerInfo {
|
||||
// baseTrigger := NewTagTrigger(testMajorVersion)
|
||||
// baseTrigger := NewPromoteTrigger(testMajorVersion)
|
||||
baseTrigger := NewCronTrigger([]string{testMajorVersion})
|
||||
baseTrigger.Name = "Test trigger on push"
|
||||
baseTrigger.Trigger = trigger{
|
||||
Repo: triggerRef{Include: []string{"gravitational/teleport"}},
|
||||
Event: triggerRef{Include: []string{"push"}},
|
||||
Branch: triggerRef{Include: []string{triggerBranch}},
|
||||
}
|
||||
|
||||
return baseTrigger
|
||||
}
|
|
@ -32,10 +32,11 @@ func main() {
|
|||
pipelines = append(pipelines, pushPipelines()...)
|
||||
pipelines = append(pipelines, tagPipelines()...)
|
||||
pipelines = append(pipelines, cronPipelines()...)
|
||||
pipelines = append(pipelines, artifactMigrationPipeline()...)
|
||||
pipelines = append(pipelines, buildOsRepoPipelines()...)
|
||||
pipelines = append(pipelines, promoteBuildPipelines()...)
|
||||
pipelines = append(pipelines, updateDocsPipeline())
|
||||
pipelines = append(pipelines, buildboxPipeline())
|
||||
pipelines = append(pipelines, buildContainerImagePipelines()...)
|
||||
pipelines = append(pipelines, publishReleasePipeline())
|
||||
|
||||
if err := writePipelines(".drone.yml", pipelines); err != nil {
|
||||
|
|
|
@ -18,13 +18,3 @@ func updateDocsPipeline() pipeline {
|
|||
// TODO: migrate
|
||||
return pipeline{}
|
||||
}
|
||||
|
||||
func verifyTaggedBuildStep() step {
|
||||
return step{
|
||||
Name: "Verify build is tagged",
|
||||
Image: "alpine:latest",
|
||||
Commands: []string{
|
||||
"[ -n ${DRONE_TAG} ] || (echo 'DRONE_TAG is not set. Is the commit tagged?' && exit 1)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,13 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func buildOsRepoPipelines() []pipeline {
|
||||
pipelines := promoteBuildOsRepoPipelines()
|
||||
pipelines = append(pipelines, artifactMigrationPipeline()...)
|
||||
|
||||
return pipelines
|
||||
}
|
||||
|
||||
func promoteBuildOsRepoPipelines() []pipeline {
|
||||
aptPipeline := promoteAptPipeline()
|
||||
yumPipeline := promoteYumPipeline()
|
||||
|
@ -183,13 +190,15 @@ func (optpb *OsPackageToolPipelineBuilder) buildPromoteOsPackagePipeline() pipel
|
|||
pipelineName := fmt.Sprintf("publish-%s", optpb.pipelineNameSuffix)
|
||||
checkoutPath := "/go/src/github.com/gravitational/teleport"
|
||||
commitName := "${DRONE_TAG}"
|
||||
checkoutStepName := "Check out code"
|
||||
|
||||
p := optpb.buildBaseOsPackagePipeline(pipelineName, checkoutStepName, checkoutPath, commitName)
|
||||
p := optpb.buildBaseOsPackagePipeline(pipelineName, checkoutPath, commitName)
|
||||
p.Trigger = triggerPromote
|
||||
p.Trigger.Repo.Include = []string{"gravitational/teleport"}
|
||||
|
||||
setupSteps := verifyValidPromoteRunSteps(checkoutPath, commitName, true)
|
||||
setupSteps := append(
|
||||
verifyValidPromoteRunSteps(),
|
||||
cloneRepoStep(checkoutPath, commitName),
|
||||
)
|
||||
|
||||
setupStepNames := make([]string, 0, len(setupSteps))
|
||||
for _, setupStep := range setupSteps {
|
||||
|
@ -218,14 +227,13 @@ func (optpb *OsPackageToolPipelineBuilder) buildMigrateOsPackagePipeline(trigger
|
|||
// DRONE_TAG is not available outside of promotion pipelines and will cause drone to fail with a
|
||||
// "migrate-apt-new-repos: bad substitution" error if used here
|
||||
commitName := "${DRONE_COMMIT}"
|
||||
checkoutStepName := "Check out code"
|
||||
|
||||
// If migrations are not configured then don't run
|
||||
if triggerBranch == "" || len(migrationVersions) == 0 {
|
||||
return buildNeverTriggerPipeline(pipelineName)
|
||||
}
|
||||
|
||||
p := optpb.buildBaseOsPackagePipeline(pipelineName, checkoutStepName, checkoutPath, commitName)
|
||||
p := optpb.buildBaseOsPackagePipeline(pipelineName, checkoutPath, commitName)
|
||||
p.Trigger = trigger{
|
||||
Repo: triggerRef{Include: []string{"gravitational/teleport"}},
|
||||
Event: triggerRef{Include: []string{"push"}},
|
||||
|
@ -268,7 +276,7 @@ func buildNeverTriggerPipeline(pipelineName string) pipeline {
|
|||
// Functions that use this method should add at least:
|
||||
// * a Trigger
|
||||
// * Steps for checkout
|
||||
func (optpb *OsPackageToolPipelineBuilder) buildBaseOsPackagePipeline(pipelineName, checkoutStepName, checkoutPath, commit string) pipeline {
|
||||
func (optpb *OsPackageToolPipelineBuilder) buildBaseOsPackagePipeline(pipelineName, checkoutPath, commit string) pipeline {
|
||||
p := newKubePipeline(pipelineName)
|
||||
p.Workspace = workspace{Path: "/go"}
|
||||
p.Volumes = []volume{
|
||||
|
@ -281,13 +289,7 @@ func (optpb *OsPackageToolPipelineBuilder) buildBaseOsPackagePipeline(pipelineNa
|
|||
volumeTmpfs,
|
||||
volumeAwsConfig,
|
||||
}
|
||||
p.Steps = []step{
|
||||
{
|
||||
Name: checkoutStepName,
|
||||
Image: "alpine/git:latest",
|
||||
Commands: toolCheckoutCommands(checkoutPath, commit),
|
||||
},
|
||||
}
|
||||
p.Steps = []step{cloneRepoStep(checkoutPath, commit)}
|
||||
setStepResourceLimits(p.Steps)
|
||||
|
||||
return p
|
||||
|
@ -310,17 +312,6 @@ func setStepResourceLimits(steps []step) {
|
|||
// }
|
||||
}
|
||||
|
||||
// Note that tags are also valid here as a tag refers to a specific commit
|
||||
func toolCheckoutCommands(checkoutPath, commit string) []string {
|
||||
commands := []string{
|
||||
fmt.Sprintf("mkdir -p %q", checkoutPath),
|
||||
fmt.Sprintf("cd %q", checkoutPath),
|
||||
`git clone https://github.com/gravitational/${DRONE_REPO_NAME}.git .`,
|
||||
fmt.Sprintf("git checkout %q", commit),
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
func (optpb *OsPackageToolPipelineBuilder) getDroneTagVersionSteps(codePath string) []step {
|
||||
return optpb.getVersionSteps(codePath, "${DRONE_TAG}", true)
|
||||
}
|
||||
|
@ -409,7 +400,7 @@ func (optpb *OsPackageToolPipelineBuilder) getVersionSteps(codePath, version str
|
|||
assumeUploadRoleStep,
|
||||
{
|
||||
Name: fmt.Sprintf("Publish %ss to %s repos for %q", optpb.packageType, strings.ToUpper(optpb.packageManagerName), version),
|
||||
Image: "golang:1.18.4-bullseye",
|
||||
Image: fmt.Sprintf("golang:%s-bullseye", GoVersion),
|
||||
Environment: optpb.environmentVars,
|
||||
Commands: append(
|
||||
toolSetupCommands,
|
||||
|
|
|
@ -39,7 +39,7 @@ func buildDockerPromotionPipelineECR() pipeline {
|
|||
volumeAwsConfig,
|
||||
}
|
||||
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, verifyTaggedBuildStep())
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, verifyTaggedStep())
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, waitForDockerStep())
|
||||
|
||||
// Pull/Push Steps
|
||||
|
@ -68,13 +68,11 @@ func buildDockerPromotionPipelineECR() pipeline {
|
|||
fmt.Sprintf("docker pull %s/gravitational/teleport:$${VERSION}", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-ent:$${VERSION}", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-ent:$${VERSION}-fips", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-operator:$${VERSION}", StagingRegistry),
|
||||
// retag images to production naming
|
||||
"echo \"---> Tagging images for $${VERSION}\"",
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport:$${VERSION} %s/gravitational/teleport:$${VERSION}", StagingRegistry, ProductionRegistry),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-ent:$${VERSION} %s/gravitational/teleport-ent:$${VERSION}", StagingRegistry, ProductionRegistry),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-ent:$${VERSION}-fips %s/gravitational/teleport-ent:$${VERSION}-fips", StagingRegistry, ProductionRegistry),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-operator:$${VERSION} %s/gravitational/teleport-operator:$${VERSION}", StagingRegistry, ProductionRegistry),
|
||||
// authenticate with production credentials
|
||||
"docker logout " + StagingRegistry,
|
||||
"aws ecr-public get-login-password --region=us-east-1 | docker login -u=\"AWS\" --password-stdin " + ProductionRegistry,
|
||||
|
@ -84,7 +82,6 @@ func buildDockerPromotionPipelineECR() pipeline {
|
|||
fmt.Sprintf("docker push %s/gravitational/teleport:$${VERSION}", ProductionRegistry),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-ent:$${VERSION}", ProductionRegistry),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-ent:$${VERSION}-fips", ProductionRegistry),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-operator:$${VERSION}", ProductionRegistry),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -106,7 +103,7 @@ func buildDockerPromotionPipelineQuay() pipeline {
|
|||
volumeAwsConfig,
|
||||
}
|
||||
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, verifyTaggedBuildStep())
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, verifyTaggedStep())
|
||||
dockerPipeline.Steps = append(dockerPipeline.Steps, waitForDockerStep())
|
||||
|
||||
// Pull/Push Steps
|
||||
|
@ -139,13 +136,11 @@ func buildDockerPromotionPipelineQuay() pipeline {
|
|||
fmt.Sprintf("docker pull %s/gravitational/teleport:$${VERSION}", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-ent:$${VERSION}", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-ent:$${VERSION}-fips", StagingRegistry),
|
||||
fmt.Sprintf("docker pull %s/gravitational/teleport-operator:$${VERSION}", StagingRegistry),
|
||||
// retag images to production naming
|
||||
"echo \"---> Tagging images for $${VERSION}\"",
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport:$${VERSION} %s/gravitational/teleport:$${VERSION}", StagingRegistry, ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-ent:$${VERSION} %s/gravitational/teleport-ent:$${VERSION}", StagingRegistry, ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-ent:$${VERSION}-fips %s/gravitational/teleport-ent:$${VERSION}-fips", StagingRegistry, ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker tag %s/gravitational/teleport-operator:$${VERSION} %s/gravitational/teleport-operator:$${VERSION}", StagingRegistry, ProductionRegistryQuay),
|
||||
// authenticate with production credentials
|
||||
"docker logout " + StagingRegistry,
|
||||
"docker login -u=\"$QUAY_USERNAME\" -p=\"$QUAY_PASSWORD\" " + ProductionRegistryQuay,
|
||||
|
@ -154,7 +149,6 @@ func buildDockerPromotionPipelineQuay() pipeline {
|
|||
fmt.Sprintf("docker push %s/gravitational/teleport:$${VERSION}", ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-ent:$${VERSION}", ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-ent:$${VERSION}-fips", ProductionRegistryQuay),
|
||||
fmt.Sprintf("docker push %s/gravitational/teleport-operator:$${VERSION}", ProductionRegistryQuay),
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import "fmt"
|
|||
|
||||
// pushCheckoutCommands builds a list of commands for Drone to check out a git commit on a push build
|
||||
func pushCheckoutCommands(b buildType) []string {
|
||||
cloneDirectory := "/go/src/github.com/gravitational/teleport"
|
||||
var commands []string
|
||||
|
||||
if b.hasTeleportConnect() {
|
||||
|
@ -25,12 +26,9 @@ func pushCheckoutCommands(b buildType) []string {
|
|||
commands = append(commands, `mkdir -p /go/src/github.com/gravitational/webapps`)
|
||||
}
|
||||
|
||||
commands = append(commands, cloneRepoCommands(cloneDirectory, "${DRONE_COMMIT_SHA}")...)
|
||||
|
||||
commands = append(commands,
|
||||
`mkdir -p /go/src/github.com/gravitational/teleport /go/cache`,
|
||||
`cd /go/src/github.com/gravitational/teleport`,
|
||||
`git init && git remote add origin ${DRONE_REMOTE_URL}`,
|
||||
`git fetch origin`,
|
||||
`git checkout -qf ${DRONE_COMMIT_SHA}`,
|
||||
// this is allowed to fail because pre-4.3 Teleport versions don't use the webassets submodule
|
||||
`git submodule update --init webassets || true`,
|
||||
`mkdir -m 0700 /root/.ssh && echo "$GITHUB_PRIVATE_KEY" > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa`,
|
||||
|
@ -39,6 +37,7 @@ func pushCheckoutCommands(b buildType) []string {
|
|||
// do a recursive submodule checkout to get both webassets and webassets/e
|
||||
// this is allowed to fail because pre-4.3 Teleport versions don't use the webassets submodule
|
||||
`git submodule update --init --recursive webassets || true`,
|
||||
`mkdir -pv /go/cache`,
|
||||
)
|
||||
|
||||
if b.hasTeleportConnect() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
ARG BUILDBOX
|
||||
FROM $BUILDBOX as builder
|
||||
# BUILDPLATFORM is provided by Docker/buildx
|
||||
FROM --platform=$BUILDPLATFORM $BUILDBOX as builder
|
||||
|
||||
WORKDIR /go/src/github.com/gravitational/teleport
|
||||
|
||||
|
@ -7,11 +8,12 @@ WORKDIR /go/src/github.com/gravitational/teleport
|
|||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
|
||||
# we have to copy the API before `go mod download` because go.mod has a replace directive for it
|
||||
# We have to copy the API before `go mod download` because go.mod has a replace directive for it
|
||||
COPY api/ api/
|
||||
|
||||
# cache deps before building and copying source
|
||||
# this way we don't need to re-download deps when the deps are the same
|
||||
# Download and Cache dependencies before building and copying source
|
||||
# This will prevent re-downloading the operator's dependencies if they have not changed as this
|
||||
# `run` layer will be cached
|
||||
RUN go mod download
|
||||
|
||||
COPY *.go ./
|
||||
|
@ -22,10 +24,20 @@ COPY operator/sidecar/ operator/sidecar/
|
|||
COPY operator/main.go operator/main.go
|
||||
COPY operator/namespace.go operator/namespace.go
|
||||
|
||||
# Build
|
||||
RUN GOOS=linux GOARCH=amd64 go build -a -o /go/bin/teleport-operator github.com/gravitational/teleport/operator
|
||||
# Compiler package should use host-triplet-agnostic name (i.e. "x86-64-linux-gnu-gcc" instead of "gcc")
|
||||
# in most cases, to avoid issues on systems with multiple versions of gcc (i.e. buildboxes)
|
||||
# TARGETOS and TARGETARCH are provided by Docker/buildx, but must be explicitly listed here
|
||||
ARG COMPILER_NAME TARGETOS TARGETARCH
|
||||
|
||||
FROM gcr.io/distroless/cc
|
||||
# Build the program
|
||||
# CGO is required for github.com/gravitational/teleport/lib/system
|
||||
RUN echo "Targeting $TARGETOS/$TARGETARCH with CC=$COMPILER_NAME" && \
|
||||
CGO_ENABLED=1 CC=$COMPILER_NAME GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
go build -a -o /go/bin/teleport-operator github.com/gravitational/teleport/operator
|
||||
|
||||
# Create the image with the build operator on the $TARGETPLATFORM
|
||||
# TARGETPLATFORM is provided by Docker/buildx
|
||||
FROM --platform=$TARGETPLATFORM gcr.io/distroless/cc
|
||||
WORKDIR /
|
||||
COPY --from=builder /go/bin/teleport-operator .
|
||||
|
||||
|
|
|
@ -20,6 +20,25 @@ SHELL = /usr/bin/env bash -o pipefail
|
|||
# include BUILDBOX_VERSION, BUILDBOX and BUILDBOX_variant variables
|
||||
include ../build.assets/images.mk
|
||||
|
||||
# Configure which compiler and buildbox to use
|
||||
OS ?= $(shell go env GOOS)
|
||||
ARCH ?= $(shell go env GOARCH)
|
||||
ifeq ("$(OS)","linux")
|
||||
ifeq ("$(ARCH)","amd64")
|
||||
COMPILER ?= x86_64-linux-gnu-gcc
|
||||
PLATFORM_BUILDBOX ?= $(BUILDBOX)
|
||||
else ifeq ("$(ARCH)","386")
|
||||
COMPILER ?= x86_64-linux-gnu-gcc
|
||||
PLATFORM_BUILDBOX ?= $(BUILDBOX)
|
||||
else ifeq ("$(ARCH)","arm")
|
||||
COMPILER ?= arm-linux-gnueabihf-gcc
|
||||
PLATFORM_BUILDBOX ?= $(BUILDBOX_ARM)
|
||||
else ifeq ("$(ARCH)","arm64")
|
||||
COMPILER ?= aarch64-linux-gnu-gcc
|
||||
PLATFORM_BUILDBOX ?= $(BUILDBOX_ARM)
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
|
@ -103,7 +122,8 @@ run: manifests generate fmt vet ## Run a controller from your host.
|
|||
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build docker image with the manager.
|
||||
docker build --build-arg BUILDBOX=$(BUILDBOX) -t ${IMG} .. -f ./Dockerfile
|
||||
docker buildx build --platform="$(OS)/$(ARCH)" --build-arg BUILDBOX=$(PLATFORM_BUILDBOX) \
|
||||
--build-arg COMPILER_NAME=$(COMPILER) -t ${IMG} --load .. -f ./Dockerfile
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push: ## Push docker image with the manager.
|
||||
|
|
Loading…
Reference in a new issue