Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: list secrets with etag support #21

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion packages/api/secrets/list_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,69 @@ 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)
}
}

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
Loading