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 }