mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-20 19:06:59 +00:00
chore(cleanup): ACT cli is redundant with Forgejo runner cli (#182)
it is not used in the context of forgejo and is mistaken sometimes for being supported. Reviewed-on: https://code.forgejo.org/forgejo/act/pulls/182 Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Co-authored-by: Earl Warren <contact@earl-warren.org> Co-committed-by: Earl Warren <contact@earl-warren.org>
This commit is contained in:
parent
c2f91f63df
commit
9f94da307d
9 changed files with 0 additions and 1307 deletions
27
cmd/dir.go
27
cmd/dir.go
|
@ -1,27 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
UserHomeDir string
|
|
||||||
CacheHomeDir string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
UserHomeDir = home
|
|
||||||
|
|
||||||
if v := os.Getenv("XDG_CACHE_HOME"); v != "" {
|
|
||||||
CacheHomeDir = v
|
|
||||||
} else {
|
|
||||||
CacheHomeDir = filepath.Join(UserHomeDir, ".cache")
|
|
||||||
}
|
|
||||||
}
|
|
38
cmd/graph.go
38
cmd/graph.go
|
@ -1,38 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
|
||||||
"github.com/nektos/act/pkg/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func drawGraph(plan *model.Plan) error {
|
|
||||||
drawings := make([]*common.Drawing, 0)
|
|
||||||
|
|
||||||
jobPen := common.NewPen(common.StyleSingleLine, 96)
|
|
||||||
arrowPen := common.NewPen(common.StyleNoLine, 97)
|
|
||||||
for i, stage := range plan.Stages {
|
|
||||||
if i > 0 {
|
|
||||||
drawings = append(drawings, arrowPen.DrawArrow())
|
|
||||||
}
|
|
||||||
|
|
||||||
ids := make([]string, 0)
|
|
||||||
for _, r := range stage.Runs {
|
|
||||||
ids = append(ids, r.String())
|
|
||||||
}
|
|
||||||
drawings = append(drawings, jobPen.DrawBoxes(ids...))
|
|
||||||
}
|
|
||||||
|
|
||||||
maxWidth := 0
|
|
||||||
for _, d := range drawings {
|
|
||||||
if d.GetWidth() > maxWidth {
|
|
||||||
maxWidth = d.GetWidth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range drawings {
|
|
||||||
d.Draw(os.Stdout, maxWidth)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
113
cmd/input.go
113
cmd/input.go
|
@ -1,113 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Input contains the input for the root command
|
|
||||||
type Input struct {
|
|
||||||
actor string
|
|
||||||
workdir string
|
|
||||||
workflowsPath string
|
|
||||||
autodetectEvent bool
|
|
||||||
eventPath string
|
|
||||||
reuseContainers bool
|
|
||||||
bindWorkdir bool
|
|
||||||
secrets []string
|
|
||||||
vars []string
|
|
||||||
envs []string
|
|
||||||
inputs []string
|
|
||||||
platforms []string
|
|
||||||
dryrun bool
|
|
||||||
forcePull bool
|
|
||||||
forceRebuild bool
|
|
||||||
noOutput bool
|
|
||||||
envfile string
|
|
||||||
inputfile string
|
|
||||||
secretfile string
|
|
||||||
varfile string
|
|
||||||
insecureSecrets bool
|
|
||||||
defaultBranch string
|
|
||||||
privileged bool
|
|
||||||
usernsMode string
|
|
||||||
containerArchitecture string
|
|
||||||
containerDaemonSocket string
|
|
||||||
containerOptions string
|
|
||||||
noWorkflowRecurse bool
|
|
||||||
useGitIgnore bool
|
|
||||||
githubInstance string
|
|
||||||
containerCapAdd []string
|
|
||||||
containerCapDrop []string
|
|
||||||
autoRemove bool
|
|
||||||
artifactServerPath string
|
|
||||||
artifactServerAddr string
|
|
||||||
artifactServerPort string
|
|
||||||
noCacheServer bool
|
|
||||||
cacheServerPath string
|
|
||||||
cacheServerAddr string
|
|
||||||
cacheServerPort uint16
|
|
||||||
jsonLogger bool
|
|
||||||
noSkipCheckout bool
|
|
||||||
remoteName string
|
|
||||||
replaceGheActionWithGithubCom []string
|
|
||||||
replaceGheActionTokenWithGithubCom string
|
|
||||||
matrix []string
|
|
||||||
actionCachePath string
|
|
||||||
actionOfflineMode bool
|
|
||||||
logPrefixJobID bool
|
|
||||||
networkName string
|
|
||||||
useNewActionCache bool
|
|
||||||
secret string
|
|
||||||
|
|
||||||
localRepository []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Input) resolve(path string) string {
|
|
||||||
basedir, err := filepath.Abs(i.workdir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if path == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(path) {
|
|
||||||
path = filepath.Join(basedir, path)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Envfile returns path to .env
|
|
||||||
func (i *Input) Envfile() string {
|
|
||||||
return i.resolve(i.envfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Secretfile returns path to secrets
|
|
||||||
func (i *Input) Secretfile() string {
|
|
||||||
return i.resolve(i.secretfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Input) Varfile() string {
|
|
||||||
return i.resolve(i.varfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workdir returns path to workdir
|
|
||||||
func (i *Input) Workdir() string {
|
|
||||||
return i.resolve(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkflowsPath returns path to workflow file(s)
|
|
||||||
func (i *Input) WorkflowsPath() string {
|
|
||||||
return i.resolve(i.workflowsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventPath returns the path to events file
|
|
||||||
func (i *Input) EventPath() string {
|
|
||||||
return i.resolve(i.eventPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inputfile returns the path to the input file
|
|
||||||
func (i *Input) Inputfile() string {
|
|
||||||
return i.resolve(i.inputfile)
|
|
||||||
}
|
|
107
cmd/list.go
107
cmd/list.go
|
@ -1,107 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func printList(plan *model.Plan) error {
|
|
||||||
type lineInfoDef struct {
|
|
||||||
jobID string
|
|
||||||
jobName string
|
|
||||||
stage string
|
|
||||||
wfName string
|
|
||||||
wfFile string
|
|
||||||
events string
|
|
||||||
}
|
|
||||||
lineInfos := []lineInfoDef{}
|
|
||||||
|
|
||||||
header := lineInfoDef{
|
|
||||||
jobID: "Job ID",
|
|
||||||
jobName: "Job name",
|
|
||||||
stage: "Stage",
|
|
||||||
wfName: "Workflow name",
|
|
||||||
wfFile: "Workflow file",
|
|
||||||
events: "Events",
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs := map[string]bool{}
|
|
||||||
duplicateJobIDs := false
|
|
||||||
|
|
||||||
jobIDMaxWidth := len(header.jobID)
|
|
||||||
jobNameMaxWidth := len(header.jobName)
|
|
||||||
stageMaxWidth := len(header.stage)
|
|
||||||
wfNameMaxWidth := len(header.wfName)
|
|
||||||
wfFileMaxWidth := len(header.wfFile)
|
|
||||||
eventsMaxWidth := len(header.events)
|
|
||||||
|
|
||||||
for i, stage := range plan.Stages {
|
|
||||||
for _, r := range stage.Runs {
|
|
||||||
jobID := r.JobID
|
|
||||||
line := lineInfoDef{
|
|
||||||
jobID: jobID,
|
|
||||||
jobName: r.String(),
|
|
||||||
stage: strconv.Itoa(i),
|
|
||||||
wfName: r.Workflow.Name,
|
|
||||||
wfFile: r.Workflow.File,
|
|
||||||
events: strings.Join(r.Workflow.On(), `,`),
|
|
||||||
}
|
|
||||||
if _, ok := jobs[jobID]; ok {
|
|
||||||
duplicateJobIDs = true
|
|
||||||
} else {
|
|
||||||
jobs[jobID] = true
|
|
||||||
}
|
|
||||||
lineInfos = append(lineInfos, line)
|
|
||||||
if jobIDMaxWidth < len(line.jobID) {
|
|
||||||
jobIDMaxWidth = len(line.jobID)
|
|
||||||
}
|
|
||||||
if jobNameMaxWidth < len(line.jobName) {
|
|
||||||
jobNameMaxWidth = len(line.jobName)
|
|
||||||
}
|
|
||||||
if stageMaxWidth < len(line.stage) {
|
|
||||||
stageMaxWidth = len(line.stage)
|
|
||||||
}
|
|
||||||
if wfNameMaxWidth < len(line.wfName) {
|
|
||||||
wfNameMaxWidth = len(line.wfName)
|
|
||||||
}
|
|
||||||
if wfFileMaxWidth < len(line.wfFile) {
|
|
||||||
wfFileMaxWidth = len(line.wfFile)
|
|
||||||
}
|
|
||||||
if eventsMaxWidth < len(line.events) {
|
|
||||||
eventsMaxWidth = len(line.events)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jobIDMaxWidth += 2
|
|
||||||
jobNameMaxWidth += 2
|
|
||||||
stageMaxWidth += 2
|
|
||||||
wfNameMaxWidth += 2
|
|
||||||
wfFileMaxWidth += 2
|
|
||||||
|
|
||||||
fmt.Printf("%*s%*s%*s%*s%*s%*s\n",
|
|
||||||
-stageMaxWidth, header.stage,
|
|
||||||
-jobIDMaxWidth, header.jobID,
|
|
||||||
-jobNameMaxWidth, header.jobName,
|
|
||||||
-wfNameMaxWidth, header.wfName,
|
|
||||||
-wfFileMaxWidth, header.wfFile,
|
|
||||||
-eventsMaxWidth, header.events,
|
|
||||||
)
|
|
||||||
for _, line := range lineInfos {
|
|
||||||
fmt.Printf("%*s%*s%*s%*s%*s%*s\n",
|
|
||||||
-stageMaxWidth, line.stage,
|
|
||||||
-jobIDMaxWidth, line.jobID,
|
|
||||||
-jobNameMaxWidth, line.jobName,
|
|
||||||
-wfNameMaxWidth, line.wfName,
|
|
||||||
-wfFileMaxWidth, line.wfFile,
|
|
||||||
-eventsMaxWidth, line.events,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if duplicateJobIDs {
|
|
||||||
fmt.Print("\nDetected multiple jobs with the same job name, use `-W` to specify the path to the specific workflow.\n")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
140
cmd/notices.go
140
cmd/notices.go
|
@ -1,140 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Notice struct {
|
|
||||||
Level string `json:"level"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayNotices(input *Input) {
|
|
||||||
select {
|
|
||||||
case notices := <-noticesLoaded:
|
|
||||||
if len(notices) > 0 {
|
|
||||||
noticeLogger := log.New()
|
|
||||||
if input.jsonLogger {
|
|
||||||
noticeLogger.SetFormatter(&log.JSONFormatter{})
|
|
||||||
} else {
|
|
||||||
noticeLogger.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableQuote: true,
|
|
||||||
DisableTimestamp: true,
|
|
||||||
PadLevelText: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\n")
|
|
||||||
for _, notice := range notices {
|
|
||||||
level, err := log.ParseLevel(notice.Level)
|
|
||||||
if err != nil {
|
|
||||||
level = log.InfoLevel
|
|
||||||
}
|
|
||||||
noticeLogger.Log(level, notice.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second * 1):
|
|
||||||
log.Debugf("Timeout waiting for notices")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var noticesLoaded = make(chan []Notice)
|
|
||||||
|
|
||||||
func loadVersionNotices(version string) {
|
|
||||||
go func() {
|
|
||||||
noticesLoaded <- getVersionNotices(version)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
const NoticeURL = "https://api.nektosact.com/notices"
|
|
||||||
|
|
||||||
func getVersionNotices(version string) []Notice {
|
|
||||||
if os.Getenv("ACT_DISABLE_VERSION_CHECK") == "1" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
noticeURL, err := url.Parse(NoticeURL)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
query := noticeURL.Query()
|
|
||||||
query.Add("os", runtime.GOOS)
|
|
||||||
query.Add("arch", runtime.GOARCH)
|
|
||||||
query.Add("version", version)
|
|
||||||
|
|
||||||
noticeURL.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest("GET", noticeURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
etag := loadNoticesEtag()
|
|
||||||
if etag != "" {
|
|
||||||
log.Debugf("Conditional GET for notices etag=%s", etag)
|
|
||||||
req.Header.Set("If-None-Match", etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newEtag := resp.Header.Get("Etag")
|
|
||||||
if newEtag != "" {
|
|
||||||
log.Debugf("Saving notices etag=%s", newEtag)
|
|
||||||
saveNoticesEtag(newEtag)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
notices := []Notice{}
|
|
||||||
if resp.StatusCode == 304 {
|
|
||||||
log.Debug("No new notices")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(¬ices); err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return notices
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadNoticesEtag() string {
|
|
||||||
p := etagPath()
|
|
||||||
content, err := os.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Unable to load etag from %s: %e", p, err)
|
|
||||||
}
|
|
||||||
return strings.TrimSuffix(string(content), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveNoticesEtag(etag string) {
|
|
||||||
p := etagPath()
|
|
||||||
err := os.WriteFile(p, []byte(strings.TrimSuffix(etag, "\n")), 0o600)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Unable to save etag to %s: %e", p, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func etagPath() string {
|
|
||||||
dir := filepath.Join(CacheHomeDir, "act")
|
|
||||||
if err := os.MkdirAll(dir, 0o777); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return filepath.Join(dir, ".notices.etag")
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (i *Input) newPlatforms() map[string]string {
|
|
||||||
platforms := map[string]string{
|
|
||||||
"ubuntu-latest": "node:16-buster-slim",
|
|
||||||
"ubuntu-22.04": "node:16-bullseye-slim",
|
|
||||||
"ubuntu-20.04": "node:16-buster-slim",
|
|
||||||
"ubuntu-18.04": "node:16-buster-slim",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range i.platforms {
|
|
||||||
pParts := strings.Split(p, "=")
|
|
||||||
if len(pParts) == 2 {
|
|
||||||
platforms[strings.ToLower(pParts[0])] = pParts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return platforms
|
|
||||||
}
|
|
781
cmd/root.go
781
cmd/root.go
|
@ -1,781 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/adrg/xdg"
|
|
||||||
"github.com/andreaskoch/go-fswatch"
|
|
||||||
docker_container "github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
gitignore "github.com/sabhiram/go-gitignore"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/artifactcache"
|
|
||||||
"github.com/nektos/act/pkg/artifacts"
|
|
||||||
"github.com/nektos/act/pkg/common"
|
|
||||||
"github.com/nektos/act/pkg/container"
|
|
||||||
"github.com/nektos/act/pkg/model"
|
|
||||||
"github.com/nektos/act/pkg/runner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute is the entry point to running the CLI
|
|
||||||
func Execute(ctx context.Context, version string) {
|
|
||||||
input := new(Input)
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "act [event name to run] [flags]\n\nIf no event name passed, will default to \"on: push\"\nIf actions handles only one event it will be used as default instead of \"on: push\"",
|
|
||||||
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
|
|
||||||
Args: cobra.MaximumNArgs(1),
|
|
||||||
RunE: newRunCommand(ctx, input),
|
|
||||||
PersistentPreRun: setup(input),
|
|
||||||
PersistentPostRun: cleanup(input),
|
|
||||||
Version: version,
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
rootCmd.Flags().BoolP("watch", "w", false, "watch the contents of the local repo and run when files change")
|
|
||||||
rootCmd.Flags().BoolP("list", "l", false, "list workflows")
|
|
||||||
rootCmd.Flags().BoolP("graph", "g", false, "draw workflows")
|
|
||||||
rootCmd.Flags().StringP("job", "j", "", "run a specific job ID")
|
|
||||||
rootCmd.Flags().BoolP("bug-report", "", false, "Display system information for bug report")
|
|
||||||
|
|
||||||
rootCmd.Flags().StringVar(&input.remoteName, "remote-name", "origin", "git remote name that will be used to retrieve url of git repo")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)")
|
|
||||||
rootCmd.Flags().StringArrayVar(&input.vars, "var", []string{}, "variable to make available to actions with optional value (e.g. --var myvar=foo or --var myvar)")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.inputs, "input", "", []string{}, "action input to make available to actions (e.g. --input myinput=foo)")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)")
|
|
||||||
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs")
|
|
||||||
rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy")
|
|
||||||
rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", true, "pull docker image(s) even if already present")
|
|
||||||
rootCmd.Flags().BoolVarP(&input.forceRebuild, "rebuild", "", true, "rebuild local action docker image(s) even if already present")
|
|
||||||
rootCmd.Flags().BoolVarP(&input.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow")
|
|
||||||
rootCmd.Flags().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file")
|
|
||||||
rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch")
|
|
||||||
rootCmd.Flags().BoolVar(&input.privileged, "privileged", false, "use privileged mode")
|
|
||||||
rootCmd.Flags().StringVar(&input.usernsMode, "userns", "", "user namespace to use")
|
|
||||||
rootCmd.Flags().BoolVar(&input.useGitIgnore, "use-gitignore", true, "Controls whether paths specified in .gitignore should be copied into container")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)")
|
|
||||||
rootCmd.Flags().BoolVar(&input.autoRemove, "rm", false, "automatically remove container(s)/volume(s) after a workflow(s) failure")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.replaceGheActionWithGithubCom, "replace-ghe-action-with-github-com", "", []string{}, "If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)")
|
|
||||||
rootCmd.Flags().StringVar(&input.replaceGheActionTokenWithGithubCom, "replace-ghe-action-token-with-github-com", "", "If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token")
|
|
||||||
rootCmd.Flags().StringArrayVarP(&input.matrix, "matrix", "", []string{}, "specify which matrix configuration to include (e.g. --matrix java:13")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.actor, "actor", "a", "nektos/act", "user that triggered the event")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow file(s)")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.workdir, "directory", "C", ".", "working directory")
|
|
||||||
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
|
|
||||||
rootCmd.PersistentFlags().BoolVar(&input.jsonLogger, "json", false, "Output logs in json format")
|
|
||||||
rootCmd.PersistentFlags().BoolVar(&input.logPrefixJobID, "log-prefix-job-id", false, "Output the job id within non-json logs instead of the entire name")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "disable container creation, validates only workflow correctness")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.varfile, "var-file", "", ".vars", "file with list of vars to read from (e.g. --var-file .vars)")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.inputfile, "input-file", "", ".input", "input file to read and use as action input")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.containerDaemonSocket, "container-daemon-socket", "", "", "URI to Docker Engine socket (e.g.: unix://~/.docker/run/docker.sock or - to disable bind mounting the socket)")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.containerOptions, "container-options", "", "", "Custom docker container options for the job container without an options property in the job definition")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.githubInstance, "github-instance", "", "github.com", "GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.artifactServerPath, "artifact-server-path", "", "", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.artifactServerAddr, "artifact-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the artifact server binds.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens.")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.noCacheServer, "no-cache-server", "", false, "Disable cache server")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.cacheServerPath, "cache-server-path", "", filepath.Join(CacheHomeDir, "actcache"), "Defines the path where the cache server stores caches.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.cacheServerAddr, "cache-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the cache server binds.")
|
|
||||||
rootCmd.PersistentFlags().Uint16VarP(&input.cacheServerPort, "cache-server-port", "", 0, "Defines the port where the artifact server listens. 0 means a randomly available port.")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.actionOfflineMode, "action-offline-mode", "", false, "If action contents exists, it will not be fetch and pull again. If turn on this,will turn off force pull")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&input.networkName, "network", "", "host", "Sets a docker network name. Defaults to host.")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&input.useNewActionCache, "use-new-action-cache", "", false, "Enable using the new Action Cache for storing Actions locally")
|
|
||||||
rootCmd.PersistentFlags().StringArrayVarP(&input.localRepository, "local-repository", "", []string{}, "Replaces the specified repository and ref with a local folder (e.g. https://github.com/test/test@v0=/home/act/test or test/test@v0=/home/act/test, the latter matches any hosts or protocols)")
|
|
||||||
rootCmd.SetArgs(args())
|
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return locations where Act's config can be found in order: XDG spec, .actrc in HOME directory, .actrc in invocation directory
|
|
||||||
func configLocations() []string {
|
|
||||||
configFileName := ".actrc"
|
|
||||||
|
|
||||||
homePath := filepath.Join(UserHomeDir, configFileName)
|
|
||||||
invocationPath := filepath.Join(".", configFileName)
|
|
||||||
|
|
||||||
// Though named xdg, adrg's lib support macOS and Windows config paths as well
|
|
||||||
// It also takes cares of creating the parent folder so we don't need to bother later
|
|
||||||
specPath, err := xdg.ConfigFile("act/actrc")
|
|
||||||
if err != nil {
|
|
||||||
specPath = homePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// This order should be enforced since the survey part relies on it
|
|
||||||
return []string{specPath, homePath, invocationPath}
|
|
||||||
}
|
|
||||||
|
|
||||||
var commonSocketPaths = []string{
|
|
||||||
"/var/run/docker.sock",
|
|
||||||
"/run/podman/podman.sock",
|
|
||||||
"$HOME/.colima/docker.sock",
|
|
||||||
"$XDG_RUNTIME_DIR/docker.sock",
|
|
||||||
"$XDG_RUNTIME_DIR/podman/podman.sock",
|
|
||||||
`\\.\pipe\docker_engine`,
|
|
||||||
"$HOME/.docker/run/docker.sock",
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns socket path or false if not found any
|
|
||||||
func socketLocation() (string, bool) {
|
|
||||||
if dockerHost, exists := os.LookupEnv("DOCKER_HOST"); exists {
|
|
||||||
return dockerHost, true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range commonSocketPaths {
|
|
||||||
if _, err := os.Lstat(os.ExpandEnv(p)); err == nil {
|
|
||||||
if strings.HasPrefix(p, `\\.\`) {
|
|
||||||
return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), true
|
|
||||||
}
|
|
||||||
return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func args() []string {
|
|
||||||
actrc := configLocations()
|
|
||||||
|
|
||||||
args := make([]string, 0)
|
|
||||||
for _, f := range actrc {
|
|
||||||
args = append(args, readArgsFile(f, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, os.Args[1:]...)
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func bugReport(ctx context.Context, version string) error {
|
|
||||||
sprintf := func(key, val string) string {
|
|
||||||
return fmt.Sprintf("%-24s%s\n", key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
report := sprintf("act version:", version)
|
|
||||||
report += sprintf("GOOS:", runtime.GOOS)
|
|
||||||
report += sprintf("GOARCH:", runtime.GOARCH)
|
|
||||||
report += sprintf("NumCPU:", fmt.Sprint(runtime.NumCPU()))
|
|
||||||
|
|
||||||
var dockerHost string
|
|
||||||
var exists bool
|
|
||||||
if dockerHost, exists = os.LookupEnv("DOCKER_HOST"); !exists {
|
|
||||||
dockerHost = "DOCKER_HOST environment variable is not set"
|
|
||||||
} else if dockerHost == "" {
|
|
||||||
dockerHost = "DOCKER_HOST environment variable is empty."
|
|
||||||
}
|
|
||||||
|
|
||||||
report += sprintf("Docker host:", dockerHost)
|
|
||||||
report += fmt.Sprintln("Sockets found:")
|
|
||||||
for _, p := range commonSocketPaths {
|
|
||||||
if _, err := os.Lstat(os.ExpandEnv(p)); err != nil {
|
|
||||||
continue
|
|
||||||
} else if _, err := os.Stat(os.ExpandEnv(p)); err != nil {
|
|
||||||
report += fmt.Sprintf("\t%s(broken)\n", p)
|
|
||||||
} else {
|
|
||||||
report += fmt.Sprintf("\t%s\n", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
report += sprintf("Config files:", "")
|
|
||||||
for _, c := range configLocations() {
|
|
||||||
args := readArgsFile(c, false)
|
|
||||||
if len(args) > 0 {
|
|
||||||
report += fmt.Sprintf("\t%s:\n", c)
|
|
||||||
for _, l := range args {
|
|
||||||
report += fmt.Sprintf("\t\t%s\n", l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vcs, ok := debug.ReadBuildInfo()
|
|
||||||
if ok && vcs != nil {
|
|
||||||
report += fmt.Sprintln("Build info:")
|
|
||||||
vcs := *vcs
|
|
||||||
report += sprintf("\tGo version:", vcs.GoVersion)
|
|
||||||
report += sprintf("\tModule path:", vcs.Path)
|
|
||||||
report += sprintf("\tMain version:", vcs.Main.Version)
|
|
||||||
report += sprintf("\tMain path:", vcs.Main.Path)
|
|
||||||
report += sprintf("\tMain checksum:", vcs.Main.Sum)
|
|
||||||
|
|
||||||
report += fmt.Sprintln("\tBuild settings:")
|
|
||||||
for _, set := range vcs.Settings {
|
|
||||||
report += sprintf(fmt.Sprintf("\t\t%s:", set.Key), set.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := container.GetHostInfo(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(report)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
report += fmt.Sprintln("Docker Engine:")
|
|
||||||
|
|
||||||
report += sprintf("\tEngine version:", info.ServerVersion)
|
|
||||||
report += sprintf("\tEngine runtime:", info.DefaultRuntime)
|
|
||||||
report += sprintf("\tCgroup version:", info.CgroupVersion)
|
|
||||||
report += sprintf("\tCgroup driver:", info.CgroupDriver)
|
|
||||||
report += sprintf("\tStorage driver:", info.Driver)
|
|
||||||
report += sprintf("\tRegistry URI:", info.IndexServerAddress)
|
|
||||||
|
|
||||||
report += sprintf("\tOS:", info.OperatingSystem)
|
|
||||||
report += sprintf("\tOS type:", info.OSType)
|
|
||||||
report += sprintf("\tOS version:", info.OSVersion)
|
|
||||||
report += sprintf("\tOS arch:", info.Architecture)
|
|
||||||
report += sprintf("\tOS kernel:", info.KernelVersion)
|
|
||||||
report += sprintf("\tOS CPU:", fmt.Sprint(info.NCPU))
|
|
||||||
report += sprintf("\tOS memory:", fmt.Sprintf("%d MB", info.MemTotal/1024/1024))
|
|
||||||
|
|
||||||
report += fmt.Sprintln("\tSecurity options:")
|
|
||||||
for _, secopt := range info.SecurityOptions {
|
|
||||||
report += fmt.Sprintf("\t\t%s\n", secopt)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(report)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readArgsFile(file string, split bool) []string {
|
|
||||||
args := make([]string, 0)
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err := f.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to close args file: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
arg := os.ExpandEnv(strings.TrimSpace(scanner.Text()))
|
|
||||||
|
|
||||||
if strings.HasPrefix(arg, "-") && split {
|
|
||||||
args = append(args, regexp.MustCompile(`\s`).Split(arg, 2)...)
|
|
||||||
} else if !split {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func setup(_ *Input) func(*cobra.Command, []string) {
|
|
||||||
return func(cmd *cobra.Command, _ []string) {
|
|
||||||
verbose, _ := cmd.Flags().GetBool("verbose")
|
|
||||||
if verbose {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
loadVersionNotices(cmd.Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanup(inputs *Input) func(*cobra.Command, []string) {
|
|
||||||
return func(cmd *cobra.Command, _ []string) {
|
|
||||||
displayNotices(inputs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEnvs(env []string) map[string]string {
|
|
||||||
envs := make(map[string]string, len(env))
|
|
||||||
for _, envVar := range env {
|
|
||||||
e := strings.SplitN(envVar, `=`, 2)
|
|
||||||
if len(e) == 2 {
|
|
||||||
envs[e[0]] = e[1]
|
|
||||||
} else {
|
|
||||||
envs[e[0]] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return envs
|
|
||||||
}
|
|
||||||
|
|
||||||
func readYamlFile(file string) (map[string]string, error) {
|
|
||||||
content, err := os.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret := map[string]string{}
|
|
||||||
if err = yaml.Unmarshal(content, &ret); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readEnvs(path string, envs map[string]string) bool {
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
var env map[string]string
|
|
||||||
if ext := filepath.Ext(path); ext == ".yml" || ext == ".yaml" {
|
|
||||||
env, err = readYamlFile(path)
|
|
||||||
} else {
|
|
||||||
env, err = godotenv.Read(path)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error loading from %s: %v", path, err)
|
|
||||||
}
|
|
||||||
for k, v := range env {
|
|
||||||
if _, ok := envs[k]; !ok {
|
|
||||||
envs[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMatrix(matrix []string) map[string]map[string]bool {
|
|
||||||
// each matrix entry should be of the form - string:string
|
|
||||||
r := regexp.MustCompile(":")
|
|
||||||
matrixes := make(map[string]map[string]bool)
|
|
||||||
for _, m := range matrix {
|
|
||||||
matrix := r.Split(m, 2)
|
|
||||||
if len(matrix) < 2 {
|
|
||||||
log.Fatalf("Invalid matrix format. Failed to parse %s", m)
|
|
||||||
}
|
|
||||||
if _, ok := matrixes[matrix[0]]; !ok {
|
|
||||||
matrixes[matrix[0]] = make(map[string]bool)
|
|
||||||
}
|
|
||||||
matrixes[matrix[0]][matrix[1]] = true
|
|
||||||
}
|
|
||||||
return matrixes
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDockerHostURI(daemonPath string) bool {
|
|
||||||
if protoIndex := strings.Index(daemonPath, "://"); protoIndex != -1 {
|
|
||||||
scheme := daemonPath[:protoIndex]
|
|
||||||
if strings.IndexFunc(scheme, func(r rune) bool {
|
|
||||||
return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z')
|
|
||||||
}) == -1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error {
|
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
|
||||||
if input.jsonLogger {
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, _ := cmd.Flags().GetBool("bug-report"); ok {
|
|
||||||
return bugReport(ctx, cmd.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer DOCKER_HOST, don't override it
|
|
||||||
socketPath, hasDockerHost := os.LookupEnv("DOCKER_HOST")
|
|
||||||
if !hasDockerHost {
|
|
||||||
// a - in containerDaemonSocket means don't mount, preserve this value
|
|
||||||
// otherwise if input.containerDaemonSocket is a filepath don't use it as socketPath
|
|
||||||
skipMount := input.containerDaemonSocket == "-" || !isDockerHostURI(input.containerDaemonSocket)
|
|
||||||
if input.containerDaemonSocket != "" && !skipMount {
|
|
||||||
socketPath = input.containerDaemonSocket
|
|
||||||
} else {
|
|
||||||
socket, found := socketLocation()
|
|
||||||
if !found {
|
|
||||||
log.Errorln("daemon Docker Engine socket not found and containerDaemonSocket option was not set")
|
|
||||||
} else {
|
|
||||||
socketPath = socket
|
|
||||||
}
|
|
||||||
if !skipMount {
|
|
||||||
input.containerDaemonSocket = socketPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Setenv("DOCKER_HOST", socketPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && input.containerArchitecture == "" {
|
|
||||||
l := log.New()
|
|
||||||
l.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableQuote: true,
|
|
||||||
DisableTimestamp: true,
|
|
||||||
})
|
|
||||||
l.Warnf(" \U000026A0 You are using Apple M-series chip and you have not specified container architecture, you might encounter issues while running act. If so, try running it with '--container-architecture linux/amd64'. \U000026A0 \n")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Loading environment from %s", input.Envfile())
|
|
||||||
envs := parseEnvs(input.envs)
|
|
||||||
_ = readEnvs(input.Envfile(), envs)
|
|
||||||
|
|
||||||
log.Debugf("Loading action inputs from %s", input.Inputfile())
|
|
||||||
inputs := parseEnvs(input.inputs)
|
|
||||||
_ = readEnvs(input.Inputfile(), inputs)
|
|
||||||
|
|
||||||
log.Debugf("Loading secrets from %s", input.Secretfile())
|
|
||||||
secrets := newSecrets(input.secrets)
|
|
||||||
_ = readEnvs(input.Secretfile(), secrets)
|
|
||||||
|
|
||||||
log.Debugf("Loading vars from %s", input.Varfile())
|
|
||||||
vars := newSecrets(input.vars)
|
|
||||||
_ = readEnvs(input.Varfile(), vars)
|
|
||||||
|
|
||||||
matrixes := parseMatrix(input.matrix)
|
|
||||||
log.Debugf("Evaluated matrix inclusions: %v", matrixes)
|
|
||||||
|
|
||||||
planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), input.noWorkflowRecurse)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
jobID, err := cmd.Flags().GetString("job")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we should just list the workflows
|
|
||||||
list, err := cmd.Flags().GetBool("list")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we should just draw the graph
|
|
||||||
graph, err := cmd.Flags().GetBool("graph")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect all events from loaded workflows
|
|
||||||
events := planner.GetEvents()
|
|
||||||
|
|
||||||
// plan with filtered jobs - to be used for filtering only
|
|
||||||
var filterPlan *model.Plan
|
|
||||||
|
|
||||||
// Determine the event name to be filtered
|
|
||||||
var filterEventName string
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
log.Debugf("Using first passed in arguments event for filtering: %s", args[0])
|
|
||||||
filterEventName = args[0]
|
|
||||||
} else if input.autodetectEvent && len(events) > 0 && len(events[0]) > 0 {
|
|
||||||
// set default event type to first event from many available
|
|
||||||
// this way user dont have to specify the event.
|
|
||||||
log.Debugf("Using first detected workflow event for filtering: %s", events[0])
|
|
||||||
filterEventName = events[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var plannerErr error
|
|
||||||
if jobID != "" {
|
|
||||||
log.Debugf("Preparing plan with a job: %s", jobID)
|
|
||||||
filterPlan, plannerErr = planner.PlanJob(jobID)
|
|
||||||
} else if filterEventName != "" {
|
|
||||||
log.Debugf("Preparing plan for a event: %s", filterEventName)
|
|
||||||
filterPlan, plannerErr = planner.PlanEvent(filterEventName)
|
|
||||||
} else {
|
|
||||||
log.Debugf("Preparing plan with all jobs")
|
|
||||||
filterPlan, plannerErr = planner.PlanAll()
|
|
||||||
}
|
|
||||||
if filterPlan == nil && plannerErr != nil {
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if list {
|
|
||||||
err = printList(filterPlan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if graph {
|
|
||||||
err = drawGraph(filterPlan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// plan with triggered jobs
|
|
||||||
var plan *model.Plan
|
|
||||||
|
|
||||||
// Determine the event name to be triggered
|
|
||||||
var eventName string
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
log.Debugf("Using first passed in arguments event: %s", args[0])
|
|
||||||
eventName = args[0]
|
|
||||||
} else if len(events) == 1 && len(events[0]) > 0 {
|
|
||||||
log.Debugf("Using the only detected workflow event: %s", events[0])
|
|
||||||
eventName = events[0]
|
|
||||||
} else if input.autodetectEvent && len(events) > 0 && len(events[0]) > 0 {
|
|
||||||
// set default event type to first event from many available
|
|
||||||
// this way user dont have to specify the event.
|
|
||||||
log.Debugf("Using first detected workflow event: %s", events[0])
|
|
||||||
eventName = events[0]
|
|
||||||
} else {
|
|
||||||
log.Debugf("Using default workflow event: push")
|
|
||||||
eventName = "push"
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the plan for this run
|
|
||||||
if jobID != "" {
|
|
||||||
log.Debugf("Planning job: %s", jobID)
|
|
||||||
plan, plannerErr = planner.PlanJob(jobID)
|
|
||||||
} else {
|
|
||||||
log.Debugf("Planning jobs for event: %s", eventName)
|
|
||||||
plan, plannerErr = planner.PlanEvent(eventName)
|
|
||||||
}
|
|
||||||
if plan != nil {
|
|
||||||
if len(plan.Stages) == 0 {
|
|
||||||
plannerErr = fmt.Errorf("Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if plan == nil && plannerErr != nil {
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if the main branch was defined
|
|
||||||
defaultbranch, err := cmd.Flags().GetString("defaultbranch")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if platforms flag is set, if not, run default image survey
|
|
||||||
if len(input.platforms) == 0 {
|
|
||||||
cfgFound := false
|
|
||||||
cfgLocations := configLocations()
|
|
||||||
for _, v := range cfgLocations {
|
|
||||||
_, err := os.Stat(v)
|
|
||||||
if os.IsExist(err) {
|
|
||||||
cfgFound = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !cfgFound && len(cfgLocations) > 0 {
|
|
||||||
// The first config location refers to the global config folder one
|
|
||||||
if err := defaultImageSurvey(cfgLocations[0]); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
input.platforms = readArgsFile(cfgLocations[0], true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deprecationWarning := "--%s is deprecated and will be removed soon, please switch to cli: `--container-options \"%[2]s\"` or `.actrc`: `--container-options %[2]s`."
|
|
||||||
if input.privileged {
|
|
||||||
log.Warnf(deprecationWarning, "privileged", "--privileged")
|
|
||||||
}
|
|
||||||
if len(input.usernsMode) > 0 {
|
|
||||||
log.Warnf(deprecationWarning, "userns", fmt.Sprintf("--userns=%s", input.usernsMode))
|
|
||||||
}
|
|
||||||
if len(input.containerCapAdd) > 0 {
|
|
||||||
log.Warnf(deprecationWarning, "container-cap-add", fmt.Sprintf("--cap-add=%s", input.containerCapAdd))
|
|
||||||
}
|
|
||||||
if len(input.containerCapDrop) > 0 {
|
|
||||||
log.Warnf(deprecationWarning, "container-cap-drop", fmt.Sprintf("--cap-drop=%s", input.containerCapDrop))
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the plan
|
|
||||||
config := &runner.Config{
|
|
||||||
Actor: input.actor,
|
|
||||||
EventName: eventName,
|
|
||||||
EventPath: input.EventPath(),
|
|
||||||
DefaultBranch: defaultbranch,
|
|
||||||
ForcePull: !input.actionOfflineMode && input.forcePull,
|
|
||||||
ForceRebuild: input.forceRebuild,
|
|
||||||
ReuseContainers: input.reuseContainers,
|
|
||||||
Workdir: input.Workdir(),
|
|
||||||
ActionCacheDir: input.actionCachePath,
|
|
||||||
ActionOfflineMode: input.actionOfflineMode,
|
|
||||||
BindWorkdir: input.bindWorkdir,
|
|
||||||
LogOutput: !input.noOutput,
|
|
||||||
JSONLogger: input.jsonLogger,
|
|
||||||
LogPrefixJobID: input.logPrefixJobID,
|
|
||||||
Env: envs,
|
|
||||||
Secrets: secrets,
|
|
||||||
Vars: vars,
|
|
||||||
Inputs: inputs,
|
|
||||||
Token: secrets["GITHUB_TOKEN"],
|
|
||||||
InsecureSecrets: input.insecureSecrets,
|
|
||||||
Platforms: input.newPlatforms(),
|
|
||||||
Privileged: input.privileged,
|
|
||||||
UsernsMode: input.usernsMode,
|
|
||||||
ContainerArchitecture: input.containerArchitecture,
|
|
||||||
ContainerDaemonSocket: input.containerDaemonSocket,
|
|
||||||
ContainerOptions: input.containerOptions,
|
|
||||||
UseGitIgnore: input.useGitIgnore,
|
|
||||||
GitHubInstance: input.githubInstance,
|
|
||||||
ContainerCapAdd: input.containerCapAdd,
|
|
||||||
ContainerCapDrop: input.containerCapDrop,
|
|
||||||
AutoRemove: input.autoRemove,
|
|
||||||
ArtifactServerPath: input.artifactServerPath,
|
|
||||||
ArtifactServerAddr: input.artifactServerAddr,
|
|
||||||
ArtifactServerPort: input.artifactServerPort,
|
|
||||||
NoSkipCheckout: input.noSkipCheckout,
|
|
||||||
RemoteName: input.remoteName,
|
|
||||||
ReplaceGheActionWithGithubCom: input.replaceGheActionWithGithubCom,
|
|
||||||
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
|
|
||||||
Matrix: matrixes,
|
|
||||||
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
|
|
||||||
}
|
|
||||||
if input.useNewActionCache || len(input.localRepository) > 0 {
|
|
||||||
if input.actionOfflineMode {
|
|
||||||
config.ActionCache = &runner.GoGitActionCacheOfflineMode{
|
|
||||||
Parent: runner.GoGitActionCache{
|
|
||||||
Path: config.ActionCacheDir,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
config.ActionCache = &runner.GoGitActionCache{
|
|
||||||
Path: config.ActionCacheDir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(input.localRepository) > 0 {
|
|
||||||
localRepositories := map[string]string{}
|
|
||||||
for _, l := range input.localRepository {
|
|
||||||
k, v, _ := strings.Cut(l, "=")
|
|
||||||
localRepositories[k] = v
|
|
||||||
}
|
|
||||||
config.ActionCache = &runner.LocalRepositoryCache{
|
|
||||||
Parent: config.ActionCache,
|
|
||||||
LocalRepositories: localRepositories,
|
|
||||||
CacheDirCache: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r, err := runner.New(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel := artifacts.Serve(ctx, input.artifactServerPath, input.artifactServerAddr, input.artifactServerPort)
|
|
||||||
|
|
||||||
const cacheURLKey = "ACTIONS_CACHE_URL"
|
|
||||||
var cacheHandler *artifactcache.Handler
|
|
||||||
if !input.noCacheServer && envs[cacheURLKey] == "" {
|
|
||||||
var err error
|
|
||||||
cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerAddr, input.cacheServerPort, input.secret, common.Logger(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
envs[cacheURLKey] = cacheHandler.ExternalURL() + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = common.WithDryrun(ctx, input.dryrun)
|
|
||||||
if watch, err := cmd.Flags().GetBool("watch"); err != nil {
|
|
||||||
return err
|
|
||||||
} else if watch {
|
|
||||||
err = watchAndRun(ctx, r.NewPlanExecutor(plan))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
|
|
||||||
executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error {
|
|
||||||
cancel()
|
|
||||||
_ = cacheHandler.Close()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
err = executor(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return plannerErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultImageSurvey(actrc string) error {
|
|
||||||
var answer string
|
|
||||||
confirmation := &survey.Select{
|
|
||||||
Message: "Please choose the default image you want to use with act:\n - Large size image: ca. 17GB download + 53.1GB storage, you will need 75GB of free disk space, snapshots of GitHub Hosted Runners without snap and pulled docker images\n - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with most actions\n - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions\n\nDefault image and other options can be changed manually in ~/.actrc (please refer to https://github.com/nektos/act#configuration for additional information about file structure)",
|
|
||||||
Help: "If you want to know why act asks you that, please go to https://github.com/nektos/act/issues/107",
|
|
||||||
Default: "Medium",
|
|
||||||
Options: []string{"Large", "Medium", "Micro"},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := survey.AskOne(confirmation, &answer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var option string
|
|
||||||
switch answer {
|
|
||||||
case "Large":
|
|
||||||
option = "-P ubuntu-latest=catthehacker/ubuntu:full-latest\n-P ubuntu-22.04=catthehacker/ubuntu:full-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:full-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:full-18.04\n"
|
|
||||||
case "Medium":
|
|
||||||
option = "-P ubuntu-latest=catthehacker/ubuntu:act-latest\n-P ubuntu-22.04=catthehacker/ubuntu:act-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:act-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:act-18.04\n"
|
|
||||||
case "Micro":
|
|
||||||
option = "-P ubuntu-latest=node:16-buster-slim\n-P ubuntu-22.04=node:16-bullseye-slim\n-P ubuntu-20.04=node:16-buster-slim\n-P ubuntu-18.04=node:16-buster-slim\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(actrc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = f.WriteString(option)
|
|
||||||
if err != nil {
|
|
||||||
_ = f.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchAndRun(ctx context.Context, fn common.Executor) error {
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreFile := filepath.Join(dir, ".gitignore")
|
|
||||||
ignore := &gitignore.GitIgnore{}
|
|
||||||
if info, err := os.Stat(ignoreFile); err == nil && !info.IsDir() {
|
|
||||||
ignore, err = gitignore.CompileIgnoreFile(ignoreFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("compile %q: %w", ignoreFile, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
folderWatcher := fswatch.NewFolderWatcher(
|
|
||||||
dir,
|
|
||||||
true,
|
|
||||||
ignore.MatchesPath,
|
|
||||||
2, // 2 seconds
|
|
||||||
)
|
|
||||||
|
|
||||||
folderWatcher.Start()
|
|
||||||
defer folderWatcher.Stop()
|
|
||||||
|
|
||||||
// run once before watching
|
|
||||||
if err := fn(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for folderWatcher.IsRunning() {
|
|
||||||
log.Debugf("Watching %s for changes", dir)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
case changes := <-folderWatcher.ChangeDetails():
|
|
||||||
log.Debugf("%s", changes.String())
|
|
||||||
if err := fn(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
|
||||||
|
|
||||||
type secrets map[string]string
|
|
||||||
|
|
||||||
func newSecrets(secretList []string) secrets {
|
|
||||||
s := make(map[string]string)
|
|
||||||
for _, secretPair := range secretList {
|
|
||||||
secretPairParts := strings.SplitN(secretPair, "=", 2)
|
|
||||||
secretPairParts[0] = strings.ToUpper(secretPairParts[0])
|
|
||||||
if strings.ToUpper(s[secretPairParts[0]]) == secretPairParts[0] {
|
|
||||||
log.Errorf("Secret %s is already defined (secrets are case insensitive)", secretPairParts[0])
|
|
||||||
}
|
|
||||||
if len(secretPairParts) == 2 {
|
|
||||||
s[secretPairParts[0]] = secretPairParts[1]
|
|
||||||
} else if env, ok := os.LookupEnv(secretPairParts[0]); ok && env != "" {
|
|
||||||
s[secretPairParts[0]] = env
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Provide value for '%s': ", secretPairParts[0])
|
|
||||||
val, err := term.ReadPassword(int(os.Stdin.Fd()))
|
|
||||||
fmt.Println()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to read input: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
s[secretPairParts[0]] = string(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s secrets) AsMap() map[string]string {
|
|
||||||
return s
|
|
||||||
}
|
|
37
main.go
37
main.go
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/nektos/act/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed VERSION
|
|
||||||
var version string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
// trap Ctrl+C and call cancel on the context
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
||||||
defer func() {
|
|
||||||
signal.Stop(c)
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-c:
|
|
||||||
cancel()
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// run the command
|
|
||||||
cmd.Execute(ctx, version)
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue