diff --git a/acceptance/README.md b/acceptance/README.md index 750cb75d1..8e8d7838e 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -159,7 +159,9 @@ When tests fail they fail like this: This is generally enough information to understand why a test has failed. However, we can get more information by providing the `-v` flag to `go test`, which turns on verbose mode and shows each command and any associated `stdio`. > [!WARNING] -> Verbose mode dumps the `testscript` environment variables, including the `GH_TOKEN`, so be careful. +> Verbose mode dumps the `testscript` environment variables, so make sure there is nothing sensitive in there. +> We have taken steps to [redact tokens](https://github.com/cli/cli/pull/9804) in log output but there's no +> guarantee it's comprehensive. By default `testscript` removes the directory in which it was running the script, and if you've been a conscientious engineer, you should be cleaning up resources using the `defer` statement. However, this can be an impediment to debugging. As such you can set `GH_ACCEPTANCE_PRESERVE_WORK_DIR=true` and `GH_ACCEPTANCE_SKIP_DEFER=true` to skip these cleanup steps. diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index bdd631703..6efa4864e 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -14,7 +14,7 @@ import ( "math/rand" "github.com/cli/cli/v2/internal/ghcmd" - "github.com/rogpeppe/go-internal/testscript" + "github.com/cli/go-internal/testscript" ) func ghMain() int { @@ -63,6 +63,15 @@ func TestAPI(t *testing.T) { testscript.Run(t, testScriptParamsFor(tsEnv, "api")) } +func TestAuth(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "auth")) +} + func TestReleases(t *testing.T) { var tsEnv testScriptEnv if err := tsEnv.fromEnv(); err != nil { @@ -86,7 +95,7 @@ func TestRepo(t *testing.T) { if err := tsEnv.fromEnv(); err != nil { t.Fatal(err) } - + testscript.Run(t, testScriptParamsFor(tsEnv, "repo")) } @@ -108,6 +117,42 @@ func TestVariables(t *testing.T) { testscript.Run(t, testScriptParamsFor(tsEnv, "variable")) } +func TestGPGKeys(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "gpg-key")) +} + +func TestLabels(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "label")) +} + +func TestSSHKeys(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "ssh-key")) +} + +func TestOrg(t *testing.T) { + var tsEnv testScriptEnv + if err := tsEnv.fromEnv(); err != nil { + t.Fatal(err) + } + + testscript.Run(t, testScriptParamsFor(tsEnv, "org")) +} + func testScriptParamsFor(tsEnv testScriptEnv, command string) testscript.Params { var files []string if tsEnv.script != "" { diff --git a/acceptance/testdata/auth/auth-login-logout.txtar b/acceptance/testdata/auth/auth-login-logout.txtar new file mode 100644 index 000000000..fb25f9eb5 --- /dev/null +++ b/acceptance/testdata/auth/auth-login-logout.txtar @@ -0,0 +1,25 @@ +# We aren't logged in at the moment, but GH_TOKEN will override the +# need to login. We are going to clear GH_TOKEN first to ensure no +# overrides are happening + +# Copy $GH_TOKEN to a new env var +env LOGIN_TOKEN=$GH_TOKEN + +# Remove GH_TOKEN env var so we don't fall back to it +env GH_TOKEN='' + +# Login to the host by feeding the token to stdin +exec echo $LOGIN_TOKEN +stdin stdout +exec gh auth login --hostname=$GH_HOST --with-token --insecure-storage + +# Check that we are logged in +exec gh auth status --hostname $GH_HOST +stdout $GH_HOST + +# Logout of the host +exec gh auth logout --hostname $GH_HOST +stderr 'Logged out of' + +# Check that we are logged out +! exec gh auth status --hostname $GH_HOST diff --git a/acceptance/testdata/auth/auth-setup-git.txtar b/acceptance/testdata/auth/auth-setup-git.txtar new file mode 100644 index 000000000..e3be28cd5 --- /dev/null +++ b/acceptance/testdata/auth/auth-setup-git.txtar @@ -0,0 +1,10 @@ +# Check that the credential helper is unset for the host. This command is +# expected to fail before gh auth setup-git is run. +! exec git config --get credential.https://${GH_HOST}.helper + +# Run the setup-git command +exec gh auth setup-git + +# Check that the credential helper is set to gh +exec git config --get credential.https://${GH_HOST}.helper +stdout '^.*gh auth git-credential$' diff --git a/acceptance/testdata/auth/auth-status.txtar b/acceptance/testdata/auth/auth-status.txtar new file mode 100644 index 000000000..2afee1eb6 --- /dev/null +++ b/acceptance/testdata/auth/auth-status.txtar @@ -0,0 +1,3 @@ +# Check the authentication status +exec gh auth status --hostname $GH_HOST +stdout '✓ Logged in to ' \ No newline at end of file diff --git a/acceptance/testdata/auth/auth-token.txtar b/acceptance/testdata/auth/auth-token.txtar new file mode 100644 index 000000000..614d11817 --- /dev/null +++ b/acceptance/testdata/auth/auth-token.txtar @@ -0,0 +1,3 @@ +# Check authentication token +exec gh auth token --hostname $GH_HOST +stdout $GH_TOKEN \ No newline at end of file diff --git a/acceptance/testdata/gpg-key/gpg-key.txtar b/acceptance/testdata/gpg-key/gpg-key.txtar new file mode 100644 index 000000000..8f0d71545 --- /dev/null +++ b/acceptance/testdata/gpg-key/gpg-key.txtar @@ -0,0 +1,36 @@ +skip 'it modifies the user''s personal GitHub account GPG keys' + +# This test requires the admin:gpg_key scope to add and delete GPG keys to and +# from the user's personal GitHub account. +# This test uses a GPG key that generated for this test only. The private key +# has been deleted + +# Add the gpg key to GH account +exec gh gpg-key add gpg-key.pub + +# Verify the gpg key was added to GH account +exec gh gpg-key list +stdout '24C30F9C9115E747' + +# Delete the gpg key from GH account +exec gh gpg-key delete --yes '24C30F9C9115E747' + +# Check the key is deleted +exec gh gpg-key list +! stdout '24C30F9C9115E747' + +-- gpg-key.pub -- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZxpWhhYJKwYBBAHaRw8BAQdAmYiobR2ai/lVWOBtlAPRG1ZEMG5Effavpt5w +n+wQ//W0R0dIIENMSSBhY2NlcHRhbmNlIHRlc3QgKGZvciBHSCBDTEkgYWNjZXB0 +YW5jZSB0ZXN0aW5nKSA8Y2xpQGdpdGh1Yi5jb20+iJkEExYKAEEWIQTEAQLLUl1x +MDSmbL0kww+ckRXnRwUCZxpWhgIbAwUJAAFRgAULCQgHAgIiAgYVCgkICwIEFgID +AQIeBwIXgAAKCRAkww+ckRXnRxkuAP9GiFi/etWxRjnkomdTaOU8Ccd6oHspuEzB +PFxOJdYslQD+MXgY5UhM/q2iEVj0tiVsfRzDqB+g2weaF5EpqIwWcQ+4OARnGlaG +EgorBgEEAZdVAQUBAQdA3D1vnVTc9URDQw/oAd1mG/zRX7vF4QrjFqFIt7uMf2gD +AQgHiH4EGBYKACYWIQTEAQLLUl1xMDSmbL0kww+ckRXnRwUCZxpWhgIbDAUJAAFR +gAAKCRAkww+ckRXnRxVuAQCngnR11jh2mob0FN0rPWce2juoJsh5gPB2d7LS4r5P +VwEA6F2FeetcP51EyKyQGTp3GpmZgk0uCGJa1G5uqT+9mgc= +=RLWi +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/acceptance/testdata/label/label.txtar b/acceptance/testdata/label/label.txtar new file mode 100644 index 000000000..dd72133da --- /dev/null +++ b/acceptance/testdata/label/label.txtar @@ -0,0 +1,25 @@ +# Setup useful env vars +env REPO=${SCRIPT_NAME}-${RANDOM_STRING} + +# Create a repository +exec gh repo create ${ORG}/${REPO} --private + +# Defer repo cleanup +defer gh repo delete --yes ${ORG}/${REPO} + +# Set the GH_REPO env var to reduce redunant flags +env GH_REPO=${ORG}/${REPO} + +# Create a custom label +exec gh label create 'acceptance-test' --description 'First Description' + +# List the labels and check our custom label is there +exec gh label list +stdout 'acceptance-test\tFirst Description' + +# Edit the label +exec gh label edit 'acceptance-test' --description 'Edited Description' + +# List the labels and check our custom label has been updated +exec gh label list +stdout 'acceptance-test\tEdited Description' diff --git a/acceptance/testdata/org/org-list.txtar b/acceptance/testdata/org/org-list.txtar new file mode 100644 index 000000000..ca114babe --- /dev/null +++ b/acceptance/testdata/org/org-list.txtar @@ -0,0 +1,6 @@ +# This test could fail if the user is a member of more than 30 organizations because +# the `gh org list` command only returns the first 30 organizations the user is a member of + +# List organizations the user is a member of +exec gh org list +stdout ${GH_ACCEPTANCE_ORG} \ No newline at end of file diff --git a/acceptance/testdata/ssh-key/ssh-key.txtar b/acceptance/testdata/ssh-key/ssh-key.txtar new file mode 100644 index 000000000..4ba8643bb --- /dev/null +++ b/acceptance/testdata/ssh-key/ssh-key.txtar @@ -0,0 +1,24 @@ +skip 'it modifies the user''s personal GitHub account SSH keys' + +# scopes admin:ssh_signing_key,admin:public_key + +# Add an SSH key to the account +exec gh ssh-key add sshKey.pub --title 'acceptance-test-key' + +# List the SSH keys +exec gh ssh-key list +stdout 'acceptance-test-key' + +# Get the ID of the key we created +exec gh api /user/keys --jq '.[] | select(.title == "acceptance-test-key") | .id' +stdout2env SSH_KEY_ID + +# Delete the SSH key +exec gh ssh-key delete --yes ${SSH_KEY_ID} + +# Check the key is deleted +exec gh ssh-key list +! stdout 'acceptance-test-key' + +-- sshKey.pub -- +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAZmdeRNskfpvYL5YHB/YJaW8hTEXpnvPMkx5Ri+YwUr acceptance diff --git a/go.mod b/go.mod index 138ef8b60..77be0c19d 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.10.1-0.20240413172830-d0be07ea6b9c github.com/cli/go-gh/v2 v2.11.0 + github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 github.com/cli/oauth v1.1.1 github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.5 @@ -125,7 +126,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rodaine/table v1.0.1 // indirect - github.com/rogpeppe/go-internal v1.13.1 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -167,5 +167,3 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect k8s.io/klog/v2 v2.120.1 // indirect ) - -replace github.com/rogpeppe/go-internal => github.com/cli/go-internal v0.0.0-20241024130215-fa3c22e38b9b diff --git a/go.sum b/go.sum index d23e604f0..fe7adc3e7 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/cli/go-gh/v2 v2.11.0 h1:TERLYMMWderKBO3lBff/JIu2+eSly2oFRgN2WvO+3eA= github.com/cli/go-gh/v2 v2.11.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE= -github.com/cli/go-internal v0.0.0-20241024130215-fa3c22e38b9b h1:ogNz+pm9aI2Zn58V+WTMzPViN2fRVq/h2H9gwdvM09k= -github.com/cli/go-internal v0.0.0-20241024130215-fa3c22e38b9b/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= +github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 h1:QDrhR4JA2n3ij9YQN0u5ZeuvRIIvsUGmf5yPlTS0w8E= +github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24/go.mod h1:rr9GNING0onuVw8MnracQHn7PcchnFlP882Y0II2KZk= github.com/cli/oauth v1.1.1 h1:459gD3hSjlKX9B1uXBuiAMdpXBUQ9QGf/NDcCpoQxPs= github.com/cli/oauth v1.1.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= @@ -368,6 +368,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index e76dd2271..6d22b6ae7 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -41,17 +41,18 @@ var HelpTopics = []helpTopic{ { name: "environment", short: "Environment variables that can be used with gh", - long: heredoc.Docf(` - %[1]sGH_TOKEN%[1]s, %[1]sGITHUB_TOKEN%[1]s (in order of precedence): an authentication token for github.com - API requests. Setting this avoids being prompted to authenticate and takes precedence over - previously stored credentials. + long: heredoc.Docf(` + %[1]sGH_TOKEN%[1]s, %[1]sGITHUB_TOKEN%[1]s (in order of precedence): an authentication token that will be used when + a command targets either github.com or a subdomain of ghe.com. Setting this avoids being prompted to + authenticate and takes precedence over previously stored credentials. - %[1]sGH_ENTERPRISE_TOKEN%[1]s, %[1]sGITHUB_ENTERPRISE_TOKEN%[1]s (in order of precedence): an authentication - token for API requests to GitHub Enterprise. When setting this, also set %[1]sGH_HOST%[1]s. + %[1]sGH_ENTERPRISE_TOKEN%[1]s, %[1]sGITHUB_ENTERPRISE_TOKEN%[1]s (in order of precedence): an authentication + token that will be used when a command targets a GitHub Enterprise Server host. - %[1]sGH_HOST%[1]s: specify the GitHub hostname for commands that would otherwise assume the - "github.com" host when not in a context of an existing repository. When setting this, - also set %[1]sGH_ENTERPRISE_TOKEN%[1]s. + %[1]sGH_HOST%[1]s: specify the GitHub hostname for commands where a hostname has not been provided, or + cannot be inferred from the context of a local Git repository. If this host was previously + authenticated with, the stored credentials will be used. Otherwise, setting %[1]sGH_TOKEN%[1]s or + %[1]sGH_ENTERPRISE_TOKEN%[1]s is required, depending on the targeted host. %[1]sGH_REPO%[1]s: specify the GitHub repository in the %[1]s[HOST/]OWNER/REPO%[1]s format for commands that otherwise operate on a local repository.