Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
ec30ef1
Added multiarch build support for teleport oss, ent, and fips
Sep 23, 2022
7685a16
Exported image/imageTag types
Sep 26, 2022
394dd8e
Resigned dronegen
Sep 27, 2022
31e475a
Removed remainder of testing changes
Sep 27, 2022
e4304e7
Removed changes to submodules
Sep 27, 2022
43b212f
Merge branch 'master' into fred/multiarch-teleport-container-images
fheinecke Sep 27, 2022
a98000e
Reverted dockerfile-fips change
Sep 28, 2022
0d04218
FIxed docs wording
Sep 28, 2022
14bf340
Un-exported most constants
Sep 28, 2022
98fca41
Removed teleport.e makefile deb call
Sep 28, 2022
186a98c
Moved "sed | cut magic" to files
Oct 3, 2022
a2cad2d
Re-added `mkdir -pv /go/cache` to push.go
Oct 3, 2022
a4defc5
Command deterministic order fix
Oct 3, 2022
dbf8717
Added staging-only tag pipeline
Oct 3, 2022
8b22ce7
Moved PR to teleport operator to minimize potential issue impact
Oct 4, 2022
3d806f4
Updated promote to pull and push without build
Oct 4, 2022
0144af4
Made cron triggers not affect canonical tags
Oct 4, 2022
1689c9b
Added check for pre-existing tags on immutable CRs
Oct 4, 2022
f6082d3
Added immutability check to manifests
Oct 4, 2022
5df9de8
Updated staging ecr to only apply $TIMESTAMP tag on cron triggers
Oct 4, 2022
c71ea40
Updated triggerinfo struct to use a triggerflag struct
Oct 4, 2022
06bf80b
Fixed makefile after git mistake
Oct 4, 2022
8d06bb8
Makefile fix
Oct 4, 2022
f16059f
Merge branch 'master' into fred/multiarch-teleport-container-images
Oct 4, 2022
c5418b8
Merge branch 'master' into fred/multiarch-teleport-container-images
Oct 4, 2022
cdde57d
PR fixes
Oct 7, 2022
9c9bd64
Moved internal tools Go version to constant
Oct 13, 2022
06e99c4
Separated container images gofile into multiple files
Oct 13, 2022
2cf7a2b
Moved testing comment
Oct 13, 2022
ecc65bf
Added licenses
Oct 13, 2022
666aed9
Reorganized and added docs for container images
Oct 13, 2022
a9e339c
Merge branch 'master' into fred/multiarch-teleport-container-images
Oct 18, 2022
f694317
Moved const to correct file
Oct 18, 2022
e08f422
Tag trigger logic test
Oct 18, 2022
dc11b97
Testing specific fix
Oct 18, 2022
9961f11
Moved testing to v10.3.2
Oct 18, 2022
c8849f0
Make semver dirs
Oct 18, 2022
2ad16dd
Refactored local registry name/socket
Oct 18, 2022
527c738
Merged previous dockerfile changes
Oct 18, 2022
c4d43df
Added TARGETOS TARGETARCH args
Oct 18, 2022
1b38f68
Updatd tag to testing tag
Oct 18, 2022
dc3963c
Promotion logic test
Oct 18, 2022
a51aefc
Promotion fixes
Oct 18, 2022
7bbcf92
Testing specific fix
Oct 18, 2022
722584f
Removed prerelease check for testing
Oct 18, 2022
5d6de2a
Added staging login commands to promote
Oct 18, 2022
907c5e1
Fixed missing credentials on promotion pull
Oct 18, 2022
10b73af
Rerun tag test with new "full" semver
Oct 18, 2022
85f710b
Made staging builds only publish full semver
Oct 18, 2022
9719fc2
Added semver logging command
Oct 18, 2022
4e121a7
Empty commit to trigger Drone
Oct 19, 2022
f4fb7d8
Promotion test
Oct 19, 2022
645574d
Fixed preceeding v on promote pull
Oct 19, 2022
f76bc4d
Empty commit to trigger Drone
Oct 19, 2022
85dfec6
Re-enabled verify not prerelease step on promote
Oct 19, 2022
f42c002
Cron trigger test
Oct 19, 2022
e21d39d
Testing fix
Oct 19, 2022
ebcaf0b
Testing fix 2
Oct 19, 2022
c745313
Added sleep timer on docker buildx build
Oct 19, 2022
a90b6d3
Testing cleanup
Oct 19, 2022
299af25
Merge branch 'master' into fred/multiarch-teleport-container-images
fheinecke Oct 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,957 changes: 2,886 additions & 71 deletions .drone.yml

