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

refactor: add dagger helpers pkg #56

Merged
merged 2 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading