Skip to content

Commit

Permalink
refactor: add dagger helpers pkg (#56)
Browse files Browse the repository at this point in the history
* move fail container to internal/dagger/helpers pkg

* make impl consistent
  • Loading branch information
aweris authored Aug 4, 2023
1 parent 42aed0e commit 29276be
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 130 deletions.
105 changes: 62 additions & 43 deletions internal/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (
"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/dagger/helpers"
)

var _ helpers.WithContainerFuncHook = new(RunnerContext)

type RunnerContext struct {
Name string `json:"name"` // Name is the name of the runner.
OS string `json:"os"` // OS is the operating system of the runner.
Expand All @@ -29,18 +32,21 @@ func NewRunnerContext() RunnerContext {
}
}

// Apply applies the RunnerContext to the given container.
func (c RunnerContext) Apply(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("RUNNER_NAME", c.Name).
WithEnvVariable("RUNNER_TEMP", c.Temp).
WithEnvVariable("RUNNER_OS", c.OS).
WithEnvVariable("RUNNER_ARCH", c.Arch).
WithEnvVariable("RUNNER_TOOL_CACHE", c.ToolCache).
WithMountedCache(c.ToolCache, config.Client().CacheVolume("RUNNER_TOOL_CACHE")).
WithEnvVariable("RUNNER_DEBUG", c.Debug)
func (c RunnerContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("RUNNER_NAME", c.Name).
WithEnvVariable("RUNNER_TEMP", c.Temp).
WithEnvVariable("RUNNER_OS", c.OS).
WithEnvVariable("RUNNER_ARCH", c.Arch).
WithEnvVariable("RUNNER_TOOL_CACHE", c.ToolCache).
WithMountedCache(c.ToolCache, config.Client().CacheVolume("RUNNER_TOOL_CACHE")).
WithEnvVariable("RUNNER_DEBUG", c.Debug)
}
}

var _ helpers.WithContainerFuncHook = new(GithubRepositoryContext)

// GithubRepositoryContext is a context that contains information about the repository.
type GithubRepositoryContext struct {
Repository string `json:"repository"` // Repository is the combination of owner and name of the repository. e.g. octocat/hello-world
Expand All @@ -65,19 +71,22 @@ func NewGithubRepositoryContext(repo *Repository) GithubRepositoryContext {
}
}

// Apply applies the GithubRepositoryContext to the given container.
func (c GithubRepositoryContext) Apply(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_REPOSITORY", c.Repository).
WithEnvVariable("GITHUB_REPOSITORY_ID", c.RepositoryID).
WithEnvVariable("GITHUB_REPOSITORY_OWNER", c.RepositoryOwner).
WithEnvVariable("GITHUB_REPOSITORY_OWNER_ID", c.RepositoryOwnerID).
WithEnvVariable("GITHUB_REPOSITORY_URL", c.RepositoryURL).
WithEnvVariable("GITHUB_WORKSPACE", c.Workspace).
WithMountedDirectory(c.Workspace, c.Dir).
WithWorkdir(c.Workspace)
func (c GithubRepositoryContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_REPOSITORY", c.Repository).
WithEnvVariable("GITHUB_REPOSITORY_ID", c.RepositoryID).
WithEnvVariable("GITHUB_REPOSITORY_OWNER", c.RepositoryOwner).
WithEnvVariable("GITHUB_REPOSITORY_OWNER_ID", c.RepositoryOwnerID).
WithEnvVariable("GITHUB_REPOSITORY_URL", c.RepositoryURL).
WithEnvVariable("GITHUB_WORKSPACE", c.Workspace).
WithMountedDirectory(c.Workspace, c.Dir).
WithWorkdir(c.Workspace)
}
}

var _ helpers.WithContainerFuncHook = new(GithubSecretsContext)

// GithubSecretsContext is a context that contains information about the secrets.
type GithubSecretsContext struct {
Token string `json:"token"` // Token is the GitHub token to use for authentication.
Expand All @@ -90,11 +99,14 @@ func NewGithubSecretsContext(token string) GithubSecretsContext {
}
}

// Apply applies the GithubSecretsContext to the given container.
func (c GithubSecretsContext) Apply(container *dagger.Container) *dagger.Container {
return container.WithSecretVariable("GITHUB_TOKEN", config.Client().SetSecret("GITHUB_TOKEN", c.Token))
func (c GithubSecretsContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.WithSecretVariable("GITHUB_TOKEN", config.Client().SetSecret("GITHUB_TOKEN", c.Token))
}
}

var _ helpers.WithContainerFuncHook = new(GithubURLContext)

// GithubURLContext is a context that contains URLs for the Github server and API.
type GithubURLContext struct {
//nolint:revive,stylecheck // ApiURL is more readable than APIURL
Expand All @@ -112,12 +124,13 @@ func NewGithubURLContext() GithubURLContext {
}
}

// Apply applies the GithubURLContext to the given container.
func (c GithubURLContext) Apply(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_API_URL", c.ApiURL).
WithEnvVariable("GITHUB_GRAPHQL_URL", c.GraphqlURL).
WithEnvVariable("GITHUB_SERVER_URL", c.ServerURL)
func (c GithubURLContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_API_URL", c.ApiURL).
WithEnvVariable("GITHUB_GRAPHQL_URL", c.GraphqlURL).
WithEnvVariable("GITHUB_SERVER_URL", c.ServerURL)
}
}

// GithubFilesContext is a context that contains paths for files and directories useful to Github Actions.
Expand All @@ -129,6 +142,8 @@ type GithubFilesContext struct {
Path string `json:"path"` // Path is the path to a temporary file that sets the system PATH variable from workflow commands.
}

var _ helpers.WithContainerFuncHook = new(GithubWorkflowContext)

// GithubWorkflowContext is a context that contains information about the workflow.
type GithubWorkflowContext struct {
Workflow string `json:"workflow"` // Workflow is the name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository.
Expand All @@ -153,18 +168,21 @@ func NewGithubWorkflowContext(repo *Repository, workflow *Workflow, runID string
}
}

// Apply applies the GithubWorkflowContext to the given container.
func (c GithubWorkflowContext) Apply(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_WORKFLOW", c.Workflow).
WithEnvVariable("GITHUB_WORKFLOW_REF", c.WorkflowRef).
WithEnvVariable("GITHUB_WORKFLOW_SHA", c.WorkflowSHA).
WithEnvVariable("GITHUB_RUN_ID", c.RunID).
WithEnvVariable("GITHUB_RUN_NUMBER", c.RunNumber).
WithEnvVariable("GITHUB_RUN_ATTEMPT", c.RunAttempt).
WithEnvVariable("GITHUB_RETENTION_DAYS", c.RetentionDays)
func (c GithubWorkflowContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.
WithEnvVariable("GITHUB_WORKFLOW", c.Workflow).
WithEnvVariable("GITHUB_WORKFLOW_REF", c.WorkflowRef).
WithEnvVariable("GITHUB_WORKFLOW_SHA", c.WorkflowSHA).
WithEnvVariable("GITHUB_RUN_ID", c.RunID).
WithEnvVariable("GITHUB_RUN_NUMBER", c.RunNumber).
WithEnvVariable("GITHUB_RUN_ATTEMPT", c.RunAttempt).
WithEnvVariable("GITHUB_RETENTION_DAYS", c.RetentionDays)
}
}

var _ helpers.WithContainerFuncHook = new(GithubJobInfoContext)

// GithubJobInfoContext is a context that contains information about the job.
type GithubJobInfoContext struct {
Job string `json:"job"` // Job is the job_id of the current job. Note: This context property is set by the Actions runner, and is only available within the execution steps of a job. Otherwise, the value of this property will be null.
Expand All @@ -177,9 +195,10 @@ func NewGithubJobInfoContext(jobID string) GithubJobInfoContext {
return GithubJobInfoContext{Job: jobID}
}

// Apply applies the GithubJobInfoContext to the given container.
func (c GithubJobInfoContext) Apply(container *dagger.Container) *dagger.Container {
return container.WithEnvVariable("GITHUB_JOB", c.Job)
func (c GithubJobInfoContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.WithEnvVariable("GITHUB_JOB", c.Job)
}
}

// JobContext contains information about the currently running job.
Expand Down
30 changes: 17 additions & 13 deletions internal/core/dagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/dagger/helpers"
)

var _ helpers.WithContainerFuncHook = new(DaggerContext)

// DaggerContext represents the dagger engine connection information should be passed to the container
type DaggerContext struct {
RunnerHost string // RunnerHost where the dagger engine is running
Expand All @@ -32,20 +35,21 @@ func NewDaggerContextFromEnv() *DaggerContext {
}
}

// Apply applies the dagger context to the container
func (d *DaggerContext) Apply(container *dagger.Container) *dagger.Container {
if d.RunnerHost != "" {
container = container.WithEnvVariable("_EXPERIMENTAL_DAGGER_RUNNER_HOST", d.RunnerHost)
}
func (d *DaggerContext) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
if d.RunnerHost != "" {
container = container.WithEnvVariable("_EXPERIMENTAL_DAGGER_RUNNER_HOST", d.RunnerHost)
}

if d.Session != "" {
container = container.WithEnvVariable("DAGGER_SESSION", d.Session)
}
if d.Session != "" {
container = container.WithEnvVariable("DAGGER_SESSION", d.Session)
}

// as a fallback, we're loading docker socket from the host.
if d.RunnerHost == "" || d.Session == "" {
container = container.WithUnixSocket("/var/run/docker.sock", config.Client().Host().UnixSocket(d.DockerSock))
}
// as a fallback, we're loading docker socket from the host.
if d.RunnerHost == "" || d.Session == "" {
container = container.WithUnixSocket("/var/run/docker.sock", config.Client().Host().UnixSocket(d.DockerSock))
}

return container
return container
}
}
34 changes: 26 additions & 8 deletions internal/core/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package core
import (
"context"
"encoding/json"
"fmt"

"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/dagger/helpers"
)

