Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replay pipeline using cli exec by downloading metadata #4103

Merged
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b9a7d6c
Add option to use WOODPECKER_METADATA_FILE to set the metadata for th…
6543 Sep 11, 2024
7b092b3
add check aganist nil and use it
6543 Sep 11, 2024
7976901
rename StepBuilder.Last to StepBuilder.Prev to be consisten with meta…
6543 Sep 11, 2024
b11e882
First draft of metadata API
6543 Sep 11, 2024
8b00bd0
first draft of debug pipeline tab
6543 Sep 11, 2024
1d0384b
Add dropdown to download workflow specific metadata
6543 Sep 11, 2024
4fd307b
Add PipelineMetadata to golang api-client
6543 Sep 11, 2024
480fc10
more test coverage
6543 Sep 12, 2024
eb2b4df
tests for cli
6543 Sep 12, 2024
fcc8688
fix lint
6543 Sep 12, 2024
055c717
fix lint
6543 Sep 12, 2024
2e4e953
fix lint
6543 Sep 12, 2024
44bfe63
any
6543 Sep 12, 2024
96d252d
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 13, 2024
60252ef
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 16, 2024
bd5571d
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 16, 2024
4f9b1d4
generate
6543 Sep 16, 2024
8cf8aff
remove unused secret struct
6543 Sep 16, 2024
0d50bac
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 16, 2024
ea24133
harden test
6543 Sep 16, 2024
09ecd9b
fix bug
6543 Sep 16, 2024
82ba011
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 17, 2024
4fc9e33
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 20, 2024
0e8285a
well good the linter catched it
6543 Sep 20, 2024
9d31b98
use SelectField
6543 Sep 20, 2024
040b773
add todo notes
6543 Sep 20, 2024
c9815d4
fmt
6543 Sep 20, 2024
6148a26
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 22, 2024
991aced
rm workflows param: go sdk
6543 Sep 22, 2024
8e42642
rm workflows param: webui
6543 Sep 22, 2024
3554a7b
rm workflows param: server
6543 Sep 22, 2024
5f2a06b
rm workflows param: test
6543 Sep 22, 2024
c8a787c
Panel
6543 Sep 23, 2024
bfed706
fmt
6543 Sep 23, 2024
a0dcd37
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 23, 2024
3eae1d2
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 24, 2024
f62f0da
show cli example in webui
6543 Sep 24, 2024
8918d91
update file name dynamic in example
6543 Sep 24, 2024
c3fd642
wow
6543 Sep 24, 2024
915fbc1
docu
6543 Sep 24, 2024
ba00568
Merge remote-tracking branch 'upstream/main' into feat/download-and-u…
6543 Sep 24, 2024
e99c837
reapply
6543 Sep 24, 2024
7b26fda
Apply suggestions from code review
6543 Sep 24, 2024
3fe3eb4
fix
6543 Sep 24, 2024
e75c8f1
Merge branch 'main' into feat/download-and-use-metadata-in-exec
6543 Sep 24, 2024
67bb314
Update cli/exec/flags.go
anbraten Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions cli/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local"
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
Expand Down Expand Up @@ -75,6 +76,7 @@
if runtime.GOOS == "windows" {
repoPath = convertPathForWindows(repoPath)
}
// TODO: respect depends_on and do parallel runs with output to multiple _windows_ e.g. tmux like
return filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
if e != nil {
return e
Expand All @@ -83,7 +85,7 @@
// check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name())
_ = runExec(ctx, c, path, repoPath) // TODO: should we drop errors or store them and report back?
_ = runExec(ctx, c, path, repoPath, false) // TODO: should we drop errors or store them and report back?

Check warning on line 88 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L88

Added line #L88 was not covered by tests
fmt.Println("")
return nil
}
Expand All @@ -102,10 +104,10 @@
if runtime.GOOS == "windows" {
repoPath = convertPathForWindows(repoPath)
}
return runExec(ctx, c, file, repoPath)
return runExec(ctx, c, file, repoPath, true)

Check warning on line 107 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L107

Added line #L107 was not covered by tests
}

