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  }