Merge branch 'trunk' into andyfeller/testscripts-variable
This commit is contained in:
commit
fa03013a23
45 changed files with 1411 additions and 115 deletions
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
|
@ -6,7 +6,7 @@ We accept pull requests for bug fixes and features where we've discussed the app
|
|||
|
||||
Please do:
|
||||
|
||||
* Check existing issues to verify that an existing [bug][bug issues] or [feature request][feature request issues] issue does not already exist for the same problem or feature.
|
||||
* Check issues to verify that a [bug][bug issues] or [feature request][feature request issues] issue does not already exist for the same problem or feature.
|
||||
* Open an issue if things aren't working as expected.
|
||||
* Open an issue to propose a significant change.
|
||||
* Open an issue to propose a design for an issue labelled [`needs-design` and `help wanted`][needs design and help wanted], following the [proposing a design guidelines](#proposing-a-design) instructions below.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ The following custom environment variables are made available to the scripts:
|
|||
* `ORG`: Set to the value of the `GH_ACCEPTANCE_ORG` env var provided to `go test`
|
||||
* `GH_TOKEN`: Set to the value of the `GH_ACCEPTANCE_TOKEN` env var provided to `go test`
|
||||
* `RANDOM_STRING`: Set to a length 10 random string of letters to help isolate globally visible resources
|
||||
* `SCRIPT_NAME`: Set to the name of the `testscript` currently running, without extension e.g. `pr-view`
|
||||
* `SCRIPT_NAME`: Set to the name of the `testscript` currently running, without extension and replacing hyphens with underscores e.g. `pr_view`
|
||||
* `HOME`: Set to the initial working directory. Required for `git` operations
|
||||
* `GH_CONFIG_DIR`: Set to the initial working directory. Required for `gh` operations
|
||||
|
||||
|
|
@ -72,13 +72,6 @@ The following custom commands are defined within [`acceptance_test.go`](./accept
|
|||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
```
|
||||
|
||||
- `env2lower`: set environment variable to the lowercase version of another environment variable
|
||||
|
||||
```txtar
|
||||
# Prepare repository name, which is only lowercase
|
||||
env2lower REPO_NAME=$RANDOM_STRING
|
||||
```
|
||||
|
||||
- `env2upper`: set environment variable to the uppercase version of another environment variable
|
||||
|
||||
```txtar
|
||||
|
|
@ -86,6 +79,22 @@ The following custom commands are defined within [`acceptance_test.go`](./accept
|
|||
env2upper ORG_SECRET_NAME=$RANDOM_STRING
|
||||
```
|
||||
|
||||
- `replace`: replace placeholders in file with interpolated content provided
|
||||
|
||||
```txtar
|
||||
env2upper SECRET_NAME=$SCRIPT_NAME_$RANDOM_STRING
|
||||
|
||||
# Modify workflow file to use generated organization secret name
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
replace .github/workflows/workflow.yml SECRET_NAME=$SECRET_NAME
|
||||
|
||||
-- workflow.yml --
|
||||
on:
|
||||
workflow_dispatch:
|
||||
env:
|
||||
ORG_SECRET: ${{ secrets.$SECRET_NAME }}
|
||||
```
|
||||
|
||||
- `stdout2env`: set environment variable containing standard output from previous command
|
||||
|
||||
```txtar
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"math/rand"
|
||||
|
||||
|
|
@ -43,6 +45,51 @@ func TestIssues(t *testing.T) {
|
|||
testscript.Run(t, testScriptParamsFor(tsEnv, "pr"))
|
||||
}
|
||||
|
||||
func TestWorkflows(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testscript.Run(t, testScriptParamsFor(tsEnv, "workflow"))
|
||||
}
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testscript.Run(t, testScriptParamsFor(tsEnv, "api"))
|
||||
}
|
||||
|
||||
func TestReleases(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testscript.Run(t, testScriptParamsFor(tsEnv, "release"))
|
||||
}
|
||||
|
||||
func TestSearches(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testscript.Run(t, testScriptParamsFor(tsEnv, "search"))
|
||||
}
|
||||
|
||||
func TestRepo(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testscript.Run(t, testScriptParamsFor(tsEnv, "repo"))
|
||||
}
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
var tsEnv testScriptEnv
|
||||
if err := tsEnv.fromEnv(); err != nil {
|
||||
|
|
@ -91,7 +138,11 @@ func sharedSetup(tsEnv testScriptEnv) func(ts *testscript.Env) error {
|
|||
if !ok {
|
||||
ts.T().Fatal("script name not found")
|
||||
}
|
||||
ts.Setenv("SCRIPT_NAME", scriptName)
|
||||
|
||||
// When using script name to uniquely identify where test data comes from,
|
||||
// some places like GitHub Actions secret names don't accept hyphens.
|
||||
// Replace them with underscores until such a time this becomes a problem.
|
||||
ts.Setenv("SCRIPT_NAME", strings.ReplaceAll(scriptName, "-", "_"))
|
||||
ts.Setenv("HOME", ts.Cd)
|
||||
ts.Setenv("GH_CONFIG_DIR", ts.Cd)
|
||||
|
||||
|
|
@ -135,23 +186,6 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript,
|
|||
}
|
||||
})
|
||||
},
|
||||
"env2lower": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! env2lower")
|
||||
}
|
||||
if len(args) == 0 {
|
||||
ts.Fatalf("usage: env2lower name=value ...")
|
||||
}
|
||||
for _, env := range args {
|
||||
i := strings.Index(env, "=")
|
||||
|
||||
if i < 0 {
|
||||
ts.Fatalf("env2lower: argument does not match name=value")
|
||||
}
|
||||
|
||||
ts.Setenv(env[:i], strings.ToLower(env[i+1:]))
|
||||
}
|
||||
},
|
||||
"env2upper": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! env2upper")
|
||||
|
|
@ -169,6 +203,43 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript,
|
|||
ts.Setenv(env[:i], strings.ToUpper(env[i+1:]))
|
||||
}
|
||||
},
|
||||
"replace": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! replace")
|
||||
}
|
||||
if len(args) < 2 {
|
||||
ts.Fatalf("usage: replace file env...")
|
||||
}
|
||||
|
||||
src := ts.MkAbs(args[0])
|
||||
ts.Logf("replace src: %s", src)
|
||||
|
||||
// Preserve the existing file mode while replacing the contents similar to native cp behavior
|
||||
info, err := os.Stat(src)
|
||||
ts.Check(err)
|
||||
mode := info.Mode() & 0o777
|
||||
data, err := os.ReadFile(src)
|
||||
ts.Check(err)
|
||||
|
||||
for _, arg := range args[1:] {
|
||||
i := strings.Index(arg, "=")
|
||||
if i < 0 {
|
||||
ts.Fatalf("replace: %s argument does not match name=value", arg)
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("$%s", arg[:i])
|
||||
value := arg[i+1:]
|
||||
ts.Logf("replace %s: %s", name, value)
|
||||
|
||||
// `replace` was originally built similar to `cp` and `cmpenv`, expanding environment variables within a file.
|
||||
// However files with content that looks like environments variable such as GitHub Actions workflows
|
||||
// were being modified unexpectedly. Thus `replace` has been designed to using string replacement
|
||||
// looking for `$KEY` specifically.
|
||||
data = []byte(strings.ReplaceAll(string(data), name, value))
|
||||
}
|
||||
|
||||
ts.Check(os.WriteFile(src, data, mode))
|
||||
},
|
||||
"stdout2env": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! stdout2env")
|
||||
|
|
@ -179,6 +250,23 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript,
|
|||
|
||||
ts.Setenv(args[0], strings.TrimRight(ts.ReadFile("stdout"), "\n"))
|
||||
},
|
||||
"sleep": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if neg {
|
||||
ts.Fatalf("unsupported: ! sleep")
|
||||
}
|
||||
if len(args) != 1 {
|
||||
ts.Fatalf("usage: sleep seconds")
|
||||
}
|
||||
|
||||
// sleep for the given number of seconds
|
||||
seconds, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
ts.Fatalf("invalid number of seconds: %v", err)
|
||||
}
|
||||
|
||||
d := time.Duration(seconds) * time.Second
|
||||
time.Sleep(d)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
3
acceptance/testdata/api/basic-graphql.txtar
vendored
Normal file
3
acceptance/testdata/api/basic-graphql.txtar
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Basic graphql request
|
||||
exec gh api graphql -f query='query { viewer { login } }'
|
||||
stdout '"login":'
|
||||
3
acceptance/testdata/api/basic-rest.txtar
vendored
Normal file
3
acceptance/testdata/api/basic-rest.txtar
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Basic REST request
|
||||
exec gh api /user
|
||||
stdout '"login":'
|
||||
12
acceptance/testdata/release/release-create.txtar
vendored
Normal file
12
acceptance/testdata/release/release-create.txtar
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a release in the repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh release create v1.2.3 --notes 'awesome release' --latest
|
||||
16
acceptance/testdata/release/release-list.txtar
vendored
Normal file
16
acceptance/testdata/release/release-list.txtar
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a release in the repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh release create v1.2.3 --notes 'awesome release' --latest
|
||||
|
||||
# List the releases
|
||||
exec gh release list
|
||||
stdout 'v1.2.3'
|
||||
26
acceptance/testdata/release/release-upload-download.txtar
vendored
Normal file
26
acceptance/testdata/release/release-upload-download.txtar
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a release in the repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh release create v1.2.3 --notes 'awesome release' --latest
|
||||
|
||||
# Upload an asset to the release
|
||||
exec gh release upload v1.2.3 ../asset.txt
|
||||
|
||||
# Download the asset from the release
|
||||
exec gh release download v1.2.3
|
||||
exists asset.txt
|
||||
|
||||
# Download the asset in archive form
|
||||
exec gh release download v1.2.3 --archive=zip
|
||||
exists $SCRIPT_NAME-$RANDOM_STRING-1.2.3.zip
|
||||
|
||||
-- asset.txt --
|
||||
Hello, world!
|
||||
16
acceptance/testdata/release/release-view.txtar
vendored
Normal file
16
acceptance/testdata/release/release-view.txtar
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a release in the repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh release create v1.2.3 --notes 'awesome release' --latest
|
||||
|
||||
# View the release
|
||||
exec gh release view v1.2.3
|
||||
stdout 'v1.2.3'
|
||||
23
acceptance/testdata/repo/repo-archive-unarchive.txtar
vendored
Normal file
23
acceptance/testdata/repo/repo-archive-unarchive.txtar
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Check that the repo exists and isn't archived
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json=isArchived --jq='.isArchived'
|
||||
stdout 'false'
|
||||
|
||||
# Archive the repo
|
||||
exec gh repo archive $ORG/$SCRIPT_NAME-$RANDOM_STRING --yes
|
||||
|
||||
# Check that the repo is archived
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json=isArchived --jq='.isArchived'
|
||||
stdout 'true'
|
||||
|
||||
# Unarchive the repo
|
||||
exec gh repo unarchive $ORG/$SCRIPT_NAME-$RANDOM_STRING --yes
|
||||
|
||||
# Check that the repo is unarchived
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json=isArchived --jq='.isArchived'
|
||||
stdout 'false'
|
||||
11
acceptance/testdata/repo/repo-clone.txtar
vendored
Normal file
11
acceptance/testdata/repo/repo-clone.txtar
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Ensure the repo was cloned
|
||||
exists $SCRIPT_NAME-$RANDOM_STRING/README.md
|
||||
9
acceptance/testdata/repo/repo-create-view.txtar
vendored
Normal file
9
acceptance/testdata/repo/repo-create-view.txtar
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Check that the repo exists
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json=name --jq='.name'
|
||||
stdout $SCRIPT_NAME-$RANDOM_STRING
|
||||
13
acceptance/testdata/repo/repo-delete.txtar
vendored
Normal file
13
acceptance/testdata/repo/repo-delete.txtar
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Check that the repo exists
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json name --jq '.name'
|
||||
stdout $SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Delete the repo
|
||||
exec gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Ensure that the repo was deleted
|
||||
! exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
stderr 'Could not resolve to a Repository with the name'
|
||||
31
acceptance/testdata/repo/repo-deploy-key.txtar
vendored
Normal file
31
acceptance/testdata/repo/repo-deploy-key.txtar
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Create and clone a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private --clone
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# cd to the repo and list the deploy keys. There should be no keys
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh repo deploy-key list --json=title
|
||||
! stdout title
|
||||
|
||||
# Add a deploy key
|
||||
exec gh repo deploy-key add ../deployKey.pub
|
||||
|
||||
# Ensure the deploy key was added
|
||||
exec gh repo deploy-key list --json=title --jq='.[].title'
|
||||
stdout myTitle
|
||||
|
||||
# Get the deploy key id
|
||||
exec gh repo deploy-key list --json=title,id --jq='.[].title="myTitle" | .[].id'
|
||||
stdout2env DEPLOY_KEY_ID
|
||||
|
||||
# Delete the deploy key
|
||||
exec gh repo deploy-key delete $DEPLOY_KEY_ID
|
||||
|
||||
# Ensure the deploy key was deleted
|
||||
exec gh repo deploy-key list --json=id --jq='.[].id'
|
||||
! stdout $DEPLOY_KEY_ID
|
||||
|
||||
-- deployKey.pub --
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAZmdeRNskfpvYL5YHB/YJaW8hTEXpnvPMkx5Ri+YwUr myTitle
|
||||
16
acceptance/testdata/repo/repo-edit.txtar
vendored
Normal file
16
acceptance/testdata/repo/repo-edit.txtar
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Check that the repo description is empty
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json description --jq '.description'
|
||||
! stdout '.'
|
||||
|
||||
# Edit the repo description
|
||||
exec gh repo edit $ORG/$SCRIPT_NAME-$RANDOM_STRING --description 'newDescription'
|
||||
|
||||
# Check that the repo description is updated
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING --json description --jq '.description'
|
||||
stdout 'newDescription'
|
||||
42
acceptance/testdata/repo/repo-fork-sync.txtar
vendored
Normal file
42
acceptance/testdata/repo/repo-fork-sync.txtar
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create and clone a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private --clone
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Fork and clone the repo
|
||||
exec gh repo fork $ORG/$SCRIPT_NAME-$RANDOM_STRING --org $ORG --fork-name $SCRIPT_NAME-$RANDOM_STRING-fork --clone
|
||||
|
||||
# Defer fork cleanup
|
||||
defer gh repo delete $ORG/$SCRIPT_NAME-$RANDOM_STRING-fork --yes
|
||||
|
||||
# Sleep so that the BE has time to sync
|
||||
sleep 5
|
||||
|
||||
# Check that the repo was forked
|
||||
exec gh repo view $ORG/$SCRIPT_NAME-$RANDOM_STRING-fork --json='isFork' --jq='.isFork'
|
||||
stdout 'true'
|
||||
|
||||
# Modify original repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mv ../asset.txt asset.txt
|
||||
exec git add .
|
||||
exec git commit -m 'Add asset.txt'
|
||||
exec git push
|
||||
|
||||
# Checkout the forked repo and ensure asset.txt is not present
|
||||
cd ../$SCRIPT_NAME-$RANDOM_STRING-fork
|
||||
exec git checkout main
|
||||
! exists asset.txt
|
||||
|
||||
# Sync the forked repo with the original repo
|
||||
exec gh repo sync
|
||||
|
||||
# Check that asset.txt now exists in the fork
|
||||
exists asset.txt
|
||||
|
||||
-- asset.txt --
|
||||
Hello, world!
|
||||
16
acceptance/testdata/repo/repo-list-rename.txtar
vendored
Normal file
16
acceptance/testdata/repo/repo-list-rename.txtar
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# List the repos and check for the new repo
|
||||
exec gh repo list $ORG --json=name --jq='.[].name'
|
||||
stdout $SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Rename the repo
|
||||
exec gh repo rename $SCRIPT_NAME-$RANDOM_STRING-renamed --repo=$ORG/$SCRIPT_NAME-$RANDOM_STRING --yes
|
||||
|
||||
# Defer repo deletion
|
||||
defer gh repo delete $ORG/$SCRIPT_NAME-$RANDOM_STRING-renamed --yes
|
||||
|
||||
# List the repos and check for the renamed repo
|
||||
exec gh repo list $ORG --json=name --jq='.[].name'
|
||||
stdout $SCRIPT_NAME-$RANDOM_STRING-renamed
|
||||
17
acceptance/testdata/repo/repo-set-default.txtar
vendored
Normal file
17
acceptance/testdata/repo/repo-set-default.txtar
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Create and clone a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private --clone
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Ensure that no default is set
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
exec gh repo set-default --view
|
||||
stderr 'no default repository has been set; use `gh repo set-default` to select one'
|
||||
|
||||
# Set the default
|
||||
exec gh repo set-default $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Check that the default is set
|
||||
exec gh repo set-default --view
|
||||
stdout $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
20
acceptance/testdata/search/search-issues.txtar
vendored
Normal file
20
acceptance/testdata/search/search-issues.txtar
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create an issue in the repo
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
exec gh issue create --title 'Feature Request' --body $RANDOM_STRING
|
||||
|
||||
# It takes some time for the issue to be created and indexed
|
||||
sleep 5
|
||||
|
||||
# Search for the issue
|
||||
exec gh search issues $RANDOM_STRING -R $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
stdout $RANDOM_STRING
|
||||
85
acceptance/testdata/secret/secret-org.txtar
vendored
85
acceptance/testdata/secret/secret-org.txtar
vendored
|
|
@ -1,16 +1,85 @@
|
|||
# Prepare organization secret, GitHub Actions uppercases secret names
|
||||
env2upper ORG_SECRET_NAME=$RANDOM_STRING
|
||||
# Setup environment variables used for testscript
|
||||
env REPO=${SCRIPT_NAME}-${RANDOM_STRING}
|
||||
env2upper SECRET_NAME=${SCRIPT_NAME}_${RANDOM_STRING}
|
||||
|
||||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$REPO --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$REPO
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$REPO
|
||||
cd $REPO
|
||||
|
||||
# Confirm organization secret does not exist, will fail admin:org scope missing
|
||||
! exec gh api /orgs/$ORG/actions/secrets/$ORG_SECRET_NAME
|
||||
stdout '"status":"404"'
|
||||
exec gh secret list --org $ORG
|
||||
! stdout $SECRET_NAME
|
||||
|
||||
# Create an organization secret
|
||||
exec gh secret set $ORG_SECRET_NAME --org $ORG --body 'does not matter as cannot read it once set'
|
||||
exec gh secret set $SECRET_NAME --org $ORG --body 'just an organization secret' --repos $REPO
|
||||
|
||||
# Defer organization secret cleanup
|
||||
defer gh secret delete $ORG_SECRET_NAME --org $ORG
|
||||
defer gh secret delete $SECRET_NAME --org $ORG
|
||||
|
||||
# Confirm organization secret exists
|
||||
# Verify new organization secret exists
|
||||
exec gh secret list --org $ORG
|
||||
stdout $ORG_SECRET_NAME
|
||||
stdout $SECRET_NAME
|
||||
|
||||
# Commit workflow file creating dispatchable workflow able to verify secret matches
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
replace .github/workflows/workflow.yml SECRET_NAME=$SECRET_NAME
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch & delete
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Verify secret matched what was set earlier
|
||||
exec gh run view $RUN_ID --log
|
||||
stdout 'GitHub Actions secret value matches$'
|
||||
|
||||
-- workflow.yml --
|
||||
# This workflow is intended to assert the value of the GitHub Actions secret was set appropriately
|
||||
name: Test Workflow Name
|
||||
on:
|
||||
# Allow workflow to be dispatched by gh workflow run
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# This workflow contains a single job called "assert" that should only pass if the GitHub Actions secret value matches
|
||||
assert:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Assert secret value matches
|
||||
env:
|
||||
ORG_SECRET: ${{ secrets.$SECRET_NAME }}
|
||||
run: |
|
||||
if [[ "$ORG_SECRET" == "just an organization secret" ]]; then
|
||||
echo "GitHub Actions secret value matches"
|
||||
else
|
||||
echo "GitHub Actions secret value does not match"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
75
acceptance/testdata/secret/secret-repo-env.txtar
vendored
75
acceptance/testdata/secret/secret-repo-env.txtar
vendored
|
|
@ -1,8 +1,10 @@
|
|||
# Force GitHub CLI to treat testscript as TTY
|
||||
env GH_FORCE_TTY=80
|
||||
# Setup environment variables used for testscript
|
||||
env REPO=$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a repository where the secret will be registered
|
||||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$REPO --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
|
|
@ -13,17 +15,66 @@ exec gh repo clone $ORG/$REPO
|
|||
cd $REPO
|
||||
|
||||
# Create a repository environment, will fail if organization does not have environment support
|
||||
exec gh api /repos/$ORG/$REPO/environments/testscripts -X PUT --jq '.name'
|
||||
stdout 'testscripts'
|
||||
|
||||
# Verify no repository environment secrets exist
|
||||
exec gh secret list --env testscripts
|
||||
stderr 'no secrets found'
|
||||
exec gh api /repos/$ORG/$REPO/environments/testscripts -X PUT
|
||||
|
||||
# Create a repository environment secret
|
||||
exec gh secret set TESTSCRIPTS_ENV --env testscripts --body 'does not matter as cannot read it once set'
|
||||
stdout 'Set Actions secret TESTSCRIPTS_ENV for'
|
||||
exec gh secret set TESTSCRIPTS_ENV --env testscripts --body 'just a repository environment secret'
|
||||
|
||||
# Verify new repository secret exists
|
||||
exec gh secret list --env testscripts
|
||||
stdout 'TESTSCRIPTS_ENV\s+less than a minute ago'
|
||||
stdout 'TESTSCRIPTS_ENV'
|
||||
|
||||
# Commit workflow file creating dispatchable workflow able to verify secret matches
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch & delete
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Verify secret matched what was set earlier
|
||||
exec gh run view $RUN_ID --log
|
||||
stdout 'GitHub Actions secret value matches$'
|
||||
|
||||
-- workflow.yml --
|
||||
# This workflow is intended to assert the value of the GitHub Actions secret was set appropriately
|
||||
name: Test Workflow Name
|
||||
on:
|
||||
# Allow workflow to be dispatched by gh workflow run
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# This workflow contains a single job called "assert" that should only pass if the GitHub Actions secret value matches
|
||||
assert:
|
||||
runs-on: ubuntu-latest
|
||||
environment: testscripts
|
||||
steps:
|
||||
- name: Assert secret value matches
|
||||
env:
|
||||
TESTSCRIPTS_ENV: ${{ secrets.TESTSCRIPTS_ENV }}
|
||||
run: |
|
||||
if [[ "$TESTSCRIPTS_ENV" == "just a repository environment secret" ]]; then
|
||||
echo "GitHub Actions secret value matches"
|
||||
else
|
||||
echo "GitHub Actions secret value does not match"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
71
acceptance/testdata/secret/secret-repo.txtar
vendored
71
acceptance/testdata/secret/secret-repo.txtar
vendored
|
|
@ -1,8 +1,10 @@
|
|||
# Force GitHub CLI to treat testscript as TTY
|
||||
env GH_FORCE_TTY=80
|
||||
# Setup environment variables used for testscript
|
||||
env REPO=$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Create a repository where the secret will be registered
|
||||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$REPO --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
|
|
@ -12,14 +14,63 @@ defer gh repo delete --yes $ORG/$REPO
|
|||
exec gh repo clone $ORG/$REPO
|
||||
cd $REPO
|
||||
|
||||
# Verify no repository secrets exist
|
||||
exec gh secret list
|
||||
stderr 'no secrets found'
|
||||
|
||||
# Create a repository secret
|
||||
exec gh secret set TESTSCRIPTS --body 'does not matter as cannot read it once set'
|
||||
stdout 'Set Actions secret TESTSCRIPTS for'
|
||||
exec gh secret set TESTSCRIPTS --body 'just a repository secret'
|
||||
|
||||
# Verify new repository secret exists
|
||||
exec gh secret list
|
||||
stdout 'TESTSCRIPTS\s+less than a minute ago'
|
||||
stdout 'TESTSCRIPTS'
|
||||
|
||||
# Commit workflow file creating dispatchable workflow able to verify secret matches
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch & delete
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Verify secret matched what was set earlier
|
||||
exec gh run view $RUN_ID --log
|
||||
stdout 'GitHub Actions secret value matches$'
|
||||
|
||||
-- workflow.yml --
|
||||
# This workflow is intended to assert the value of the GitHub Actions secret was set appropriately
|
||||
name: Test Workflow Name
|
||||
on:
|
||||
# Allow workflow to be dispatched by gh workflow run
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# This workflow contains a single job called "assert" that should only pass if the GitHub Actions secret value matches
|
||||
assert:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Assert secret value matches
|
||||
env:
|
||||
TESTSCRIPTS: ${{ secrets.TESTSCRIPTS }}
|
||||
run: |
|
||||
if [[ "$TESTSCRIPTS" == "just a repository secret" ]]; then
|
||||
echo "GitHub Actions secret value matches"
|
||||
else
|
||||
echo "GitHub Actions secret value does not match"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
69
acceptance/testdata/workflow/cache-list-delete.txtar
vendored
Normal file
69
acceptance/testdata/workflow/cache-list-delete.txtar
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# List the cache
|
||||
exec gh cache list
|
||||
stdout 'Linux-values'
|
||||
|
||||
# Delete the cache
|
||||
exec gh cache delete 'Linux-values'
|
||||
|
||||
-- workflow.yml --
|
||||
name: Test Workflow Name
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache values
|
||||
id: cache-values
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: values.txt
|
||||
key: ${{ runner.os }}-values
|
||||
|
||||
- name: Generate values file
|
||||
if: steps.cache-values.outputs.cache-hit != 'true'
|
||||
run: echo "values" > values.txt
|
||||
73
acceptance/testdata/workflow/run-cancel.txtar
vendored
Normal file
73
acceptance/testdata/workflow/run-cancel.txtar
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to cancel
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# cancel the workflow run
|
||||
exec gh run cancel $RUN_ID
|
||||
stdout '✓ Request to cancel workflow [0-9]+ submitted.'
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID
|
||||
|
||||
# Check the workflow run is cancelled
|
||||
exec gh run list --json conclusion --jq '.[0].conclusion'
|
||||
stdout 'cancelled'
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: sleep 30
|
||||
74
acceptance/testdata/workflow/run-delete.txtar
vendored
Normal file
74
acceptance/testdata/workflow/run-delete.txtar
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch & delete
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Delete the workflow run
|
||||
exec gh run delete $RUN_ID
|
||||
stdout '✓ Request to delete workflow submitted.'
|
||||
|
||||
# It takes some time for a workflow run to be deleted
|
||||
sleep 5
|
||||
|
||||
# Check the workflow run is cancelled, which is implied by an empty list
|
||||
exec gh run list
|
||||
stdout ''
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
70
acceptance/testdata/workflow/run-download.txtar
vendored
Normal file
70
acceptance/testdata/workflow/run-download.txtar
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to watch
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Download the artifact
|
||||
exec gh run download $RUN_ID
|
||||
|
||||
# Check if we downloaded the artifact
|
||||
exists ./my-artifact/world.txt
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- run: echo hello > world.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: my-artifact
|
||||
path: world.txt
|
||||
72
acceptance/testdata/workflow/run-rerun.txtar
vendored
Normal file
72
acceptance/testdata/workflow/run-rerun.txtar
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to rerun
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# Rerun the workflow run
|
||||
exec gh run rerun $RUN_ID
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
66
acceptance/testdata/workflow/run-view.txtar
vendored
Normal file
66
acceptance/testdata/workflow/run-view.txtar
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Get the run ID we want to view
|
||||
exec gh run list --json databaseId --jq '.[0].databaseId'
|
||||
stdout2env RUN_ID
|
||||
|
||||
# Wait for workflow to complete
|
||||
exec gh run watch $RUN_ID --exit-status
|
||||
|
||||
# View the workflow run
|
||||
exec gh run view $RUN_ID
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
66
acceptance/testdata/workflow/workflow-enable-disable.txtar
vendored
Normal file
66
acceptance/testdata/workflow/workflow-enable-disable.txtar
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# disable the workflow
|
||||
exec gh workflow disable 'Test Workflow Name'
|
||||
|
||||
# Check that the listing shows it is disabled
|
||||
exec gh workflow list --all
|
||||
stdout 'Test\s+Workflow\s+Name\s+disabled_manually\s+\d+'
|
||||
|
||||
# enable the workflow
|
||||
exec gh workflow enable 'Test Workflow Name'
|
||||
|
||||
# Check the workflow is indeed enabled
|
||||
exec gh workflow list
|
||||
stdout 'Test\s+Workflow\s+Name\s+active\s+\d+'
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
52
acceptance/testdata/workflow/workflow-list.txtar
vendored
Normal file
52
acceptance/testdata/workflow/workflow-list.txtar
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
62
acceptance/testdata/workflow/workflow-run.txtar
vendored
Normal file
62
acceptance/testdata/workflow/workflow-run.txtar
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
# Run the workflow
|
||||
exec gh workflow run 'Test Workflow Name'
|
||||
|
||||
# It takes some time for a workflow run to register
|
||||
sleep 10
|
||||
|
||||
# Check the workflow run exists
|
||||
exec gh run list
|
||||
stdout 'Test Workflow Name'
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
52
acceptance/testdata/workflow/workflow-view.txtar
vendored
Normal file
52
acceptance/testdata/workflow/workflow-view.txtar
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Use gh as a credential helper
|
||||
exec gh auth setup-git
|
||||
|
||||
# Create a repository with a file so it has a default branch
|
||||
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
|
||||
|
||||
# Defer repo cleanup
|
||||
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# Clone the repo
|
||||
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
|
||||
|
||||
# commit the workflow file
|
||||
cd $SCRIPT_NAME-$RANDOM_STRING
|
||||
mkdir .github/workflows
|
||||
mv ../workflow.yml .github/workflows/workflow.yml
|
||||
exec git add .github/workflows/workflow.yml
|
||||
exec git commit -m 'Create workflow file'
|
||||
exec git push -u origin main
|
||||
|
||||
# Sleep because it takes a second for the workflow to register
|
||||
sleep 1
|
||||
|
||||
# Check the workflow is indeed created
|
||||
exec gh workflow view 'Test Workflow Name'
|
||||
stdout 'Test Workflow Name - workflow.yml'
|
||||
|
||||
-- workflow.yml --
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Test Workflow Name
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
10
go.mod
10
go.mod
|
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.5
|
||||
github.com/creack/pty v1.1.23
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.5
|
||||
github.com/gabriel-vasile/mimetype v1.4.6
|
||||
github.com/gdamore/tcell/v2 v2.5.4
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.20.2
|
||||
|
|
@ -43,10 +43,10 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/zalando/go-keyring v0.2.5
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/term v0.24.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/term v0.25.0
|
||||
golang.org/x/text v0.19.0
|
||||
google.golang.org/grpc v1.64.1
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
|
|
@ -159,7 +159,7 @@ require (
|
|||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
|
|
|
|||
20
go.sum
20
go.sum
|
|
@ -147,8 +147,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
|
||||
|
|
@ -486,8 +486,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
|
@ -496,8 +496,8 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -519,15 +519,15 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
|||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
|||
|
|
@ -16,12 +16,19 @@ func VerifyCertExtensions(results []*AttestationProcessingResult, tenant, owner,
|
|||
return errors.New("no attestations proccessing results")
|
||||
}
|
||||
|
||||
var atLeastOneVerified bool
|
||||
for _, attestation := range results {
|
||||
if err := verifyCertExtensions(attestation, tenant, owner, repo, issuer); err != nil {
|
||||
return err
|
||||
}
|
||||
atLeastOneVerified = true
|
||||
}
|
||||
|
||||
if atLeastOneVerified {
|
||||
return nil
|
||||
} else {
|
||||
return ErrNoAttestationsVerified
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyCertExtensions(attestation *AttestationProcessingResult, tenant, owner, repo, issuer string) error {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
|
|
@ -48,6 +49,8 @@ type LiveSigstoreVerifier struct {
|
|||
config SigstoreConfig
|
||||
}
|
||||
|
||||
var ErrNoAttestationsVerified = errors.New("no attestations were verified")
|
||||
|
||||
// NewLiveSigstoreVerifier creates a new LiveSigstoreVerifier struct
|
||||
// that is used to verify artifacts and attestations against the
|
||||
// Public Good, GitHub, or a custom trusted root.
|
||||
|
|
@ -170,18 +173,20 @@ func getLowestCertInChain(ca *root.CertificateAuthority) (*x509.Certificate, err
|
|||
}
|
||||
|
||||
func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) *SigstoreResults {
|
||||
// initialize the processing results before attempting to verify
|
||||
// initialize the processing apResults before attempting to verify
|
||||
// with multiple verifiers
|
||||
results := make([]*AttestationProcessingResult, len(attestations))
|
||||
apResults := make([]*AttestationProcessingResult, len(attestations))
|
||||
for i, att := range attestations {
|
||||
apr := &AttestationProcessingResult{
|
||||
Attestation: att,
|
||||
}
|
||||
results[i] = apr
|
||||
apResults[i] = apr
|
||||
}
|
||||
|
||||
var atLeastOneVerified bool
|
||||
|
||||
totalAttestations := len(attestations)
|
||||
for i, apr := range results {
|
||||
for i, apr := range apResults {
|
||||
v.config.Logger.VerbosePrintf("Verifying attestation %d/%d against the configured Sigstore trust roots\n", i+1, totalAttestations)
|
||||
|
||||
// determine which verifier should attempt verification against the bundle
|
||||
|
|
@ -212,10 +217,15 @@ func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy ve
|
|||
"SUCCESS - attestation signature verified with \"%s\"\n", issuer,
|
||||
))
|
||||
apr.VerificationResult = result
|
||||
atLeastOneVerified = true
|
||||
}
|
||||
|
||||
return &SigstoreResults{
|
||||
VerifyResults: results,
|
||||
if atLeastOneVerified {
|
||||
return &SigstoreResults{
|
||||
VerifyResults: apResults,
|
||||
}
|
||||
} else {
|
||||
return &SigstoreResults{Error: ErrNoAttestationsVerified}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,21 @@ func TestLiveSigstoreVerifier(t *testing.T) {
|
|||
require.Len(t, res.VerifyResults, 0)
|
||||
require.ErrorContains(t, res.Error, "unsupported bundle version")
|
||||
})
|
||||
|
||||
t.Run("with no attestations", func(t *testing.T) {
|
||||
attestations := []*api.Attestation{}
|
||||
require.Len(t, attestations, 0)
|
||||
|
||||
verifier := NewLiveSigstoreVerifier(SigstoreConfig{
|
||||
Logger: io.NewTestHandler(),
|
||||
TrustedRoot: test.NormalizeRelativePath("../test/data/trusted_root.json"),
|
||||
})
|
||||
|
||||
res := verifier.Verify(attestations, publicGoodPolicy(t))
|
||||
require.Len(t, res.VerifyResults, 0)
|
||||
require.NotNil(t, res.Error)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func publicGoodPolicy(t *testing.T) verify.PolicyBuilder {
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
defer prShared.PreserveInput(opts.IO, &tb, &err)()
|
||||
|
||||
if opts.Title == "" {
|
||||
err = prShared.TitleSurvey(opts.Prompter, &tb)
|
||||
err = prShared.TitleSurvey(opts.Prompter, opts.IO, &tb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ func TestIssueCreate_recover(t *testing.T) {
|
|||
|
||||
pm := &prompter.PrompterMock{}
|
||||
pm.InputFunc = func(p, d string) (string, error) {
|
||||
if p == "Title" {
|
||||
if p == "Title (required)" {
|
||||
return d, nil
|
||||
} else {
|
||||
return "", prompter.NoSuchPromptErr(p)
|
||||
|
|
@ -736,7 +736,7 @@ func TestIssueCreate_continueInBrowser(t *testing.T) {
|
|||
|
||||
pm := &prompter.PrompterMock{}
|
||||
pm.InputFunc = func(p, d string) (string, error) {
|
||||
if p == "Title" {
|
||||
if p == "Title (required)" {
|
||||
return "hello", nil
|
||||
} else {
|
||||
return "", prompter.NoSuchPromptErr(p)
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ func createRun(opts *CreateOptions) (err error) {
|
|||
} else {
|
||||
|
||||
if !opts.TitleProvided {
|
||||
err = shared.TitleSurvey(opts.Prompter, state)
|
||||
err = shared.TitleSurvey(opts.Prompter, opts.IO, state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1210,7 +1210,7 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
promptStubs: func(pm *prompter.PrompterMock) {
|
||||
pm.InputFunc = func(p, d string) (string, error) {
|
||||
if p == "Title" {
|
||||
if p == "Title (required)" {
|
||||
return d, nil
|
||||
} else {
|
||||
return "", prompter.NoSuchPromptErr(p)
|
||||
|
|
@ -1316,7 +1316,7 @@ func Test_createRun(t *testing.T) {
|
|||
}
|
||||
|
||||
pm.InputFunc = func(p, d string) (string, error) {
|
||||
if p == "Title" {
|
||||
if p == "Title (required)" {
|
||||
return d, nil
|
||||
} else if p == "Body" {
|
||||
return d, nil
|
||||
|
|
|
|||
|
|
@ -110,10 +110,17 @@ func BodySurvey(p Prompt, state *IssueMetadataState, templateContent string) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func TitleSurvey(p Prompt, state *IssueMetadataState) error {
|
||||
result, err := p.Input("Title", state.Title)
|
||||
if err != nil {
|
||||
return err
|
||||
func TitleSurvey(p Prompt, io *iostreams.IOStreams, state *IssueMetadataState) error {
|
||||
var err error
|
||||
result := ""
|
||||
for result == "" {
|
||||
result, err = p.Input("Title (required)", state.Title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == "" {
|
||||
fmt.Fprintf(io.ErrOut, "%s Title cannot be blank\n", io.ColorScheme().FailureIcon())
|
||||
}
|
||||
}
|
||||
|
||||
if result != state.Title {
|
||||
|
|
|
|||
|
|
@ -158,3 +158,44 @@ type testEditor struct {
|
|||
func (e testEditor) Edit(filename, text string) (string, error) {
|
||||
return e.edit(text)
|
||||
}
|
||||
|
||||
func TestTitleSurvey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prompterMockInputs []string
|
||||
expectedTitle string
|
||||
expectStderr bool
|
||||
}{
|
||||
{
|
||||
name: "title provided",
|
||||
prompterMockInputs: []string{"title"},
|
||||
expectedTitle: "title",
|
||||
},
|
||||
{
|
||||
name: "first input empty",
|
||||
prompterMockInputs: []string{"", "title"},
|
||||
expectedTitle: "title",
|
||||
expectStderr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, _, stderr := iostreams.Test()
|
||||
pm := prompter.NewMockPrompter(t)
|
||||
for _, input := range tt.prompterMockInputs {
|
||||
pm.RegisterInput("Title (required)", func(string, string) (string, error) {
|
||||
return input, nil
|
||||
})
|
||||
}
|
||||
|
||||
state := &IssueMetadataState{}
|
||||
err := TitleSurvey(pm, io, state)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedTitle, state.Title)
|
||||
if tt.expectStderr {
|
||||
assert.Equal(t, "X Title cannot be blank\n", stderr.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,16 +180,22 @@ func (r *Run) ExportData(fields []string) map[string]interface{} {
|
|||
for _, j := range r.Jobs {
|
||||
steps := make([]interface{}, 0, len(j.Steps))
|
||||
for _, s := range j.Steps {
|
||||
var stepCompletedAt time.Time
|
||||
if !s.CompletedAt.IsZero() {
|
||||
stepCompletedAt = s.CompletedAt
|
||||
}
|
||||
steps = append(steps, map[string]interface{}{
|
||||
"name": s.Name,
|
||||
"status": s.Status,
|
||||
"conclusion": s.Conclusion,
|
||||
"number": s.Number,
|
||||
"name": s.Name,
|
||||
"status": s.Status,
|
||||
"conclusion": s.Conclusion,
|
||||
"number": s.Number,
|
||||
"startedAt": s.StartedAt,
|
||||
"completedAt": stepCompletedAt,
|
||||
})
|
||||
}
|
||||
var completedAt time.Time
|
||||
var jobCompletedAt time.Time
|
||||
if !j.CompletedAt.IsZero() {
|
||||
completedAt = j.CompletedAt
|
||||
jobCompletedAt = j.CompletedAt
|
||||
}
|
||||
jobs = append(jobs, map[string]interface{}{
|
||||
"databaseId": j.ID,
|
||||
|
|
@ -198,7 +204,7 @@ func (r *Run) ExportData(fields []string) map[string]interface{} {
|
|||
"name": j.Name,
|
||||
"steps": steps,
|
||||
"startedAt": j.StartedAt,
|
||||
"completedAt": completedAt,
|
||||
"completedAt": jobCompletedAt,
|
||||
"url": j.URL,
|
||||
})
|
||||
}
|
||||
|
|
@ -225,11 +231,13 @@ type Job struct {
|
|||
}
|
||||
|
||||
type Step struct {
|
||||
Name string
|
||||
Status Status
|
||||
Conclusion Conclusion
|
||||
Number int
|
||||
Log *zip.File
|
||||
Name string
|
||||
Status Status
|
||||
Conclusion Conclusion
|
||||
Number int
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
Log *zip.File
|
||||
}
|
||||
|
||||
type Steps []Step
|
||||
|
|
|
|||
|
|
@ -110,8 +110,12 @@ func TestRun_Duration(t *testing.T) {
|
|||
|
||||
func TestRunExportData(t *testing.T) {
|
||||
oldestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:13Z")
|
||||
oldestStepStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:15Z")
|
||||
oldestStepCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:10Z")
|
||||
oldestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:16Z")
|
||||
newestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:55Z")
|
||||
newestStepStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:01Z")
|
||||
newestStepCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:23:10Z")
|
||||
newestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:23:16Z")
|
||||
|
||||
tests := []struct {
|
||||
|
|
@ -132,10 +136,12 @@ func TestRunExportData(t *testing.T) {
|
|||
Name: "macos",
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "success",
|
||||
Number: 1,
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "success",
|
||||
Number: 1,
|
||||
StartedAt: oldestStepStartedAt,
|
||||
CompletedAt: oldestStepCompletedAt,
|
||||
},
|
||||
},
|
||||
StartedAt: oldestStartedAt,
|
||||
|
|
@ -144,7 +150,7 @@ func TestRunExportData(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"conclusion":"success","name":"Checkout","number":1,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"}]}`,
|
||||
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:21:10Z","conclusion":"success","name":"Checkout","number":1,"startedAt":"2022-07-20T11:20:15Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"}]}`,
|
||||
},
|
||||
{
|
||||
name: "exports workflow run's multiple jobs",
|
||||
|
|
@ -158,10 +164,12 @@ func TestRunExportData(t *testing.T) {
|
|||
Name: "macos",
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "success",
|
||||
Number: 1,
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "success",
|
||||
Number: 1,
|
||||
StartedAt: oldestStepStartedAt,
|
||||
CompletedAt: oldestStepCompletedAt,
|
||||
},
|
||||
},
|
||||
StartedAt: oldestStartedAt,
|
||||
|
|
@ -175,10 +183,12 @@ func TestRunExportData(t *testing.T) {
|
|||
Name: "windows",
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "error",
|
||||
Number: 2,
|
||||
Name: "Checkout",
|
||||
Status: "completed",
|
||||
Conclusion: "error",
|
||||
Number: 2,
|
||||
StartedAt: newestStepStartedAt,
|
||||
CompletedAt: newestStepCompletedAt,
|
||||
},
|
||||
},
|
||||
StartedAt: newestStartedAt,
|
||||
|
|
@ -187,7 +197,7 @@ func TestRunExportData(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"conclusion":"success","name":"Checkout","number":1,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"},{"completedAt":"2022-07-20T11:23:16Z","conclusion":"error","databaseId":234567,"name":"windows","startedAt":"2022-07-20T11:20:55Z","status":"completed","steps":[{"conclusion":"error","name":"Checkout","number":2,"status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/234567"}]}`,
|
||||
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:21:10Z","conclusion":"success","name":"Checkout","number":1,"startedAt":"2022-07-20T11:20:15Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"},{"completedAt":"2022-07-20T11:23:16Z","conclusion":"error","databaseId":234567,"name":"windows","startedAt":"2022-07-20T11:20:55Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:23:10Z","conclusion":"error","name":"Checkout","number":2,"startedAt":"2022-07-20T11:21:01Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/234567"}]}`,
|
||||
},
|
||||
{
|
||||
name: "exports workflow run with attempt count",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue