From b1792403beae6e96c0638a3b1b383636ef62b74b Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Thu, 17 Oct 2024 10:09:08 -0400 Subject: [PATCH 1/7] Add testscripts for `gh secret`, helper cmds These are initial testscripts for confidence around core `gh secret` subcommands around 1) repository, 2) repository environment, 3) organization secrets. Each testscript exercises the 3 core subcommands of `gh secret`: 1) setting, 2) listing, and 3) deleting each type of secret. Note should be made that repository environments do not exist for free accounts such as `gh-acceptance-testing`. --- acceptance/acceptance_test.go | 29 +++++++++++++++++++ acceptance/testdata/secret/secret-org.txtar | 16 ++++++++++ .../testdata/secret/secret-repo-env.txtar | 28 ++++++++++++++++++ acceptance/testdata/secret/secret-repo.txtar | 24 +++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 acceptance/testdata/secret/secret-org.txtar create mode 100644 acceptance/testdata/secret/secret-repo-env.txtar create mode 100644 acceptance/testdata/secret/secret-repo.txtar diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 5b4fe7b58..7c135abfe 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -43,6 +43,15 @@ func TestIssues(t *testing.T) { testscript.Run(t, testScriptParamsFor(tsEnv, "pr")) } +func TestSecrets(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "secret")) +} + func testScriptParamsFor(tsEnv testScriptEnv, command string) testscript.Params { var files []string if tsEnv.script != "" { @@ -117,6 +126,26 @@ 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) != 2 { + ts.Fatalf("usage: env2lower old new") + } + + ts.Setenv(args[1], strings.ToLower(ts.Getenv(args[0]))) + }, + "env2upper": func(ts *testscript.TestScript, neg bool, args []string) { + if neg { + ts.Fatalf("unsupported: ! env2upper") + } + if len(args) != 2 { + ts.Fatalf("usage: env2upper old new") + } + + ts.Setenv(args[1], strings.ToUpper(ts.Getenv(args[0]))) + }, "stdout2env": func(ts *testscript.TestScript, neg bool, args []string) { if neg { ts.Fatalf("unsupported: ! stdout2env") diff --git a/acceptance/testdata/secret/secret-org.txtar b/acceptance/testdata/secret/secret-org.txtar new file mode 100644 index 000000000..9d28ff428 --- /dev/null +++ b/acceptance/testdata/secret/secret-org.txtar @@ -0,0 +1,16 @@ +# Prepare organization secret, GitHub Actions uppercases secret names +env2upper RANDOM_STRING ORG_SECRET_NAME + +# 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"' + +# Create an organization secret +exec gh secret set $ORG_SECRET_NAME --org $ORG --body 'does not matter as cannot read it once set' + +# Defer organization secret cleanup +defer gh secret delete $ORG_SECRET_NAME --org $ORG + +# Confirm organization secret exists +exec gh secret list --org $ORG +stdout $ORG_SECRET_NAME diff --git a/acceptance/testdata/secret/secret-repo-env.txtar b/acceptance/testdata/secret/secret-repo-env.txtar new file mode 100644 index 000000000..2a8972b37 --- /dev/null +++ b/acceptance/testdata/secret/secret-repo-env.txtar @@ -0,0 +1,28 @@ +# Force GitHub CLI to treat testscript as TTY +env GH_FORCE_TTY=80 + +# Create a repository where the secret will be registered +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 +cd $SCRIPT_NAME-$RANDOM_STRING + +# Create a repository environment +exec gh api /repos/$ORG/$SCRIPT_NAME-$RANDOM_STRING/environments/testscripts -X PUT --jq '.name' +stdout 'testscripts' + +# Verify no repository environment secrets exist +exec gh secret list --env testscripts +stderr 'no secrets found' + +# 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' + +# Verify new repository secret exists +exec gh secret list --env testscripts +stdout 'TESTSCRIPTS_ENV\s+less than a minute ago' diff --git a/acceptance/testdata/secret/secret-repo.txtar b/acceptance/testdata/secret/secret-repo.txtar new file mode 100644 index 000000000..d2c0a31c2 --- /dev/null +++ b/acceptance/testdata/secret/secret-repo.txtar @@ -0,0 +1,24 @@ +# Setup environment variables to force TTY +env GH_FORCE_TTY=80 + +# Create a repository where the secret will be registered +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 + +# Verify no repository secrets exist +cd $SCRIPT_NAME-$RANDOM_STRING +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' + +# Verify new repository secret exists +exec gh secret list +stdout 'TESTSCRIPTS\s+less than a minute ago' From 5bbe69b1d11c3898f4a77ca02b30f82cc65f55fe Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Thu, 17 Oct 2024 10:11:50 -0400 Subject: [PATCH 2/7] Update secret note about potential failure Environments are only supported for GitHub Actions with certain plans; `gh-acceptance-testing` at this time does not have this support. --- acceptance/testdata/secret/secret-repo-env.txtar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/testdata/secret/secret-repo-env.txtar b/acceptance/testdata/secret/secret-repo-env.txtar index 2a8972b37..aba0994c8 100644 --- a/acceptance/testdata/secret/secret-repo-env.txtar +++ b/acceptance/testdata/secret/secret-repo-env.txtar @@ -11,7 +11,7 @@ defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING cd $SCRIPT_NAME-$RANDOM_STRING -# Create a repository environment +# Create a repository environment, will fail if organization does not have environment support exec gh api /repos/$ORG/$SCRIPT_NAME-$RANDOM_STRING/environments/testscripts -X PUT --jq '.name' stdout 'testscripts' From 7c534e60109756c0f2bafa988cc388f9fe37ccc0 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Thu, 17 Oct 2024 10:49:17 -0400 Subject: [PATCH 3/7] Refactor env2upper, env2lower; add docs This commit refactors 2 new custom commands to work more like the native env command, allowing for multiple environment variables to be set. Additionally, the documentation on acceptance tests have been expanded to list out custom commands. --- acceptance/README.md | 33 +++++++++++++++++++++ acceptance/acceptance_test.go | 26 ++++++++++++---- acceptance/testdata/secret/secret-org.txtar | 2 +- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/acceptance/README.md b/acceptance/README.md index a1cd3f216..98f179f2c 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -61,6 +61,39 @@ The following custom environment variables are made available to the scripts: * `HOME`: Set to the initial working directory. Required for `git` operations * `GH_CONFIG_DIR`: Set to the initial working directory. Required for `gh` operations +#### Custom Commands + +The following custom commands are defined within [`acceptance_test.go`](./acceptance_test.go) to help with writing tests: + +- `defer`: register a command to run after the testscript completes + + ```txtar + # Defer repo cleanup + 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 + ``` + +- `env2uooer`: set environment variable to the uppercase version of another environment variable + + ```txtar + # Prepare organization secret, GitHub Actions uppercases secret names + env2upper ORG_SECRET_NAME=$RANDOM_STRING + ``` + +- `stdout2env`: set environment variable containing standard output from previous command + + ```txtar + # Create the PR + exec gh pr create --title 'Feature Title' --body 'Feature Body' --assignee '@me' --label 'bug' + stdout2env PR_URL + ``` + ### Acceptance Test VS Code Support Due to the `//go:build acceptance` build constraint, some functionality is limited because `gopls` isn't being informed about the tag. To resolve this, set the following in your `settings.json`: diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 7c135abfe..35ea7b40b 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -130,21 +130,35 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript, if neg { ts.Fatalf("unsupported: ! env2lower") } - if len(args) != 2 { - ts.Fatalf("usage: env2lower old new") + if len(args) == 0 { + ts.Fatalf("usage: env2lower name=value ...") } + for _, env := range args { + i := strings.Index(env, "=") - ts.Setenv(args[1], strings.ToLower(ts.Getenv(args[0]))) + 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") } - if len(args) != 2 { - ts.Fatalf("usage: env2upper old new") + if len(args) == 0 { + ts.Fatalf("usage: env2upper name=value ...") } + for _, env := range args { + i := strings.Index(env, "=") - ts.Setenv(args[1], strings.ToUpper(ts.Getenv(args[0]))) + if i < 0 { + ts.Fatalf("env2upper: argument does not match name=value") + } + + ts.Setenv(env[:i], strings.ToUpper(env[i+1:])) + } }, "stdout2env": func(ts *testscript.TestScript, neg bool, args []string) { if neg { diff --git a/acceptance/testdata/secret/secret-org.txtar b/acceptance/testdata/secret/secret-org.txtar index 9d28ff428..d6d2fd213 100644 --- a/acceptance/testdata/secret/secret-org.txtar +++ b/acceptance/testdata/secret/secret-org.txtar @@ -1,5 +1,5 @@ # Prepare organization secret, GitHub Actions uppercases secret names -env2upper RANDOM_STRING ORG_SECRET_NAME +env2upper ORG_SECRET_NAME=$RANDOM_STRING # Confirm organization secret does not exist, will fail admin:org scope missing ! exec gh api /orgs/$ORG/actions/secrets/$ORG_SECRET_NAME From e91052dd9b42c8934cec08147893c9eb76f2cd3b Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Thu, 17 Oct 2024 10:59:54 -0400 Subject: [PATCH 4/7] Fix typo in custom command doc --- acceptance/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/README.md b/acceptance/README.md index 98f179f2c..732369bc0 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -79,7 +79,7 @@ The following custom commands are defined within [`acceptance_test.go`](./accept env2lower REPO_NAME=$RANDOM_STRING ``` -- `env2uooer`: set environment variable to the uppercase version of another environment variable +- `env2upper`: set environment variable to the uppercase version of another environment variable ```txtar # Prepare organization secret, GitHub Actions uppercases secret names From e33511370670bcfad7bfe01523d5c64b17a7cda3 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Thu, 17 Oct 2024 16:02:39 -0400 Subject: [PATCH 5/7] Minor polish / consistency --- acceptance/testdata/secret/secret-repo-env.txtar | 11 ++++++----- acceptance/testdata/secret/secret-repo.txtar | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/acceptance/testdata/secret/secret-repo-env.txtar b/acceptance/testdata/secret/secret-repo-env.txtar index aba0994c8..b5236ea92 100644 --- a/acceptance/testdata/secret/secret-repo-env.txtar +++ b/acceptance/testdata/secret/secret-repo-env.txtar @@ -1,18 +1,19 @@ # Force GitHub CLI to treat testscript as TTY env GH_FORCE_TTY=80 +env REPO=$SCRIPT_NAME-$RANDOM_STRING # Create a repository where the secret will be registered -exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private +exec gh repo create $ORG/$REPO --add-readme --private # Defer repo cleanup -defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING +defer gh repo delete --yes $ORG/$REPO # Clone the repo -exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING -cd $SCRIPT_NAME-$RANDOM_STRING +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/$SCRIPT_NAME-$RANDOM_STRING/environments/testscripts -X PUT --jq '.name' +exec gh api /repos/$ORG/$REPO/environments/testscripts -X PUT --jq '.name' stdout 'testscripts' # Verify no repository environment secrets exist diff --git a/acceptance/testdata/secret/secret-repo.txtar b/acceptance/testdata/secret/secret-repo.txtar index d2c0a31c2..f69a06e57 100644 --- a/acceptance/testdata/secret/secret-repo.txtar +++ b/acceptance/testdata/secret/secret-repo.txtar @@ -1,17 +1,18 @@ -# Setup environment variables to force TTY +# Force GitHub CLI to treat testscript as TTY env GH_FORCE_TTY=80 +env REPO=$SCRIPT_NAME-$RANDOM_STRING # Create a repository where the secret will be registered -exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private +exec gh repo create $ORG/$REPO --add-readme --private # Defer repo cleanup -defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING +defer gh repo delete --yes $ORG/$REPO # Clone the repo -exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING +exec gh repo clone $ORG/$REPO +cd $REPO # Verify no repository secrets exist -cd $SCRIPT_NAME-$RANDOM_STRING exec gh secret list stderr 'no secrets found' From f4f161c09686225d2ee9981620073a13081cce67 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Fri, 18 Oct 2024 15:56:03 -0400 Subject: [PATCH 6/7] Refactor `gh secret` testscript This is a bit of a refactor based on the work done in `gh workflow` as a better approach to verify secrets created are what we expect. Changes made: 1. Removed `env2lower` as it wasn't being used in testscripts 2. Added `replace` custom command to deal with testing organization workflow secrets 3. Refactored secret testscripts to create and run workflow that tests the value of the secret provided 4. Minor reordering of test `acceptance` test functions as appending to the end is confusing and adds conflicts 5. Removed stdout TTY assertions --- acceptance/README.md | 23 +++-- acceptance/acceptance_test.go | 93 +++++++++++-------- acceptance/testdata/secret/secret-org.txtar | 85 +++++++++++++++-- .../testdata/secret/secret-repo-env.txtar | 75 ++++++++++++--- acceptance/testdata/secret/secret-repo.txtar | 71 ++++++++++++-- 5 files changed, 272 insertions(+), 75 deletions(-) diff --git a/acceptance/README.md b/acceptance/README.md index 732369bc0..82d7e1ab6 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -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 diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index e3f439d36..58e27f506 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -27,13 +27,13 @@ func TestMain(m *testing.M) { })) } -func TestPullRequests(t *testing.T) { +func TestAPI(t *testing.T) { var tsEnv testScriptEnv if err := tsEnv.fromEnv(); err != nil { t.Fatal(err) } - testscript.Run(t, testScriptParamsFor(tsEnv, "pr")) + testscript.Run(t, testScriptParamsFor(tsEnv, "api")) } func TestIssues(t *testing.T) { @@ -45,6 +45,24 @@ func TestIssues(t *testing.T) { testscript.Run(t, testScriptParamsFor(tsEnv, "pr")) } +func TestPullRequests(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "pr")) +} + +func TestReleases(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "release")) +} + func TestSecrets(t *testing.T) { var tsEnv testScriptEnv if err := tsEnv.fromEnv(); err != nil { @@ -63,25 +81,6 @@ func TestWorkflows(t *testing.T) { 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")) ->>>>>>> trunk -} - func testScriptParamsFor(tsEnv testScriptEnv, command string) testscript.Params { var files []string if tsEnv.script != "" { @@ -156,23 +155,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") @@ -190,6 +172,41 @@ 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) + 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 `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") diff --git a/acceptance/testdata/secret/secret-org.txtar b/acceptance/testdata/secret/secret-org.txtar index d6d2fd213..2bc6a845b 100644 --- a/acceptance/testdata/secret/secret-org.txtar +++ b/acceptance/testdata/secret/secret-org.txtar @@ -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 diff --git a/acceptance/testdata/secret/secret-repo-env.txtar b/acceptance/testdata/secret/secret-repo-env.txtar index b5236ea92..a9a2c7353 100644 --- a/acceptance/testdata/secret/secret-repo-env.txtar +++ b/acceptance/testdata/secret/secret-repo-env.txtar @@ -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 diff --git a/acceptance/testdata/secret/secret-repo.txtar b/acceptance/testdata/secret/secret-repo.txtar index f69a06e57..ed336626f 100644 --- a/acceptance/testdata/secret/secret-repo.txtar +++ b/acceptance/testdata/secret/secret-repo.txtar @@ -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 From acf62dacf2a42ad094ff19e0442f87e3da19e528 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Mon, 21 Oct 2024 11:53:05 -0400 Subject: [PATCH 7/7] Address @williammartin PR feedback --- acceptance/README.md | 2 +- acceptance/acceptance_test.go | 10 ++++++++-- acceptance/testdata/secret/secret-org.txtar | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/acceptance/README.md b/acceptance/README.md index 82d7e1ab6..750cb75d1 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -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 diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 58e27f506..de708e38c 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -111,7 +111,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) @@ -182,6 +186,8 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript, 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 @@ -198,7 +204,7 @@ func sharedCmds(tsEnv testScriptEnv) map[string]func(ts *testscript.TestScript, value := arg[i+1:] ts.Logf("replace %s: %s", name, value) - // `replace` was originally built similar to `cmpenv`, expanding environment variables within a file. + // `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. diff --git a/acceptance/testdata/secret/secret-org.txtar b/acceptance/testdata/secret/secret-org.txtar index 2bc6a845b..7d383009c 100644 --- a/acceptance/testdata/secret/secret-org.txtar +++ b/acceptance/testdata/secret/secret-org.txtar @@ -1,6 +1,6 @@ # Setup environment variables used for testscript -env REPO=$SCRIPT_NAME-$RANDOM_STRING -env2upper SECRET_NAME=$SCRIPT_NAME_$RANDOM_STRING +env REPO=${SCRIPT_NAME}-${RANDOM_STRING} +env2upper SECRET_NAME=${SCRIPT_NAME}_${RANDOM_STRING} # Use gh as a credential helper exec gh auth setup-git