dronegen: drone config generator (#6071)

This commit is contained in:
Gus Luxton 2021-03-22 22:32:45 -03:00 committed by GitHub
parent 8739417729
commit 026d3419c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 3394 additions and 2242 deletions

4259
.drone.yml

File diff suppressed because it is too large Load diff

View file

@ -176,6 +176,23 @@ else
$(MAKE) --no-print-directory release-unix
endif
# These are aliases used to make build commands uniform.
.PHONY: release-amd64
release-amd64:
$(MAKE) release ARCH=amd64
.PHONY: release-386
release-386:
$(MAKE) release ARCH=386
.PHONY: release-arm
release-arm:
$(MAKE) release ARCH=arm
.PHONY: release-arm64
release-arm64:
$(MAKE) release ARCH=arm64
#
# make release-unix - Produces a binary release tarball containing teleport,
# tctl, and tsh.
@ -604,3 +621,8 @@ update-webassets: WEBAPPS_BRANCH ?= 'master'
update-webassets: TELEPORT_BRANCH ?= 'master'
update-webassets:
build.assets/webapps/update-teleport-webassets.sh -w $(WEBAPPS_BRANCH) -t $(TELEPORT_BRANCH)
# dronegen generates .drone.yml config
.PHONY: dronegen
dronegen:
go run ./dronegen

View file

@ -15,8 +15,8 @@ OS ?= linux
ARCH ?= amd64
RUNTIME ?= go1.15.5
UID ?= $$(id -u)
GID ?= $$(id -g)
UID := $$(id -u)
GID := $$(id -g)
PROTOC_VER ?= 3.6.1
PROTOC_PLATFORM := linux-x86_64
@ -29,6 +29,12 @@ BUILDBOX_CENTOS6_FIPS=quay.io/gravitational/teleport-buildbox-centos6-fips:$(RUN
BUILDBOX_ARM=quay.io/gravitational/teleport-buildbox-arm:$(RUNTIME)
BUILDBOX_ARM_FIPS=quay.io/gravitational/teleport-buildbox-arm-fips:$(RUNTIME)
# These variables are used to dynamically change the name of the buildbox Docker image used by the 'release'
# target. The other solution was to remove the 'buildbox' dependency from the 'release' target, but this would
# make it harder to run `make -C build.assets release` locally as the buildbox would not automatically be built.
BUILDBOX_NAME=$(BUILDBOX)
BUILDBOX_FIPS_NAME=$(BUILDBOX_FIPS)
DOCSBOX=quay.io/gravitational/next:main
ifneq ("$(KUBECONFIG)","")
@ -84,33 +90,40 @@ build-binaries-fips: buildbox-fips
#
.PHONY:buildbox
buildbox:
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
--build-arg RUNTIME=$(RUNTIME) \
--build-arg PROTOC_VER=$(PROTOC_VER) \
--build-arg GOGO_PROTO_TAG=$(GOGO_PROTO_TAG) \
--build-arg PROTOC_PLATFORM=$(PROTOC_PLATFORM) \
--cache-from $(BUILDBOX) \
--tag $(BUILDBOX) .
if [[ "$(BUILDBOX_NAME)" == "$(BUILDBOX)" ]]; then \
if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX); fi; \
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
--build-arg RUNTIME=$(RUNTIME) \
--build-arg PROTOC_VER=$(PROTOC_VER) \
--build-arg GOGO_PROTO_TAG=$(GOGO_PROTO_TAG) \
--build-arg PROTOC_PLATFORM=$(PROTOC_PLATFORM) \
--cache-from $(BUILDBOX) \
--tag $(BUILDBOX) . ; \
fi
#
# Builds a Docker buildbox for FIPS
#
.PHONY:buildbox-fips
buildbox-fips:
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
--build-arg RUNTIME=$(RUNTIME) \
--cache-from $(BUILDBOX_FIPS) \
--tag $(BUILDBOX_FIPS) -f Dockerfile-fips .
if [[ "$(BUILDBOX_FIPS_NAME)" == "$(BUILDBOX_FIPS)" ]]; then \
if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX_FIPS); fi; \
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
--build-arg RUNTIME=$(RUNTIME) \
--cache-from $(BUILDBOX_FIPS) \
--tag $(BUILDBOX_FIPS) -f Dockerfile-fips . ; \
fi
#
# Builds a Docker buildbox for CentOS 6 builds
#
.PHONY:buildbox-centos6
buildbox-centos6:
@if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX_CENTOS6); fi;
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
@ -123,6 +136,7 @@ buildbox-centos6:
#
.PHONY:buildbox-centos6-fips
buildbox-centos6-fips:
@if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX_CENTOS6_FIPS); fi;
docker build \
--build-arg UID=$(UID) \
--build-arg GID=$(GID) \
@ -137,6 +151,7 @@ buildbox-centos6-fips:
#
.PHONY:buildbox-arm
buildbox-arm: buildbox
@if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX_ARM); fi;
docker build \
--build-arg RUNTIME=$(RUNTIME) \
--cache-from $(BUILDBOX) \
@ -150,6 +165,7 @@ buildbox-arm: buildbox
#
.PHONY:buildbox-arm-fips
buildbox-arm-fips: buildbox-fips
@if [[ $${DRONE} == "true" ]]; then docker pull $(BUILDBOX_ARM_FIPS); fi;
docker build \
--build-arg RUNTIME=$(RUNTIME) \
--cache-from $(BUILDBOX_FIPS) \
@ -218,20 +234,55 @@ enter: buildbox
#
# Create a Teleport package using the build container.
# Don't use this target directly; call named Makefile targets like release-amd64.
#
.PHONY:release
release: buildbox
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX) \
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_NAME) \
/usr/bin/make release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME)
# These are aliases used to make build commands uniform.
.PHONY: release-amd64
release-amd64:
$(MAKE) release ARCH=amd64
.PHONY: release-386
release-386:
$(MAKE) release ARCH=386
.PHONY: release-arm
release-arm: buildbox-arm
$(MAKE) release ARCH=arm BUILDBOX_NAME=$(BUILDBOX_ARM)
.PHONY: release-arm64
release-arm64: buildbox-arm
$(MAKE) release ARCH=arm64 BUILDBOX_NAME=$(BUILDBOX_ARM)
.PHONY: release-amd64-fips
release-amd64-fips:
$(MAKE) release-fips ARCH=amd64 FIPS=yes BUILDBOX_FIPS_NAME=$(BUILDBOX_FIPS)
.PHONY: release-arm64-fips
release-arm64-fips: buildbox-arm-fips
$(MAKE) release-fips ARCH=arm64 FIPS=yes BUILDBOX_FIPS_NAME=$(BUILDBOX_ARM_FIPS)
.PHONY: release-amd64-centos6
release-amd64-centos6: buildbox-centos6
$(MAKE) release-centos6 ARCH=amd64
.PHONY: release-amd64-centos6-fips
release-amd64-centos6-fips: buildbox-centos6-fips
$(MAKE) release-centos6-fips ARCH=amd64 FIPS=yes
#
# Create a Teleport FIPS package using the build container.
# This is a special case because it only builds and packages the Enterprise FIPS binaries, no OSS.
# CI should not use this target, it should use named Makefile targets like release-amd64-fips.
#
.PHONY:release-fips
release-fips: buildbox-fips
@if [ -z ${VERSION} ]; then echo "VERSION is not set"; exit 1; fi
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_FIPS) \
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_FIPS_NAME) \
/usr/bin/make -C e release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME) FIPS=yes VERSION=$(VERSION) GITTAG=v$(VERSION)
#
@ -259,45 +310,6 @@ release-windows: buildbox
docker run $(DOCKERFLAGS) -i $(NOROOT) $(BUILDBOX) \
/usr/bin/make release -e ADDFLAGS="$(ADDFLAGS)" OS=windows
#
# Create an ARM Teleport package using the build container.
#
.PHONY:release-arm
release-arm: ARCH=arm
release-arm: buildbox-arm
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_ARM) \
/usr/bin/make release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME)
#
# Create an ARM64 Teleport package using the build container.
#
.PHONY:release-arm64
release-arm64: ARCH=arm64
release-arm64: buildbox-arm
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_ARM) \
/usr/bin/make release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME)
#
# Create an ARM FIPS Teleport package using the build container.
#
.PHONY:release-arm-fips
release-arm-fips: ARCH=arm
release-arm-fips: buildbox-arm-fips
@if [ -z ${VERSION} ]; then echo "VERSION is not set"; exit 1; fi
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_ARM_FIPS) \
/usr/bin/make -C e release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME) FIPS=yes VERSION=$(VERSION) GITTAG=v$(VERSION)
#
# Create an ARM64 FIPS Teleport package using the build container.
#
.PHONY:release-arm64-fips
release-arm64-fips: ARCH=arm64
release-arm64-fips: buildbox-arm-fips
@if [ -z ${VERSION} ]; then echo "VERSION is not set"; exit 1; fi
docker run $(DOCKERFLAGS) $(BCCFLAGS) -i $(NOROOT) $(BUILDBOX_ARM_FIPS) \
/usr/bin/make -C e release -e ADDFLAGS="$(ADDFLAGS)" OS=$(OS) ARCH=$(ARCH) RUNTIME=$(RUNTIME) FIPS=yes VERSION=$(VERSION) GITTAG=v$(VERSION)
#
# Run docs tester to detect problems.
#

