Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Adding ci/cd pipeline using dagger #37

Merged
merged 21 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
73 changes: 52 additions & 21 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,64 @@
name: Build

on:
# Build only triggers once lint and test workflow successfully completes
workflow_run:
workflows: [Lint]
types:
- completed

jobs:
dagger-build:
dagger-build-satellite:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
# unless you specify the success on the event,
# this job will also run if the lint workflow was skipped(which is also the case if the condition for the file extension type is false).
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Call Dagger Function to build and publish on Github
uses: dagger/dagger-for-github@v5
with:
version: latest
verb: call
module: github.com/container-registry/harbor-satellite
args: start --name=satellite --source=. --release=./ci --GITHUB-TOKEN=$GITHUB_TOKEN --version=$GITHUB_SHA --REPO_OWNER=$GITHUB_USERNAME --REPO_NAME=harbor-satellite --RELEASE_NAME=satellite
### Uncomment the below line if you wish to see the traces of the workflow on dagger cloud
### register to dagger cloud and get the token
#### https://dagger.cloud/
# cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_USERNAME: ${{ github.repository_owner }}
GITHUB_SHA: ${{ github.sha }}

build-ground-control:
runs-on: ubuntu-latest
# unless you specify the success on the event,
permissions:
contents: write
packages: write
# unless you specify the success on the event,
# this job will also run if the lint workflow was skipped(which is also the case if the condition for the file extension type is false).
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
-
name: Checkout repository
uses: actions/checkout@v4
-
name: Setup go
uses: actions/setup-go@v4
with:
go-version: '>=1.22'
-
name: Install
run: go get dagger.io/dagger@latest
-
name: Install Dagger CLI
run: cd /usr/local && { curl -L https://dl.dagger.io/dagger/install.sh | sh; cd -; }
-
name: Build with Dagger
run: dagger run go run ci/main.go

- name: Checkout repository
uses: actions/checkout@v4

- name: Call Dagger Function to build and publish on Github
uses: dagger/dagger-for-github@v5
with:
version: latest
verb: call
module: github.com/container-registry/harbor-satellite
args: start --name=ground-control --source=./ground-control --release=./ci --GITHUB-TOKEN=$GITHUB_TOKEN --version=$GITHUB_SHA --REPO_OWNER=$GITHUB_USERNAME --REPO_NAME=harbor-satellite --RELEASE_NAME=ground-control
### Uncomment the below line if you wish to see the traces of the workflow on dagger cloud
### register to dagger cloud and get the token
#### https://dagger.cloud/
# cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_USERNAME: ${{ github.repository_owner }}
GITHUB_SHA: ${{ github.sha }}
4 changes: 4 additions & 0 deletions ci/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/dagger.gen.go linguist-generated
/internal/dagger/** linguist-generated
/internal/querybuilder/** linguist-generated
/internal/telemetry/** linguist-generated
4 changes: 4 additions & 0 deletions ci/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/dagger.gen.go
/internal/dagger
/internal/querybuilder
/internal/telemetry
51 changes: 51 additions & 0 deletions ci/ground_control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"context"
"fmt"
"log/slog"

"container-registry.com/harbor-satellite/ci/internal/dagger"
)

func (m *HarborSatellite) StartGroundControlCI(ctx context.Context, source *dagger.Directory, release *dagger.Directory, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME, name string) error {
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
// Build Ground Control
slog.Info("Building Ground Control")
outputDir := m.Build(ctx, source, name)

release_output, err := m.Release(ctx, outputDir, release, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME, name)
if err != nil {
slog.Error("Failed to release Ground Control")
slog.Error(err.Error())
slog.Error((fmt.Sprintf("Release Directory: %s", release_output)))
return err
}
return nil
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
}

func (m *HarborSatellite) ExecuteTestsForGroundControl(ctx context.Context, source *dagger.Directory) (string, error) {
goContainer := dag.Container().
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
From(DEFAULT_GO)

containerWithDocker, err := m.Attach(ctx, goContainer, "24.0")
if err != nil {
return "", err
}
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
dockerHost, err := containerWithDocker.EnvVariable(ctx, "DOCKER_HOST")
if err != nil {
return "", err
}
fmt.Printf("Docker Host: %s\n", dockerHost)

goContainer = containerWithDocker.
WithMountedDirectory("/app", source).
WithWorkdir("/app").
WithExec([]string{"go", "test", "./..."})

output, err := goContainer.Stdout(ctx)
if err != nil {
return output, err
}
fmt.Print(output)
return output, nil
}
123 changes: 107 additions & 16 deletions ci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,122 @@ package main

import (
"context"
"fmt"
"log/slog"
"os"

"dagger.io/dagger"
"container-registry.com/harbor-satellite/ci/internal/dagger"
)

func main() {
ctx := context.Background()
const (
DEFAULT_GO = "golang:1.22"
PROJ_MOUNT = "/app"
OUT_DIR = "/binaries"
)

// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
type HarborSatellite struct{}

// Returns a container that echoes whatever string argument is provided
func (m *HarborSatellite) ContainerEcho(stringArg string) *dagger.Container {
return dag.Container().From("alpine:latest").WithExec([]string{"echo", stringArg})
}
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved

// Returns lines that match a pattern in the files of the provided Directory
func (m *HarborSatellite) Grepdir(ctx context.Context, directoryArg *dagger.Directory, pattern string) (string, error) {
return dag.Container().
From("alpine:latest").
WithMountedDirectory("/mnt", directoryArg).
WithWorkdir("/mnt").
WithExec([]string{"grep", "-R", pattern, "."}).
Stdout(ctx)
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
}

func (m *HarborSatellite) Start(ctx context.Context, name string, source *dagger.Directory, release *dagger.Directory, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME string) {

if name == "" {
slog.Error("Please provide the app name (satellite or ground-control) as an argument")
os.Exit(1)
}
defer client.Close()

// use a golang:1.19 container
// get version
// execute
golang := client.Container().From("golang:1.21").WithExec([]string{"go", "version"})
switch name {
case "satellite":
slog.Info("Starting satellite CI")
err := m.StartSatelliteCi(ctx, source, release, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME, name)
if err != nil {
slog.Error("Failed to start satellite CI")
os.Exit(1)
}
case "ground-control":
slog.Info("Starting ground-control CI")
err := m.StartGroundControlCI(ctx, source, release, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME, name)
if err != nil {
slog.Error("Failed to complete ground-control CI")
os.Exit(1)
}
default:
slog.Error("Invalid app name. Please provide either 'satellite' or 'ground-control'")
os.Exit(1)
}
}
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved

func (m *HarborSatellite) Build(ctx context.Context, source *dagger.Directory, name string) *dagger.Directory {
return m.build(source, name)
}
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved

func (m *HarborSatellite) Release(ctx context.Context, directory *dagger.Directory, release *dagger.Directory, GITHUB_TOKEN, VERSION, REPO_OWNER, REPO_NAME, RELEASE_NAME, name string) (string, error) {

version, err := golang.Stdout(ctx)
releaseContainer := dag.Container().
From("alpine:latest").
WithDirectory(".", directory).
WithDirectory(".", release).
WithExec([]string{"apk", "add", "--no-cache", "bash", "curl"}).
WithEnvVariable("GITHUB_API_TOKEN", GITHUB_TOKEN).
WithEnvVariable("VERSION", fmt.Sprintf("%s-%s", name, VERSION)).
WithEnvVariable("REPO_OWNER", REPO_OWNER).
WithEnvVariable("REPO_NAME", REPO_NAME).
WithEnvVariable("RELEASE_NAME", RELEASE_NAME).
WithEnvVariable("OUT_DIR", OUT_DIR).
WithExec([]string{"chmod", "+x", "release.sh"}).
WithExec([]string{"ls", "-lR", "."}).
WithExec([]string{"bash", "-c", "./release.sh"})
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
output, err := releaseContainer.Stdout(ctx)
if err != nil {
panic(err)
return output, err
}
// print output
slog.Info("Hello from Dagger!", "version", version)

// Return the updated release directory
return output, nil
}

func (m *HarborSatellite) build(source *dagger.Directory, name string) *dagger.Directory {
fmt.Print("Building Satellite\n")
gooses := []string{"linux", "darwin"}
goarches := []string{"amd64", "arm64"}
binaryName := name // base name for the binary

// create empty directory to put build artifacts
outputs := dag.Directory()

golang := dag.Container().
From(DEFAULT_GO).
WithDirectory(PROJ_MOUNT, source).
WithWorkdir(PROJ_MOUNT)
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
for _, goos := range gooses {
for _, goarch := range goarches {
// create the full binary name with OS and architecture
outputBinary := fmt.Sprintf("%s/%s-%s-%s", OUT_DIR, binaryName, goos, goarch)

// build artifact with specified binary name
build := golang.
WithEnvVariable("GOOS", goos).
WithEnvVariable("GOARCH", goarch).
WithExec([]string{"go", "build", "-o", outputBinary})

// add build to outputs
outputs = outputs.WithDirectory(OUT_DIR, build.Directory(OUT_DIR))
}
}

// return build directory
return outputs
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
}
91 changes: 91 additions & 0 deletions ci/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash

function check_if_success() {
if [ $? -ne 0 ]; then
echo "Error running last command"
exit $?
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
fi
}

function print_help() {
echo "Usage:
./release.sh

Make sure the following environment variables are set:
GITHUB_API_TOKEN - Your GitHub API token
VERSION - The tag name for the release (e.g., v1.0.0)
REPO_OWNER - The GitHub username or organization name
REPO_NAME - The repository name
RELEASE_NAME - The name of the release (e.g., \"Release v1.0.0\")
OUT_DIR - Directory containing the pre-built binaries
PRERELEASE - Set to 'true' for a prerelease, otherwise leave empty or set to 'false'

Example:
export GITHUB_API_TOKEN=your_token
export VERSION=v1.0.0
export REPO_OWNER=your_username
export REPO_NAME=your_repo
export RELEASE_NAME=\"Release v1.0.0\"
export OUT_DIR=./bin
export PRERELEASE=false
./release.sh"
}

if [ "$1" == "--help" ] || [ "-h" == "$1" ]; then
print_help
exit 0
fi

# Check for required environment variables
if [ -z "$GITHUB_API_TOKEN" ] || [ -z "$VERSION" ] || [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ] || [ -z "$RELEASE_NAME" ] || [ -z "$OUT_DIR" ]; then
echo "Missing one or more required environment variables."
print_help
exit 1
fi

echo "Tag Name: $VERSION"
echo "Release Name: $RELEASE_NAME"
echo "Prerelease: $PRERELEASE"
echo "Repo Owner: $REPO_OWNER"
echo "Repo Name: $REPO_NAME"

# Ensure PRERELEASE is a valid boolean value
if [ "$PRERELEASE" = "true" ]; then
PRERELEASE_JSON="true"
else
PRERELEASE_JSON="false"
fi

# Create GitHub release
GITHUB_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases"
JSON=$(cat <<EOF
{
"tag_name": "$VERSION",
"name": "$RELEASE_NAME",
"body": "$RELEASE_NAME",
"prerelease": $PRERELEASE_JSON
}
EOF
)

echo "Creating GitHub release..."
RESP=$(curl -s -X POST --data "$JSON" -H "Content-Type: application/json" -H "Authorization: token $GITHUB_API_TOKEN" $GITHUB_URL)
check_if_success

UPLOAD_URL=$(echo "$RESP" | sed -n 's/.*"upload_url": "\([^{]*\){.*/\1/p')
if [ -z "$UPLOAD_URL" ]; then
echo "Failed to create release or extract upload URL."
echo "Response: $RESP"
exit 1
fi

# Upload the binaries
for pkg in "$OUT_DIR"/*
do
echo "Uploading $pkg"
FILENAME=$(basename "$pkg")
UPLOAD=$(curl -s --data-binary @"$pkg" -H "Content-Type: application/octet-stream" -H "Authorization: token $GITHUB_API_TOKEN" "$UPLOAD_URL?name=$FILENAME")
Mehul-Kumar-27 marked this conversation as resolved.
Show resolved Hide resolved
check_if_success
done

echo "Release $RELEASE_NAME deployed successfully."
Loading