Skip to content

Commit

Permalink
1507 Zarf Remove Assumes K8s (#1556)
Browse files Browse the repository at this point in the history
## Description

This allows Zarf to remove things that are not in a k8s cluster.

## Related Issue

Fixes #1507

## Type of change

- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Other (security config, docs update, etc)

## Checklist before merging

- [X] Test, docs, adr added or updated as needed
- [X] [Contributor Guide
Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow)
followed
  • Loading branch information
Racer159 authored Apr 5, 2023
1 parent bc33979 commit 485e434
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 47 deletions.
2 changes: 1 addition & 1 deletion examples/component-actions/test-configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: simple-configmap
name: remove-test-configmap
namespace: zarf
data:
templateme.properties: |
Expand Down
16 changes: 13 additions & 3 deletions examples/component-actions/zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ components:
description: multiline & description demo
- cmd: sleep 1

- name: on-deploy
- name: on-deploy-and-remove
actions:
# runs during "zarf package deploy"
onDeploy:
Expand All @@ -62,6 +62,14 @@ components:
# runs after the component is deployed
after:
- cmd: touch test-deploy-after.txt
# runs during "zarf package remove"
onRemove:
# runs before anything else from the component is removed
before:
- cmd: rm test-deploy-before.txt
# runs after everything else from the component is removed
after:
- cmd: rm test-deploy-after.txt

- name: on-deploy-with-variable
actions:
Expand Down Expand Up @@ -145,9 +153,11 @@ components:
# runs during "zarf package remove"
onRemove:
before:
- cmd: touch test-remove-before.txt
# because this runs before remove this should be found
- cmd: ./zarf tools kubectl get configmap -n zarf remove-test-configmap || echo "Not Found"
after:
- cmd: touch test-remove-after.txt
# because this runs after remove this should no longer be found
- cmd: ./zarf tools kubectl get configmap -n zarf remove-test-configmap || echo "Not Found"

- name: on-deploy-with-env-var
actions:
Expand Down
7 changes: 4 additions & 3 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,14 @@ var packageRemoveCmd = &cobra.Command{
message.Fatalf(err, lang.CmdPackageRemoveExtractErr)
}

var pkgConfig types.ZarfPackage
var pkg types.ZarfPackage
configPath := filepath.Join(tempPath, config.ZarfYAML)
if err := utils.ReadYaml(configPath, &pkgConfig); err != nil {
if err := utils.ReadYaml(configPath, &pkg); err != nil {
message.Fatalf(err, lang.CmdPackageRemoveReadZarfErr)
}

pkgName = pkgConfig.Metadata.Name
pkgName = pkg.Metadata.Name
pkgConfig.Pkg = pkg
}

// Configure the packager
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/packager/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum
return charts, fmt.Errorf("unable to process the component files: %w", err)
}