100
dronegen/common.go Normal file
View file

@ -0,0 +1,100 @@
package main
import "fmt"
var (
triggerPullRequest = trigger{
Event: triggerRef{Include: []string{"pull_request"}},
Repo: triggerRef{Include: []string{"gravitational/*"}},
}
triggerPush = trigger{
Event: triggerRef{Include: []string{"push"}, Exclude: []string{"pull_request"}},
Branch: triggerRef{Include: []string{"master", "branch/*"}},
Repo: triggerRef{Include: []string{"gravitational/*"}},
}
triggerTag = trigger{
Event: triggerRef{Include: []string{"tag"}},
Ref: triggerRef{Include: []string{"refs/tags/v*"}},
Repo: triggerRef{Include: []string{"gravitational/*"}},
}
volumeDocker = volume{
Name: "dockersock",
Temp: &volumeTemp{},
}
volumeTmpfs = volume{
Name: "tmpfs",
Temp: &volumeTemp{Medium: "memory"},
}
volumeTmpDind = volume{
Name: "tmp-dind",
Temp: &volumeTemp{},
}
volumeTmpIntegration = volume{
Name: "tmp-integration",
Temp: &volumeTemp{},
}
volumeRefTmpfs = volumeRef{
Name: "tmpfs",
Path: "/tmpfs",
}
volumeRefDocker = volumeRef{
Name: "dockersock",
Path: "/var/run",
}
volumeRefTmpDind = volumeRef{
Name: "tmp-dind",
Path: "/tmp",
}
volumeRefTmpIntegration = volumeRef{
Name: "tmp-integration",
Path: "/tmp",
}
// TODO(gus): Set this from `make -C build.assets print-runtime-version` or similar rather
// than hardcoding it. Also remove the usage of RUNTIME as a pipeline-level environment variable
// (as support for these varies among Drone runners) and only set it for steps that need it.
goRuntime = value{raw: "go1.15.5"}
)
type buildType struct {
os string
arch string
fips bool
centos6 bool
}
// dockerService generates a docker:dind service
// It includes the Docker socket volume by default, plus any extra volumes passed in
func dockerService(v ...volumeRef) service {
return service{
Name: "Start Docker",
Image: "docker:dind",
Volumes: append(v, volumeRefDocker),
}
}
// 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/centos6 combo
func releaseMakefileTarget(b buildType) string {
makefileTarget := fmt.Sprintf("release-%s", b.arch)
if b.centos6 {
makefileTarget += "-centos6"
}
if b.fips {
makefileTarget += "-fips"
}
return makefileTarget
}

6
dronegen/cron.go Normal file
View file

@ -0,0 +1,6 @@
package main
func cronPipelines() []pipeline {
// TODO: migrate
return nil
}

