feat: collect and prefetch actions specified by use as urls
This commit is contained in:
parent
5490bba12f
commit
5410c7f6c3
5 changed files with 95 additions and 11 deletions
54
internal/actions/act.go
Normal file
54
internal/actions/act.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Implementation for basic Action-related operations.
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
logger "courgette/internal/logging"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CliClient interface {
|
||||||
|
Clone(url string, destination string) error
|
||||||
|
Exec(args ...string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitClient struct{}
|
||||||
|
|
||||||
|
func (g GitClient) Exec(args ...string) error {
|
||||||
|
cmd := exec.Command("git", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GitClient) Clone(url string, destination string) error {
|
||||||
|
return g.Exec("clone", url, destination, "--depth", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a key that can be used as a directory / cache-key string
|
||||||
|
// based on the provided Action repository url.
|
||||||
|
func GetActionKey(url string) string {
|
||||||
|
url = strings.ToLower(url)
|
||||||
|
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
url = strings.TrimPrefix(url, "http://")
|
||||||
|
} else if strings.HasPrefix(url, "https://") {
|
||||||
|
url = strings.TrimPrefix(url, "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(url, "/")
|
||||||
|
|
||||||
|
return strings.Join(parts, "__")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefetches and caches the action defined by <actionName> at <destination>.
|
||||||
|
//
|
||||||
|
// <actionName> is expected to be the full url of the repository where
|
||||||
|
// the action lives.
|
||||||
|
func PrefetchAction(client CliClient, actionName string, destination string) error {
|
||||||
|
logger.Info("Prefetching action: %s to %s", actionName, destination)
|
||||||
|
|
||||||
|
return client.Clone(actionName, destination)
|
||||||
|
}
|
6
internal/cache/cache_manager.go
vendored
6
internal/cache/cache_manager.go
vendored
|
@ -30,14 +30,14 @@ func (c CacheManager) Path(elems ...string) string {
|
||||||
|
|
||||||
// Checks whether a specific top-level items exists in the cache.
|
// Checks whether a specific top-level items exists in the cache.
|
||||||
func (c CacheManager) Exists(key string) bool {
|
func (c CacheManager) Exists(key string) bool {
|
||||||
if _, err := os.Stat(c.Path(key)); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(c.Path(key)); errors.Is(err, os.ErrNotExist) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes an element from the cache, if it exists.
|
// Deletes an element from the cache, if it exists.
|
||||||
func (c CacheManager) Evict(key string) {
|
func (c CacheManager) Evict(key string) {
|
||||||
os.RemoveAll(c.Path(key))
|
os.RemoveAll(c.Path(key))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func ExecuteWorkflow(configuration Configuration, workflowFile string) error {
|
||||||
runnerInstance := runner.NewRunner(
|
runnerInstance := runner.NewRunner(
|
||||||
driver,
|
driver,
|
||||||
configuration.Runner.Labels,
|
configuration.Runner.Labels,
|
||||||
configuration.Cache.Dir,
|
configuration.GetCacheDir(),
|
||||||
)
|
)
|
||||||
|
|
||||||
workflow, err := workflow.FromYamlFile(workflowFile)
|
workflow, err := workflow.FromYamlFile(workflowFile)
|
||||||
|
|
|
@ -2,10 +2,11 @@ package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
actions "courgette/internal/actions"
|
||||||
cache "courgette/internal/cache"
|
cache "courgette/internal/cache"
|
||||||
driver "courgette/internal/driver"
|
driver "courgette/internal/driver"
|
||||||
logger "courgette/internal/logging"
|
logger "courgette/internal/logging"
|
||||||
workflow "courgette/internal/workflow"
|
workflows "courgette/internal/workflow"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -36,7 +37,7 @@ func NewRunner(driver driver.ContainerDriver, labels map[string]string, cacheRoo
|
||||||
// This is the high-level call that will set up the container
|
// This is the high-level call that will set up the container
|
||||||
// that the jobs will be executed in, run the jobs's steps and
|
// that the jobs will be executed in, run the jobs's steps and
|
||||||
// tear down the container once no longer useful.
|
// tear down the container once no longer useful.
|
||||||
func (r *Runner) RunWorkflow(workflow workflow.Workflow) TaskTracker {
|
func (r *Runner) RunWorkflow(workflow workflows.Workflow) TaskTracker {
|
||||||
logger.Info("Executing workflow: %s", workflow.SourcePath)
|
logger.Info("Executing workflow: %s", workflow.SourcePath)
|
||||||
rootTracker := NewTaskTracker(nil)
|
rootTracker := NewTaskTracker(nil)
|
||||||
|
|
||||||
|
@ -44,6 +45,15 @@ func (r *Runner) RunWorkflow(workflow workflow.Workflow) TaskTracker {
|
||||||
|
|
||||||
defer r.deferred.RunAllDeferredTasks()
|
defer r.deferred.RunAllDeferredTasks()
|
||||||
|
|
||||||
|
workflow.Walk(func(n interface{}) {
|
||||||
|
switch n.(type) {
|
||||||
|
case workflows.Step:
|
||||||
|
if useAction := n.(workflows.Step).Use; useAction != "" {
|
||||||
|
actions.PrefetchAction(actions.GitClient{}, useAction, r.Cache.Path(actions.GetActionKey(useAction)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for _, group := range workflow.GetJobsAsGroups() {
|
for _, group := range workflow.GetJobsAsGroups() {
|
||||||
var groupWait sync.WaitGroup
|
var groupWait sync.WaitGroup
|
||||||
|
|
||||||
|
@ -72,7 +82,7 @@ func (r *Runner) RunWorkflow(workflow workflow.Workflow) TaskTracker {
|
||||||
// a WaitGroup to coordinate concurrent tasks) and updates the tracker with results.
|
// a WaitGroup to coordinate concurrent tasks) and updates the tracker with results.
|
||||||
|
|
||||||
func (r Runner) runJob(jobContext context.Context, jobTracker *TaskTracker, jobWaitGroup *sync.WaitGroup) {
|
func (r Runner) runJob(jobContext context.Context, jobTracker *TaskTracker, jobWaitGroup *sync.WaitGroup) {
|
||||||
job := jobContext.Value("currentJob").(workflow.Job)
|
job := jobContext.Value("currentJob").(workflows.Job)
|
||||||
containerName := fmt.Sprintf("runner-%s", jobTracker.TaskId)
|
containerName := fmt.Sprintf("runner-%s", jobTracker.TaskId)
|
||||||
|
|
||||||
defer jobWaitGroup.Done()
|
defer jobWaitGroup.Done()
|
||||||
|
@ -134,11 +144,11 @@ func (r *Runner) RunJobInContainer(imageUri string, containerId string, jobConte
|
||||||
r.Driver.Stop(containerId)
|
r.Driver.Stop(containerId)
|
||||||
})
|
})
|
||||||
|
|
||||||
job := jobContext.Value("currentJob").(workflow.Job)
|
job := jobContext.Value("currentJob").(workflows.Job)
|
||||||
|
|
||||||
logger.Info("Started %s", containerId)
|
logger.Info("Started %s", containerId)
|
||||||
for stepIndex, step := range job.Steps {
|
for stepIndex, step := range job.Steps {
|
||||||
workflow := jobContext.Value("workflow").(workflow.Workflow)
|
workflow := jobContext.Value("workflow").(workflows.Workflow)
|
||||||
stepCwd := workflow.GetWorkingDirectory(job.Name, stepIndex)
|
stepCwd := workflow.GetWorkingDirectory(job.Name, stepIndex)
|
||||||
stepEnv := workflow.GetEnv(job.Name, stepIndex)
|
stepEnv := workflow.GetEnv(job.Name, stepIndex)
|
||||||
stepShell := workflow.GetShell(job.Name, stepIndex)
|
stepShell := workflow.GetShell(job.Name, stepIndex)
|
||||||
|
|
|
@ -22,6 +22,13 @@ type Job struct {
|
||||||
} `yaml:"defaults"`
|
} `yaml:"defaults"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j Job) Walk(handler func(node interface{})) {
|
||||||
|
handler(j)
|
||||||
|
|
||||||
|
for _, step := range j.Steps {
|
||||||
|
step.Walk(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
func (j Job) Validate() []error {
|
func (j Job) Validate() []error {
|
||||||
validationErrors := []error{}
|
validationErrors := []error{}
|
||||||
|
|
||||||
|
@ -46,13 +53,18 @@ type Step struct {
|
||||||
ContinueOnError bool `yaml:"continue-on-error"`
|
ContinueOnError bool `yaml:"continue-on-error"`
|
||||||
Env map[string]string `yaml:"env"`
|
Env map[string]string `yaml:"env"`
|
||||||
Shell string `yaml:"shell"`
|
Shell string `yaml:"shell"`
|
||||||
|
Use string `yaml:"use"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Step) Walk(handler func(node interface{})) {
|
||||||
|
handler(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Step) Validate() []error {
|
func (s Step) Validate() []error {
|
||||||
validationErrors := []error{}
|
validationErrors := []error{}
|
||||||
|
|
||||||
if s.Run == "" {
|
if s.Run == "" && s.Use == "" {
|
||||||
validationErrors = append(validationErrors, errors.New("Missing \"run\" field on step."))
|
validationErrors = append(validationErrors, errors.New("Must have a \"run\" or \"step\" clause."))
|
||||||
}
|
}
|
||||||
|
|
||||||
return validationErrors
|
return validationErrors
|
||||||
|
@ -64,6 +76,14 @@ type Workflow struct {
|
||||||
Env map[string]string `yaml:"env"`
|
Env map[string]string `yaml:"env"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w Workflow) Walk(handler func(node interface{})) {
|
||||||
|
handler(w)
|
||||||
|
|
||||||
|
for _, job := range w.Jobs {
|
||||||
|
job.Walk(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the given workflow's job+step working directory inside the container
|
// Returns the given workflow's job+step working directory inside the container
|
||||||
// that runs the job.
|
// that runs the job.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue