diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml index 19abb9d..e16f426 100644 --- a/.github/workflows/commitlint.yaml +++ b/.github/workflows/commitlint.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml index 8990f9f..01560a6 100644 --- a/.github/workflows/dependency-review.yaml +++ b/.github/workflows/dependency-review.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Dependency Review uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4cfa529..c68d757 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,7 +11,7 @@ jobs: steps: # Checkout the repo and setup the tooling for this job - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 @@ -38,7 +38,7 @@ jobs: steps: # Checkout the repo and setup the tooling for this job - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 @@ -78,7 +78,7 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 diff --git a/.github/workflows/scan-codeql.yaml b/.github/workflows/scan-codeql.yaml index 97ad74c..304d889 100644 --- a/.github/workflows/scan-codeql.yaml +++ b/.github/workflows/scan-codeql.yaml @@ -36,7 +36,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup golang uses: ./.github/actions/golang @@ -45,7 +45,7 @@ jobs: run: make build-cli-linux-amd - name: Initialize CodeQL - uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 env: CODEQL_EXTRACTOR_GO_BUILD_TRACING: on with: @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scan-lint.yaml b/.github/workflows/scan-lint.yaml index 8467f11..a0982e8 100644 --- a/.github/workflows/scan-lint.yaml +++ b/.github/workflows/scan-lint.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup golang uses: ./.github/actions/golang @@ -26,7 +26,7 @@ jobs: extra_args: --all-files --verbose # pre-commit run --all-files --verbose - name: Run Revive Action by pulling pre-built image - uses: docker://morphy/revive-action:v2@sha256:540bffd78895d1525b034b861d29edcb96577bcb3b187a5199342dc8656034ee + uses: docker://morphy/revive-action:v2@sha256:2e13c242d68654085351d56a75b1145b4e804cbd465b0f36fbeb7d9760ae49c1 with: config: revive.toml # Exclude patterns, separated by semicolons (optional) diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml index f5560a5..fab8abf 100644 --- a/.github/workflows/scorecard.yaml +++ b/.github/workflows/scorecard.yaml @@ -22,7 +22,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: persist-credentials: false @@ -45,6 +45,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 + uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: sarif_file: results.sarif diff --git a/.github/workflows/test-e2e-pr.yaml b/.github/workflows/test-e2e-pr.yaml index 68cf848..6eff40f 100644 --- a/.github/workflows/test-e2e-pr.yaml +++ b/.github/workflows/test-e2e-pr.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup golang uses: ./.github/actions/golang diff --git a/.github/workflows/test-schema.yaml b/.github/workflows/test-schema.yaml index 8681ae1..12698b7 100644 --- a/.github/workflows/test-schema.yaml +++ b/.github/workflows/test-schema.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup golang uses: ./.github/actions/golang diff --git a/.github/workflows/test-unit-pr.yaml b/.github/workflows/test-unit-pr.yaml index c5caeb8..17d1140 100644 --- a/.github/workflows/test-unit-pr.yaml +++ b/.github/workflows/test-unit-pr.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Setup golang uses: ./.github/actions/golang diff --git a/src/pkg/runner/runner.go b/src/pkg/runner/runner.go index 28496e4..8348c60 100644 --- a/src/pkg/runner/runner.go +++ b/src/pkg/runner/runner.go @@ -6,9 +6,10 @@ package runner import ( "fmt" + "net/url" "os" + "path" "path/filepath" - "regexp" "strings" "github.com/defenseunicorns/maru-runner/src/config" @@ -30,26 +31,36 @@ type Runner struct { // Run runs a task from tasks file func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]string) error { - runner := Runner{ - TasksFile: tasksFile, - TaskNameMap: map[string]bool{}, - variableConfig: GetMaruVariableConfig(), - } - // Populate the variables loaded in the task file - err := runner.variableConfig.PopulateVariables(runner.TasksFile.Variables, setVariables) + // Populate the variables loaded in the root task file + rootVariables := tasksFile.Variables + rootVariableConfig := GetMaruVariableConfig() + err := rootVariableConfig.PopulateVariables(rootVariables, setVariables) if err != nil { return err } // Check to see if running an included task directly - includeTaskName, err := runner.loadIncludedTaskFile(taskName) + tasksFile, taskName, err = loadIncludedTaskFile(tasksFile, taskName, rootVariableConfig.GetSetVariables()) + if err != nil { + return err + } + + // Populate the variables from the root and included file (if these are the same it will just use the same list) + combinedVariables := helpers.MergeSlices(rootVariables, tasksFile.Variables, func(a, b variables.InteractiveVariable[variables.ExtraVariableInfo]) bool { + return a.Name == b.Name + }) + combinedVariableConfig := GetMaruVariableConfig() + err = combinedVariableConfig.PopulateVariables(combinedVariables, setVariables) if err != nil { return err } - // if running an included task directly, update the task name - if len(includeTaskName) > 0 { - taskName = includeTaskName + + // Create the runner client to execute the task file + runner := Runner{ + TasksFile: tasksFile, + TaskNameMap: map[string]bool{}, + variableConfig: combinedVariableConfig, } task, err := runner.getTask(taskName) @@ -57,7 +68,7 @@ func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]str return err } - // can't call a task directly from the CLI if it has inputs that are required without a default + // Check that this task is a valid task we can call (i.e. has defaults for any inputs since those cannot be set on the CLI) if err := validateActionableTaskCall(task.Name, task.Inputs, nil); err != nil { return err } @@ -95,69 +106,52 @@ func (r *Runner) processIncludes(tasksFile types.TasksFile, setVariables map[str return nil } -func (r *Runner) importTasks(includes []map[string]string, dir string, setVariables map[string]string) error { +func (r *Runner) importTasks(includes []map[string]string, currentFileLocation string, setVariables map[string]string) error { // iterate through includes, open the file, and unmarshal it into a Task - var includeFilenameKey string - var includeFilename string - dir = filepath.Dir(dir) + var includeFileLocationKey string + var includeFileLocation string for _, include := range includes { if len(include) > 1 { return fmt.Errorf("included item %s must have only one key", include) } // grab first and only value from include map for k, v := range include { - includeFilenameKey = k - includeFilename = v + includeFileLocationKey = k + includeFileLocation = v break } - includeFilename = utils.TemplateString(r.variableConfig.GetSetVariables(), includeFilename) - - var tasksFile types.TasksFile - var includePath string - // check if included file is a url - if helpers.IsURL(includeFilename) { - // If file is a url download it to a tmp directory - tmpDir, err := utils.MakeTempDir(config.TempDirectory) - defer os.RemoveAll(tmpDir) - if err != nil { - return err - } - includePath = filepath.Join(tmpDir, filepath.Base(includeFilename)) - if err := utils.DownloadToFile(includeFilename, includePath); err != nil { - return fmt.Errorf(lang.ErrDownloading, includeFilename, err) - } - } else { - includePath = filepath.Join(dir, includeFilename) - } + includeFileLocation = utils.TemplateString(r.variableConfig.GetSetVariables(), includeFileLocation) - if err := utils.ReadYaml(includePath, &tasksFile); err != nil { + absIncludeFileLocation, tasksFile, err := loadIncludeTask(currentFileLocation, includeFileLocation) + if err != nil { return fmt.Errorf("unable to read included file: %w", err) } // prefix task names and actions with the includes key for i, t := range tasksFile.Tasks { - tasksFile.Tasks[i].Name = includeFilenameKey + ":" + t.Name + tasksFile.Tasks[i].Name = includeFileLocationKey + ":" + t.Name if len(tasksFile.Tasks[i].Actions) > 0 { for j, a := range tasksFile.Tasks[i].Actions { if a.TaskReference != "" && !strings.Contains(a.TaskReference, ":") { - tasksFile.Tasks[i].Actions[j].TaskReference = includeFilenameKey + ":" + a.TaskReference + tasksFile.Tasks[i].Actions[j].TaskReference = includeFileLocationKey + ":" + a.TaskReference } } } } - err := r.checkProcessedTasksForLoops(tasksFile) + + err = r.checkProcessedTasksForLoops(tasksFile) if err != nil { return err } r.TasksFile.Tasks = append(r.TasksFile.Tasks, tasksFile.Tasks...) - r.processTemplateMapVariables(tasksFile) + r.mergeVariablesFromIncludedTask(tasksFile) // recursively import tasks from included files if tasksFile.Includes != nil { - if err := r.importTasks(tasksFile.Includes, includePath, setVariables); err != nil { + if err := r.importTasks(tasksFile.Includes, absIncludeFileLocation, setVariables); err != nil { return err } } @@ -177,7 +171,7 @@ func (r *Runner) checkProcessedTasksForLoops(tasksFile types.TasksFile) error { return nil } -func (r *Runner) processTemplateMapVariables(tasksFile types.TasksFile) { +func (r *Runner) mergeVariablesFromIncludedTask(tasksFile types.TasksFile) { // grab variables from included file for _, v := range tasksFile.Variables { if _, ok := r.variableConfig.GetSetVariable(v.Name); !ok { @@ -186,76 +180,71 @@ func (r *Runner) processTemplateMapVariables(tasksFile types.TasksFile) { } } -func (r *Runner) loadIncludedTaskFile(taskName string) (string, error) { +func loadIncludedTaskFile(taskFile types.TasksFile, taskName string, setVariables variables.SetVariableMap[variables.ExtraVariableInfo]) (types.TasksFile, string, error) { // Check if running task directly from included task file includedTask := strings.Split(taskName, ":") if len(includedTask) == 2 { includeName := includedTask[0] includeTaskName := includedTask[1] // Get referenced include file - for _, includes := range r.TasksFile.Includes { + for _, includes := range taskFile.Includes { if includeFileLocation, ok := includes[includeName]; ok { - return r.loadIncludeTask(includeFileLocation, includeTaskName) + includeFileLocation = utils.TemplateString(setVariables, includeFileLocation) + + absIncludeFileLocation, includedTasksFile, err := loadIncludeTask(config.TaskFileLocation, includeFileLocation) + config.TaskFileLocation = absIncludeFileLocation + return includedTasksFile, includeTaskName, err } } } else if len(includedTask) > 2 { - return "", fmt.Errorf("invalid task name: %s", taskName) + return taskFile, taskName, fmt.Errorf("invalid task name: %s", taskName) } - return "", nil + return taskFile, taskName, nil } -func (r *Runner) loadIncludeTask(includeFileLocation string, includeTaskName string) (string, error) { - var fullPath string - templatePattern := `\${[^}]+}` - re := regexp.MustCompile(templatePattern) +func loadIncludeTask(currentFileLocation, includeFileLocation string) (string, types.TasksFile, error) { + var localPath string + var includedTasksFile types.TasksFile + var absIncludeFileLocation string + var err error - // check for templated variables in includeFileLocation value - if re.MatchString(includeFileLocation) { - includeFileLocation = utils.TemplateString(r.variableConfig.GetSetVariables(), includeFileLocation) + if !helpers.IsURL(includeFileLocation) { + if helpers.IsURL(currentFileLocation) { + currentURL, err := url.Parse(currentFileLocation) + if err != nil { + return absIncludeFileLocation, includedTasksFile, err + } + currentURL.Path = path.Join(path.Dir(currentURL.Path), includeFileLocation) + absIncludeFileLocation = currentURL.String() + } else { + // Calculate the full path for local (and most remote) references + absIncludeFileLocation = filepath.Join(filepath.Dir(currentFileLocation), includeFileLocation) + } + } else { + absIncludeFileLocation = includeFileLocation } - // check if included file is a url - if helpers.IsURL(includeFileLocation) { + + // If the file is in fact a URL we need to download and load the YAML + if helpers.IsURL(absIncludeFileLocation) { // If file is a url download it to a tmp directory tmpDir, err := utils.MakeTempDir(config.TempDirectory) if err != nil { - return "", fmt.Errorf("error creating %s: %w", tmpDir, err) + return absIncludeFileLocation, includedTasksFile, fmt.Errorf("error creating %s: %w", tmpDir, err) } // Remove tmpDir, but not until tasks have been loaded defer os.RemoveAll(tmpDir) - fullPath = filepath.Join(tmpDir, filepath.Base(includeFileLocation)) - if err := utils.DownloadToFile(includeFileLocation, fullPath); err != nil { - return "", fmt.Errorf(lang.ErrDownloading, includeFileLocation, err) + localPath = filepath.Join(tmpDir, filepath.Base(absIncludeFileLocation)) + if err := utils.DownloadToFile(absIncludeFileLocation, localPath); err != nil { + return absIncludeFileLocation, includedTasksFile, fmt.Errorf(lang.ErrDownloading, absIncludeFileLocation, err) } } else { - // set include path based on task file location - fullPath = filepath.Join(filepath.Dir(config.TaskFileLocation), includeFileLocation) + localPath = absIncludeFileLocation } - // update config.TaskFileLocation which gets used globally - config.TaskFileLocation = fullPath // Set TasksFile to include task file - var err error - r.TasksFile, err = loadTasksFileFromPath(fullPath) - if err != nil { - return "", err - } - - taskName := includeTaskName - return taskName, nil -} - -func loadTasksFileFromPath(fullPath string) (types.TasksFile, error) { - var tasksFile types.TasksFile - // get included TasksFile - if _, err := os.Stat(fullPath); os.IsNotExist(err) { - return types.TasksFile{}, fmt.Errorf("%s not found: %w", config.TaskFileLocation, err) - } - err := utils.ReadYaml(fullPath, &tasksFile) - if err != nil { - return types.TasksFile{}, fmt.Errorf("cannot unmarshal %s: %w", config.TaskFileLocation, err) - } - return tasksFile, nil + err = utils.ReadYaml(localPath, &includedTasksFile) + return absIncludeFileLocation, includedTasksFile, err } func (r *Runner) getTask(taskName string) (types.Task, error) { diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index 501ffd5..62242d7 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -113,7 +113,7 @@ func MakeTempDir(basePath string) (string, error) { // DownloadToFile downloads a given URL to the target filepath func DownloadToFile(src string, dst string) (err error) { - message.SLog.Debug("Downloading %s to %s", src, dst) + message.SLog.Debug(fmt.Sprintf("Downloading %s to %s", src, dst)) // check if the parsed URL has a checksum // if so, remove it and use the checksum to validate the file src, checksum, err := parseChecksum(src) diff --git a/src/test/e2e/runner_inputs_test.go b/src/test/e2e/runner_inputs_test.go index bf5ee5d..1cf8230 100644 --- a/src/test/e2e/runner_inputs_test.go +++ b/src/test/e2e/runner_inputs_test.go @@ -122,6 +122,7 @@ func TestRunnerInputs(t *testing.T) { t.Run("test that env vars can be used as inputs and take precedence over default vals", func(t *testing.T) { os.Setenv("MARU_FOO", "im an env var") stdOut, stdErr, err := e2e.Maru("run", "variable-as-input", "--file", "src/test/tasks/inputs/tasks.yaml") + os.Unsetenv("MARU_FOO") require.NoError(t, err, stdOut, stdErr) require.Contains(t, stdErr, "im an env var") }) @@ -129,7 +130,26 @@ func TestRunnerInputs(t *testing.T) { t.Run("test that a --set var has the greatest precedence for inputs", func(t *testing.T) { os.Setenv("MARU_FOO", "im an env var") stdOut, stdErr, err := e2e.Maru("run", "variable-as-input", "--file", "src/test/tasks/inputs/tasks.yaml", "--set", "foo=most specific") + os.Unsetenv("MARU_FOO") require.NoError(t, err, stdOut, stdErr) require.Contains(t, stdErr, "most specific") }) + + t.Run("test that variables in directly called included tasks take the root default", func(t *testing.T) { + stdOut, stdErr, err := e2e.Maru("run", "with:echo-foo", "--file", "src/test/tasks/inputs/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "default-value") + }) + + t.Run("test that variables in directly called included tasks take empty set values", func(t *testing.T) { + stdOut, stdErr, err := e2e.Maru("run", "with:echo-foo", "--file", "src/test/tasks/inputs/tasks.yaml", "--set", "foo=''") + require.NoError(t, err, stdOut, stdErr) + require.NotContains(t, stdErr, "default-value") + }) + + t.Run("test that variables in directly called included tasks pass through even when not in the root", func(t *testing.T) { + stdOut, stdErr, err := e2e.Maru("run", "with:echo-bar", "--file", "src/test/tasks/inputs/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "default-value") + }) } diff --git a/src/test/e2e/runner_test.go b/src/test/e2e/runner_test.go index 0248f7a..bf0a1f1 100644 --- a/src/test/e2e/runner_test.go +++ b/src/test/e2e/runner_test.go @@ -118,6 +118,20 @@ func TestTaskRunner(t *testing.T) { require.Contains(t, stdErr, "defenseunicorns is a pretty ok company") }) + t.Run("run remote-import back to local", func(t *testing.T) { + t.Parallel() + + // get current git revision + gitRev, err := e2e.GetGitRevision() + if err != nil { + return + } + setVar := fmt.Sprintf("GIT_REVISION=%s", gitRev) + stdOut, stdErr, err := e2e.Maru("run", "remote-import-to-local", "--set", setVar, "--file", "src/test/tasks/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "baz") + }) + t.Run("run rerun-tasks", func(t *testing.T) { t.Parallel() stdOut, stdErr, err := e2e.Maru("run", "rerun-tasks", "--file", "src/test/tasks/tasks.yaml") @@ -273,6 +287,8 @@ func TestTaskRunner(t *testing.T) { os.Setenv("MARU_LOG_LEVEL", "debug") os.Setenv("MARU_TO_BE_OVERWRITTEN", "env-var") stdOut, stdErr, err := e2e.Maru("run", "echo-env-var", "--file", "src/test/tasks/tasks.yaml") + os.Unsetenv("MARU_LOG_LEVEL") + os.Unsetenv("MARU_TO_BE_OVERWRITTEN") require.NoError(t, err, stdOut, stdErr) require.NotContains(t, stdErr, "default") require.Contains(t, stdErr, "env-var") diff --git a/src/test/tasks/inputs/tasks-with-inputs.yaml b/src/test/tasks/inputs/tasks-with-inputs.yaml index 0ec7ac3..7e374af 100644 --- a/src/test/tasks/inputs/tasks-with-inputs.yaml +++ b/src/test/tasks/inputs/tasks-with-inputs.yaml @@ -1,3 +1,9 @@ +variables: + - name: FOO + default: include-value + - name: BAR + default: default-value + tasks: - name: has-default inputs: @@ -45,3 +51,11 @@ tasks: actions: - cmd: | echo ${{ index .inputs "deprecated-message" }} + + - name: echo-foo + actions: + - cmd: echo $FOO + + - name: echo-bar + actions: + - cmd: echo $BAR diff --git a/src/test/tasks/more-tasks/baz.yaml b/src/test/tasks/more-tasks/baz.yaml new file mode 100644 index 0000000..06083f1 --- /dev/null +++ b/src/test/tasks/more-tasks/baz.yaml @@ -0,0 +1,4 @@ +tasks: + - name: baz + actions: + - cmd: "echo baz" diff --git a/src/test/tasks/remote-import-tasks.yaml b/src/test/tasks/remote-import-tasks.yaml index 800d685..1c26d39 100644 --- a/src/test/tasks/remote-import-tasks.yaml +++ b/src/test/tasks/remote-import-tasks.yaml @@ -1,5 +1,6 @@ includes: - remote: https://raw.githubusercontent.com/defenseunicorns/maru-runner/${GIT_REVISION}/src/test/tasks/even-more-tasks-to-import.yaml + - baz: ./more-tasks/baz.yaml tasks: - name: echo-var @@ -7,3 +8,7 @@ tasks: - task: remote:set-var - cmd: | echo "${PRETTY_OK_COMPANY} is a pretty ok company" + + - name: local-baz + actions: + - task: baz:baz diff --git a/src/test/tasks/tasks.yaml b/src/test/tasks/tasks.yaml index 1b28c94..6f04a4b 100644 --- a/src/test/tasks/tasks.yaml +++ b/src/test/tasks/tasks.yaml @@ -27,6 +27,9 @@ tasks: - name: remote-import actions: - task: remote:echo-var + - name: remote-import-to-local + actions: + - task: remote:local-baz - name: action actions: - cmd: echo "specific test string"