Skip to content

Commit

Permalink
refactor: move load custom actions to core
Browse files Browse the repository at this point in the history
  • Loading branch information
aweris committed Aug 24, 2023
1 parent 3047d50 commit 874dbaf
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 180 deletions.
165 changes: 165 additions & 0 deletions internal/core/custom_action.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package core

import (
"context"
"fmt"
"path"
"path/filepath"
"regexp"
"strings"

"dagger.io/dagger"

"gopkg.in/yaml.v3"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/internal/fs"
"github.com/aweris/gale/internal/log"
)

type CustomAction struct {
Expand Down Expand Up @@ -147,3 +157,158 @@ func (c *CustomActionRuns) PostCondition() (bool, string) {

return true, c.PostIf
}

// LoadActionFromSource loads an action from given source. Source can be a local directory or a remote repository.
func LoadActionFromSource(ctx context.Context, source string) (*CustomAction, error) {
var target string

// no need to load action if it is a local action
if isLocalAction(source) {
target = source
} else {
target = filepath.Join(config.GhxActionsDir(), source)

// ensure action exists locally
if err := ensureActionExistsLocally(ctx, source, target); err != nil {
return nil, err
}
}

dir, err := getActionDirectory(target)
if err != nil {
return nil, err
}

meta, err := getCustomActionMeta(ctx, dir)
if err != nil {
return nil, err
}

return &CustomAction{Meta: meta, Path: target, Dir: dir}, nil
}

// isLocalAction checks if the given source is a local action
func isLocalAction(source string) bool {
return strings.HasPrefix(source, "./") || filepath.IsAbs(source) || strings.HasPrefix(source, "/")
}

// ensureActionExistsLocally ensures that the action exists locally. If the action does not exist locally, it will be
// downloaded from the source to the target directory.
func ensureActionExistsLocally(ctx context.Context, source, target string) error {
// check if action exists locally
exist, err := fs.Exists(target)
if err != nil {
return fmt.Errorf("failed to check if action exists locally: %w", err)
}

// do nothing if target path already exists
if exist {
log.Debugf("action already exists locally", "source", source, "target", target)
return nil
}

log.Debugf("action does not exist locally, downloading...", "source", source, "target", target)

dir, err := getActionDirectory(source)
if err != nil {
return err
}

// export the action to the target directory
_, err = dir.Export(ctx, target)
if err != nil {
return err
}

return nil
}

// getCustomActionMeta returns the meta information about the custom action from the action directory.
func getCustomActionMeta(ctx context.Context, actionDir *dagger.Directory) (*CustomActionMeta, error) {
var meta CustomActionMeta

file, err := findActionMetadataFileName(ctx, actionDir)
if err != nil {
return nil, err
}

content, err := actionDir.File(file).Contents(ctx)
if err != nil {
return nil, err
}

err = yaml.Unmarshal([]byte(content), &meta)
if err != nil {
return nil, err
}

return &meta, nil
}

// getActionDirectory returns the directory of the action from given source.
func getActionDirectory(source string) (*dagger.Directory, error) {
// if path is relative, use the host to resolve the path
if isLocalAction(source) {
return config.Client().Host().Directory(source), nil
}

// if path is not a relative path, it must be a remote repository in the format "{owner}/{repo}/{path}@{ref}"
// if {path} is not present in the input string, an empty string is returned for the path component.
actionRepo, actionPath, actionRef, err := parseRepoRef(source)
if err != nil {
return nil, fmt.Errorf("failed to parse repo ref %s: %v", source, err)
}

// if path is empty, use the root of the repo as the action directory
if actionPath == "" {
actionPath = "."
}

// TODO: handle enterprise github instances as well
// TODO: handle ref type (branch, tag, commit) currently only tags are supported
return config.Client().Git(path.Join("github.com", actionRepo)).Tag(actionRef).Tree().Directory(actionPath), nil
}

// findActionMetadataFileName finds the action.yml or action.yaml file in the root of the action directory.
func findActionMetadataFileName(ctx context.Context, dir *dagger.Directory) (string, error) {
// list all entries in the root of the action directory
entries, entriesErr := dir.Entries(ctx)
if entriesErr != nil {
return "", fmt.Errorf("failed to list entries for: %v", entriesErr)
}

file := ""

// find action.yml or action.yaml exists in the root of the action repo
for _, entry := range entries {
if entry == "action.yml" || entry == "action.yaml" {
file = entry
break
}
}

// if action.yml or action.yaml does not exist, return an error
if file == "" {
return "", fmt.Errorf("action.yml or action.yaml not found in the root of the action directory")
}

return file, nil
}

// parseRepoRef parses a string in the format "{owner}/{repo}/{path}@{ref}" and returns the parsed components.
// If {path} is not present in the input string, an empty string is returned for the path component.
func parseRepoRef(input string) (repo string, path string, ref string, err error) {
regex := regexp.MustCompile(`^([^/]+)/([^/@]+)(?:/([^@]+))?@(.+)$`)
matches := regex.FindStringSubmatch(input)

if len(matches) == 0 {
err = fmt.Errorf("invalid input format: %q", input)
return
}

repo = strings.Join([]string{matches[1], matches[2]}, "/")
path = matches[3]
ref = matches[4]

return
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package actions_test
package core_test

import (
"context"
Expand All @@ -9,7 +9,7 @@ import (
"dagger.io/dagger"

"github.com/aweris/gale/internal/config"
"github.com/aweris/gale/tools/ghx/actions"
"github.com/aweris/gale/internal/core"
)

func TestCustomActionManager_GetCustomAction(t *testing.T) {
Expand All @@ -31,7 +31,7 @@ func TestCustomActionManager_GetCustomAction(t *testing.T) {
config.SetClient(client)

t.Run("download missing action", func(t *testing.T) {
ca, err := actions.LoadActionFromSource(ctx, "actions/checkout@v2")
ca, err := core.LoadActionFromSource(ctx, "actions/checkout@v2")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func TestCustomActionManager_GetCustomAction(t *testing.T) {
t.Fatal(err)
}

ca, err := actions.LoadActionFromSource(ctx, "some/action@v1")
ca, err := core.LoadActionFromSource(ctx, "some/action@v1")
if err != nil {
t.Fatal(err)
}
Expand Down
174 changes: 0 additions & 174 deletions tools/ghx/actions/custom_action.go

This file was deleted.

Loading

0 comments on commit 874dbaf

Please sign in to comment.