Large diffs are not rendered by default.

18 changes: 0 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ VERSION=11.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)
Expand Down Expand Up @@ -1055,23 +1054,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:
Expand Down
64 changes: 48 additions & 16 deletions dronegen/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ 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"
)

var (
Expand Down Expand Up @@ -97,12 +100,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),
}
}
Expand Down Expand Up @@ -207,6 +218,19 @@ func dockerService(v ...volumeRef) service {
}
}

// Starts a container registry service at `drone-docker-registry:5000`
// This can be pushed/pulled to via `docker push/pull drone-docker-registry:5000/image:tag`
func dockerRegistryService() service {
// 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
return service{
Name: "drone-docker-registry",
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 {
Expand Down Expand Up @@ -255,17 +279,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",
`timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" != "200" ]; do sleep 1; done'`,
},
}
}

return []step{tagStep, cloneStep, verifyStep}
func verifyValidPromoteRunSteps() []step {
tagStep := verifyTaggedStep()
verifyStep := verifyNotPrereleaseStep()

return []step{tagStep, verifyStep}
}

func verifyTaggedStep() step {
Expand All @@ -287,13 +317,15 @@ func cloneRepoStep(clonePath, commit string) step {
}
}

func verifyNotPrereleaseStep(checkoutPath string) step {
func verifyNotPrereleaseStep() step {
clonePath := "/tmp/repo"
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")),
Image: fmt.Sprintf("golang:%s-alpine", GoVersion),
Commands: append(
cloneRepoCommands(clonePath, "${DRONE_TAG}"),
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)",
},
),
}
}
232 changes: 232 additions & 0 deletions dronegen/container_image_products.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// 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 string, 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 string, tag *ImageTag) *Image {
return &Image{
Repo: repo,
Name: name,
Tag: tag,
}
},
DockerfileArgBuilder: func(arch string) []string {
gccPackage := ""
compilerName := ""
switch arch {
case "x86_64", "amd64":
gccPackage = "gcc-x86-64-linux-gnu"
compilerName = "x86_64-linux-gnu-gcc"
case "i686", "i386":
gccPackage = "gcc-multilib-i686-linux-gnu"
compilerName = "i686-linux-gnu-gcc"
case "arm64", "aarch64":
gccPackage = "gcc-aarch64-linux-gnu"
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":
gccPackage = "gcc-arm-linux-gnueabihf"
compilerName = "arm-linux-gnueabihf-gcc"
}

return []string{
fmt.Sprintf("COMPILER_PACKAGE=%s", gccPackage),
fmt.Sprintf("COMPILER_NAME=%s", compilerName),
}
},
}
}

func (p *Product) getBaseImage(arch string, version *ReleaseVersion) *Image {
return &Image{
Name: p.Name,
Tag: &ImageTag{
ShellBaseValue: version.ShellVersion,
DisplayBaseValue: version.MajorVersion,
Arch: arch,
},
}
}

func (p *Product) GetLocalRegistryImage(arch string, version *ReleaseVersion) *Image {
image := p.getBaseImage(arch, version)
image.Repo = localRegistry

return image
}

func (p *Product) GetStagingRegistryImage(arch string, version *ReleaseVersion, stagingRepo *ContainerRepo) *Image {
image := p.getBaseImage(arch, version)
image.Repo = stagingRepo.RegistryDomain

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 _, supportedArch := range p.SupportedArchs {
// Include steps for building images from scratch
if flags.ShouldBuildNewImages {
archBuildStep, archBuildStepDetail := p.createBuildStep(supportedArch, version)

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 {
// Generate build details that point to staging images
archBuildStepDetails = append(archBuildStepDetails, &buildStepOutput{
StepName: "",
BuiltImage: p.GetStagingRegistryImage(supportedArch, version, stagingRepo),
Version: version,
Product: p,
})
}
}

for _, containerRepo := range getReposToPublishTo(productionRepos, stagingRepo, flags) {
steps = append(steps, containerRepo.buildSteps(archBuildStepDetails)...)
}

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 {
telportImageName := p.GetLocalRegistryImage(arch, version)
return fmt.Sprintf("Build %s image %q", p.Name, telportImageName.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) (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 %q", 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

step := step{
Name: p.GetBuildStepName(arch, version),
Image: "docker",
Volumes: dockerVolumeRefs(),
Environment: map[string]value{
"DOCKER_BUILDKIT": {
raw: "1",
},
},
Commands: []string{
"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", localRegistry, 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,
}
}
Loading