// Job represents a single job in a GitHub Actions workflow
Expand All @@ -20,23 +22,39 @@ type Job struct {
// TBD: add more fields when needed
}

var _ helpers.WithContainerFuncHook = new(JobRun)

// JobRun represents a job run configuration that is passed to the container
type JobRun struct {
RunID string `json:"runID"` // RunID is the ID of the run
Job Job `json:"job"` // Job is the job to run
}

// MarshalJobRunToDir marshals the job run to a dagger directory
func MarshalJobRunToDir(_ context.Context, jobRun *JobRun) (*dagger.Directory, error) {
data, err := json.Marshal(jobRun)
if err != nil {
return nil, err
// NewJobRun creates a new job run
func NewJobRun(runID string, job Job) JobRun {
return JobRun{
RunID: runID,
Job: job,
}
}

dir := config.Client().Directory()
func (j JobRun) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
data, err := json.Marshal(j)
if err != nil {
return helpers.FailPipeline(container, err)
}

dir = dir.WithNewFile("job_run.json", string(data))
if len(data) == 0 {
return helpers.FailPipeline(container, fmt.Errorf("job run is empty"))
}

return dir, nil
dir := config.Client().Directory()

dir = dir.WithNewFile("job_run.json", string(data))

return container.WithDirectory(config.GhxRunDir(j.RunID), dir)
}
}

