Skip to content

Commit 371bc06

Browse files
committed
refactor: remove
Signed-off-by: Philip Laine <[email protected]>
1 parent 669777a commit 371bc06

File tree

6 files changed

+274
-11
lines changed

6 files changed

+274
-11
lines changed

src/cmd/package.go

+43-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/spf13/viper"
2121
"oras.land/oras-go/v2/registry"
2222

23+
"github.com/zarf-dev/zarf/src/api/v1alpha1"
2324
"github.com/zarf-dev/zarf/src/cmd/common"
2425
"github.com/zarf-dev/zarf/src/config"
2526
"github.com/zarf-dev/zarf/src/config/lang"
@@ -270,19 +271,21 @@ var packageRemoveCmd = &cobra.Command{
270271
if err != nil {
271272
return err
272273
}
273-
pkgConfig.PkgOpts.PackageSource = packageSource
274-
src, err := identifyAndFallbackToClusterSource()
274+
275+
cluster, _ := cluster.NewCluster()
276+
pkg, err := packageFromSourceOrCluster(cmd.Context(), cluster, packageSource)
275277
if err != nil {
276278
return err
277279
}
278-
pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src))
280+
removeOpt := packager2.RemoveOptions{
281+
Cluster: cluster,
282+
OptionalComponents: pkgConfig.PkgOpts.OptionalComponents,
283+
Pkg: pkg,
284+
}
285+
err = packager2.Remove(cmd.Context(), removeOpt)
279286
if err != nil {
280287
return err
281288
}
282-
defer pkgClient.ClearTempPaths()
283-
if err := pkgClient.Remove(cmd.Context()); err != nil {
284-
return fmt.Errorf("unable to remove the package with an error of: %w", err)
285-
}
286289
return nil
287290
},
288291
ValidArgsFunction: getPackageCompletionArgs,
@@ -382,7 +385,39 @@ func choosePackage(args []string) (string, error) {
382385
return path, nil
383386
}
384387