func runExec(ctx context.Context, c *cli.Command, file, repoPath string) error {
func runExec(ctx context.Context, c *cli.Command, file, repoPath string, singleExec bool) error {

Check warning on line 110 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L110

Added line #L110 was not covered by tests
dat, err := os.ReadFile(file)
if err != nil {
return err
Expand All @@ -120,19 +122,28 @@
axes = append(axes, matrix.Axis{})
}
for _, axis := range axes {
err := execWithAxis(ctx, c, file, repoPath, axis)
err := execWithAxis(ctx, c, file, repoPath, axis, singleExec)

Check warning on line 125 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L125

Added line #L125 was not covered by tests
if err != nil {
return err
}
}
return nil
}

func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis) error {
metadata, err := metadataFromContext(ctx, c, axis)
func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis, singleExec bool) error {
var metadataWorkflow *metadata.Workflow
if !singleExec {
// TODO: proper try to use the engine to generate the same metadata for workflows
// https://github.com/woodpecker-ci/woodpecker/pull/3967
metadataWorkflow.Name = strings.TrimSuffix(strings.TrimSuffix(file, ".yaml"), ".yml")
}
metadata, err := metadataFromContext(ctx, c, axis, metadataWorkflow)

Check warning on line 140 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L133-L140

Added lines #L133 - L140 were not covered by tests
if err != nil {
return fmt.Errorf("could not create metadata: %w", err)
} else if metadata == nil {
return fmt.Errorf("metadata is nil")

Check warning on line 144 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L143-L144

Added lines #L143 - L144 were not covered by tests
}

environ := metadata.Environ()
var secrets []compiler.Secret
for key, val := range metadata.Workflow.Matrix {
Expand Down Expand Up @@ -237,7 +248,7 @@
c.String("netrc-password"),
c.String("netrc-machine"),
),
compiler.WithMetadata(metadata),
compiler.WithMetadata(*metadata),

Check warning on line 251 in cli/exec/exec.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/exec.go#L251

Added line #L251 was not covered by tests
compiler.WithSecret(secrets...),
compiler.WithEnviron(pipelineEnv),
).Compile(conf)
Expand Down
5 changes: 5 additions & 0 deletions cli/exec/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ var flags = []cli.Flag{
Name: "repo-path",
Usage: "path to local repository",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_METADATA_FILE"),
Name: "metadata-file",
Usage: "path to metadata file who has stored the pipeline environment to emulate (can be downloaded from an existing pipeline), infos can be overwritten.",
anbraten marked this conversation as resolved.
Show resolved Hide resolved
},
&cli.DurationFlag{
Sources: cli.EnvVars("WOODPECKER_TIMEOUT"),
Name: "timeout",
Expand Down
222 changes: 123 additions & 99 deletions cli/exec/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"context"
"encoding/json"
"fmt"
"os"
"runtime"
"strings"

Expand All @@ -29,112 +30,135 @@
)

// return the metadata from the cli context.
func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis) (metadata.Metadata, error) {
func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w *metadata.Workflow) (*metadata.Metadata, error) {
m := &metadata.Metadata{}

if c.IsSet("metadata-file") {
metadataFile, err := os.Open(c.String("metadata-file"))
if err != nil {
return nil, err
}

Check warning on line 40 in cli/exec/metadata.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/metadata.go#L39-L40

Added lines #L39 - L40 were not covered by tests
defer metadataFile.Close()

if err := json.NewDecoder(metadataFile).Decode(m); err != nil {
return nil, err
}
}

platform := c.String("system-platform")
if platform == "" {
platform = runtime.GOOS + "/" + runtime.GOARCH
}

fullRepoName := c.String("repo-name")
repoOwner := ""
repoName := ""
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
repoOwner = fullRepoName[:idx]
repoName = fullRepoName[idx+1:]
}

