Skip to main content

Real-World Example

The following Go code is a real-world Build Controller, and demonstrates many of the dynamic features of the BuildBeaver Go SDK.

package main

import (
"fmt"

"github.com/buildbeaver/go-sdk/bb"
)

var useNodeJSDockerImage = bb.NewDocker().
Image("node:16.16.0-buster").
Pull(bb.DockerPullIfNotExists).
Shell("/bin/bash")

var goJobFingerprint = []string{
`find build/scripts -type f | sort | xargs sha1sum`,
`find backend/ -name '*.go' -not -path "*/vendor/*" -type f | sort | xargs sha1sum`,
`sha1sum backend/go.mod`,
`sha1sum backend/go.sum`,
}

var frontendJobFingerprint = []string{
`find build/scripts -type f | sort | xargs sha1sum`,
`find frontend/ -not -path "*/node_modules/*" -not -path "frontend/public/*" -type f | sort | xargs sha1sum`,
}

func main() {
bb.Workflows(
bb.NewWorkflow().Name("base").Handler(submitBaseJobs),
bb.NewWorkflow().Name("generate").Handler(submitGenerateJobs),
bb.NewWorkflow().Name("unit-test").Handler(submitUnitTestJobs),
bb.NewWorkflow().Name("integration-test").Handler(submitIntegrationTestJobs),
bb.NewWorkflow().Name("build").Handler(submitBuildJobs),
bb.NewWorkflow().Name("frontend").Handler(submitFrontEndJobs),
bb.NewWorkflow().Name("openapi").Handler(submitOpenAPIJobs),
)
}

func submitBaseJobs(w *bb.Workflow) error {
w.Job(bb.NewJob().
Name("base-images").
Desc("Builds the base image needed for the build pipeline").
Docker(bb.NewDocker().
Image("docker:20.10").
Pull("if-not-exists")).
Fingerprint("sha1sum build/docker/go-builder/Dockerfile").
Env(bb.NewEnv().
Name("AWS_ACCESS_KEY_ID").
ValueFromSecret("AWS_ACCESS_KEY_ID")).
Env(bb.NewEnv().
Name("AWS_SECRET_ACCESS_KEY").
ValueFromSecret("AWS_SECRET_ACCESS_KEY")).
Step(bb.NewStep().
Name("go-builder").
Commands(
"apk add bash git aws-cli",
"git config --global --add safe.directory $(pwd)",
"./build/scripts/build-docker.sh -t $BB_JOB_FINGERPRINT -p go-builder")).
OnSuccess(func(event *bb.JobStatusChangedEvent) {
// Calculate the docker image name from this job's fingerprint
jGraph := w.GetBuild().MustGetJobGraph(event.JobID)
goDockerConfig := bb.NewDocker().
Image(fmt.Sprintf("us-west-2.amazonaws.com/go-builder:%s", *jGraph.Job.Fingerprint)).
Pull(bb.DockerPullIfNotExists).
Shell("/bin/bash").
AWSAuth(bb.NewAWSAuth().
Region("us-west-2").
AccessKeyIDFromSecret("AWS_ACCESS_KEY_ID").
SecretAccessKeyFromSecret("AWS_SECRET_ACCESS_KEY"))
w.SetOutput("go-docker-config", goDockerConfig)
}))
w.MustSubmit()

goDockerConfig := w.MustWaitForOutput("base", "go-docker-config").(*bb.DockerConfig)

w.Job(bb.NewJob().
Name("backend-preflight").
Desc("Performs preflight checks on all backend code").
Depends("base.base-images").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Step(bb.NewStep().
Name("lint").
Commands("./build/scripts/ci/backend-preflight-lint.sh")))

return nil
}

func submitGenerateJobs(w *bb.Workflow) error {
goDockerConfig := w.MustWaitForOutput("base", "go-docker-config").(*bb.DockerConfig)

w.Job(bb.NewJob().
Name("backend-generate").
Desc("Generates all backend code (wire files, protobufs etc.)").
Depends("base.backend-preflight").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Step(bb.NewStep().
Name("wire").
Commands("./build/scripts/ci/backend-generate-wire.sh")).
Artifact(bb.NewArtifact().
Name("wire").
Paths("backend/*/app/wire_gen.go", "backend/*/app/*/wire_gen.go")).
Artifact(bb.NewArtifact().
Name("grpc").
Paths("backend/api/grpc/*.pb.go")))

return nil
}

func submitUnitTestJobs(w *bb.Workflow) error {
goDockerConfig := w.MustWaitForOutput("base", "go-docker-config").(*bb.DockerConfig)

w.Job(bb.NewJob().
Name("backend-sqlite").
Desc("Runs all backend unit tests on top of SQLite").
Depends("generate.backend-generate.artifacts").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Env(bb.NewEnv().
Name("TEST_DB_DRIVER").
Value("sqlite3")).
Step(bb.NewStep().
Name("test").
Commands(
". build/scripts/lib/go-env.sh",
"cd backend && go test -v -count=1 -mod=vendor -short ./...")))

w.Job(bb.NewJob().
Name("backend-postgres").
Desc("Runs all backend unit tests on top of Postgres").
Depends("generate.backend-generate.artifacts").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Env(bb.NewEnv().
Name("TEST_DB_DRIVER").
Value("postgres")).
Env(bb.NewEnv().
Name("TEST_CONNECTION_STRING").
Value("postgres://username:use-secrets-dont-put-password-here@postgres:5432/?sslmode=disable")).
Step(bb.NewStep().
Name("wait-for-postgres").
Commands(
`for i in {1..15}; do pg_isready -d "${TEST_CONNECTION_STRING}" && break || sleep 1; done`)).
Step(bb.NewStep().
Name("test").
Commands(
". build/scripts/lib/go-env.sh",
"cd backend && go test -v -count=1 -mod=vendor -short ./...")).
Service(bb.NewService().
Name("postgres").
Image("postgres:14").
Env(bb.NewEnv().
Name("POSTGRES_USER").
Value("user-name")).
Env(bb.NewEnv().
Name("POSTGRES_PASSWORD").
Value("use-secrets-dont-put-password-here"))))
return nil
}