28
dronegen/drone_cli.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"os"
"os/exec"
)
func checkDroneCLI() error {
if _, err := exec.LookPath("drone"); err != nil {
return fmt.Errorf("can't find drone CLI in $PATH: %w; get it from https://docs.drone.io/cli/install/", err)
}
if os.Getenv("DRONE_SERVER") == "" || os.Getenv("DRONE_TOKEN") == "" {
return fmt.Errorf("$DRONE_SERVER and/or $DRONE_TOKEN env vars not set; get them at https://drone.gravitational.io/account")
}
return nil
}
func signDroneConfig() error {
out, err := exec.Command("drone", "sign", "gravitational/teleport", "--save").CombinedOutput()
if err != nil {
if len(out) > 0 {
err = fmt.Errorf("drone signing failed: %v\noutput:\n%s", err, out)
}
return err
}
return nil
}

122
dronegen/main.go Normal file
View file

@ -0,0 +1,122 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
)
func main() {
if err := checkDroneCLI(); err != nil {
fmt.Println(err)
os.Exit(1)
}
var pipelines []pipeline
pipelines = append(pipelines, testPipelines()...)
pipelines = append(pipelines, pushPipelines()...)
pipelines = append(pipelines, tagPipelines()...)
pipelines = append(pipelines, cronPipelines()...)
pipelines = append(pipelines, promoteBuildPipeline())
pipelines = append(pipelines, updateDocsPipeline())
if err := writePipelines(".drone.yml", pipelines); err != nil {
fmt.Println("failed writing drone pipelines:", err)
os.Exit(1)
}
if err := signDroneConfig(); err != nil {
fmt.Println("failed signing .drone.yml:", err)
os.Exit(1)
}
}
func writePipelines(path string, newPipelines []pipeline) error {
// Read the existing config and replace only those pipelines defined in
// newPipelines.
//
// TODO: When all pipelines are migrated, remove this merging logic and
// write the file directly. This will be simpler and allow cleanup of
// pipelines when they are removed from this generator.
existingConfig, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read existing config: %w", err)
}
existingPipelines, err := parsePipelines(existingConfig)
if err != nil {
return fmt.Errorf("failed to parse existing config: %w", err)
}
newPipelinesSet := make(map[string]pipeline, len(newPipelines))
for _, p := range newPipelines {
// TODO: remove this check once promoteBuildPipeline and
// updateDocsPipeline are implemented.
if p.Name == "" {
continue
}
newPipelinesSet[p.Name] = p
}
pipelines := existingPipelines
// Overwrite all existing pipelines with new ones that have the same name.
for i, p := range pipelines {
if np, ok := newPipelinesSet[p.Name]; ok {
out, err := yaml.Marshal(np)
if err != nil {
return fmt.Errorf("failed to encode pipelines: %w", err)
}
// Add a little note about this being generated.
out = append([]byte(np.comment), out...)
pipelines[i] = parsedPipeline{pipeline: np, raw: out}
delete(newPipelinesSet, np.Name)
}
}
// If we decide to add new pipelines before everything is migrated to this
// generator, this check needs to change.
if len(newPipelinesSet) != 0 {
var names []string
for n := range newPipelinesSet {
names = append(names, n)
}
return fmt.Errorf("pipelines %q don't exist in the current config, aborting", names)
}
var pipelinesEnc [][]byte
for _, p := range pipelines {
pipelinesEnc = append(pipelinesEnc, p.raw)
}
configData := bytes.Join(pipelinesEnc, []byte("\n---\n"))
return ioutil.WriteFile(path, configData, 0664)
}
// parsedPipeline is a single pipeline parsed from .drone.yml along with its
// unparsed form. It's used to preserve YAML comments and minimize diffs due to
// formatting.
//
// TODO: remove this when all pipelines are migrated. All comments will be
// moved to this generator instead.
type parsedPipeline struct {
pipeline
raw []byte
}
func parsePipelines(data []byte) ([]parsedPipeline, error) {
chunks := bytes.Split(data, []byte("\n---\n"))
var pipelines []parsedPipeline
for _, c := range chunks {
// Discard the signature, it will be re-generated.
if bytes.HasPrefix(c, []byte("kind: signature")) {
continue
}
var p pipeline
if err := yaml.UnmarshalStrict(c, &p); err != nil {
return nil, err
}
pipelines = append(pipelines, parsedPipeline{pipeline: p, raw: c})
}
return pipelines, nil
}

11
dronegen/misc.go Normal file
View file

@ -0,0 +1,11 @@
package main
func promoteBuildPipeline() pipeline {
// TODO: migrate
return pipeline{}
}
func updateDocsPipeline() pipeline {
// TODO: migrate
return pipeline{}
}

134
dronegen/push.go Normal file
View file

