Add ability to set environments secrets

This commit is contained in:
Bruno Alla 2021-06-02 15:53:40 +01:00
parent d396674e12
commit 32856c987d
3 changed files with 78 additions and 3 deletions

View file

@ -90,6 +90,15 @@ func putOrgSecret(client *api.Client, host string, pk *PubKey, opts SetOptions,
return putSecret(client, host, path, payload)
}
func putEnvSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, envName string, secretName, eValue string) error {
payload := SecretPayload{
EncryptedValue: eValue,
KeyID: pk.ID,
}
path := fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(repo), envName, secretName)
return putSecret(client, repo.RepoHost(), path, payload)
}
func putRepoSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, secretName, eValue string) error {
payload := SecretPayload{
EncryptedValue: eValue,

View file

@ -33,6 +33,7 @@ type SetOptions struct {
SecretName string
OrgName string
EnvName string
Body string
Visibility string
RepositoryNames []string
@ -48,7 +49,7 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
cmd := &cobra.Command{
Use: "set <secret-name>",
Short: "Create or update secrets",
Long: "Locally encrypt a new or updated secret at either the repository or organization level and send it to GitHub for storage.",
Long: "Locally encrypt a new or updated secret at either the repository, environment, or organization level and send it to GitHub for storage.",
Example: heredoc.Doc(`
Paste secret in prompt
$ gh secret set MYSECRET
@ -59,6 +60,9 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
Use file as secret value
$ gh secret set MYSECRET < file.json
Set environment level secret
$ gh secret set MYSECRET -bval --env=anEnv
Set organization level secret visible to entire organization
$ gh secret set MYSECRET -bval --org=anOrg --visibility=all
@ -75,6 +79,10 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
if err := cmdutil.MutuallyExclusive("specify only one of `--org` or `--env`", opts.OrgName != "", opts.EnvName != ""); err != nil {
return err
}
opts.SecretName = args[0]
err := validSecretName(opts.SecretName)
@ -115,7 +123,8 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
return setRun(opts)
},
}
cmd.Flags().StringVarP(&opts.OrgName, "org", "o", "", "List secrets for an organization")
cmd.Flags().StringVarP(&opts.OrgName, "org", "o", "", "Set a secret for an organization")
cmd.Flags().StringVarP(&opts.EnvName, "env", "e", "", "Set a secret for an organization")
cmd.Flags().StringVarP(&opts.Visibility, "visibility", "v", "private", "Set visibility for an organization secret: `all`, `private`, or `selected`")
cmd.Flags().StringSliceVarP(&opts.RepositoryNames, "repos", "r", []string{}, "List of repository names for `selected` visibility")
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "A value for the secret. Reads from STDIN if not specified.")
@ -136,6 +145,7 @@ func setRun(opts *SetOptions) error {
client := api.NewClientFromHTTP(c)
orgName := opts.OrgName
envName := opts.EnvName
var baseRepo ghrepo.Interface
if orgName == "" {
@ -175,7 +185,11 @@ func setRun(opts *SetOptions) error {
if orgName != "" {
err = putOrgSecret(client, host, pk, *opts, encoded)
} else {
err = putRepoSecret(client, pk, baseRepo, opts.SecretName, encoded)
if envName != "" {
err = putEnvSecret(client, pk, baseRepo, envName, opts.SecretName, encoded)
} else {
err = putRepoSecret(client, pk, baseRepo, opts.SecretName, encoded)
}
}
if err != nil {
return fmt.Errorf("failed to set secret: %w", err)

View file

@ -100,6 +100,17 @@ func TestNewCmdSet(t *testing.T) {
OrgName: "",
},
},
{
name: "env",
cli: `cool_secret -b"a secret" -eRelease`,
wants: SetOptions{
SecretName: "cool_secret",
Visibility: shared.Private,
Body: "a secret",
OrgName: "",
EnvName: "Release",
},
},
{
name: "vis all",
cli: `cool_secret --org coolOrg -b"cool" -vall`,
@ -160,6 +171,7 @@ func TestNewCmdSet(t *testing.T) {
assert.Equal(t, tt.wants.Body, gotOpts.Body)
assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility)
assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
assert.Equal(t, tt.wants.EnvName, gotOpts.EnvName)
assert.ElementsMatch(t, tt.wants.RepositoryNames, gotOpts.RepositoryNames)
})
}
@ -204,6 +216,46 @@ func Test_setRun_repo(t *testing.T) {
assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
}
func Test_setRun_env(t *testing.T) {
reg := &httpmock.Registry{}
reg.Register(httpmock.REST("GET", "repos/owner/repo/actions/secrets/public-key"),
httpmock.JSONResponse(PubKey{ID: "123", Key: "CDjXqf7AJBXWhMczcy+Fs7JlACEptgceysutztHaFQI="}))
reg.Register(httpmock.REST("PUT", "repos/owner/repo/environments/development/secrets/cool_secret"), httpmock.StatusStringResponse(201, `{}`))
io, _, _, _ := iostreams.Test()
opts := &SetOptions{
HttpClient: func() (*http.Client, error) {
return &http.Client{Transport: reg}, nil
},
Config: func() (config.Config, error) { return config.NewBlankConfig(), nil },
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("owner/repo")
},
EnvName: "development",
IO: io,
SecretName: "cool_secret",
Body: "a secret",
// Cribbed from https://github.com/golang/crypto/commit/becbf705a91575484002d598f87d74f0002801e7
RandomOverride: bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}),
}
err := setRun(opts)
assert.NoError(t, err)
reg.Verify(t)
data, err := ioutil.ReadAll(reg.Requests[1].Body)
assert.NoError(t, err)
var payload SecretPayload
err = json.Unmarshal(data, &payload)
assert.NoError(t, err)
assert.Equal(t, payload.KeyID, "123")
assert.Equal(t, payload.EncryptedValue, "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV39vl3BHiGSgj8zJapDxgB2BwqNqRhpjC4=")
}
func Test_setRun_org(t *testing.T) {
tests := []struct {
name string