@@ -2,13 +2,10 @@ package set
22
33import (
44 "encoding/base64"
5- "errors"
65 "fmt"
76 "io"
87 "io/ioutil"
98 "net/http"
10- "regexp"
11- "strings"
129
1310 "github.com/AlecAivazis/survey/v2"
1411 "github.com/MakeNowJust/heredoc"
@@ -34,6 +31,7 @@ type SetOptions struct {
3431 SecretName string
3532 OrgName string
3633 EnvName string
34+ UserSecrets bool
3735 Body string
3836 Visibility string
3937 RepositoryNames []string
@@ -49,25 +47,39 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
4947 cmd := & cobra.Command {
5048 Use : "set <secret-name>" ,
5149 Short : "Create or update secrets" ,
52- Long : "Locally encrypt a new or updated secret at either the repository, environment, or organization level and send it to GitHub for storage." ,
50+ Long : heredoc .Doc (`
51+ Set a value for a secret on one of the following levels:
52+ - repository (default): available to Actions runs in a repository
53+ - environment: available to Actions runs for a deployment environment in a repository
54+ - organization: available to Actions runs within an organization
55+ - user: available to Codespaces for your user
56+
57+ Organization and user secrets can optionally be restricted to only be available to
58+ specific repositories.
59+
60+ Secret values are locally encrypted before being sent to GitHub.
61+ ` ),
5362 Example : heredoc .Doc (`
54- Paste secret in prompt
63+ # Paste secret value for the current repository in an interactive prompt
5564 $ gh secret set MYSECRET
5665
57- Use environment variable as secret value
58- $ gh secret set MYSECRET -b"${ ENV_VALUE} "
66+ # Read secret value from an environment variable
67+ $ gh secret set MYSECRET --body "$ ENV_VALUE"
5968
60- Use file as secret value
61- $ gh secret set MYSECRET < file.json
69+ # Read secret value from a file
70+ $ gh secret set MYSECRET < myfile.txt
6271
63- Set environment level secret
64- $ gh secret set MYSECRET -bval -- env=anEnv
72+ # Set secret for a deployment environment in the current repository
73+ $ gh secret set MYSECRET -- env myenvironment
6574
66- Set organization level secret visible to entire organization
67- $ gh secret set MYSECRET -bval -- org=anOrg --visibility= all
75+ # Set organization- level secret visible to both public and private repositories
76+ $ gh secret set MYSECRET -- org myOrg --visibility all
6877
69- Set organization level secret visible only to certain repositories
70- $ gh secret set MYSECRET -bval --org=anOrg --repos="repo1,repo2,repo3"
78+ # Set organization-level secret visible to specific repositories
79+ $ gh secret set MYSECRET --org myOrg --repos repo1,repo2,repo3
80+
81+ # Set user-level secret for Codespaces
82+ $ gh secret set MYSECRET --user
7183` ),
7284 Args : func (cmd * cobra.Command , args []string ) error {
7385 if len (args ) != 1 {
@@ -79,35 +91,30 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
7991 // support `-R, --repo` override
8092 opts .BaseRepo = f .BaseRepo
8193
82- if err := cmdutil .MutuallyExclusive ("specify only one of `--org` or `--env `" , opts .OrgName != "" , opts .EnvName != "" ); err != nil {
94+ if err := cmdutil .MutuallyExclusive ("specify only one of `--org`, `--env`, or `--user `" , opts .OrgName != "" , opts .EnvName != "" , opts . UserSecrets ); err != nil {
8395 return err
8496 }
8597
8698 opts .SecretName = args [0 ]
8799
88- err := validSecretName (opts .SecretName )
89- if err != nil {
90- return err
91- }
92-
93100 if cmd .Flags ().Changed ("visibility" ) {
94101 if opts .OrgName == "" {
95- return cmdutil .FlagErrorf ("--visibility not supported for repository secrets; did you mean to pass --org? " )
102+ return cmdutil .FlagErrorf ("` --visibility` is only supported with ` --org` " )
96103 }
97104
98105 if opts .Visibility != shared .All && opts .Visibility != shared .Private && opts .Visibility != shared .Selected {
99- return cmdutil .FlagErrorf ("--visibility must be one of ` all`, ` private` , or ` selected` " )
106+ return cmdutil .FlagErrorf ("` --visibility` must be one of \" all\" , \" private\" , or \" selected\" " )
100107 }
101108
102- if opts .Visibility != shared .Selected && cmd . Flags (). Changed ( "repos" ) {
103- return cmdutil .FlagErrorf ("--repos only supported when --visibility=' selected' " )
109+ if opts .Visibility != shared .Selected && len ( opts . RepositoryNames ) > 0 {
110+ return cmdutil .FlagErrorf ("` --repos` is only supported with ` --visibility=selected` " )
104111 }
105112
106- if opts .Visibility == shared .Selected && ! cmd . Flags (). Changed ( "repos" ) {
107- return cmdutil .FlagErrorf ("--repos flag required when --visibility=' selected' " )
113+ if opts .Visibility == shared .Selected && len ( opts . RepositoryNames ) == 0 {
114+ return cmdutil .FlagErrorf ("` --repos` list required with ` --visibility=selected` " )
108115 }
109116 } else {
110- if cmd . Flags (). Changed ( "repos" ) {
117+ if len ( opts . RepositoryNames ) > 0 {
111118 opts .Visibility = shared .Selected
112119 }
113120 }
@@ -119,11 +126,13 @@ func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command
119126 return setRun (opts )
120127 },
121128 }
122- cmd .Flags ().StringVarP (& opts .OrgName , "org" , "o" , "" , "Set a secret for an organization" )
123- cmd .Flags ().StringVarP (& opts .EnvName , "env" , "e" , "" , "Set a secret for an environment" )
124- cmd .Flags ().StringVarP (& opts .Visibility , "visibility" , "v" , "private" , "Set visibility for an organization secret: `all`, `private`, or `selected`" )
125- cmd .Flags ().StringSliceVarP (& opts .RepositoryNames , "repos" , "r" , []string {}, "List of repository names for `selected` visibility" )
126- cmd .Flags ().StringVarP (& opts .Body , "body" , "b" , "" , "A value for the secret. Reads from STDIN if not specified." )
129+
130+ cmd .Flags ().StringVarP (& opts .OrgName , "org" , "o" , "" , "Set `organization` secret" )
131+ cmd .Flags ().StringVarP (& opts .EnvName , "env" , "e" , "" , "Set deployment `environment` secret" )
132+ cmd .Flags ().BoolVarP (& opts .UserSecrets , "user" , "u" , false , "Set a secret for your user" )
133+ cmd .Flags ().StringVarP (& opts .Visibility , "visibility" , "v" , "private" , "Set visibility for an organization secret: `{all|private|selected}`" )
134+ cmd .Flags ().StringSliceVarP (& opts .RepositoryNames , "repos" , "r" , []string {}, "List of `repositories` that can access an organization or user secret" )
135+ cmd .Flags ().StringVarP (& opts .Body , "body" , "b" , "" , "The value for the secret (reads from standard input if not specified)" )
127136
128137 return cmd
129138}
@@ -144,7 +153,7 @@ func setRun(opts *SetOptions) error {
144153 envName := opts .EnvName
145154
146155 var baseRepo ghrepo.Interface
147- if orgName == "" {
156+ if orgName == "" && ! opts . UserSecrets {
148157 baseRepo , err = opts .BaseRepo ()
149158 if err != nil {
150159 return fmt .Errorf ("could not determine base repo: %w" , err )
@@ -166,6 +175,8 @@ func setRun(opts *SetOptions) error {
166175 pk , err = getOrgPublicKey (client , host , orgName )
167176 } else if envName != "" {
168177 pk , err = getEnvPubKey (client , baseRepo , envName )
178+ } else if opts .UserSecrets {
179+ pk , err = getUserPublicKey (client , host )
169180 } else {
170181 pk , err = getRepoPubKey (client , baseRepo )
171182 }
@@ -184,6 +195,8 @@ func setRun(opts *SetOptions) error {
184195 err = putOrgSecret (client , host , pk , * opts , encoded )
185196 } else if envName != "" {
186197 err = putEnvSecret (client , pk , baseRepo , envName , opts .SecretName , encoded )
198+ } else if opts .UserSecrets {
199+ err = putUserSecret (client , host , pk , * opts , encoded )
187200 } else {
188201 err = putRepoSecret (client , pk , baseRepo , opts .SecretName , encoded )
189202 }
@@ -193,7 +206,9 @@ func setRun(opts *SetOptions) error {
193206
194207 if opts .IO .IsStdoutTTY () {
195208 target := orgName
196- if orgName == "" {
209+ if opts .UserSecrets {
210+ target = "your user"
211+ } else if orgName == "" {
197212 target = ghrepo .FullName (baseRepo )
198213 }
199214 cs := opts .IO .ColorScheme ()
@@ -203,28 +218,6 @@ func setRun(opts *SetOptions) error {
203218 return nil
204219}
205220
206- func validSecretName (name string ) error {
207- if name == "" {
208- return errors .New ("secret name cannot be blank" )
209- }
210-
211- if strings .HasPrefix (name , "GITHUB_" ) {
212- return errors .New ("secret name cannot begin with GITHUB_" )
213- }
214-
215- leadingNumber := regexp .MustCompile (`^[0-9]` )
216- if leadingNumber .MatchString (name ) {
217- return errors .New ("secret name cannot start with a number" )
218- }
219-
220- validChars := regexp .MustCompile (`^([0-9]|[a-z]|[A-Z]|_)+$` )
221- if ! validChars .MatchString (name ) {
222- return errors .New ("secret name can only contain letters, numbers, and _" )
223- }
224-
225- return nil
226- }
227-
228221func getBody (opts * SetOptions ) ([]byte , error ) {
229222 if opts .Body == "" {
230223 if opts .IO .CanPrompt () {
0 commit comments