@ -0,0 +1,134 @@
package main
import "fmt"
// pushCheckoutCommands builds a list of commands for Drone to check out a git commit on a push build
func pushCheckoutCommands(fips bool) []string {
commands := []string{
`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`,
`ssh-keyscan -H github.com > /root/.ssh/known_hosts 2>/dev/null && chmod 600 /root/.ssh/known_hosts`,
`git submodule update --init e`,
// 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`,
`rm -f /root/.ssh/id_rsa`,
}
if fips {
commands = append(commands, `if [[ "${DRONE_TAG}" != "" ]]; then echo "${DRONE_TAG##v}" > /go/.version.txt; else egrep ^VERSION Makefile | cut -d= -f2 > /go/.version.txt; fi; cat /go/.version.txt`)
}
return commands
}
// pushBuildCommands generates a list of commands for Drone to build an artifact as part of a push build
func pushBuildCommands(b buildType) []string {
commands := []string{
`apk add --no-cache make`,
`chown -R $UID:$GID /go`,
`cd /go/src/github.com/gravitational/teleport`,
}
if b.fips {
commands = append(commands,
`export VERSION=$(cat /go/.version.txt)`,
)
}
commands = append(commands,
fmt.Sprintf(`make -C build.assets %s`, releaseMakefileTarget(b)),
)
return commands
}
// pushPipelines builds all applicable push pipeline combinations
func pushPipelines() []pipeline {
var ps []pipeline
for _, arch := range []string{"amd64", "386", "arm", "arm64"} {
for _, fips := range []bool{false, true} {
if (arch == "386" || arch == "arm") && fips {
// FIPS mode not supported on i386/ARM
continue
}
ps = append(ps, pushPipeline(buildType{os: "linux", arch: arch, fips: fips}))
}
}
// Only amd64 Windows is supported for now.
ps = append(ps, pushPipeline(buildType{os: "windows", arch: "amd64"}))
return ps
}
// pushPipeline generates a push pipeline for a given combination of os/arch/FIPS
func pushPipeline(b buildType) pipeline {
if b.os == "" {
panic("b.os must be set")
}
if b.arch == "" {
panic("b.arch must be set")
}
pipelineName := fmt.Sprintf("push-build-%s-%s", b.os, b.arch)
pushEnvironment := map[string]value{
"UID": value{raw: "1000"},
"GID": value{raw: "1000"},
"GOPATH": value{raw: "/go"},
"OS": value{raw: b.os},
"ARCH": value{raw: b.arch},
}
if b.fips {
pipelineName += "-fips"
pushEnvironment["FIPS"] = value{raw: "yes"}
}
p := newKubePipeline(pipelineName)
p.Environment = map[string]value{
"RUNTIME": goRuntime,
"UID": value{raw: "1000"},
"GID": value{raw: "1000"},
}
p.Trigger = triggerPush
p.Workspace = workspace{Path: "/go"}
p.Volumes = dockerVolumes()
p.Services = []service{
dockerService(),
}
p.Steps = []step{
{
Name: "Check out code",
Image: "docker:git",
Environment: map[string]value{
"GITHUB_PRIVATE_KEY": value{fromSecret: "GITHUB_PRIVATE_KEY"},
},
Commands: pushCheckoutCommands(b.fips),
},
{
Name: "Build artifacts",
Image: "docker",
Environment: pushEnvironment,
Volumes: dockerVolumeRefs(),
Commands: pushBuildCommands(b),
},
{
Name: "Send Slack notification",
Image: "plugins/slack",
Settings: map[string]value{
"webhook": value{fromSecret: "SLACK_WEBHOOK_DEV_TELEPORT"},
},
Template: []string{
`*{{#success build.status}}{{ else }}{{/success}} {{ uppercasefirst build.status }}: Build #{{ build.number }}* (type: ` + "`{{ build.event }}`" + `)
` + "`${DRONE_STAGE_NAME}`" + ` artifact build failed.
*Warning:* This is a genuine failure to build the Teleport binary from ` + "`{{ build.branch }}`" + ` (likely due to a bad merge or commit) and should be investigated immediately.
Commit: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commit/{{ build.commit }}|{{ truncate build.commit 8 }}>
Branch: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commits/{{ build.branch }}|{{ repo.owner }}/{{ repo.name }}:{{ build.branch }}>
Author: <https://github.com/{{ build.author }}|{{ build.author }}>
<{{ build.link }}|Visit Drone build page >
`,
},
When: &condition{Status: []string{"failure"}},
},
}
return p
}

379
dronegen/tag.go Normal file
View file

