Skip to content

Commit

Permalink
feat: list secrets with etag support
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielHougaard committed Nov 2, 2024
1 parent cf4f874 commit 64323ca
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 1 deletion.
58 changes: 57 additions & 1 deletion packages/api/secrets/list_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,71 @@ package api

import (
"fmt"
"strings"

"github.com/go-resty/resty/v2"
"github.com/infisical/go-sdk/packages/errors"
)

const callListSecretsV3RawOperation = "CallListSecretsV3Raw"
const callListSecretsWithETagV3RawOperation = "CallListSecretsWithETagV3Raw"

func CallListSecretsV3(httpClient *resty.Client, request ListSecretsV3RawRequest) (ListSecretsV3RawResponse, error) {
func CallListSecretsWithETagV3(httpClient *resty.Client, request ListSecretsV3RawWithETagRequest) (response ListSecretsV3RawResponse, serverETag string, isModified bool, err error) {

secretsResponse := ListSecretsV3RawResponse{}

if request.SecretPath == "" {
request.SecretPath = "/"
}

if request.CurrentETag != "" {

isWeakETag := strings.HasPrefix(request.CurrentETag, "W/")
if isWeakETag {
request.CurrentETag = strings.TrimPrefix(request.CurrentETag, "W/")
}

request.CurrentETag = strings.TrimPrefix(request.CurrentETag, "\"")
request.CurrentETag = strings.TrimSuffix(request.CurrentETag, "\"")
request.CurrentETag = fmt.Sprintf("\"%s\"", request.CurrentETag)

if isWeakETag {
request.CurrentETag = fmt.Sprintf("W/%s", request.CurrentETag)
}
}

fmt.Printf("ETAG: %s\n", request.CurrentETag)

res, err := httpClient.R().
SetResult(&secretsResponse).
SetHeader("if-none-match", request.CurrentETag).
SetQueryParams(map[string]string{
"workspaceId": request.ProjectID,
"workspaceSlug": request.ProjectSlug,
"environment": request.Environment,
"secretPath": request.SecretPath,
"expandSecretReferences": fmt.Sprintf("%t", request.ExpandSecretReferences),
"include_imports": fmt.Sprintf("%t", request.IncludeImports),
"recursive": fmt.Sprintf("%t", request.Recursive),
}).Get("/v3/secrets/raw")

if err != nil {
return ListSecretsV3RawResponse{}, "", false, errors.NewRequestError(callListSecretsWithETagV3RawOperation, err)
}

if res.IsError() {
return ListSecretsV3RawResponse{}, "", false, errors.NewAPIErrorWithResponse(callListSecretsWithETagV3RawOperation, res)
}

var modified = true
if res.StatusCode() == 304 || (res.Header().Get("etag") == request.CurrentETag && request.CurrentETag != "") {
modified = false
}

return secretsResponse, res.Header().Get("etag"), modified, nil
}

func CallListSecretsV3(httpClient *resty.Client, request ListSecretsV3RawRequest) (ListSecretsV3RawResponse, error) {
secretsResponse := ListSecretsV3RawResponse{}

if request.SecretPath == "" {
Expand Down
19 changes: 19 additions & 0 deletions packages/api/secrets/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ type ListSecretsV3RawRequest struct {
SecretPath string `json:"secretPath,omitempty"`
}

type ListSecretsV3RawWithETagRequest struct {
AttachToProcessEnv bool `json:"-"`
CurrentETag string `json:"-"`
// ProjectId and ProjectSlug are used to fetch secrets from the project. Only one of them is required.
ProjectID string `json:"workspaceId,omitempty"`
ProjectSlug string `json:"workspaceSlug,omitempty"`
Environment string `json:"environment"`
ExpandSecretReferences bool `json:"expandSecretReferences"`
IncludeImports bool `json:"include_imports"`
Recursive bool `json:"recursive"`
SecretPath string `json:"secretPath,omitempty"`
}

type ListSecretsV3RawResponse struct {
Secrets []models.Secret `json:"secrets"`
Imports []models.SecretImport `json:"imports"`
Expand Down Expand Up @@ -84,3 +97,9 @@ type DeleteSecretV3RawRequest struct {
type DeleteSecretV3RawResponse struct {
Secret models.Secret `json:"secret"`
}

type ListSecretsWithETagResponse struct {
Secrets []models.Secret
ETag string
IsModified bool
}
6 changes: 6 additions & 0 deletions packages/models/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ type Secret struct {
SecretPath string `json:"secretPath,omitempty"`
}

type ListSecretsWithETagResult struct {
Secrets []Secret
ETag string
IsModified bool
}

type SecretImport struct {
SecretPath string `json:"secretPath"`
Environment string `json:"environment"`
Expand Down
47 changes: 47 additions & 0 deletions secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
)

type ListSecretsOptions = api.ListSecretsV3RawRequest
type ListSecretsWithETagOptions = api.ListSecretsV3RawWithETagRequest
type RetrieveSecretOptions = api.RetrieveSecretV3RawRequest
type UpdateSecretOptions = api.UpdateSecretV3RawRequest
type CreateSecretOptions = api.CreateSecretV3RawRequest
type DeleteSecretOptions = api.DeleteSecretV3RawRequest

type SecretsInterface interface {
List(options ListSecretsOptions) ([]models.Secret, error)
ListWithETag(options ListSecretsWithETagOptions) (models.ListSecretsWithETagResult, error)
Retrieve(options RetrieveSecretOptions) (models.Secret, error)
Update(options UpdateSecretOptions) (models.Secret, error)
Create(options CreateSecretOptions) (models.Secret, error)
Expand Down Expand Up @@ -64,6 +66,51 @@ func (s *Secrets) List(options ListSecretsOptions) ([]models.Secret, error) {
return util.SortSecretsByKeys(secrets), nil
}

func (s *Secrets) ListWithETag(options ListSecretsWithETagOptions) (models.ListSecretsWithETagResult, error) {
res, etag, isModified, err := api.CallListSecretsWithETagV3(s.client.httpClient, options)

if err != nil {
return models.ListSecretsWithETagResult{}, err
}

if options.Recursive {
util.EnsureUniqueSecretsByKey(&res.Secrets)
}

secrets := append([]models.Secret(nil), res.Secrets...) // Clone main secrets slice, we will modify this if imports are enabled
if options.IncludeImports {

// Append secrets from imports
for _, importBlock := range res.Imports {
for _, importSecret := range importBlock.Secrets {
// Only append the secret if it is not already in the list, imports take precedence
if !util.ContainsSecret(secrets, importSecret.SecretKey) {
secrets = append(secrets, importSecret)
}
}
}
}

if options.AttachToProcessEnv {
for _, secret := range secrets {
// Only set the environment variable if it is not already set
if os.Getenv(secret.SecretKey) == "" {
os.Setenv(secret.SecretKey, secret.SecretValue)
}
}

}

sortedSecrets := util.SortSecretsByKeys(secrets)

return models.ListSecretsWithETagResult{
Secrets: sortedSecrets,
ETag: etag,
IsModified: isModified,
}, nil

}

func (s *Secrets) Retrieve(options RetrieveSecretOptions) (models.Secret, error) {
res, err := api.CallRetrieveSecretV3(s.client.httpClient, options)

Expand Down

0 comments on commit 64323ca

Please sign in to comment.