mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
feat(secret,variable): read values like gh and report stored names
secret set and variable set read the value from -b/--body, -F/--body-file or standard input when it is not a terminal (one trailing line break removed), and otherwise prompt for it; --env-file sets every NAME=value of a dotenv file. --value and --value-file keep working. The scope flags are -o/--org and --account on each command, and secret list no longer offers --account, which Gitea cannot list. Commands report what they did, naming the scope and the name Gitea stores: "Set Actions secret LOWER_NAME for alice/x (names are upper-cased)"; --json returns the stored name and omits an unknown creation time. Deleting asks "Delete secret X from alice/x?". Lists show NAME, CREATED and DESCRIPTION for secrets and NAME, VALUE and DESCRIPTION for variables; variable view shows a record and the new variable get prints the raw value. A missing secret or variable is reported by name and scope. The global --user selects the account tea acts as, unlike gh's --user, so secret and variable add a note on a terminal pointing to --account when it is given, as run list does for --actor. Help explains the scopes. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
5 files changed
+641
-185
No files matched your search
@@ -7,9 +7,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/tea/internal/clierr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -32,3 +35,144 @@ func TestSecretValueFileDropsTrailingLineEnding(t *testing.T) {
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"secret", "set", "BUILD_TOKEN", "-R", "alice/project", "--value-file", "-", "--yes"}))
|
||||
require.Equal(t, "sensitive", stored)
|
||||
}
|
||||
|
||||
func TestActionValueSetReadsValuesAndReportsStoredNames(t *testing.T) {
|
||||
envFile := filepath.Join(t.TempDir(), ".env")
|
||||
require.NoError(t, os.WriteFile(envFile, []byte("# deploy\nAPI_KEY=abc\nexport region=\"eu\\nwest\"\nMODE=fast # inline\n"), 0o600))
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
stdin string
|
||||
requests []string
|
||||
out, err string
|
||||
}{
|
||||
{
|
||||
"lower-case name from --body",
|
||||
[]string{"secret", "set", "lower_name", "-b", "x"},
|
||||
"",
|
||||
[]string{`PUT actions/secrets/lower_name {"data":"x","description":""}`},
|
||||
"",
|
||||
"✓ Set Actions secret LOWER_NAME for alice/project (names are upper-cased)\n",
|
||||
},
|
||||
{
|
||||
"standard input",
|
||||
[]string{"secret", "set", "TOKEN"},
|
||||
"piped\n",
|
||||
[]string{`PUT actions/secrets/TOKEN {"data":"piped","description":""}`},
|
||||
"", "✓ Set Actions secret TOKEN for alice/project\n",
|
||||
},
|
||||
{
|
||||
"JSON names the stored secret",
|
||||
[]string{"secret", "set", "token", "--body", "x", "--json"},
|
||||
"",
|
||||
[]string{`PUT actions/secrets/token {"data":"x","description":""}`},
|
||||
`{"name":"TOKEN","description":""}`, "",
|
||||
},
|
||||
{
|
||||
"organization",
|
||||
[]string{"secret", "set", "TOKEN", "-o", "acme", "-b", "x"},
|
||||
"",
|
||||
[]string{`PUT api/v1/orgs/acme/actions/secrets/TOKEN {"data":"x","description":""}`},
|
||||
"", "✓ Set Actions secret TOKEN for organization acme\n",
|
||||
},
|
||||
{
|
||||
"account",
|
||||
[]string{"secret", "set", "TOKEN", "--account", "-b", "x"},
|
||||
"",
|
||||
[]string{`PUT api/v1/user/actions/secrets/TOKEN {"data":"x","description":""}`},
|
||||
"", "✓ Set Actions secret TOKEN for your account\n",
|
||||
},
|
||||
{
|
||||
"dotenv file",
|
||||
[]string{"variable", "set", "--env-file", envFile, "-o", "acme"},
|
||||
"",
|
||||
[]string{
|
||||
"GET api/v1/orgs/acme/actions/variables/API_KEY", `POST api/v1/orgs/acme/actions/variables/API_KEY {"value":"abc","description":""}`,
|
||||
"GET api/v1/orgs/acme/actions/variables/region", `POST api/v1/orgs/acme/actions/variables/region {"value":"eu\nwest","description":""}`,
|
||||
"GET api/v1/orgs/acme/actions/variables/MODE", `POST api/v1/orgs/acme/actions/variables/MODE {"value":"fast","description":""}`,
|
||||
},
|
||||
"",
|
||||
"✓ Set Actions variable API_KEY for organization acme\n✓ Set Actions variable REGION for organization acme (names are upper-cased)\n✓ Set Actions variable MODE for organization acme\n",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
server, requests := actionsAPI(t, map[string]string{
|
||||
"PUT actions/secrets/lower_name": "204 ", "PUT actions/secrets/TOKEN": "204 ", "PUT actions/secrets/token": "204 ",
|
||||
"PUT api/v1/orgs/acme/actions/secrets/TOKEN": "204 ", "PUT api/v1/user/actions/secrets/TOKEN": "204 ",
|
||||
"GET api/v1/orgs/acme/actions/variables/API_KEY": `404 {}`, "POST api/v1/orgs/acme/actions/variables/API_KEY": "201 ",
|
||||
"GET api/v1/orgs/acme/actions/variables/region": `404 {}`, "POST api/v1/orgs/acme/actions/variables/region": "201 ",
|
||||
"GET api/v1/orgs/acme/actions/variables/MODE": `404 {}`, "POST api/v1/orgs/acme/actions/variables/MODE": "201 ",
|
||||
})
|
||||
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
||||
deps.Streams.In = strings.NewReader(tc.stdin)
|
||||
args := tc.args
|
||||
if !strings.Contains(strings.Join(args, " "), "-o acme") && !strings.Contains(strings.Join(args, " "), "--account") {
|
||||
args = append(args, "-R", "alice/project")
|
||||
}
|
||||
require.NoError(t, Execute(context.Background(), deps, args))
|
||||
require.Equal(t, tc.requests, *requests)
|
||||
if tc.out != "" {
|
||||
require.JSONEq(t, tc.out, out.String())
|
||||
} else {
|
||||
require.Empty(t, out.String())
|
||||
}
|
||||
require.Equal(t, tc.err, diagnostics.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionValueListsShowScopeAndExplainTheAccountFlags(t *testing.T) {
|
||||
server, _ := actionsAPI(t, map[string]string{
|
||||
"GET actions/secrets": `[{"name":"DEPLOY_TOKEN","description":"Deploy token","created_at":"2028-01-01T10:00:00Z"}]`,
|
||||
"GET actions/variables": `[{"name":"ENVIRONMENT","data":"staging","description":"Target environment"}]`,
|
||||
})
|
||||
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
||||
deps.Streams.ErrTTY = true
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"secret", "list", "-R", "alice/project", "--user", "alice"}))
|
||||
require.Equal(t, "Showing 1 of 1 secrets in alice/project\n\nNAME CREATED DESCRIPTION\nDEPLOY_TOKEN about 2 hours ago Deploy token\n", out.String())
|
||||
require.Equal(t, "note: --user selects the account tea acts as (alice); Gitea cannot list your account's secrets; set or delete them with --account\n", diagnostics.String())
|
||||
out.Reset()
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"variable", "list", "-R", "alice/project"}))
|
||||
require.Equal(t, "Showing 1 of 1 variables in alice/project\n\nNAME VALUE DESCRIPTION\nENVIRONMENT staging Target environment\n", out.String())
|
||||
err := Execute(context.Background(), deps, []string{"secret", "list", "--account"})
|
||||
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
||||
require.ErrorContains(t, err, "unknown flag: --account")
|
||||
}
|
||||
|
||||
func TestVariableGetAndView(t *testing.T) {
|
||||
server, _ := actionsAPI(t, map[string]string{"GET actions/variables/region": `{"name":"REGION","data":"eu\nwest","description":"Where to deploy"}`})
|
||||
for _, tc := range []struct {
|
||||
args []string
|
||||
tty bool
|
||||
want string
|
||||
}{
|
||||
{[]string{"variable", "get", "region"}, false, "eu\nwest\n"},
|
||||
{[]string{"variable", "view", "region"}, true, "REGION\nScope: alice/project\nDescription: Where to deploy\n\neu\nwest\n"},
|
||||
{[]string{"variable", "view", "region"}, false, "title:\tREGION\nscope:\talice/project\ndescription:\tWhere to deploy\n--\neu\nwest\n"},
|
||||
} {
|
||||
deps, out, _ := actionsDeps(t, server.URL, tc.tty)
|
||||
require.NoError(t, Execute(context.Background(), deps, append(tc.args, "-R", "alice/project")))
|
||||
require.Equal(t, tc.want, out.String(), "%v tty=%t", tc.args, tc.tty)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionValueDeleteConfirmsWithTheScope(t *testing.T) {
|
||||
server, requests := actionsAPI(t, map[string]string{"DELETE actions/secrets/deploy_token": "204 "})
|
||||
for _, accept := range []bool{false, true} {
|
||||
*requests = nil
|
||||
deps, _, diagnostics := actionsDeps(t, server.URL, true)
|
||||
prompter := &scriptedPrompter{accept: accept}
|
||||
deps.Prompter = prompter
|
||||
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
||||
err := Execute(context.Background(), deps, []string{"secret", "delete", "deploy_token", "-R", "alice/project"})
|
||||
require.Equal(t, []string{"Delete secret DEPLOY_TOKEN from alice/project?"}, prompter.confirmations)
|
||||
if !accept {
|
||||
require.Equal(t, clierr.Canceled, clierr.ExitCode(err))
|
||||
require.Empty(t, *requests)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"DELETE actions/secrets/deploy_token"}, *requests)
|
||||
require.Equal(t, "✓ Deleted Actions secret DEPLOY_TOKEN from alice/project\n", diagnostics.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user