// UnmarshalJobRunFromDir unmarshal the job run from a dagger directory
Expand Down
25 changes: 25 additions & 0 deletions internal/dagger/helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package helpers

import (
"context"

"dagger.io/dagger"
)

// FailPipeline returns a container that immediately fails with the given error. This useful for forcing a pipeline to
// fail inside chaining operations.
func FailPipeline(container *dagger.Container, err error) *dagger.Container {
// fail the container with the given error
container = container.WithExec([]string{"sh", "-c", "echo " + err.Error() + " && exit 1"})

// forced evaluation of the pipeline to immediately fail
container, _ = container.Sync(context.Background())

return container
}

// WithContainerFuncHook is an interface that ensures that implementers have a WithContainerFunc method.
type WithContainerFuncHook interface {
// WithContainerFunc returns a dagger function that allows the user to modify the container.
WithContainerFunc() dagger.WithContainerFunc
}
19 changes: 10 additions & 9 deletions internal/dagger/services/artifact_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import (
"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/dagger/helpers"
"github.com/aweris/gale/internal/version"
)

var _ helpers.WithContainerFuncHook = new(ArtifactService)

// ArtifactService is the dagger service definitions for the services/artifact directory.
type ArtifactService struct {
client *dagger.Client
Expand Down Expand Up @@ -49,15 +52,13 @@ func (a *ArtifactService) Container() *dagger.Container {
return a.container
}

// ServiceBinding returns a container with the artifact service binding and all necessary configurations. The method
// signature is compatible with the dagger.WithContainerFunc type. It can be used to as
// container.With(service.ServiceBinding) to bind the service to the container.
func (a *ArtifactService) ServiceBinding(container *dagger.Container) *dagger.Container {
container = container.WithServiceBinding(a.alias, a.container)
container = container.WithEnvVariable("ACTIONS_RUNTIME_URL", fmt.Sprintf("http://%s:%s/", a.alias, a.port))
container = container.WithEnvVariable("ACTIONS_RUNTIME_TOKEN", "token") // dummy token, not used by service

return container
func (a *ArtifactService) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
return container.
WithServiceBinding(a.alias, a.container).
WithEnvVariable("ACTIONS_RUNTIME_URL", fmt.Sprintf("http://%s:%s/", a.alias, a.port)).
WithEnvVariable("ACTIONS_RUNTIME_TOKEN", "token") // dummy token, not used by service
}
}