var changedFiles []string
changedFilesRaw := c.String("pipeline-files")
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
if err := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); err != nil {
return metadata.Metadata{}, fmt.Errorf("pipeline-files detected json but could not parse it: %w", err)
metadataFileAndOverrideOrDefault(c, "repo-name", func(fullRepoName string) {
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
m.Repo.Owner = fullRepoName[:idx]
m.Repo.Name = fullRepoName[idx+1:]
}
} else {
for _, file := range strings.Split(changedFilesRaw, ",") {
changedFiles = append(changedFiles, strings.TrimSpace(file))
}, c.String)

var err error
metadataFileAndOverrideOrDefault(c, "pipeline-files", func(changedFilesRaw string) {
var changedFiles []string
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
if jsonErr := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); jsonErr != nil {
err = fmt.Errorf("pipeline-files detected json but could not parse it: %w", jsonErr)
}

Check warning on line 66 in cli/exec/metadata.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/metadata.go#L64-L66

Added lines #L64 - L66 were not covered by tests
} else {
for _, file := range strings.Split(changedFilesRaw, ",") {
changedFiles = append(changedFiles, strings.TrimSpace(file))
}
}
m.Curr.Commit.ChangedFiles = changedFiles
}, c.String)
if err != nil {
return nil, err
}

Check warning on line 76 in cli/exec/metadata.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/metadata.go#L75-L76

Added lines #L75 - L76 were not covered by tests

