Skip to content
Merged
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
23 changes: 14 additions & 9 deletions pkg/provisioner/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import (
"github.com/cloudposse/atmos/pkg/schema"
)

// ProvisionResult holds the result of a backend provisioning operation.
type ProvisionResult struct {
Warnings []string // Warning messages to display after spinner completes.
}

// BackendCreateFunc is a function that creates a Terraform backend.
type BackendCreateFunc func(
ctx context.Context,
atmosConfig *schema.AtmosConfiguration,
backendConfig map[string]any,
authContext *schema.AuthContext,
) error
) (*ProvisionResult, error)

// BackendDeleteFunc is a function that deletes a Terraform backend.
type BackendDeleteFunc func(
Expand Down Expand Up @@ -190,35 +195,35 @@ func BackendName(backendType string, backendConfig map[string]any) string {
}

// ProvisionBackend provisions a backend if provisioning is enabled.
// Returns an error if provisioning fails or no provisioner is registered.
// Returns ProvisionResult with any warnings, and an error if provisioning fails or no provisioner is registered.
func ProvisionBackend(
ctx context.Context,
atmosConfig *schema.AtmosConfiguration,
componentConfig map[string]any,
authContext *schema.AuthContext,
) error {
) (*ProvisionResult, error) {
defer perf.Track(atmosConfig, "backend.ProvisionBackend")()

// Check if provisioning is enabled.
provision, ok := componentConfig["provision"].(map[string]any)
if !ok {
return errUtils.Build(errUtils.ErrProvisioningNotConfigured).
return nil, errUtils.Build(errUtils.ErrProvisioningNotConfigured).
WithExplanation("No 'provision' configuration found for this component").
WithHint("Add 'provision.backend.enabled: true' to the component's stack configuration").
Err()
}

backend, ok := provision["backend"].(map[string]any)
if !ok {
return errUtils.Build(errUtils.ErrProvisioningNotConfigured).
return nil, errUtils.Build(errUtils.ErrProvisioningNotConfigured).
WithExplanation("No 'provision.backend' configuration found for this component").
WithHint("Add 'provision.backend.enabled: true' to the component's stack configuration").
Err()
}

enabled, ok := backend["enabled"].(bool)
if !ok || !enabled {
return errUtils.Build(errUtils.ErrProvisioningNotConfigured).
return nil, errUtils.Build(errUtils.ErrProvisioningNotConfigured).
WithExplanation("Backend provisioning is not enabled for this component").
WithHint("Set 'provision.backend.enabled: true' in the component's stack configuration").
Err()
Expand All @@ -227,18 +232,18 @@ func ProvisionBackend(
// Get backend configuration.
backendConfig, ok := componentConfig["backend"].(map[string]any)
if !ok {
return fmt.Errorf("%w: backend configuration not found", errUtils.ErrBackendNotFound)
return nil, fmt.Errorf("%w: backend configuration not found", errUtils.ErrBackendNotFound)
}

backendType, ok := componentConfig["backend_type"].(string)
if !ok {
return fmt.Errorf("%w: backend_type not specified", errUtils.ErrBackendTypeRequired)
return nil, fmt.Errorf("%w: backend_type not specified", errUtils.ErrBackendTypeRequired)
}

// Get create function for backend type.
createFunc := GetBackendCreate(backendType)
if createFunc == nil {
return fmt.Errorf("%w: %s", errUtils.ErrCreateNotImplemented, backendType)
return nil, fmt.Errorf("%w: %s", errUtils.ErrCreateNotImplemented, backendType)
}

// Execute create function.
Expand Down
76 changes: 38 additions & 38 deletions pkg/provisioner/backend/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func TestRegisterBackendCreate(t *testing.T) {
// Reset registry before test.
resetBackendRegistry()

mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return nil
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockProvisioner)
Expand All @@ -46,12 +46,12 @@ func TestGetBackendCreate_MultipleTypes(t *testing.T) {
// Reset registry before test.
resetBackendRegistry()

s3Provisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return nil
s3Provisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return &ProvisionResult{}, nil
}

gcsProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return nil
gcsProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", s3Provisioner)
Expand Down Expand Up @@ -109,8 +109,8 @@ func TestGetBackendDelete_MultipleTypes(t *testing.T) {

func TestResetRegistryForTesting(t *testing.T) {
// Register some functions first.
mockCreator := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return nil
mockCreator := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return &ProvisionResult{}, nil
}
mockDeleter := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext, force bool) error {
return nil
Expand All @@ -135,8 +135,8 @@ func TestResetRegistryForTesting_ClearsAllEntries(t *testing.T) {
// Reset at start.
ResetRegistryForTesting()

mockCreator := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return nil
mockCreator := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return &ProvisionResult{}, nil
}
mockDeleter := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext, force bool) error {
return nil
Expand Down Expand Up @@ -180,7 +180,7 @@ func TestProvisionBackend_NoProvisioningConfiguration(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err, "Should return error when no provisioning configuration exists")
assert.ErrorIs(t, err, errUtils.ErrProvisioningNotConfigured)
}
Expand All @@ -203,7 +203,7 @@ func TestProvisionBackend_NoBackendProvisioningConfiguration(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err, "Should return error when no backend provisioning configuration exists")
assert.ErrorIs(t, err, errUtils.ErrProvisioningNotConfigured)
}
Expand All @@ -226,7 +226,7 @@ func TestProvisionBackend_ProvisioningDisabled(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err, "Should return error when provisioning is explicitly disabled")
assert.ErrorIs(t, err, errUtils.ErrProvisioningNotConfigured)
}
Expand All @@ -247,7 +247,7 @@ func TestProvisionBackend_ProvisioningEnabledMissingField(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err, "Should return error when enabled field is missing")
assert.ErrorIs(t, err, errUtils.ErrProvisioningNotConfigured)
}
Expand All @@ -266,7 +266,7 @@ func TestProvisionBackend_MissingBackendConfiguration(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err)
assert.ErrorIs(t, err, errUtils.ErrBackendNotFound)
assert.Contains(t, err.Error(), "backend configuration not found")
Expand All @@ -289,7 +289,7 @@ func TestProvisionBackend_MissingBackendType(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err)
assert.ErrorIs(t, err, errUtils.ErrBackendTypeRequired)
assert.Contains(t, err.Error(), "backend_type not specified")
Expand All @@ -315,7 +315,7 @@ func TestProvisionBackend_UnsupportedBackendType(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err)
assert.ErrorIs(t, err, errUtils.ErrCreateNotImplemented)
assert.Contains(t, err.Error(), "unsupported")
Expand All @@ -332,11 +332,11 @@ func TestProvisionBackend_Success(t *testing.T) {
var capturedBackendConfig map[string]any
var capturedAuthContext *schema.AuthContext

mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
provisionerCalled = true
capturedBackendConfig = backendConfig
capturedAuthContext = authContext
return nil
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockProvisioner)
Expand All @@ -354,7 +354,7 @@ func TestProvisionBackend_Success(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.NoError(t, err)
assert.True(t, provisionerCalled, "Provisioner should have been called")
assert.NotNil(t, capturedBackendConfig)
Expand All @@ -372,9 +372,9 @@ func TestProvisionBackend_WithAuthContext(t *testing.T) {

var capturedAuthContext *schema.AuthContext

mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
capturedAuthContext = authContext
return nil
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockProvisioner)
Expand All @@ -399,7 +399,7 @@ func TestProvisionBackend_WithAuthContext(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, authContext)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, authContext)
require.NoError(t, err)
require.NotNil(t, capturedAuthContext)
require.NotNil(t, capturedAuthContext.AWS)
Expand All @@ -414,8 +414,8 @@ func TestProvisionBackend_ProvisionerFailure(t *testing.T) {
ctx := context.Background()
atmosConfig := &schema.AtmosConfiguration{}

mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
return errors.New("bucket creation failed: permission denied")
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
return nil, errors.New("bucket creation failed: permission denied")
}

RegisterBackendCreate("s3", mockProvisioner)
Expand All @@ -433,7 +433,7 @@ func TestProvisionBackend_ProvisionerFailure(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "bucket creation failed")
assert.Contains(t, err.Error(), "permission denied")
Expand All @@ -449,14 +449,14 @@ func TestProvisionBackend_MultipleBackendTypes(t *testing.T) {
s3Called := false
gcsCalled := false

mockS3Provisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockS3Provisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
s3Called = true
return nil
return &ProvisionResult{}, nil
}

mockGCSProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockGCSProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
gcsCalled = true
return nil
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockS3Provisioner)
Expand All @@ -476,7 +476,7 @@ func TestProvisionBackend_MultipleBackendTypes(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfigS3, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfigS3, nil)
require.NoError(t, err)
assert.True(t, s3Called, "S3 provisioner should have been called")
assert.False(t, gcsCalled, "GCS provisioner should not have been called")
Expand All @@ -499,7 +499,7 @@ func TestProvisionBackend_MultipleBackendTypes(t *testing.T) {
},
}

