Adding set secrets from env files
Allows to set multiple secrets from an env file.
gh secret set -f secrets.env
The env file follows a simple format as defined in https://github.com/joho/godotenv
SPAM=eggs
FOO="bar"
This commit is contained in:
parent
ca25026613
commit
5589583e4d
4 changed files with 85 additions and 38 deletions
1
go.mod
1
go.mod
|
|
@ -20,6 +20,7 @@ require (
|
|||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/henvic/httpretty v0.0.6
|
||||
github.com/itchyny/gojq v0.12.6
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -286,6 +286,8 @@ github.com/itchyny/gojq v0.12.6 h1:VjaFn59Em2wTxDNGcrRkDK9ZHMNa8IksOgL13sLL4d0=
|
|||
github.com/itchyny/gojq v0.12.6/go.mod h1:ZHrkfu7A+RbZLy5J1/JKpS4poEqrzItSTGDItqsfP0A=
|
||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ func putSecret(client *api.Client, host, path string, payload interface{}) error
|
|||
return client.REST(host, "PUT", path, requestBody, nil)
|
||||
}
|
||||
|
||||
func putOrgSecret(client *api.Client, host string, pk *PubKey, opts SetOptions, eValue string) error {
|
||||
secretName := opts.SecretName
|
||||
func putOrgSecret(client *api.Client, host string, pk *PubKey, opts SetOptions, secretName string, eValue string) error {
|
||||
orgName := opts.OrgName
|
||||
visibility := opts.Visibility
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
|
@ -36,6 +37,7 @@ type SetOptions struct {
|
|||
DoNotStore bool
|
||||
Visibility string
|
||||
RepositoryNames []string
|
||||
EnvFile string
|
||||
}
|
||||
|
||||
func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
|
||||
|
|
@ -81,6 +83,9 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
|
||||
# Set user-level secret for Codespaces
|
||||
$ gh secret set MYSECRET --user
|
||||
|
||||
# Set multiple secrets imported from the ".env" file
|
||||
$ gh secret set -f .env
|
||||
`),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -91,8 +96,12 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
return err
|
||||
}
|
||||
|
||||
if err := cmdutil.MutuallyExclusive("specify only one of `--body` or `--env-file`", opts.Body != "", opts.EnvFile != ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if !opts.DoNotStore {
|
||||
if !opts.DoNotStore && opts.EnvFile == "" {
|
||||
return cmdutil.FlagErrorf("must pass name argument")
|
||||
}
|
||||
} else {
|
||||
|
|
@ -136,14 +145,15 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
|
|||
cmd.Flags().StringSliceVarP(&opts.RepositoryNames, "repos", "r", []string{}, "List of `repositories` that can access an organization or user secret")
|
||||
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "The value for the secret (reads from standard input if not specified)")
|
||||
cmd.Flags().BoolVar(&opts.DoNotStore, "no-store", false, "Print the encrypted, base64-encoded value instead of storing it on Github")
|
||||
cmd.Flags().StringVarP(&opts.EnvFile, "env-file", "f", "", "Load secret names and values from a dotenv-formatted `file`")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func setRun(opts *SetOptions) error {
|
||||
body, err := getBody(opts)
|
||||
secrets, err := getSecretsFromOptions(opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand secret body: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := opts.HttpClient()
|
||||
|
|
@ -187,42 +197,77 @@ func setRun(opts *SetOptions) error {
|
|||
return fmt.Errorf("failed to fetch public key: %w", err)
|
||||
}
|
||||
|
||||
eBody, err := box.SealAnonymous(nil, body, &pk.Raw, opts.RandomOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt body: %w", err)
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(eBody)
|
||||
if opts.DoNotStore {
|
||||
_, err := fmt.Fprintln(opts.IO.Out, encoded)
|
||||
return err
|
||||
}
|
||||
|
||||
if orgName != "" {
|
||||
err = putOrgSecret(client, host, pk, *opts, encoded)
|
||||
} else if envName != "" {
|
||||
err = putEnvSecret(client, pk, baseRepo, envName, opts.SecretName, encoded)
|
||||
} else if opts.UserSecrets {
|
||||
err = putUserSecret(client, host, pk, *opts, encoded)
|
||||
} else {
|
||||
err = putRepoSecret(client, pk, baseRepo, opts.SecretName, encoded)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set secret: %w", err)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
target := orgName
|
||||
if opts.UserSecrets {
|
||||
target = "your user"
|
||||
} else if orgName == "" {
|
||||
target = ghrepo.FullName(baseRepo)
|
||||
for secretKey, secret := range secrets {
|
||||
// for env files the randomoverride used in tests needs to be reset on every iteration
|
||||
if len(opts.EnvFile) > 0 {
|
||||
if rd, ok := opts.RandomOverride.(io.Seeker); ok {
|
||||
if _, err := rd.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("internal error: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.Out, "%s Set secret %s for %s\n", cs.SuccessIconWithColor(cs.Green), opts.SecretName, target)
|
||||
eBody, err := box.SealAnonymous(nil, secret[:], &pk.Raw, opts.RandomOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt body: %w", err)
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(eBody)
|
||||
if opts.DoNotStore {
|
||||
_, err := fmt.Fprintln(opts.IO.Out, encoded)
|
||||
return err
|
||||
}
|
||||
|
||||
if orgName != "" {
|
||||
err = putOrgSecret(client, host, pk, *opts, secretKey, encoded)
|
||||
} else if envName != "" {
|
||||
err = putEnvSecret(client, pk, baseRepo, envName, secretKey, encoded)
|
||||
} else if opts.UserSecrets {
|
||||
err = putUserSecret(client, host, pk, *opts, encoded)
|
||||
} else {
|
||||
err = putRepoSecret(client, pk, baseRepo, secretKey, encoded)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set secret: %w", err)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
target := orgName
|
||||
if opts.UserSecrets {
|
||||
target = "your user"
|
||||
} else if orgName == "" {
|
||||
target = ghrepo.FullName(baseRepo)
|
||||
}
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintf(opts.IO.Out, "%s Set secret %s for %s\n", cs.SuccessIconWithColor(cs.Green), secretKey, target)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSecretsFromOptions(opts *SetOptions) (map[string][]byte, error) {
|
||||
secrets := make(map[string][]byte)
|
||||
|
||||
if len(opts.EnvFile) > 0 {
|
||||
envs, err := godotenv.Read(opts.EnvFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could no open env file: %w", err)
|
||||
}
|
||||
for key, value := range envs {
|
||||
secrets[key] = []byte(value)
|
||||
}
|
||||
|
||||
} else {
|
||||
body, err := getBody(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("did not understand secret body: %w", err)
|
||||
}
|
||||
secrets[opts.SecretName] = body
|
||||
}
|
||||
|
||||
return nil
|
||||
if len(secrets) == 0 {
|
||||
return nil, fmt.Errorf("no secrets defined")
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
func getBody(opts *SetOptions) ([]byte, error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue