Ctrl K
ComunidadGuías…

codersdk/workspacesecrets.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package codersdk

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/google/uuid"
)

// WorkspaceSecret is the metadata of a secret linked to a workspace build.
// Workspace secrets have no description, cannot be disabled, and are never
// updated, so Description is empty, Enabled is true, and UpdatedAt equals
// CreatedAt. The secret value is never included in API responses.
type WorkspaceSecret struct {
	UserSecret
	WorkspaceBuildID uuid.UUID `json:"workspace_build_id" format:"uuid"`
	// Ephemeral secrets are delivered to their build only and not copied to
	// the next one.
	Ephemeral bool `json:"ephemeral"`
	// ClearedAt is set once a later build superseded the secret and its
	// value was dropped. Cleared secrets are not delivered and replace
	// nothing.
	ClearedAt *time.Time `json:"cleared_at,omitempty" format:"date-time"`
	// EnvReplaces is the ID of the workspace owner's user secret that this
	// secret displaces on env_name.
	EnvReplaces *uuid.UUID `json:"env_replaces,omitempty" format:"uuid"`
	// FileReplaces is the ID of the workspace owner's user secret that this
	// secret displaces on file_path.
	FileReplaces *uuid.UUID `json:"file_replaces,omitempty" format:"uuid"`
}

// WorkspaceBuildSecrets lists the metadata of the secrets linked to a
// workspace build, including secrets whose values a later build cleared.
func (c *Client) WorkspaceBuildSecrets(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceSecret, error) {
	res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspacebuilds/%s/secrets", workspaceBuildID), nil)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return nil, ReadBodyAsError(res)
	}
	var secrets []WorkspaceSecret
	return secrets, ReadBodyAsJSON(res, &secrets)
}

// WorkspaceSecretInput sets or removes a workspace secret as part of a
// workspace or workspace build request.
//
// Workspace secrets are delivered to the workspace only through the agent
// manifest (as an environment variable, a file, or both). They are never
// passed to the provisioner, so they do not appear in workspace build
// parameters or Terraform state, and they cannot be read back through the
// API. Every build holds its own copy of the secrets it receives.
// Non-ephemeral secrets carry forward to later builds until a request
// replaces them by name, removes them with a null value, or sets another
// secret on their env_name or file_path. A secret applies as a whole, so
// taking over either of its targets drops it entirely. Ephemeral secrets
// are delivered to that build only.
type WorkspaceSecretInput struct {
	Name string `json:"name"`
	// Value is the plaintext secret. A null value removes the secret. An
	// empty string sets an empty secret.
	Value *string `json:"value"`
	// EnvName is the environment variable to inject the secret as. Empty
	// means no env injection. Required when file_path is empty and value is
	// set.
	EnvName string `json:"env_name,omitempty"`
	// FilePath is the path to write the secret to inside the workspace.
	// Empty means no file is written. Deployments may disable file path
	// delivery.
	FilePath string `json:"file_path,omitempty"`
	// Ephemeral secrets are delivered to this build only and are not
	// carried forward to the next build.
	Ephemeral bool `json:"ephemeral,omitempty"`
}

// IsRemoval reports whether the input removes the secret instead of setting
// it.
func (s WorkspaceSecretInput) IsRemoval() bool {
	return s.Value == nil
}

// ValidateWorkspaceSecretInput validates a single workspace secret input.
// It reuses the user secret rules for name, value, and delivery targets.
func ValidateWorkspaceSecretInput(in WorkspaceSecretInput) []ValidationError {
	var validations []ValidationError
	if err := UserSecretNameValid(in.Name); err != nil {
		validations = append(validations, ValidationError{Field: UserSecretNameField, Detail: err.Error()})
	}
	if in.IsRemoval() {
		// An omitted value also decodes as null, so reject removals that
		// look like a set request with a missing value.
		if in.EnvName != "" || in.FilePath != "" || in.Ephemeral {
			validations = append(validations, ValidationError{
				Field:  UserSecretValueField,
				Detail: WorkspaceSecretRemovalWithFieldsDetail,
			})
		}
		return validations
	}
	if err := UserSecretValueValid(*in.Value); err != nil {
		validations = append(validations, ValidationError{Field: UserSecretValueField, Detail: err.Error()})
	}
	if err := UserSecretEnvNameValid(in.EnvName); err != nil {
		validations = append(validations, ValidationError{Field: UserSecretEnvNameField, Detail: err.Error()})
	}
	if err := UserSecretFilePathValid(in.FilePath); err != nil {
		validations = append(validations, ValidationError{Field: UserSecretFilePathField, Detail: err.Error()})
	}
	if in.EnvName == "" && in.FilePath == "" {
		validations = append(validations, ValidationError{
			Field:  UserSecretEnvNameField,
			Detail: WorkspaceSecretInjectionTargetRequiredDetail,
		})
	}
	return validations
}

// WorkspaceSecretRemovalWithFieldsDetail explains that a removal carries
// only a name.
const WorkspaceSecretRemovalWithFieldsDetail = "A null value removes the secret, so env_name, file_path, and ephemeral must not be set. Set value to set the secret." //nolint:gosec // G101: message text, not a hardcoded credential.

// WorkspaceSecretInjectionTargetRequiredDetail explains that a workspace
// secret has no disabled state, so every set request needs a delivery
// target.
const WorkspaceSecretInjectionTargetRequiredDetail = "A workspace secret must have at least one of env_name or file_path set." //nolint:gosec // G101: message text, not a hardcoded credential.