// Repo
metadataFileAndOverrideOrDefault(c, "repo-remote-id", func(s string) { m.Repo.RemoteID = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-url", func(s string) { m.Repo.ForgeURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-scm", func(s string) { m.Repo.SCM = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-default-branch", func(s string) { m.Repo.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-clone-url", func(s string) { m.Repo.CloneURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-clone-ssh-url", func(s string) { m.Repo.CloneSSHURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-private", func(b bool) { m.Repo.Private = b }, c.Bool)
metadataFileAndOverrideOrDefault(c, "repo-trusted", func(b bool) { m.Repo.Trusted = b }, c.Bool)

// Current Pipeline
metadataFileAndOverrideOrDefault(c, "pipeline-number", func(i int64) { m.Curr.Number = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-parent", func(i int64) { m.Curr.Parent = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-created", func(i int64) { m.Curr.Created = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-started", func(i int64) { m.Curr.Started = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-finished", func(i int64) { m.Curr.Finished = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-status", func(s string) { m.Curr.Status = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-url", func(s string) { m.Curr.ForgeURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-to", func(s string) { m.Curr.DeployTo = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-task", func(s string) { m.Curr.DeployTask = s }, c.String)

// Current Pipeline Commit
metadataFileAndOverrideOrDefault(c, "commit-sha", func(s string) { m.Curr.Commit.Sha = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-ref", func(s string) { m.Curr.Commit.Ref = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-refspec", func(s string) { m.Curr.Commit.Refspec = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-branch", func(s string) { m.Curr.Commit.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-name", func(s string) { m.Curr.Commit.Author.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-email", func(s string) { m.Curr.Commit.Author.Email = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-avatar", func(s string) { m.Curr.Commit.Author.Avatar = s }, c.String)

metadataFileAndOverrideOrDefault(c, "commit-pull-labels", func(sl []string) { m.Curr.Commit.PullRequestLabels = sl }, c.StringSlice)
metadataFileAndOverrideOrDefault(c, "commit-release-is-pre", func(b bool) { m.Curr.Commit.IsPrerelease = b }, c.Bool)

// Previous Pipeline
metadataFileAndOverrideOrDefault(c, "prev-pipeline-number", func(i int64) { m.Prev.Number = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-created", func(i int64) { m.Prev.Created = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-started", func(i int64) { m.Prev.Started = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-finished", func(i int64) { m.Prev.Finished = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-status", func(s string) { m.Prev.Status = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-url", func(s string) { m.Prev.ForgeURL = s }, c.String)

// Previous Pipeline Commit
metadataFileAndOverrideOrDefault(c, "prev-commit-sha", func(s string) { m.Prev.Commit.Sha = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-ref", func(s string) { m.Prev.Commit.Ref = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-refspec", func(s string) { m.Prev.Commit.Refspec = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-branch", func(s string) { m.Prev.Commit.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-name", func(s string) { m.Prev.Commit.Author.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-email", func(s string) { m.Prev.Commit.Author.Email = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-avatar", func(s string) { m.Prev.Commit.Author.Avatar = s }, c.String)

// Workflow
metadataFileAndOverrideOrDefault(c, "workflow-name", func(s string) { m.Workflow.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "workflow-number", func(i int64) { m.Workflow.Number = int(i) }, c.Int)
m.Workflow.Matrix = axis

// Step
metadataFileAndOverrideOrDefault(c, "step-name", func(s string) { m.Step.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "step-number", func(i int64) { m.Step.Number = int(i) }, c.Int)

// System
metadataFileAndOverrideOrDefault(c, "system-name", func(s string) { m.Sys.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "system-url", func(s string) { m.Sys.URL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "system-host", func(s string) { m.Sys.Host = s }, c.String)
m.Sys.Platform = platform
m.Sys.Version = version.Version

// Forge
metadataFileAndOverrideOrDefault(c, "forge-type", func(s string) { m.Forge.Type = s }, c.String)
metadataFileAndOverrideOrDefault(c, "forge-url", func(s string) { m.Forge.URL = s }, c.String)

if w != nil {
m.Workflow = *w

Check warning on line 153 in cli/exec/metadata.go

View check run for this annotation

Codecov / codecov/patch

cli/exec/metadata.go#L153

Added line #L153 was not covered by tests
}

return metadata.Metadata{
Repo: metadata.Repo{
Name: repoName,
Owner: repoOwner,
RemoteID: c.String("repo-remote-id"),
ForgeURL: c.String("repo-url"),
SCM: c.String("repo-scm"),
Branch: c.String("repo-default-branch"),
CloneURL: c.String("repo-clone-url"),
CloneSSHURL: c.String("repo-clone-ssh-url"),
Private: c.Bool("repo-private"),
Trusted: c.Bool("repo-trusted"),
},
Curr: metadata.Pipeline{
Number: c.Int("pipeline-number"),
Parent: c.Int("pipeline-parent"),
Created: c.Int("pipeline-created"),
Started: c.Int("pipeline-started"),
Finished: c.Int("pipeline-finished"),
Status: c.String("pipeline-status"),
Event: c.String("pipeline-event"),
ForgeURL: c.String("pipeline-url"),
DeployTo: c.String("pipeline-deploy-to"),
DeployTask: c.String("pipeline-deploy-task"),
Commit: metadata.Commit{
Sha: c.String("commit-sha"),
Ref: c.String("commit-ref"),
Refspec: c.String("commit-refspec"),
Branch: c.String("commit-branch"),
Message: c.String("commit-message"),
Author: metadata.Author{
Name: c.String("commit-author-name"),
Email: c.String("commit-author-email"),
Avatar: c.String("commit-author-avatar"),
},
PullRequestLabels: c.StringSlice("commit-pull-labels"),
IsPrerelease: c.Bool("commit-release-is-pre"),
ChangedFiles: changedFiles,
},
},
Prev: metadata.Pipeline{
Number: c.Int("prev-pipeline-number"),
Created: c.Int("prev-pipeline-created"),
Started: c.Int("prev-pipeline-started"),
Finished: c.Int("prev-pipeline-finished"),
Status: c.String("prev-pipeline-status"),
Event: c.String("prev-pipeline-event"),
ForgeURL: c.String("prev-pipeline-url"),
Commit: metadata.Commit{
Sha: c.String("prev-commit-sha"),
Ref: c.String("prev-commit-ref"),
Refspec: c.String("prev-commit-refspec"),
Branch: c.String("prev-commit-branch"),
Message: c.String("prev-commit-message"),
Author: metadata.Author{
Name: c.String("prev-commit-author-name"),
Email: c.String("prev-commit-author-email"),
Avatar: c.String("prev-commit-author-avatar"),
},
},
},
Workflow: metadata.Workflow{
Name: c.String("workflow-name"),
Number: int(c.Int("workflow-number")),
Matrix: axis,
},
Step: metadata.Step{
Name: c.String("step-name"),
Number: int(c.Int("step-number")),
},
Sys: metadata.System{
Name: c.String("system-name"),
URL: c.String("system-url"),
Host: c.String("system-host"),
Platform: platform,
Version: version.Version,
},
Forge: metadata.Forge{
Type: c.String("forge-type"),
URL: c.String("forge-url"),
},
}, nil
return m, nil
}

// metadataFileAndOverrideOrDefault will either use the flag default or if metadata file is set only overload if explicit set.
func metadataFileAndOverrideOrDefault[T any](c *cli.Command, flag string, setter func(T), getter func(string) T) {
if !c.IsSet("metadata-file") || c.IsSet(flag) {
setter(getter(flag))
}
}
Loading