golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/secret/flag.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package secret 6 7 import ( 8 "context" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "strings" 13 14 "cloud.google.com/go/compute/metadata" 15 secretmanager "cloud.google.com/go/secretmanager/apiv1" 16 "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" 17 ) 18 19 // FlagResolver contains the dependencies necessary to resolve a Secret flag. 20 type FlagResolver struct { 21 Context context.Context 22 Client secretClient 23 DefaultProjectID string 24 } 25 26 const secretSuffix = "[ specify `secret:[project name/]<secret name>` to read from Secret Manager ]" 27 28 // Flag defines a string flag on set that will be resolved using r. 29 // The return value is the address of a string variable that stores the value of the flag. 30 func (r *FlagResolver) Flag(set *flag.FlagSet, name, usage string) *string { 31 p := new(string) 32 r.FlagVar(set, p, name, usage) 33 return p 34 } 35 36 // FlagVar defines a string flag on set that will be resolved using r. 37 // The argument p points to a string variable in which to store the value of the flag. 38 func (r *FlagResolver) FlagVar(set *flag.FlagSet, p *string, name, usage string) { 39 suffixedUsage := usage + "\n" + secretSuffix 40 set.Func(name, suffixedUsage, func(flagValue string) error { 41 value, err := r.ResolveSecret(flagValue) 42 if err != nil { 43 return err 44 } 45 *p = value 46 return nil 47 }) 48 } 49 50 // ResolveSecret resolves a string value, which need not be a flag. 51 func (r *FlagResolver) ResolveSecret(flagValue string) (string, error) { 52 if r.Client == nil || r.Context == nil { 53 return "", fmt.Errorf("secret resolver was not initialized") 54 } 55 if !strings.HasPrefix(flagValue, "secret:") { 56 return flagValue, nil 57 } 58 59 secretName := strings.TrimPrefix(flagValue, "secret:") 60 projectID := r.DefaultProjectID 61 if parts := strings.SplitN(secretName, "/", 2); len(parts) == 2 { 62 projectID, secretName = parts[0], parts[1] 63 } 64 if projectID == "" { 65 return "", fmt.Errorf("missing project ID: none specified in %q, and no default set (not on GCP?)", secretName) 66 } 67 result, err := r.Client.AccessSecretVersion(r.Context, &secretmanagerpb.AccessSecretVersionRequest{ 68 Name: buildNamePath(projectID, secretName, "latest"), 69 }) 70 if err != nil { 71 return "", fmt.Errorf("reading secret %q from project %v failed: %v", secretName, projectID, err) 72 } 73 return string(result.Payload.GetData()), nil 74 } 75 76 // JSONVarFlag defines a flag on set that behaves like Flag and then 77 // json.Unmarshals the resulting string into value. 78 func (r *FlagResolver) JSONVarFlag(set *flag.FlagSet, value interface{}, name, usage string) { 79 suffixedUsage := usage + "\n" + fmt.Sprintf("A JSON representation of a %T.", value) + "\n" + secretSuffix 80 set.Func(name, suffixedUsage, func(flagValue string) error { 81 stringValue, err := r.ResolveSecret(flagValue) 82 if err != nil { 83 return err 84 } 85 return json.Unmarshal([]byte(stringValue), value) 86 }) 87 } 88 89 // DefaultResolver is the FlagResolver used by the convenience functions. 90 var DefaultResolver FlagResolver 91 92 // Flag defines a string flag on flag.CommandLine that supports Secret Manager 93 // resolution for values like "secret:<secret name>". InitFlagSupport must be 94 // called before flag.Parse. 95 func Flag(name, usage string) *string { 96 return DefaultResolver.Flag(flag.CommandLine, name, usage) 97 } 98 99 // FlagVar defines a string flag on flag.CommandLine that supports Secret Manager 100 // resolution for values like "secret:<secret name>". InitFlagSupport must be 101 // called before flag.Parse. 102 func FlagVar(p *string, name, usage string) { 103 DefaultResolver.FlagVar(flag.CommandLine, p, name, usage) 104 } 105 106 // JSONVarFlag defines a flag on flag.CommandLine that behaves like Flag 107 // and then json.Unmarshals the resulting string into value. 108 func JSONVarFlag(value interface{}, name, usage string) { 109 DefaultResolver.JSONVarFlag(flag.CommandLine, value, name, usage) 110 } 111 112 // InitFlagSupport initializes the dependencies for flags declared with Flag. 113 func InitFlagSupport(ctx context.Context) error { 114 client, err := secretmanager.NewClient(ctx) 115 if err != nil { 116 return err 117 } 118 DefaultResolver = FlagResolver{ 119 Context: ctx, 120 Client: client, 121 } 122 if metadata.OnGCE() { 123 projectID, err := metadata.ProjectID() 124 if err != nil { 125 return err 126 } 127 DefaultResolver.DefaultProjectID = projectID 128 } 129 130 return nil 131 }