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
This commit is contained in:
Andy Feller 2024-10-18 15:56:03 -04:00
parent 1571a113c2
commit f4f161c096
5 changed files with 272 additions and 75 deletions

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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