385-
// TODO: This code does not seem to do what it was intended.
388+
func packageFromSourceOrCluster(ctx context.Context, cluster *cluster.Cluster, src string) (v1alpha1.ZarfPackage, error) {
389+
_, err := packager2.IdentifySource(src)
390+
if err != nil {
391+
if cluster == nil {
392+
return v1alpha1.ZarfPackage{}, fmt.Errorf("cannot get Zarf package from Kubernetes without configuration")
393+
}
394+
depPkg, err := cluster.GetDeployedPackage(ctx, src)
395+
if err != nil {
396+
return v1alpha1.ZarfPackage{}, err
397+
}
398+
return depPkg.Data, nil
399+
}
400+
401+
loadOpt := packager2.LoadOptions{
402+
Source: src,
403+
SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation,
404+
Filter: filters.Empty(),
405+
}
406+
pkgPaths, err := packager2.LoadPackage(ctx, loadOpt)
407+
if err != nil {
408+
return v1alpha1.ZarfPackage{}, err
409+
}
410+
defer os.RemoveAll(pkgPaths.Base)
411+
pkg, _, err := pkgPaths.ReadZarfYAML()
412+
if err != nil {
413+
return v1alpha1.ZarfPackage{}, err
414+
}
415+
return pkg, nil
416+
}
417+
418+
// NOTE: If the source is identified nil is returned because packager will create the source if it is nil.
419+
// If it can't be identified the cluster source is used causing packager to ignore the configured package source.
420+
// Use of cluster package source is limited to a few functions which is why this is not the default behavior.
386421
func identifyAndFallbackToClusterSource() (sources.PackageSource, error) {
387422
identifiedSrc := sources.Identify(pkgConfig.PkgOpts.PackageSource)
388423
if identifiedSrc == "" {

src/internal/packager2/load.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type LoadOptions struct {
3838

3939
// LoadPackage optionally fetches and loads the package from the given source.
4040
func LoadPackage(ctx context.Context, opt LoadOptions) (*layout.PackagePaths, error) {
41-
srcType, err := identifySource(opt.Source)
41+
srcType, err := IdentifySource(opt.Source)
4242
if err != nil {
4343
return nil, err
4444
}
@@ -162,7 +162,8 @@ func LoadPackage(ctx context.Context, opt LoadOptions) (*layout.PackagePaths, er
162162
return pkgPaths, nil
163163
}
164164

165-
func identifySource(src string) (string, error) {
165+
// IdentifySource returns the source type for the given source.
166+
func IdentifySource(src string) (string, error) {
166167
parsed, err := url.Parse(src)
167168
if err == nil && parsed.Scheme != "" && parsed.Host != "" {
168169
return parsed.Scheme, nil

src/internal/packager2/load_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestIdentifySource(t *testing.T) {
128128
t.Run(tt.name, func(t *testing.T) {
129129
t.Parallel()
130130

131-
srcType, err := identifySource(tt.src)
131+
srcType, err := IdentifySource(tt.src)
132132
require.NoError(t, err)
133133
require.Equal(t, tt.expectedSrcType, srcType)
134134
})

src/internal/packager2/remove.go

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
3+
4+
package packager2
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"runtime"
11+
"slices"
12+
13+
"helm.sh/helm/v3/pkg/action"
14+
"helm.sh/helm/v3/pkg/cli"
15+
"helm.sh/helm/v3/pkg/storage/driver"
16+
17+
"github.com/zarf-dev/zarf/src/api/v1alpha1"
18+
"github.com/zarf-dev/zarf/src/config"
19+
"github.com/zarf-dev/zarf/src/pkg/cluster"
20+
"github.com/zarf-dev/zarf/src/pkg/message"
21+
"github.com/zarf-dev/zarf/src/pkg/packager/actions"
22+
"github.com/zarf-dev/zarf/src/pkg/packager/filters"
23+
"github.com/zarf-dev/zarf/src/types"
24+
)
25+
26+
// RemoveOptions are the options for Remove.
27+
type RemoveOptions struct {
28+
Cluster *cluster.Cluster
29+
OptionalComponents string
30+
Pkg v1alpha1.ZarfPackage
31+
}
32+
33+
// Remove removes a package that was already deployed onto a cluster, uninstalling all installed helm charts.
34+
func Remove(ctx context.Context, opt RemoveOptions) error {
35+
// If components were provided; just remove the things we were asked to remove
36+
filter := filters.Combine(
37+
filters.ByLocalOS(runtime.GOOS),
38+
filters.BySelectState(opt.OptionalComponents),
39+
)
40+
components, err := filter.Apply(opt.Pkg)
41+
if err != nil {
42+
return err
43+
}
44+
// Check that cluster is configured if required.
45+
requiresCluster := false
46+
componentIdx := map[string]v1alpha1.ZarfComponent{}
47+
for _, component := range components {
48+
componentIdx[component.Name] = component
49+
if component.RequiresCluster() {
50+
if opt.Cluster == nil {
51+
return fmt.Errorf("component %s requires cluster access but none was configured", component.Name)
52+
}
53+
requiresCluster = true
54+
}
55+
}
56+
57+
// Get or build the secret for the deployed package
58+
depPkg := &types.DeployedPackage{}
59+
if requiresCluster {
60+
depPkg, err = opt.Cluster.GetDeployedPackage(ctx, opt.Pkg.Metadata.Name)
61+
if err != nil {
62+
return fmt.Errorf("unable to load the secret for the package we are attempting to remove: %s", err.Error())
63+
}
64+
} else {
65+
// If we do not need the cluster, create a deployed components object based on the info we have
66+
depPkg.Name = opt.Pkg.Metadata.Name
67+
depPkg.Data = opt.Pkg
68+
for _, component := range components {
69+
depPkg.DeployedComponents = append(depPkg.DeployedComponents, types.DeployedComponent{Name: component.Name})
70+
}
71+
}
72+
73+
reverseDepComps := depPkg.DeployedComponents
74+
slices.Reverse(reverseDepComps)
75+
for i, depComp := range reverseDepComps {
76+
// Only remove the component if it was requested or if we are removing the whole package.
77+
comp, ok := componentIdx[depComp.Name]
78+
if !ok {
79+
continue
80+
}
81+
82+
err := func() error {
83+
err := actions.Run(ctx, comp.Actions.OnRemove.Defaults, comp.Actions.OnRemove.Before, nil)
84+
if err != nil {
85+
return fmt.Errorf("unable to run the before action: %w", err)
86+
}
87+
88+
reverseInstalledCharts := depComp.InstalledCharts
89+
slices.Reverse(reverseInstalledCharts)
90+
if opt.Cluster != nil {
91+
for _, chart := range reverseInstalledCharts {
92+
settings := cli.New()
93+
actionConfig := &action.Configuration{}
94+
// TODO (phillebaba): Get credentials from cluster instead of reading again.
95+
err := actionConfig.Init(settings.RESTClientGetter(), chart.Namespace, "", nil)
96+
if err != nil {
97+
return err
98+
}
99+
client := action.NewUninstall(actionConfig)
100+
client.KeepHistory = false
101+
client.Wait = true
102+
client.Timeout = config.ZarfDefaultTimeout
103+
_, err = client.Run(chart.ChartName)
104+
if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
105+
return fmt.Errorf("unable to uninstall the helm chart %s in the namespace %s: %w", chart.ChartName, chart.Namespace, err)
106+
}
107+
if errors.Is(err, driver.ErrReleaseNotFound) {
108+
message.Warnf("Helm release for helm chart '%s' in the namespace '%s' was not found. Was it already removed?", chart.ChartName, chart.Namespace)
109+
}
110+
111+
// Pop the removed helm chart from the installed charts slice.
112+
depPkg.DeployedComponents[len(depPkg.DeployedComponents)-i].InstalledCharts = depComp.InstalledCharts[:len(depComp.InstalledCharts)-1]
113+
err = opt.Cluster.UpdateDeployedPackage(ctx, *depPkg)
114+
if err != nil {
115+
// We warn and ignore errors because we may have removed the cluster that this package was inside of
116+
message.Warnf("Unable to update the secret for package %s, this may be normal if the cluster was removed: %s", depPkg.Name, err.Error())
117+
}
118+
}
119+
}
120+
121+
err = actions.Run(ctx, comp.Actions.OnRemove.Defaults, comp.Actions.OnRemove.After, nil)
122+
if err != nil {
123+
return fmt.Errorf("unable to run the after action: %w", err)
124+
}
125+
err = actions.Run(ctx, comp.Actions.OnRemove.Defaults, comp.Actions.OnRemove.OnSuccess, nil)
126+
if err != nil {
127+
return fmt.Errorf("unable to run the success action: %w", err)
128+
}
129+
130+
// Pop the removed component from deploy components slice.
131+
if opt.Cluster != nil {
132+
depPkg.DeployedComponents = depPkg.DeployedComponents[:len(depPkg.DeployedComponents)-1]
133+
err = opt.Cluster.UpdateDeployedPackage(ctx, *depPkg)
134+
if err != nil {
135+
// We warn and ignore errors because we may have removed the cluster that this package was inside of
136+
message.Warnf("Unable to update the secret for package %s, this may be normal if the cluster was removed: %s", depPkg.Name, err.Error())
137+
}
138+
}
139+
return nil
140+
}()
141+
if err != nil {
142+
err := actions.Run(ctx, comp.Actions.OnRemove.Defaults, comp.Actions.OnRemove.OnFailure, nil)
143+
if err != nil {
144+
// TODO: I think we should return an error if failure action fails. Does this break existing use cases?
145+
return fmt.Errorf("unable to run the failure action: %w", err)
146+
}
147+
return nil
148+
}
149+
}
150+
151+
// All the installed components were deleted, therefore this package is no longer actually deployed
152+
if opt.Cluster != nil && len(depPkg.DeployedComponents) == 0 {
153+
err := opt.Cluster.DeleteDeployedPackage(ctx, depPkg.Name)
154+
if err != nil {
155+
message.Warnf("Unable to delete the secret for package %s, tis may be normal if the cluster was removed: %s", depPkg.Name, err.Error())
156+
}
157+
}
158+
159+
return nil
160+
}

src/pkg/cluster/zarf.go

+59
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,65 @@ func (c *Cluster) GetDeployedPackage(ctx context.Context, packageName string) (*
7474
return deployedPackage, nil
7575
}
7676

77+
// UpdateDeployedPackage updates the deployed package metadata.
78+
func (c *Cluster) UpdateDeployedPackage(ctx context.Context, depPkg types.DeployedPackage) error {
79+
secretName := config.ZarfPackagePrefix + depPkg.Name
80+
packageSecretData, err := json.Marshal(depPkg)
81+
if err != nil {
82+
return err
83+
}
84+
packageSecret := &corev1.Secret{
85+
TypeMeta: metav1.TypeMeta{
86+
APIVersion: corev1.SchemeGroupVersion.String(),
87+
Kind: "Secret",
88+
},
89+
ObjectMeta: metav1.ObjectMeta{
90+
Name: secretName,
91+
Namespace: ZarfNamespaceName,
92+
Labels: map[string]string{
93+
ZarfManagedByLabel: "zarf",
94+
ZarfPackageInfoLabel: depPkg.Name,
95+
},
96+
},
97+
Type: corev1.SecretTypeOpaque,
98+
Data: map[string][]byte{
99+
"data": packageSecretData,
100+
},
101+
}
102+
err = func() error {
103+
_, err := c.Clientset.CoreV1().Secrets(packageSecret.Namespace).Get(ctx, packageSecret.Name, metav1.GetOptions{})
104+
if err != nil && !kerrors.IsNotFound(err) {
105+
return err
106+
}
107+
if kerrors.IsNotFound(err) {
108+
_, err = c.Clientset.CoreV1().Secrets(packageSecret.Namespace).Create(ctx, packageSecret, metav1.CreateOptions{})
109+
if err != nil {
110+
return fmt.Errorf("unable to create the zarf state secret: %w", err)
111+
}
112+
return nil
113+
}
114+
_, err = c.Clientset.CoreV1().Secrets(packageSecret.Namespace).Update(ctx, packageSecret, metav1.UpdateOptions{})
115+
if err != nil {
116+
return fmt.Errorf("unable to update the zarf state secret: %w", err)
117+
}
118+
return nil
119+
}()
120+
if err != nil {
121+
return err
122+
}
123+
return nil
124+
}
125+
126+
// DeleteDeployedPackage removes the metadata for the deployed package.
127+
func (c *Cluster) DeleteDeployedPackage(ctx context.Context, packageName string) error {
128+
secretName := config.ZarfPackagePrefix + packageName
129+
err := c.Clientset.CoreV1().Secrets(ZarfNamespaceName).Delete(ctx, secretName, metav1.DeleteOptions{})
130+
if err != nil {
131+
return err
132+
}
133+
return nil
134+
}
135+
77136
// StripZarfLabelsAndSecretsFromNamespaces removes metadata and secrets from existing namespaces no longer manged by Zarf.
78137
func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) {
79138
spinner := message.NewProgressSpinner("Removing zarf metadata & secrets from existing namespaces not managed by Zarf")

src/test/e2e/02_component_actions_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ func TestComponentActions(t *testing.T) {
6464

6565
// Remove the simple script that should pass.
6666
stdOut, stdErr, err = e2e.Zarf(t, "package", "remove", path, "--components=on-deploy-and-remove", "--confirm")
67+
fmt.Println()
68+
fmt.Println()
69+
fmt.Println("output")
70+
fmt.Println("stdout", stdOut)
71+
fmt.Println("stderr", stdErr)
72+
fmt.Println("error", err)
73+
fmt.Println()
74+
fmt.Println()
6775
require.NoError(t, err, stdOut, stdErr)
6876

6977
// Check that the deploy artifacts were removed.

0 commit comments

Comments
 (0)