func submitIntegrationTestJobs(w *bb.Workflow) error {
goDockerConfig := w.MustWaitForOutput("base", "go-docker-config").(*bb.DockerConfig)

w.Job(bb.NewJob().
Name("backend-sqlite").
Desc("Runs all backend integration tests on top of SQLite").
Depends("generate.backend-generate.artifacts").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Env(bb.NewEnv().
Name("TEST_DB_DRIVER").
Value("sqlite3")).
Step(bb.NewStep().
Name("test").
Commands(
". build/scripts/lib/go-env.sh",
"cd backend && go test -v -count=1 -mod=vendor -run Integration ./...")))

w.Job(bb.NewJob().
Name("backend-postgres").
Desc("Runs all backend integration tests on top of Postgres").
Depends("generate.backend-generate.artifacts").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Env(bb.NewEnv().
Name("TEST_DB_DRIVER").
Value("postgres")).
Env(bb.NewEnv().
Name("TEST_CONNECTION_STRING").
Value("postgres://username:use-secrets-dont-put-password-here@postgres:5432/?sslmode=disable")).
Step(bb.NewStep().
Name("wait-for-postgres").
Commands(
`for i in {1..15}; do pg_isready -d "${TEST_CONNECTION_STRING}" && break || sleep 1; done`)).
Step(bb.NewStep().
Name("test").
Commands(
". build/scripts/lib/go-env.sh",
"cd backend && go test -v -count=1 -mod=vendor -run Integration ./...")).
Service(bb.NewService().
Name("postgres").
Image("postgres:14").
Env(bb.NewEnv().
Name("POSTGRES_USER").
Value("user-name")).
Env(bb.NewEnv().
Name("POSTGRES_PASSWORD").
Value("use-secrets-dont-put-password-here"))))
return nil
}

func submitBuildJobs(w *bb.Workflow) error {
goDockerConfig := w.MustWaitForOutput("base", "go-docker-config").(*bb.DockerConfig)

w.Job(bb.NewJob().
Name("backend-build").
Desc("Builds all backend binaries").
Depends(
"generate.backend-generate.artifacts",
"unit-test.backend-sqlite",
"unit-test.backend-postgres",
"integration-test.backend-sqlite",
"integration-test.backend-postgres").
Docker(goDockerConfig).
Fingerprint(goJobFingerprint...).
Step(bb.NewStep().
Name("go").
Commands("./build/scripts/ci/backend-build.sh")).
Artifact(bb.NewArtifact().
Name("go-binaries").
Paths("build/output/go/bin/*")))
return nil
}

func submitFrontEndJobs(w *bb.Workflow) error {
w.Job(bb.NewJob().
Name("preflight").
Desc("Performs preflight checks on all frontend code").
Docker(useNodeJSDockerImage).
Fingerprint(frontendJobFingerprint...).
Step(bb.NewStep().
Name("lint").
Commands(
"npm install --location=global \"prettier@2.8.4\"",
"./build/scripts/ci/frontend-preflight-lint.sh")))

w.Job(bb.NewJob().
Name("unit-test").
Desc("Runs all frontend unit tests").
Depends("frontend.preflight").
Docker(useNodeJSDockerImage).
Fingerprint(frontendJobFingerprint...).
Step(bb.NewStep().
Name("install").
Commands(
". build/scripts/lib/node-env.sh",
"cd frontend && yarn install")).
Step(bb.NewStep().
Name("test").
Commands(
". build/scripts/lib/node-env.sh",
"cd frontend && yarn test")))

w.Job(bb.NewJob().
Name("build").
Desc("Builds all frontend code").
Depends("frontend.unit-test").
Docker(useNodeJSDockerImage).
Fingerprint(frontendJobFingerprint...).
Step(bb.NewStep().
Name("install").
Commands(
". build/scripts/lib/node-env.sh",
"cd frontend && yarn install")).
Step(bb.NewStep().
Name("build").
Commands(
". build/scripts/lib/node-env.sh",
"cd frontend && yarn build")))
return nil
}

func submitOpenAPIJobs(w *bb.Workflow) error {
w.Job(bb.NewJob().
Name("generate-code").
Desc("Generates API Clients from our OpenAPI specs").
Depends("base.backend-preflight").
Docker(bb.NewDocker().
Image("openapitools/openapi-generator-cli:v6.5.0").
Pull(bb.DockerPullIfNotExists).
Shell("/bin/bash")).
Fingerprint(
"sha1sum backend/server/api/rest/openapi/dynamic-openapi.yaml",
"sha1sum backend/server/api/rest/openapi/core-openapi.yaml").
Step(bb.NewStep().
Name("core-go").
Commands("./build/scripts/ci/backend-openapi.sh core-openapi.yaml go sdk/core/go/client")).
Step(bb.NewStep().
Name("core-python").
Commands("./build/scripts/ci/backend-openapi.sh core-openapi.yaml python sdk/core/python/client")).
Step(bb.NewStep().
Name("dynamic-go").
Commands("bb_DEBUG=1 ./build/scripts/ci/backend-openapi.sh dynamic-openapi.yaml go sdk/dynamic/go/client")))
return nil
}