if !valueTemplate.Ready() && (hasImages || hasCharts || hasManifests || hasRepos) {
if !valueTemplate.Ready() && (hasImages || hasCharts || hasManifests || hasRepos || hasDataInjections) {

// Make sure we have access to the cluster
if p.cluster == nil {
Expand Down
103 changes: 76 additions & 27 deletions src/pkg/packager/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,77 @@ func (p *Packager) Remove(packageName string) (err error) {
spinner := message.NewProgressSpinner("Removing zarf package %s", packageName)
defer spinner.Stop()

if p.cluster == nil {
p.cluster, err = cluster.NewClusterWithWait(30 * time.Second)
if err != nil {
return fmt.Errorf("unable to connect to the Kubernetes cluster: %w", err)
// If components were provided; just remove the things we were asked to remove
requestedComponents := strings.Split(p.cfg.DeployOpts.Components, ",")
partialRemove := len(requestedComponents) > 0 && requestedComponents[0] != ""

// Determine if we need the cluster
requiresCluster := false

// If we have package components check them for images, charts, manifests, etc
for _, component := range p.cfg.Pkg.Components {
// Flip requested based on if this is a partial removal
requested := !partialRemove

if utils.SliceContains(requestedComponents, component.Name) {
requested = true
}

if requested {
hasImages := len(component.Images) > 0
hasCharts := len(component.Charts) > 0
hasManifests := len(component.Manifests) > 0
hasRepos := len(component.Repos) > 0
hasDataInjections := len(component.DataInjections) > 0

if hasImages || hasCharts || hasManifests || hasRepos || hasDataInjections {
requiresCluster = true
}
}
}

// Get the secret for the deployed package
secretName := config.ZarfPackagePrefix + packageName
packageSecret, err := p.cluster.Kube.GetSecret(cluster.ZarfNamespace, secretName)
if err != nil {
return fmt.Errorf("unable to get the secret for the package we are attempting to remove: %w", err)
// If we don't have a package defined we will need to pull it from the cluster
if len(p.cfg.Pkg.Components) < 1 {
requiresCluster = true
}

// Get the list of components the package had deployed
// Get the secret for the deployed package
deployedPackage := types.DeployedPackage{}
err = json.Unmarshal(packageSecret.Data["data"], &deployedPackage)
if err != nil {
return fmt.Errorf("unable to load the secret for the package we are attempting to remove: %w", err)
}
secretName := config.ZarfPackagePrefix + packageName

// If components were provided; just remove the things we were asked to remove
requestedComponents := strings.Split(p.cfg.DeployOpts.Components, ",")
partialRemove := len(requestedComponents) > 0 && requestedComponents[0] != ""
if requiresCluster {
// If we need the cluster, connect to it and pull the package secret
if p.cluster == nil {
p.cluster, err = cluster.NewClusterWithWait(30 * time.Second)
if err != nil {
return fmt.Errorf("unable to connect to the Kubernetes cluster: %w", err)
}
}

packageSecret, err := p.cluster.Kube.GetSecret(cluster.ZarfNamespace, secretName)
if err != nil {
return fmt.Errorf("unable to get the secret for the package we are attempting to remove: %w", err)
}

// Get the list of components the package had deployed
err = json.Unmarshal(packageSecret.Data["data"], &deployedPackage)
if err != nil {
return fmt.Errorf("unable to load the secret for the package we are attempting to remove: %w", err)
}
} else {
// If we do not need the cluster, create a deployed components object based on the info we have
deployedPackage.Name = packageName
deployedPackage.Data = p.cfg.Pkg
if partialRemove {
for _, r := range requestedComponents {
deployedPackage.DeployedComponents = append(deployedPackage.DeployedComponents, types.DeployedComponent{Name: r})
}
} else {
for _, c := range p.cfg.Pkg.Components {
deployedPackage.DeployedComponents = append(deployedPackage.DeployedComponents, types.DeployedComponent{Name: c.Name})
}
}
}

for _, c := range utils.Reverse(deployedPackage.DeployedComponents) {
// Only remove the component if it was requested or if we are removing the whole package
Expand All @@ -64,16 +111,19 @@ func (p *Packager) Remove(packageName string) (err error) {
}

func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage, secretName string) {
// Save the new secret with the removed components removed from the secret
newPackageSecret := p.cluster.Kube.GenerateSecret(cluster.ZarfNamespace, secretName, corev1.SecretTypeOpaque)
newPackageSecret.Labels[cluster.ZarfPackageInfoLabel] = p.cfg.Pkg.Metadata.Name
// Only attempt to update the package secret if we are actually connected to a cluster
if p.cluster != nil {
// Save the new secret with the removed components removed from the secret
newPackageSecret := p.cluster.Kube.GenerateSecret(cluster.ZarfNamespace, secretName, corev1.SecretTypeOpaque)
newPackageSecret.Labels[cluster.ZarfPackageInfoLabel] = p.cfg.Pkg.Metadata.Name

newPackageSecretData, _ := json.Marshal(deployedPackage)
newPackageSecret.Data["data"] = newPackageSecretData
newPackageSecretData, _ := json.Marshal(deployedPackage)
newPackageSecret.Data["data"] = newPackageSecretData

err := p.cluster.Kube.CreateOrUpdateSecret(newPackageSecret)
if err != nil {
message.Warnf("Unable to update the %s package secret: %#v", secretName, err)
err := p.cluster.Kube.CreateOrUpdateSecret(newPackageSecret)
if err != nil {
message.Warnf("Unable to update the %s package secret: %#v", secretName, err)
}
}
}

Expand Down Expand Up @@ -114,7 +164,6 @@ func (p *Packager) removeComponent(deployedPackage types.DeployedPackage, deploy
return t.ChartName == chart.ChartName
})
p.updatePackageSecret(deployedPackage, secretName)

}

if err := p.runActions(onRemove.Defaults, onRemove.After, nil); err != nil {
Expand All @@ -132,7 +181,7 @@ func (p *Packager) removeComponent(deployedPackage types.DeployedPackage, deploy
return t.Name == c.Name
})

if len(deployedPackage.DeployedComponents) == 0 {
if len(deployedPackage.DeployedComponents) == 0 && p.cluster != nil {
// All the installed components were deleted, therefore this package is no longer actually deployed
packageSecret, err := p.cluster.Kube.GetSecret(cluster.ZarfNamespace, secretName)
if err != nil {
Expand Down
11 changes: 10 additions & 1 deletion src/test/e2e/02_component_actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,23 @@ func TestComponentActions(t *testing.T) {
/* Deploy */
path := fmt.Sprintf("build/zarf-package-component-actions-%s.tar.zst", e2e.arch)
// Deploy the simple script that should pass.
stdOut, stdErr, err = e2e.execZarfCommand("package", "deploy", path, "--confirm", "--components=on-deploy")
stdOut, stdErr, err = e2e.execZarfCommand("package", "deploy", path, "--confirm", "--components=on-deploy-and-remove")
require.NoError(t, err, stdOut, stdErr)

// Check that the deploy artifacts were created.
for _, artifact := range deployArtifacts {
require.FileExists(t, artifact)
}

// Remove the simple script that should pass.
stdOut, stdErr, err = e2e.execZarfCommand("package", "remove", path, "--confirm", "--components=on-deploy-and-remove")
require.NoError(t, err, stdOut, stdErr)

// Check that the deploy artifacts were created.
for _, artifact := range deployArtifacts {
require.NoFileExists(t, artifact)
}

// Deploy the simple action that should fail the timeout.
stdOut, stdErr, err = e2e.execZarfCommand("package", "deploy", path, "--confirm", "--components=on-deploy-with-timeout")
require.Error(t, err, stdOut, stdErr)
Expand Down
15 changes: 4 additions & 11 deletions src/test/e2e/31_component_action_remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,15 @@ func TestComponentActionRemove(t *testing.T) {
e2e.setupWithCluster(t)
defer e2e.teardown(t)

removeArtifacts := []string{
"test-remove-before.txt",
"test-remove-after.txt",
}
e2e.cleanFiles(removeArtifacts...)
defer e2e.cleanFiles(removeArtifacts...)

path := fmt.Sprintf("build/zarf-package-component-actions-%s.tar.zst", e2e.arch)

stdOut, stdErr, err := e2e.execZarfCommand("package", "deploy", path, "--confirm", "--components=on-remove")
require.NoError(t, err, stdOut, stdErr)

stdOut, stdErr, err = e2e.execZarfCommand("package", "remove", path, "--confirm", "--components=on-remove")
require.NoError(t, err, stdOut, stdErr)

for _, artifact := range removeArtifacts {
require.FileExists(t, artifact)
}
require.Contains(t, stdErr, "NAME")
require.Contains(t, stdErr, "DATA")
require.Contains(t, stdErr, "remove-test-configmap")
require.Contains(t, stdErr, "Not Found")
}

0 comments on commit 485e434

Please sign in to comment.