Skip to content
38 changes: 38 additions & 0 deletions api/v1alpha1/blueprint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ type Kustomization struct {
// Components to include in the kustomization.
Components []string `yaml:"components,omitempty"`

// Cleanup lists resources to clean up after the kustomization is applied.
Cleanup []string `yaml:"cleanup,omitempty"`

// PostBuild is a post-build step to run after the kustomization is applied.
PostBuild *PostBuild `yaml:"postBuild,omitempty"`
}
Expand Down Expand Up @@ -268,6 +271,7 @@ func (b *Blueprint) DeepCopy() *Blueprint {
Wait: kustomization.Wait,
Force: kustomization.Force,
Components: slices.Clone(kustomization.Components),
Cleanup: slices.Clone(kustomization.Cleanup),
PostBuild: postBuildCopy,
}
}
Expand Down Expand Up @@ -385,3 +389,37 @@ func (b *Blueprint) Merge(overlay *Blueprint) {
b.Kustomizations = overlay.Kustomizations
}
}

// DeepCopy creates a deep copy of the Kustomization object.
func (k *Kustomization) DeepCopy() *Kustomization {
if k == nil {
return nil
}

var postBuildCopy *PostBuild
if k.PostBuild != nil {
postBuildCopy = &PostBuild{
Substitute: make(map[string]string),
SubstituteFrom: slices.Clone(k.PostBuild.SubstituteFrom),
}
for key, value := range k.PostBuild.Substitute {
postBuildCopy.Substitute[key] = value
}
}

return &Kustomization{
Name: k.Name,
Path: k.Path,
Source: k.Source,
DependsOn: slices.Clone(k.DependsOn),
Interval: k.Interval,
RetryInterval: k.RetryInterval,
Timeout: k.Timeout,
Patches: slices.Clone(k.Patches),
Wait: k.Wait,
Force: k.Force,
Components: slices.Clone(k.Components),
Cleanup: slices.Clone(k.Cleanup),
PostBuild: postBuildCopy,
}
}
20 changes: 19 additions & 1 deletion cmd/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

var (
cleanFlag bool
cleanFlag bool
skipK8sFlag bool
)

var downCmd = &cobra.Command{
Expand Down Expand Up @@ -48,6 +49,22 @@ var downCmd = &cobra.Command{
shell := controller.ResolveShell()
configHandler := controller.ResolveConfigHandler()

// Run blueprint cleanup before stack down
blueprintHandler := controller.ResolveBlueprintHandler()
if blueprintHandler == nil {
return fmt.Errorf("No blueprint handler found")
}
if err := blueprintHandler.LoadConfig(); err != nil {
return fmt.Errorf("Error loading blueprint config: %w", err)
}
if !skipK8sFlag {
if err := blueprintHandler.Down(); err != nil {
return fmt.Errorf("Error running blueprint down: %w", err)
}
} else {
fmt.Fprintln(os.Stderr, "Skipping Kubernetes cleanup (--skip-k8s set)")
}

// Tear down the stack components
stack := controller.ResolveStack()
if stack == nil {
Expand Down Expand Up @@ -100,5 +117,6 @@ var downCmd = &cobra.Command{

func init() {
downCmd.Flags().BoolVar(&cleanFlag, "clean", false, "Clean up context specific artifacts")
downCmd.Flags().BoolVar(&skipK8sFlag, "skip-k8s", false, "Skip Kubernetes cleanup (blueprint cleanup)")
rootCmd.AddCommand(downCmd)
}
52 changes: 52 additions & 0 deletions cmd/down_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"
"testing"

"github.com/windsorcli/cli/pkg/blueprint"
"github.com/windsorcli/cli/pkg/config"
"github.com/windsorcli/cli/pkg/controller"
"github.com/windsorcli/cli/pkg/stack"
Expand Down Expand Up @@ -297,4 +298,55 @@ contexts:
t.Errorf("Expected error to contain 'Error deleting .volumes folder', got: %v", err)
}
})

t.Run("CleanupCalledBeforeStackDown", func(t *testing.T) {
mocks := setupDownMocks(t)
callOrder := []string{}
mocks.BlueprintHandler.DownFunc = func() error {
callOrder = append(callOrder, "cleanup")
return nil
}
mocks.Stack.DownFunc = func() error {
callOrder = append(callOrder, "stackdown")
return nil
}
rootCmd.SetArgs([]string{"down"})
err := Execute(mocks.Controller)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if len(callOrder) != 2 || callOrder[0] != "cleanup" || callOrder[1] != "stackdown" {
t.Errorf("Expected Cleanup before stack.Down, got call order: %v", callOrder)
}
})

t.Run("ErrorNilBlueprintHandler", func(t *testing.T) {
mocks := setupDownMocks(t)
mocks.Controller.ResolveBlueprintHandlerFunc = func() blueprint.BlueprintHandler {
return nil
}
rootCmd.SetArgs([]string{"down"})
err := Execute(mocks.Controller)
if err == nil {
t.Error("Expected error, got nil")
}
if err == nil || err.Error() != "No blueprint handler found" {
t.Errorf("Expected 'No blueprint handler found', got %v", err)
}
})

t.Run("ErrorCleanup", func(t *testing.T) {
mocks := setupDownMocks(t)
mocks.BlueprintHandler.DownFunc = func() error {
return fmt.Errorf("cleanup failed")
}
rootCmd.SetArgs([]string{"down"})
err := Execute(mocks.Controller)
if err == nil {
t.Error("Expected error, got nil")
}
if err == nil || !strings.Contains(err.Error(), "Error running blueprint down: cleanup failed") {
t.Errorf("Expected error containing 'Error running blueprint down: cleanup failed', got %v", err)
}
})
}
2 changes: 1 addition & 1 deletion cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var installCmd = &cobra.Command{

// If wait flag is set, wait for kustomizations to be ready
if waitFlag {
if err := blueprintHandler.WaitForKustomizations(); err != nil {
if err := blueprintHandler.WaitForKustomizations("⏳ Waiting for kustomizations to be ready"); err != nil {
return fmt.Errorf("failed waiting for kustomizations: %w", err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func TestInstallCmd(t *testing.T) {

// Set up a flag to check if WaitForKustomizations is called
called := false
mocks.BlueprintHandler.WaitForKustomizationsFunc = func() error {
mocks.BlueprintHandler.WaitForKustomizationsFunc = func(message string, names ...string) error {
called = true
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ var upCmd = &cobra.Command{
}
// If wait flag is set, poll for kustomization readiness
if waitFlag {
if err := blueprintHandler.WaitForKustomizations(); err != nil {
if err := blueprintHandler.WaitForKustomizations("⏳ Waiting for kustomizations to be ready"); err != nil {
return fmt.Errorf("Error waiting for kustomizations: %w", err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ func TestUpCmd(t *testing.T) {
defer func() { installFlag = false; waitFlag = false }()

called := false
mocks.BlueprintHandler.WaitForKustomizationsFunc = func() error {
mocks.BlueprintHandler.WaitForKustomizationsFunc = func(message string, names ...string) error {
called = true
return nil
}
Expand Down
Loading
Loading