github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/secrets/client.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secrets
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  
     9  	"github.com/juju/juju/api/base"
    10  	"github.com/juju/juju/core/secrets"
    11  	"github.com/juju/juju/rpc/params"
    12  )
    13  
    14  // Client is the api client for the Secrets facade.
    15  type Client struct {
    16  	base.ClientFacade
    17  	facade base.FacadeCaller
    18  }
    19  
    20  // NewClient creates a secrets api client.
    21  func NewClient(caller base.APICallCloser) *Client {
    22  	frontend, backend := base.NewClientFacade(caller, "Secrets")
    23  	return &Client{ClientFacade: frontend, facade: backend}
    24  }
    25  
    26  // SecretDetails holds a secret metadata and value.
    27  type SecretDetails struct {
    28  	Metadata  secrets.SecretMetadata
    29  	Access    []secrets.AccessInfo
    30  	Revisions []secrets.SecretRevisionMetadata
    31  	Value     secrets.SecretValue
    32  	Error     string
    33  }
    34  
    35  func toGrantInfo(grants []params.AccessInfo) []secrets.AccessInfo {
    36  	result := make([]secrets.AccessInfo, len(grants))
    37  	for i, g := range grants {
    38  		result[i] = secrets.AccessInfo{
    39  			Target: g.TargetTag,
    40  			Scope:  g.ScopeTag,
    41  			Role:   g.Role,
    42  		}
    43  	}
    44  	return result
    45  }
    46  
    47  // ListSecrets lists the available secrets.
    48  func (api *Client) ListSecrets(reveal bool, filter secrets.Filter) ([]SecretDetails, error) {
    49  	arg := params.ListSecretsArgs{
    50  		ShowSecrets: reveal,
    51  		Filter: params.SecretsFilter{
    52  			OwnerTag: filter.OwnerTag,
    53  			Revision: filter.Revision,
    54  			Label:    filter.Label,
    55  		},
    56  	}
    57  	if filter.URI != nil {
    58  		uri := filter.URI.String()
    59  		arg.Filter.URI = &uri
    60  	}
    61  	var response params.ListSecretResults
    62  	err := api.facade.FacadeCall("ListSecrets", arg, &response)
    63  	if err != nil {
    64  		return nil, errors.Trace(err)
    65  	}
    66  	result := make([]SecretDetails, len(response.Results))
    67  	for i, r := range response.Results {
    68  		details := SecretDetails{
    69  			Metadata: secrets.SecretMetadata{
    70  				Version:          r.Version,
    71  				OwnerTag:         r.OwnerTag,
    72  				RotatePolicy:     secrets.RotatePolicy(r.RotatePolicy),
    73  				NextRotateTime:   r.NextRotateTime,
    74  				LatestRevision:   r.LatestRevision,
    75  				LatestExpireTime: r.LatestExpireTime,
    76  				Description:      r.Description,
    77  				Label:            r.Label,
    78  				CreateTime:       r.CreateTime,
    79  				UpdateTime:       r.UpdateTime,
    80  			},
    81  			Access: toGrantInfo(r.Access),
    82  		}
    83  		uri, err := secrets.ParseURI(r.URI)
    84  		if err == nil {
    85  			details.Metadata.URI = uri
    86  		} else {
    87  			details.Error = err.Error()
    88  		}
    89  		details.Revisions = make([]secrets.SecretRevisionMetadata, len(r.Revisions))
    90  		for i, r := range r.Revisions {
    91  			details.Revisions[i] = secrets.SecretRevisionMetadata{
    92  				Revision:    r.Revision,
    93  				BackendName: r.BackendName,
    94  				CreateTime:  r.CreateTime,
    95  				UpdateTime:  r.UpdateTime,
    96  				ExpireTime:  r.ExpireTime,
    97  			}
    98  		}
    99  		if reveal && r.Value != nil {
   100  			if r.Value.Error == nil {
   101  				if data := secrets.NewSecretValue(r.Value.Data); !data.IsEmpty() {
   102  					details.Value = data
   103  				}
   104  			} else {
   105  				details.Error = r.Value.Error.Error()
   106  			}
   107  		}
   108  		result[i] = details
   109  	}
   110  	return result, err
   111  }
   112  
   113  func (c *Client) CreateSecret(name, description string, data map[string]string) (string, error) {
   114  	if c.BestAPIVersion() < 2 {
   115  		return "", errors.NotSupportedf("user secrets")
   116  	}
   117  	var results params.StringResults
   118  	arg := params.CreateSecretArg{
   119  		UpsertSecretArg: params.UpsertSecretArg{
   120  			Content: params.SecretContentParams{Data: data},
   121  		},
   122  	}
   123  	if name != "" {
   124  		arg.Label = &name
   125  	}
   126  	if description != "" {
   127  		arg.Description = &description
   128  	}
   129  
   130  	err := c.facade.FacadeCall("CreateSecrets", params.CreateSecretArgs{Args: []params.CreateSecretArg{arg}}, &results)
   131  	if err != nil {
   132  		return "", errors.Trace(err)
   133  	}
   134  	if len(results.Results) != 1 {
   135  		return "", errors.Errorf("expected 1 result, got %d", len(results.Results))
   136  	}
   137  	result := results.Results[0]
   138  	if result.Error != nil {
   139  		return "", params.TranslateWellKnownError(result.Error)
   140  	}
   141  	return result.Result, nil
   142  }
   143  
   144  // UpdateSecret updates an existing secret.
   145  func (c *Client) UpdateSecret(
   146  	uri *secrets.URI, name string, autoPrune *bool,
   147  	newName string, description string, data map[string]string,
   148  ) error {
   149  	if c.BestAPIVersion() < 2 {
   150  		return errors.NotSupportedf("user secrets")
   151  	}
   152  	var results params.ErrorResults
   153  	arg := params.UpdateUserSecretArg{
   154  		AutoPrune: autoPrune,
   155  		UpsertSecretArg: params.UpsertSecretArg{
   156  			Content: params.SecretContentParams{Data: data},
   157  		},
   158  	}
   159  	if uri == nil && name == "" {
   160  		return errors.New("must specify either URI or name")
   161  	}
   162  	if uri != nil && name != "" {
   163  		return errors.New("must specify either URI or name but not both")
   164  	}
   165  	if uri != nil {
   166  		arg.URI = uri.String()
   167  	}
   168  	if name != "" {
   169  		arg.ExistingLabel = name
   170  	}
   171  	if newName != "" {
   172  		arg.UpsertSecretArg.Label = &newName
   173  	}
   174  	if description != "" {
   175  		arg.UpsertSecretArg.Description = &description
   176  	}
   177  	err := c.facade.FacadeCall("UpdateSecrets", params.UpdateUserSecretArgs{Args: []params.UpdateUserSecretArg{arg}}, &results)
   178  	if err != nil {
   179  		return errors.Trace(err)
   180  	}
   181  	if len(results.Results) != 1 {
   182  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   183  	}
   184  	result := results.Results[0]
   185  	if result.Error != nil {
   186  		return params.TranslateWellKnownError(result.Error)
   187  	}
   188  	return nil
   189  }
   190  
   191  func (c *Client) RemoveSecret(uri *secrets.URI, name string, revision *int) error {
   192  	if c.BestAPIVersion() < 2 {
   193  		return errors.NotSupportedf("user secrets")
   194  	}
   195  	arg := params.DeleteSecretArg{
   196  		URI:   uri.String(),
   197  		Label: name,
   198  	}
   199  	if revision != nil {
   200  		arg.Revisions = append(arg.Revisions, *revision)
   201  	}
   202  
   203  	var results params.ErrorResults
   204  	err := c.facade.FacadeCall("RemoveSecrets", params.DeleteSecretArgs{Args: []params.DeleteSecretArg{arg}}, &results)
   205  	if err != nil {
   206  		return errors.Trace(err)
   207  	}
   208  	if len(results.Results) != 1 {
   209  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   210  	}
   211  	result := results.Results[0]
   212  	if result.Error != nil {
   213  		return params.TranslateWellKnownError(result.Error)
   214  	}
   215  	return nil
   216  }
   217  
   218  // GrantSecret grants access to a secret to the specified applications.
   219  func (c *Client) GrantSecret(uri *secrets.URI, name string, apps []string) ([]error, error) {
   220  	if c.BestAPIVersion() < 2 {
   221  		return nil, errors.NotSupportedf("user secrets")
   222  	}
   223  	var uriString string
   224  	if uri != nil {
   225  		uriString = uri.String()
   226  	}
   227  	arg := params.GrantRevokeUserSecretArg{
   228  		URI:          uriString,
   229  		Label:        name,
   230  		Applications: apps,
   231  	}
   232  
   233  	var results params.ErrorResults
   234  	err := c.facade.FacadeCall("GrantSecret", arg, &results)
   235  	if err != nil {
   236  		return nil, errors.Trace(err)
   237  	}
   238  	if len(results.Results) != len(apps) {
   239  		return nil, errors.Errorf("expected %d results, got %d", len(apps), len(results.Results))
   240  	}
   241  	return processErrors(results), nil
   242  }
   243  
   244  func processErrors(results params.ErrorResults) []error {
   245  	errors := make([]error, len(results.Results))
   246  	for i, result := range results.Results {
   247  		if result.Error != nil {
   248  			errors[i] = params.TranslateWellKnownError(result.Error)
   249  		} else {
   250  			errors[i] = nil
   251  		}
   252  	}
   253  	return errors
   254  }
   255  
   256  // RevokeSecret revokes access to a secret from the specified applications.
   257  func (c *Client) RevokeSecret(uri *secrets.URI, name string, apps []string) ([]error, error) {
   258  	if c.BestAPIVersion() < 2 {
   259  		return nil, errors.NotSupportedf("user secrets")
   260  	}
   261  
   262  	var uriString string
   263  	if uri != nil {
   264  		uriString = uri.String()
   265  	}
   266  	arg := params.GrantRevokeUserSecretArg{
   267  		URI:          uriString,
   268  		Label:        name,
   269  		Applications: apps,
   270  	}
   271  
   272  	var results params.ErrorResults
   273  	err := c.facade.FacadeCall("RevokeSecret", arg, &results)
   274  	if err != nil {
   275  		return nil, errors.Trace(err)
   276  	}
   277  	if len(results.Results) != len(apps) {
   278  		return nil, errors.Errorf("expected %d results, got %d", len(apps), len(results.Results))
   279  	}
   280  	return processErrors(results), nil
   281  }