@ -0,0 +1,379 @@
package main
import (
"fmt"
)
const (
// rpmPackage is the RPM package type
rpmPackage = "rpm"
// debPackage is the DEB package type
debPackage = "deb"
)
// tagCheckoutCommands builds a list of commands for Drone to check out a git commit on a tag build
func tagCheckoutCommands(fips bool) []string {
commands := []string{
`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:-$DRONE_COMMIT}`,
// fetch enterprise submodules
`mkdir -m 0700 /root/.ssh && echo -n "$GITHUB_PRIVATE_KEY" > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa`,
`ssh-keyscan -H github.com > /root/.ssh/known_hosts 2>/dev/null && chmod 600 /root/.ssh/known_hosts`,
`git submodule update --init 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`,
`rm -f /root/.ssh/id_rsa`,
// create necessary directories
`mkdir -p /go/cache /go/artifacts`,
// set version
`if [[ "${DRONE_TAG}" != "" ]]; then echo "${DRONE_TAG##v}" > /go/.version.txt; else egrep ^VERSION Makefile | cut -d= -f2 > /go/.version.txt; fi; cat /go/.version.txt`,
}
return commands
}
// tagBuildCommands generates a list of commands for Drone to build an artifact as part of a tag build
func tagBuildCommands(b buildType) []string {
commands := []string{
`apk add --no-cache make`,
`chown -R $UID:$GID /go`,
`cd /go/src/github.com/gravitational/teleport`,
}
if b.fips {
commands = append(commands,
"export VERSION=$(cat /go/.version.txt)",
)
}
commands = append(commands,
fmt.Sprintf(
`make -C build.assets %s`, releaseMakefileTarget(b),
),
)
return commands
}
// tagCopyArtifactCommands generates a set of commands to find and copy built tarball artifacts as part of a tag build
func tagCopyArtifactCommands(b buildType) []string {
extension := ".tar.gz"
if b.os == "windows" {
extension = ".zip"
}
commands := []string{
`cd /go/src/github.com/gravitational/teleport`,
}
// don't copy OSS artifacts for any FIPS build
if !b.fips {
commands = append(commands,
fmt.Sprintf(`find . -maxdepth 1 -iname "teleport*%s" -print -exec cp {} /go/artifacts \;`, extension),
)
}
// copy enterprise artifacts
if b.os == "windows" {
commands = append(commands,
`export VERSION=$(cat /go/.version.txt)`,
`cp /go/artifacts/teleport-v$${VERSION}-windows-amd64-bin.zip /go/artifacts/teleport-ent-v$${VERSION}-windows-amd64-bin.zip`,
)
} else {
commands = append(commands,
`find e/ -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts \;`,
)
}
// we need to specifically rename artifacts which are created for CentOS 6
// these is the only special case where renaming is not handled inside the Makefile
if b.centos6 {
commands = append(commands, `export VERSION=$(cat /go/.version.txt)`)
if !b.fips {
commands = append(commands,
`mv /go/artifacts/teleport-v$${VERSION}-linux-amd64-bin.tar.gz /go/artifacts/teleport-v$${VERSION}-linux-amd64-centos6-bin.tar.gz`,
`mv /go/artifacts/teleport-ent-v$${VERSION}-linux-amd64-bin.tar.gz /go/artifacts/teleport-ent-v$${VERSION}-linux-amd64-centos6-bin.tar.gz`,
)
} else {
commands = append(commands,
`mv /go/artifacts/teleport-ent-v$${VERSION}-linux-amd64-fips-bin.tar.gz /go/artifacts/teleport-ent-v$${VERSION}-linux-amd64-centos6-fips-bin.tar.gz`,
)
}
}
// generate checksums
commands = append(commands, fmt.Sprintf(`cd /go/artifacts && for FILE in teleport*%s; do sha256sum $FILE > $FILE.sha256; done && ls -l`, extension))
return commands
}
type s3Settings struct {
region string
source string
target string
stripPrefix string
}
// uploadToS3Step generates an S3 upload step
func uploadToS3Step(s s3Settings) step {
return step{
Name: "Upload to S3",
Image: "plugins/s3",
Settings: map[string]value{
"bucket": value{fromSecret: "AWS_S3_BUCKET"},
"access_key": value{fromSecret: "AWS_ACCESS_KEY_ID"},
"secret_key": value{fromSecret: "AWS_SECRET_ACCESS_KEY"},
"region": value{raw: s.region},
"source": value{raw: s.source},
"target": value{raw: s.target},
"strip_prefix": value{raw: s.stripPrefix},
},
}
}
// tagPipelines builds all applicable tag pipeline combinations
func tagPipelines() []pipeline {
var ps []pipeline
// regular tarball builds
for _, arch := range []string{"amd64", "386", "arm", "arm64"} {
for _, fips := range []bool{false, true} {
if (arch == "386" || arch == "arm") && fips {
// FIPS mode not supported on i386 or ARM
continue
}
ps = append(ps, tagPipeline(buildType{os: "linux", arch: arch, fips: fips}))
if arch == "arm" || arch == "arm64" {
// TODO(gus): support needs to be added upstream for building ARM/ARM64 packages first
continue
}
for _, packageType := range []string{rpmPackage, debPackage} {
ps = append(ps, tagPackagePipeline(packageType, buildType{os: "linux", arch: arch, fips: fips}))
}
}
}
// Only amd64 Windows is supported for now.
ps = append(ps, tagPipeline(buildType{os: "windows", arch: "amd64"}))
// Also add the two CentOS 6 artifacts.
ps = append(ps, tagPipeline(buildType{os: "linux", arch: "amd64", centos6: true}))
ps = append(ps, tagPipeline(buildType{os: "linux", arch: "amd64", centos6: true, fips: true}))
return ps
}
// tagPipeline generates a tag pipeline for a given combination of os/arch/FIPS
func tagPipeline(b buildType) pipeline {
if b.os == "" {
panic("b.os must be set")
}
if b.arch == "" {
panic("b.arch must be set")
}
pipelineName := fmt.Sprintf("build-%s-%s", b.os, b.arch)
if b.centos6 {
pipelineName += "-centos6"
}
tagEnvironment := map[string]value{
"UID": value{raw: "1000"},
"GID": value{raw: "1000"},
"GOPATH": value{raw: "/go"},
"OS": value{raw: b.os},
"ARCH": value{raw: b.arch},
}
if b.fips {
pipelineName += "-fips"
tagEnvironment["FIPS"] = value{raw: "yes"}
}
p := newKubePipeline(pipelineName)
p.Environment = map[string]value{
"RUNTIME": goRuntime,
}
p.Trigger = triggerTag
p.Workspace = workspace{Path: "/go"}
p.Volumes = dockerVolumes()
p.Services = []service{
dockerService(),
}
p.Steps = []step{
{
Name: "Check out code",
Image: "docker:git",
Environment: map[string]value{
"GITHUB_PRIVATE_KEY": value{fromSecret: "GITHUB_PRIVATE_KEY"},
},
Commands: tagCheckoutCommands(b.fips),
},
{
Name: "Build artifacts",
Image: "docker",
Environment: tagEnvironment,
Volumes: dockerVolumeRefs(),
Commands: tagBuildCommands(b),
},
{
Name: "Copy artifacts",
Image: "docker",
Commands: tagCopyArtifactCommands(b),
},
uploadToS3Step(s3Settings{
region: "us-west-2",
source: "/go/artifacts/*",
target: "teleport/tag/${DRONE_TAG##v}",
stripPrefix: "/go/artifacts/",
}),
}
return p
}
// tagDownloadArtifactCommands generates a set of commands to download appropriate artifacts for creating a package as part of a tag build
func tagDownloadArtifactCommands(b buildType) []string {
commands := []string{
`export VERSION=$(cat /go/.version.txt)`,
`if [[ "${DRONE_TAG}" != "" ]]; then export S3_PATH="tag/$${DRONE_TAG##v}/"; else export S3_PATH="tag/"; fi`,
}
artifactOSS := true
artifactType := fmt.Sprintf("%s-%s", b.os, b.arch)
if b.fips {
artifactType += "-fips"
artifactOSS = false
}
if artifactOSS {
commands = append(commands,
fmt.Sprintf(`aws s3 cp s3://$AWS_S3_BUCKET/teleport/$${S3_PATH}teleport-v$${VERSION}-%s-bin.tar.gz /go/artifacts/`, artifactType),
)
}
commands = append(commands,
fmt.Sprintf(`aws s3 cp s3://$AWS_S3_BUCKET/teleport/$${S3_PATH}teleport-ent-v$${VERSION}-%s-bin.tar.gz /go/artifacts/`, artifactType),
)
return commands
}
// tagCopyPackageArtifactCommands generates a set of commands to find and copy built package artifacts as part of a tag build
func tagCopyPackageArtifactCommands(b buildType, packageType string) []string {
commands := []string{
`cd /go/src/github.com/gravitational/teleport`,
}
if !b.fips {
commands = append(commands, fmt.Sprintf(`find build -maxdepth 1 -iname "teleport*.%s*" -print -exec cp {} /go/artifacts \;`, packageType))
}
commands = append(commands, fmt.Sprintf(`find e/build -maxdepth 1 -iname "teleport*.%s*" -print -exec cp {} /go/artifacts \;`, packageType))
return commands
}
// tagPackagePipeline generates a tag package pipeline for a given combination of os/arch/FIPS
func tagPackagePipeline(packageType string, b buildType) pipeline {
if packageType == "" {
panic("packageType must be set")
}
if b.os == "" {
panic("b.os must be set")
}
if b.arch == "" {
panic("b.arch must be set")
}
environment := map[string]value{
"ARCH": value{raw: b.arch},
"TMPDIR": value{raw: "/go"},
"ENT_TARBALL_PATH": value{raw: "/go/artifacts"},
}
dependentPipeline := fmt.Sprintf("build-%s-%s", b.os, b.arch)
packageBuildCommands := []string{
`apk add --no-cache bash curl gzip make tar`,
`cd /go/src/github.com/gravitational/teleport`,
`export VERSION=$(cat /go/.version.txt)`,
}
makeCommand := fmt.Sprintf("make %s", packageType)
if b.fips {
dependentPipeline += "-fips"
environment["FIPS"] = value{raw: "yes"}
environment["RUNTIME"] = value{raw: "fips"}
makeCommand = fmt.Sprintf("make -C e %s", packageType)
} else {
environment["OSS_TARBALL_PATH"] = value{raw: "/go/artifacts"}
}
packageDockerVolumes := dockerVolumes()
packageDockerVolumeRefs := dockerVolumeRefs()
packageDockerService := dockerService()
switch packageType {
case rpmPackage:
environment["GNUPG_DIR"] = value{raw: "/tmpfs/gnupg"}
environment["GPG_RPM_SIGNING_ARCHIVE"] = value{fromSecret: "GPG_RPM_SIGNING_ARCHIVE"}
packageBuildCommands = append(packageBuildCommands,
`mkdir -m0700 $GNUPG_DIR`,
`echo "$GPG_RPM_SIGNING_ARCHIVE" | base64 -d | tar -xzf - -C $GNUPG_DIR`,
`chown -R root:root $GNUPG_DIR`,
makeCommand,
`rm -rf $GNUPG_DIR`,
)
// RPM builds require tmpfs to hold the key material in memory.
packageDockerVolumes = dockerVolumes(volumeTmpfs)
packageDockerVolumeRefs = dockerVolumeRefs(volumeRefTmpfs)
packageDockerService = dockerService(volumeRefTmpfs)
case debPackage:
packageBuildCommands = append(packageBuildCommands,
makeCommand,
)
default:
panic("packageType is not set")
}
pipelineName := fmt.Sprintf("%s-%s", dependentPipeline, packageType)
p := newKubePipeline(pipelineName)
p.Trigger = triggerTag
p.DependsOn = []string{dependentPipeline}
p.Workspace = workspace{Path: "/go"}
p.Volumes = packageDockerVolumes
p.Services = []service{
packageDockerService,
}
p.Steps = []step{
{
Name: "Check out code",
Image: "docker:git",
Environment: map[string]value{
"GITHUB_PRIVATE_KEY": value{fromSecret: "GITHUB_PRIVATE_KEY"},
},
Commands: tagCheckoutCommands(b.fips),
},
{
Name: "Download artifacts from S3",
Image: "amazon/aws-cli",
Environment: map[string]value{
"AWS_REGION": value{raw: "us-west-2"},
"AWS_S3_BUCKET": value{fromSecret: "AWS_S3_BUCKET"},
"AWS_ACCESS_KEY_ID": value{fromSecret: "AWS_ACCESS_KEY_ID"},
"AWS_SECRET_ACCESS_KEY": value{fromSecret: "AWS_SECRET_ACCESS_KEY"},
},
Commands: tagDownloadArtifactCommands(b),
},
{
Name: "Build artifacts",
Image: "docker",
Environment: environment,
Volumes: packageDockerVolumeRefs,
Commands: packageBuildCommands,
},
{
Name: "Copy artifacts",
Image: "docker",
Commands: tagCopyPackageArtifactCommands(b, packageType),
},
uploadToS3Step(s3Settings{
region: "us-west-2",
source: "/go/artifacts/*",
target: "teleport/tag/${DRONE_TAG##v}",
stripPrefix: "/go/artifacts/",
}),
}
return p
}