// Artifacts returns a artifact directory for the given run ID.
Expand Down
30 changes: 23 additions & 7 deletions internal/dagger/tools/ghx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,37 @@ import (
"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/dagger/helpers"
"github.com/aweris/gale/internal/version"
)

// Ghx returns a dagger file for the ghx binary. It'll return an error if the binary is not available.
func Ghx(ctx context.Context) (*dagger.File, error) {
var _ helpers.WithContainerFuncHook = new(Ghx)

type Ghx struct {
tag string
file *dagger.File
}

// NewGhxBinary returns a dagger file for the ghx binary. It'll return an error if the binary is not available.
func NewGhxBinary() *Ghx {
v := version.GetVersion()

tag := v.GitVersion

file := config.Client().Container().From("ghcr.io/aweris/gale/tools/ghx:" + tag).File("/ghx")

// check, if the file doesn't exist or is empty
if size, err := file.Size(ctx); size == 0 || err != nil {
return nil, fmt.Errorf("ghx@%s binary not available", tag)
}
return &Ghx{tag: tag, file: file}
}

func (g *Ghx) WithContainerFunc() dagger.WithContainerFunc {
return func(container *dagger.Container) *dagger.Container {
// check, if the file doesn't exist or is empty
if size, err := g.file.Size(context.Background()); size == 0 || err != nil {
return helpers.FailPipeline(container, fmt.Errorf("ghx@%s binary not available", g.tag))
}

return file, nil
return container.WithFile("/usr/local/bin/ghx", g.file).
WithEnvVariable("GHX_HOME", config.GhxHome()).
WithMountedCache(config.GhxActionsDir(), config.Client().CacheVolume("actions"))
}
}
Loading

0 comments on commit 29276be

Please sign in to comment.