Skip to content

Commit

Permalink
refactor: isolate container init from workflow execution (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
aweris authored Nov 13, 2023
1 parent b7673e2 commit 9208407
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 78 deletions.
22 changes: 8 additions & 14 deletions daggerverse/gale/gale.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import (
// Gale is a Dagger module for running Github Actions workflows.
type Gale struct{}

// Runner represents a runner to run a Github Actions workflow in.
func (g *Gale) Runner() *Runner {
return &Runner{}
}

// List returns a list of workflows and their jobs with the given options.
func (g *Gale) List(
// context to use for the operation
Expand Down Expand Up @@ -69,6 +74,8 @@ func (g *Gale) List(

// Run runs the workflow with the given options.
func (g *Gale) Run(
// context to use for the operation
ctx context.Context,
// The directory containing the repository source. If source is provided, rest of the options are ignored.
source Optional[*Directory],
// The name of the repository. Format: owner/name.
Expand Down Expand Up @@ -98,23 +105,10 @@ func (g *Gale) Run(
// GitHub token to use for authentication.
token Optional[*Secret],
) *WorkflowRun {
var base *Container

if image, ok := image.Get(); ok {
base = dag.Container().From(image)
} else if container, ok := container.Get(); ok {
base = container
} else {
base = dag.Container().From("ghcr.io/catthehacker/ubuntu:act-latest")
}

return &WorkflowRun{
Runner: g.Runner().Container(ctx, image, container, source, repo, tag, branch),
Config: WorkflowRunConfig{
Base: base,
Source: source.GetOr(nil),
Repo: repo.GetOr(""),
Branch: branch.GetOr(""),
Tag: tag.GetOr(""),
WorkflowsDir: workflowsDir.GetOr(".github/workflows"),
WorkflowFile: workflowFile.GetOr(nil),
Workflow: workflow.GetOr(""),
Expand Down
1 change: 1 addition & 0 deletions daggerverse/gale/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21.3
require (
github.com/99designs/gqlgen v0.17.31
github.com/Khan/genqlient v0.6.0
github.com/google/uuid v1.4.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.4.0
)
Expand Down
2 changes: 2 additions & 0 deletions daggerverse/gale/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
15 changes: 5 additions & 10 deletions daggerverse/gale/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ import (
"strings"
)

// getRepoInfo returns the repository info from the given options.
func getRepoInfo(source Optional[*Directory], repo, tag, branch Optional[string]) *RepoInfo {
// convert workflows list options to repo source options
opts := RepoInfoOpts{
// getRepoInfo returns a RepoInfo object based on the provided options.
func getRepoInfo(source Optional[*Directory], repo, branch, tag Optional[string]) *RepoInfo {
return dag.Repo().Info(RepoInfoOpts{
Source: source.GetOr(nil),
Repo: repo.GetOr(""),
Tag: tag.GetOr(""),
Branch: branch.GetOr(""),
}

// get the repository source working directory from the options -- default value handled by the repo module, so we
// don't need to handle it here.
return dag.Repo().Info(opts)
Tag: tag.GetOr(""),
})
}

// getWorkflowsDir returns the workflows directory from the given options.
Expand Down
122 changes: 122 additions & 0 deletions daggerverse/gale/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"context"
"fmt"

"github.com/google/uuid"
)

type Runner struct{}

// RunnerContainer represents a container to run a Github Actions workflow in.
type RunnerContainer struct {
// Unique identifier for the runner container.
RunnerID string

// Initialized container to run the workflow in.
Ctr *Container
}

// getRunnerContainer returns a runner container for the given container if it is initialized.
func (r *Runner) getRunnerContainer(ctx context.Context, container *Container) *RunnerContainer {
id := isContainerInitialized(ctx, container)
if id == "" {
return nil
}

return &RunnerContainer{RunnerID: id, Ctr: container}
}

// Container initializes a new runner container with the given options.
func (r *Runner) Container(
// context to use for the operation
ctx context.Context,
// Image to use for the runner. If --image and --container provided together, --container takes precedence.
image Optional[string],
// Container to use for the runner. If --image and --container provided together, --container takes precedence.
container Optional[*Container],
// The directory containing the repository source. If source is provided, rest of the options are ignored.
source Optional[*Directory],
// The name of the repository. Format: owner/name.
repo Optional[string],
// Tag name to check out. Only one of branch or tag can be used. Precedence is as follows: tag, branch.
tag Optional[string],
// Branch name to check out. Only one of branch or tag can be used. Precedence is as follows: tag, branch.
branch Optional[string],
) *RunnerContainer {
// check if container is already initialized
if ctr, ok := container.Get(); ok {
id := isContainerInitialized(ctx, ctr)
if id != "" {
println(fmt.Sprintf("skip container initialization, container already initialized with id: %s", id))
println(fmt.Sprintf("WARNING: given source and repo options are ignored, using the initialized container"))

return &RunnerContainer{RunnerID: id, Ctr: ctr}
}
}

var (
id = uuid.New().String()
imageVal = image.GetOr("ghcr.io/catthehacker/ubuntu:act-latest")
ctr = container.GetOr(dag.Container().From(imageVal))
info = getRepoInfo(source, repo, branch, tag)
path = getRunnerCacheVolumeMountPath(id)
cache = getRunnerCacheVolume(id)
)

// configure internal components
ctr = ctr.With(dag.Source().Ghx().Binary)
ctr = ctr.With(dag.Source().ArtifactService().BindAsService)
ctr = ctr.With(dag.Source().ArtifactCacheService().BindAsService)

// configure repo
ctr = ctr.With(info.Configure)

// configure runner cache
ctr = ctr.WithEnvVariable("GALE_RUNNER_CACHE", path)
ctr = ctr.WithMountedCache(path, cache, ContainerWithMountedCacheOpts{Sharing: Shared})

// add env variable to the container to indicate container is configured
ctr = ctr.WithEnvVariable("GALE_RUNNER_ID", id)
ctr = ctr.WithEnvVariable("GALE_CONFIGURED", "true")

return &RunnerContainer{RunnerID: id, Ctr: ctr}
}

func (rc *RunnerContainer) Container() *Container {
return rc.Ctr
}

func (rc *RunnerContainer) getRunnerCachePath() string {
return getRunnerCacheVolumeMountPath(rc.RunnerID)
}

// isContainerInitialized checks if the given container is initialized and returns the runner id if it is.
func isContainerInitialized(ctx context.Context, container *Container) string {
val, err := container.EnvVariable(ctx, "GALE_CONFIGURED")
if err != nil {
return ""
}

if val != "true" {
return ""
}

runnerID, err := container.EnvVariable(ctx, "GALE_RUNNER_ID")
if err != nil {
return ""
}

return runnerID
}

// getRunnerCacheVolumeMountPath returns the mount path for the cache volume with the given id.
func getRunnerCacheVolumeMountPath(id string) string {
return fmt.Sprintf("/home/runner/_temp/gale/%s", id)
}

// getRunnerCacheVolume returns a cache volume for the given id.
func getRunnerCacheVolume(id string) *CacheVolume {
return dag.CacheVolume(fmt.Sprintf("gale-runner-%s", id))
}
65 changes: 11 additions & 54 deletions daggerverse/gale/workflow_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,15 @@ import (
)

type WorkflowRun struct {
// Base container to use for the workflow run.
Runner *RunnerContainer

// Configuration of the workflow run.
Config WorkflowRunConfig
}

// WorkflowRunConfig holds the configuration of a workflow run.
type WorkflowRunConfig struct {
// Base container to use for the workflow run.
Base *Container

// Directory containing the repository source.
Source *Directory

// Name of the repository. Format: owner/name.
Repo string

// Branch name to check out. Only one of branch or tag can be used. Precedence: tag, branch.
Branch string

// Tag name to check out. Only one of branch or tag can be used. Precedence: tag, branch.
Tag string

// Path to the workflow directory.
WorkflowsDir string

Expand Down Expand Up @@ -69,13 +58,8 @@ type WorkflowRunReport struct {
}

// Sync runs the workflow and returns the container that ran the workflow.
func (wr *WorkflowRun) Sync(ctx context.Context) (*Container, error) {
container, err := wr.run(ctx)
if err != nil {
return nil, err
}

return container, nil
func (wr *WorkflowRun) Sync() (*Container, error) {
return wr.run()
}

// Directory returns the directory of the workflow run information.
Expand All @@ -90,7 +74,7 @@ func (wr *WorkflowRun) Directory(
// Adds the uploaded artifacts to the exported directory. (default: false)
includeArtifacts Optional[bool],
) (*Directory, error) {
container, err := wr.run(ctx)
container, err := wr.run()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -127,11 +111,8 @@ func (wr *WorkflowRun) Directory(
return dir, nil
}

func (wr *WorkflowRun) run(ctx context.Context) (*Container, error) {
container, err := wr.container(ctx)
if err != nil {
return nil, err
}
func (wr *WorkflowRun) run() (*Container, error) {
container := wr.Runner.Ctr

// loading request scoped configs

Expand All @@ -158,38 +139,14 @@ func (wr *WorkflowRun) run(ctx context.Context) (*Container, error) {
return container, nil
}

func (wr *WorkflowRun) container(ctx context.Context) (*Container, error) {
container := wr.Config.Base
func (wr *WorkflowRun) configure(c *Container) *Container {
container := c

// set github token as secret if provided
if wr.Config.Token != nil {
container = container.WithSecretVariable("GITHUB_TOKEN", wr.Config.Token)
}

// configure internal components
container = container.With(dag.Source().Ghx().Binary)
container = container.With(dag.Source().ArtifactService().BindAsService)
container = container.With(dag.Source().ArtifactCacheService().BindAsService)

// configure repo
info := dag.Repo().Info(RepoInfoOpts{
Source: wr.Config.Source,
Repo: wr.Config.Repo,
Branch: wr.Config.Branch,
Tag: wr.Config.Tag,
})

container = container.With(info.Configure)

// add env variable to the container to indicate container is configured
container = container.WithEnvVariable("GALE_CONFIGURED", "true")

return container, nil
}

func (wr *WorkflowRun) configure(c *Container) *Container {
container := c

if wr.Config.WorkflowFile != nil {
path := "/home/runner/_temp/_github_workflow/.gale/dagger.yaml"

Expand Down

0 comments on commit 9208407

Please sign in to comment.