err = ProvisionBackend(ctx, atmosConfig, componentConfigGCS, nil)
_, err = ProvisionBackend(ctx, atmosConfig, componentConfigGCS, nil)
require.NoError(t, err)
assert.False(t, s3Called, "S3 provisioner should not have been called")
assert.True(t, gcsCalled, "GCS provisioner should have been called")
Expand All @@ -515,11 +515,11 @@ func TestConcurrentBackendProvisioning(t *testing.T) {
var callCount int
var mu sync.Mutex

mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
mu.Lock()
callCount++
mu.Unlock()
return nil
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockProvisioner)
Expand Down Expand Up @@ -550,7 +550,7 @@ func TestConcurrentBackendProvisioning(t *testing.T) {
"backend": baseComponentConfig["backend"],
"provision": baseComponentConfig["provision"],
}
err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
assert.NoError(t, err)
}()
}
Expand Down Expand Up @@ -603,9 +603,9 @@ func TestProvisionBackend_EnabledWrongType(t *testing.T) {
resetBackendRegistry()

provisionerCalled := false
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) error {
mockProvisioner := func(ctx context.Context, atmosConfig *schema.AtmosConfiguration, backendConfig map[string]any, authContext *schema.AuthContext) (*ProvisionResult, error) {
provisionerCalled = true
return nil
return &ProvisionResult{}, nil
}

RegisterBackendCreate("s3", mockProvisioner)
Expand All @@ -623,7 +623,7 @@ func TestProvisionBackend_EnabledWrongType(t *testing.T) {
},
}

err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
_, err := ProvisionBackend(ctx, atmosConfig, componentConfig, nil)
if tt.shouldError {
require.Error(t, err)
assert.ErrorIs(t, err, errUtils.ErrProvisioningNotConfigured)
Expand Down
Loading
Loading