248
dronegen/tests.go Normal file
View file

@ -0,0 +1,248 @@
package main
func testPipelines() []pipeline {
return []pipeline{
testCodePipeline(),
testDocsPipeline(),
}
}
// testCheckoutCommands returns a set of commands for checking out Teleport's code
// Setting enterprise to true will also add a check against the Github API to determine whether
// the pull request comes from a code fork (and will only check out Enterprise code if it does not)
func testCheckoutCommands(enterprise bool) []string {
commands := []string{
`mkdir -p /tmpfs/go/src/github.com/gravitational/teleport /tmpfs/go/cache`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`git init && git remote add origin ${DRONE_REMOTE_URL}`,
`# handle pull requests
if [ "${DRONE_BUILD_EVENT}" = "pull_request" ]; then
git fetch origin +refs/heads/${DRONE_COMMIT_BRANCH}:
git checkout ${DRONE_COMMIT_BRANCH}
git fetch origin ${DRONE_COMMIT_REF}:
git merge ${DRONE_COMMIT}
# handle tags
elif [ "${DRONE_BUILD_EVENT}" = "tag" ]; then
git fetch origin +refs/tags/${DRONE_TAG}:
git checkout -qf FETCH_HEAD
# handle pushes/other events
else
if [ "${DRONE_COMMIT_BRANCH}" = "" ]; then
git fetch origin
git checkout -qf ${DRONE_COMMIT_SHA}
else
git fetch origin +refs/heads/${DRONE_COMMIT_BRANCH}:
git checkout ${DRONE_COMMIT} -b ${DRONE_COMMIT_BRANCH}
fi
fi
`,
}
if enterprise {
commands = append(commands,
// this is allowed to fail because pre-4.3 Teleport versions
// don't use the webassets submodule.
`git submodule update --init webassets || true`,
// use the Github API to check whether this PR comes from a forked repo or not.
// if it does, don't check out the Enterprise code.
`if [ "${DRONE_BUILD_EVENT}" = "pull_request" ]; then
apk add --no-cache curl jq
export PR_REPO=$(curl -Ls https://api.github.com/repos/gravitational/${DRONE_REPO_NAME}/pulls/${DRONE_PULL_REQUEST} | jq -r '.head.repo.full_name')
echo "---> Source repo for PR ${DRONE_PULL_REQUEST}: $${PR_REPO}"
# if the source repo for the PR matches DRONE_REPO, then this is not a PR raised from a fork
if [ "$${PR_REPO}" = "${DRONE_REPO}" ] || [ "${DRONE_REPO}" = "gravitational/teleport-private" ]; then
mkdir -m 0700 /root/.ssh && echo -n "$GITHUB_PRIVATE_KEY" > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa
ssh-keyscan -H github.com > /root/.ssh/known_hosts 2>/dev/null && chmod 600 /root/.ssh/known_hosts
git submodule update --init e
# 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
rm -f /root/.ssh/id_rsa
fi
fi
`,
)
}
return commands
}
// testCodePipeline returns a pipeline which runs the linter plus unit and integration tests
func testCodePipeline() pipeline {
p := newKubePipeline("test")
p.Environment = map[string]value{
"RUNTIME": goRuntime,
"UID": value{raw: "1000"},
"GID": value{raw: "1000"},
}
p.Trigger = triggerPullRequest
p.Workspace = workspace{Path: "/go"}
p.Volumes = dockerVolumes(volumeTmpfs, volumeTmpDind, volumeTmpIntegration)
p.Services = []service{
dockerService(volumeRefTmpfs, volumeRefTmpDind),
}
goEnvironment := map[string]value{
"GOCACHE": value{raw: "/tmpfs/go/cache"},
"GOPATH": value{raw: "/tmpfs/go"},
}
p.Steps = []step{
{
Name: "Check out code",
Image: "docker:git",
Environment: map[string]value{
"GITHUB_PRIVATE_KEY": value{fromSecret: "GITHUB_PRIVATE_KEY"},
},
Volumes: []volumeRef{
volumeRefTmpfs,
},
Commands: testCheckoutCommands(true),
},
{
Name: "Build buildbox",
Image: "docker",
Volumes: dockerVolumeRefs(volumeRefTmpfs),
Commands: []string{
`apk add --no-cache make`,
`chown -R $UID:$GID /tmpfs/go`,
`docker pull quay.io/gravitational/teleport-buildbox:$RUNTIME || true`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`make -C build.assets buildbox`,
},
},
{
Name: "Run linter",
Image: "docker",
Environment: goEnvironment,
Volumes: dockerVolumeRefs(volumeRefTmpfs),
Commands: []string{
`apk add --no-cache make`,
`chown -R $UID:$GID /tmpfs/go`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`make -C build.assets lint`,
},
},
{
// https://discourse.drone.io/t/how-to-exit-a-pipeline-early-without-failing/3951
// this step looks at the output of git diff --raw to determine
// whether any files which don't match the pattern '^docs/',
// '.mdx$' or '.md$' were changed. if there are no changes to
// non-docs code, we skip the Teleport tests and exit early with a
// special Drone exit code to speed up iteration on docs (as milv
// is much quicker to run)
Name: "Optionally skip tests",
Image: "docker:git",
Volumes: []volumeRef{
volumeRefTmpfs,
},
Commands: []string{
`cd /tmpfs/go/src/github.com/gravitational/teleport
echo -e "\n---> git diff --raw ${DRONE_COMMIT}..origin/${DRONE_COMMIT_BRANCH:-master}\n"
git diff --raw ${DRONE_COMMIT}..origin/${DRONE_COMMIT_BRANCH:-master}
git diff --raw ${DRONE_COMMIT}..origin/${DRONE_COMMIT_BRANCH:-master} | awk '{print $6}' | grep -Ev '^docs/' | grep -Ev '.mdx$' | grep -Ev '.md$' | grep -v ^$ | wc -l > /tmp/.change_count.txt
export CHANGE_COUNT=$(cat /tmp/.change_count.txt | tr -d '\n')
echo -e "\n---> Non-docs changes detected: $$CHANGE_COUNT"
if [ $$CHANGE_COUNT -gt 0 ]; then
echo "---> Teleport tests will run normally"
else
echo "---> Skipping Teleport tests and exiting early"
exit 78
fi
echo ""
`,
},
},
{
Name: "Run unit and chaos tests",
Image: "docker",
Environment: goEnvironment,
Volumes: dockerVolumeRefs(volumeRefTmpfs),
Commands: []string{
`apk add --no-cache make`,
`chown -R $UID:$GID /tmpfs/go`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`make -C build.assets test`,
},
},
{
// some integration tests can only be run as root, so we handle
// these separately using a build tag
Name: "Run root-only integration tests",
Image: "docker",
Environment: goEnvironment,
Volumes: dockerVolumeRefs(volumeRefTmpfs, volumeRefTmpIntegration),
Commands: []string{
`apk add --no-cache make`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`make -C build.assets integration-root`,
},
},
{
Name: "Run integration tests",
Image: "docker",
Environment: map[string]value{
"GOCACHE": value{raw: "/tmpfs/go/cache"},
"GOPATH": value{raw: "/tmpfs/go"},
"INTEGRATION_CI_KUBECONFIG": value{fromSecret: "INTEGRATION_CI_KUBECONFIG"},
"KUBECONFIG": value{raw: "/tmpfs/go/kubeconfig.ci"},
"TEST_KUBE": value{raw: "true"},
},
Volumes: dockerVolumeRefs(volumeRefTmpfs, volumeRefTmpIntegration),
Commands: []string{
`apk add --no-cache make`,
// write kubeconfig to disk for use in kube integration tests
`echo "$INTEGRATION_CI_KUBECONFIG" > "$KUBECONFIG"`,
`chown -R $UID:$GID /tmpfs/go`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`make -C build.assets integration`,
`rm -f "$KUBECONFIG"`,
},
},
}
return p
}
func testDocsPipeline() pipeline {
p := newKubePipeline("test-docs")
p.Trigger = triggerPullRequest
p.Workspace = workspace{Path: "/go"}
p.Volumes = dockerVolumes(volumeTmpfs)
p.Services = []service{
dockerService(volumeRefTmpfs),
}
p.Steps = []step{
{
Name: "Check out code",
Image: "docker:git",
Volumes: []volumeRef{
volumeRefTmpfs,
},
Commands: testCheckoutCommands(false),
},
{
Name: "Run docs tests",
Image: "docker:git",
Environment: map[string]value{
"GOCACHE": value{raw: "/tmpfs/go/cache"},
"UID": value{raw: "1000"},
"GID": value{raw: "1000"},
},
Volumes: dockerVolumeRefs(volumeRefTmpfs),
Commands: []string{
`apk add --no-cache make`,
`cd /tmpfs/go/src/github.com/gravitational/teleport`,
`chown -R $UID:$GID /tmpfs/go`,
`git diff --raw ${DRONE_COMMIT}..origin/${DRONE_COMMIT_BRANCH:-master} | awk '{print $6}' | grep -E '^docs' | { grep -v ^$ || true; } > /tmp/docs-changes.txt`,
`if [ $(cat /tmp/docs-changes.txt | wc -l) -gt 0 ]; then
echo "---> Changes to docs detected"
cat /tmp/docs-changes.txt
echo "---> Checking for trailing whitespace"
make docs-test-whitespace
echo "---> Checking for dead links"
make -C build.assets test-docs
else
echo "---> No changes to docs detected, not running tests"
fi
`,
},
},
}
return p
}

