From d396674e12e1fdaba930043eae2f208b751c635c Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 2 Jun 2021 15:53:03 +0100 Subject: [PATCH 1/3] Fix a typo in the docs --- docs/project-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project-layout.md b/docs/project-layout.md index cf594a2e7..cf9758b46 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -75,7 +75,7 @@ and talk through which code gets run in order. This task might be tricky. Typically, gh commands do things like look up information from the git repository in the current directory, query the GitHub API, scan the user's `~/.ssh/config` file, clone or fetch git repositories, etc. Naturally, none of these things should ever happen for real when running tests, unless -you are sure that any filesystem operations are stricly scoped to a location made for and maintained by the +you are sure that any filesystem operations are strictly scoped to a location made for and maintained by the test itself. To avoid actually running things like making real API requests or shelling out to `git` commands, we stub them. You should look at how that's done within some existing tests. From 32856c987dc8b4ab688f7cbb23d16bb9313c1eba Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Wed, 2 Jun 2021 15:53:40 +0100 Subject: [PATCH 2/3] Add ability to set environments secrets --- pkg/cmd/secret/set/http.go | 9 ++++++ pkg/cmd/secret/set/set.go | 20 +++++++++++-- pkg/cmd/secret/set/set_test.go | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/secret/set/http.go b/pkg/cmd/secret/set/http.go index 8c589adec..3ecee37c0 100644 --- a/pkg/cmd/secret/set/http.go +++ b/pkg/cmd/secret/set/http.go @@ -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, diff --git a/pkg/cmd/secret/set/set.go b/pkg/cmd/secret/set/set.go index cb1975ad3..9bb664e5a 100644 --- a/pkg/cmd/secret/set/set.go +++ b/pkg/cmd/secret/set/set.go @@ -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 ", 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) diff --git a/pkg/cmd/secret/set/set_test.go b/pkg/cmd/secret/set/set_test.go index 7f9273eae..f1c2b7d76 100644 --- a/pkg/cmd/secret/set/set_test.go +++ b/pkg/cmd/secret/set/set_test.go @@ -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 From 0931531e2fac2322f37ed28a106183188b87a547 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Wed, 2 Jun 2021 13:27:19 -0500 Subject: [PATCH 3/3] collapse conditional --- pkg/cmd/secret/set/set.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/secret/set/set.go b/pkg/cmd/secret/set/set.go index 9bb664e5a..8f6de703d 100644 --- a/pkg/cmd/secret/set/set.go +++ b/pkg/cmd/secret/set/set.go @@ -184,12 +184,10 @@ func setRun(opts *SetOptions) error { if orgName != "" { err = putOrgSecret(client, host, pk, *opts, encoded) + } else if envName != "" { + err = putEnvSecret(client, pk, baseRepo, envName, opts.SecretName, encoded) } else { - if envName != "" { - err = putEnvSecret(client, pk, baseRepo, envName, opts.SecretName, encoded) - } else { - err = putRepoSecret(client, pk, baseRepo, opts.SecretName, encoded) - } + err = putRepoSecret(client, pk, baseRepo, opts.SecretName, encoded) } if err != nil { return fmt.Errorf("failed to set secret: %w", err)