197
dronegen/types.go Normal file
View file

@ -0,0 +1,197 @@
package main
import (
"errors"
"fmt"
"runtime"
"strings"
)
// Types to mirror the YAML fields of the drone config.
// See https://docs.drone.io/pipeline/kubernetes/syntax/ and https://docs.drone.io/pipeline/exec/syntax/.
type pipeline struct {
comment string
Kind string `yaml:"kind"`
Type string `yaml:"type"`
Name string `yaml:"name"`
Environment map[string]value `yaml:"environment,omitempty"`
Trigger trigger `yaml:"trigger"`
Workspace workspace `yaml:"workspace,omitempty"`
Platform platform `yaml:"platform,omitempty"`
Clone clone `yaml:"clone,omitempty"`
DependsOn []string `yaml:"depends_on,omitempty"`
Concurrency concurrency `yaml:"concurrency,omitempty"`
Steps []step `yaml:"steps"`
Services []service `yaml:"services,omitempty"`
Volumes []volume `yaml:"volumes,omitempty"`
}
func newKubePipeline(name string) pipeline {
return pipeline{
comment: generatedComment(),
Kind: "pipeline",
Type: "kubernetes",
Name: name,
Clone: clone{Disable: true},
}
}
//nolint:deadcode,unused
func newExecPipeline(name string) pipeline {
return pipeline{
comment: generatedComment(),
Kind: "pipeline",
Type: "exec",
Name: name,
Clone: clone{Disable: true},
}
}
func generatedComment() string {
c := `################################################
# Generated using dronegen, do not edit by hand!
# Use 'make dronegen' to update.
`
_, file, line, ok := runtime.Caller(2)
if ok {
// Trim off the local path to the repo.
i := strings.LastIndex(file, "dronegen")
if i > 0 {
file = file[i:]
}
c += fmt.Sprintf("# Generated at %s:%d\n", file, line)
}
c += "################################################\n\n"
return c
}
type trigger struct {
Event triggerRef `yaml:"event,omitempty"`
Cron triggerRef `yaml:"cron,omitempty"`
Target triggerRef `yaml:"target,omitempty"`
Ref triggerRef `yaml:"ref,omitempty"`
Repo triggerRef `yaml:"repo"`
Branch triggerRef `yaml:"branch,omitempty"`
}
type triggerRef struct {
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
}
// UnmarshalYAML parses trigger references as either a list of strings, or as
// include/exclude lists. Both are allowed by drone.
func (v *triggerRef) UnmarshalYAML(unmarshal func(interface{}) error) error {
var rawItems []string
if err := unmarshal(&rawItems); err == nil {
v.Include = rawItems
return nil
}
var regular struct {
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
}
if err := unmarshal(&regular); err == nil {
v.Include = regular.Include
v.Exclude = regular.Exclude
return nil
}
return errors.New("can't unmarshal the value as either string or from_secret reference")
}
type workspace struct {
Path string `yaml:"path"`
}
type clone struct {
Disable bool `yaml:"disable"`
}
type platform struct {
OS string `yaml:"os"`
Arch string `yaml:"arch"`
}
type concurrency struct {
Limit int `yaml:"limit"`
}
type volume struct {
Name string `yaml:"name"`
Temp *volumeTemp `yaml:"temp,omitempty"`
Claim *volumeClaim `yaml:"claim,omitempty"`
}
type volumeTemp struct {
Medium string `yaml:"medium,omitempty"`
}
type volumeClaim struct {
Name string `yaml:"name"`
}
type service struct {
Name string `yaml:"name"`
Image string `yaml:"image"`
Volumes []volumeRef `yaml:"volumes"`
}
type volumeRef struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
}
type step struct {
Name string `yaml:"name"`
Image string `yaml:"image"`
Commands []string `yaml:"commands"`
Environment map[string]value `yaml:"environment,omitempty"`
Volumes []volumeRef `yaml:"volumes,omitempty"`
Settings map[string]value `yaml:"settings,omitempty"`
Template []string `yaml:"template,omitempty"`
When *condition `yaml:"when,omitempty"`
Failure string `yaml:"failure,omitempty"`
}
type condition struct {
Status []string `yaml:"status,omitempty"`
}
// value is a string value for key:value pairs like "environment" or
// "settings". Values can be either inline strings (raw) or references to
// secrets stored in Drone (fromSecret).
type value struct {
raw string
fromSecret string
}
type valueFromSecret struct {
FromSecret string `yaml:"from_secret"`
}
func (v value) MarshalYAML() (interface{}, error) {
if v.raw != "" && v.fromSecret != "" {
return nil, fmt.Errorf("value %+v has both raw and fromSecret set, can only have one", v)
}
if v.raw != "" {
return v.raw, nil
}
if v.fromSecret != "" {
return valueFromSecret{FromSecret: v.fromSecret}, nil
}
return nil, fmt.Errorf("value has neither raw nor fromSecret set, need one")
}
func (v *value) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&v.raw); err == nil {
return nil
}
var fs valueFromSecret
if err := unmarshal(&fs); err == nil {
v.fromSecret = fs.FromSecret
return nil
}
return errors.New("can't unmarshal the value as either string or from_secret reference")
}

2
e

@ -1 +1 @@
Subproject commit 3ec91b6ad87acdab25d262097edd12302c7c8b53
Subproject commit 6fe0a12b02ca332c4c76724f0c15e47c6e41af1f