Skip to content

Commit a70c12f

Browse files
authored
feat: recording deployed component status in package deploy (#3556)
Signed-off-by: Steven Gettys <[email protected]>
1 parent 46cec01 commit a70c12f

File tree

8 files changed

+163
-12
lines changed

8 files changed

+163
-12
lines changed

src/internal/packager2/load_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func TestPackageFromSourceOrCluster(t *testing.T) {
150150
c := &cluster.Cluster{
151151
Clientset: fake.NewClientset(),
152152
}
153-
_, err = c.RecordPackageDeployment(ctx, pkg, nil)
153+
_, err = c.RecordPackageDeployment(ctx, pkg, nil, 1)
154154
require.NoError(t, err)
155155
pkg, err = GetPackageFromSourceOrCluster(ctx, c, "test", false, "")
156156
require.NoError(t, err)

src/pkg/cluster/zarf.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) {
156156
}
157157

158158
// RecordPackageDeployment saves metadata about a package that has been deployed to the cluster.
159-
func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg v1alpha1.ZarfPackage, components []types.DeployedComponent) (*types.DeployedPackage, error) {
159+
func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg v1alpha1.ZarfPackage, components []types.DeployedComponent, generation int) (*types.DeployedPackage, error) {
160160
packageName := pkg.Metadata.Name
161161

162162
// TODO: This is done for backwards compatibility and could be removed in the future.
@@ -175,6 +175,7 @@ func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg v1alpha1.Zarf
175175
Data: pkg,
176176
DeployedComponents: components,
177177
ConnectStrings: connectStrings,
178+
Generation: generation,
178179
}
179180

180181
packageData, err := json.Marshal(deployedPackage)

src/pkg/packager/deploy.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ func (p *Packager) deployComponents(ctx context.Context) ([]types.DeployedCompon
151151

152152
// Process all the components we are deploying
153153
for _, component := range p.cfg.Pkg.Components {
154+
packageGeneration := 1
154155
// Connect to cluster if a component requires it.
155156
if component.RequiresCluster() {
156157
timeout := cluster.DefaultTimeout
@@ -162,10 +163,16 @@ func (p *Packager) deployComponents(ctx context.Context) ([]types.DeployedCompon
162163
if err := p.connectToCluster(connectCtx); err != nil {
163164
return nil, fmt.Errorf("unable to connect to the Kubernetes cluster: %w", err)
164165
}
166+
// If this package has been deployed before, increment the package generation within the secret
167+
if existingDeployedPackage, _ := p.cluster.GetDeployedPackage(ctx, p.cfg.Pkg.Metadata.Name); existingDeployedPackage != nil {
168+
packageGeneration = existingDeployedPackage.Generation + 1
169+
}
165170
}
166171

167172
deployedComponent := types.DeployedComponent{
168-
Name: component.Name,
173+
Name: component.Name,
174+
Status: types.ComponentStatusDeploying,
175+
ObservedGeneration: packageGeneration,
169176
}
170177

171178
// Ensure we don't overwrite any installedCharts data when updating the package secret
@@ -180,7 +187,12 @@ func (p *Packager) deployComponents(ctx context.Context) ([]types.DeployedCompon
180187

181188
deployedComponents = append(deployedComponents, deployedComponent)
182189
idx := len(deployedComponents) - 1
183-
190+
if p.isConnectedToCluster() {
191+
if _, err := p.cluster.RecordPackageDeployment(ctx, p.cfg.Pkg, deployedComponents, packageGeneration); err != nil {
192+
message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error())
193+
l.Debug("unable to record package deployment", "component", component.Name, "error", err.Error())
194+
}
195+
}
184196
// Deploy the component
185197
var charts []types.InstalledChart
186198
var deployErr error
@@ -201,9 +213,9 @@ func (p *Packager) deployComponents(ctx context.Context) ([]types.DeployedCompon
201213

202214
if deployErr != nil {
203215
onFailure()
204-
216+
deployedComponents[idx].Status = types.ComponentStatusFailed
205217
if p.isConnectedToCluster() {
206-
if _, err := p.cluster.RecordPackageDeployment(ctx, p.cfg.Pkg, deployedComponents); err != nil {
218+
if _, err := p.cluster.RecordPackageDeployment(ctx, p.cfg.Pkg, deployedComponents, packageGeneration); err != nil {
207219
message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error())
208220
l.Debug("unable to record package deployment", "component", component.Name, "error", err.Error())
209221
}
@@ -213,8 +225,9 @@ func (p *Packager) deployComponents(ctx context.Context) ([]types.DeployedCompon
213225

214226
// Update the package secret to indicate that we successfully deployed this component
215227
deployedComponents[idx].InstalledCharts = charts
228+
deployedComponents[idx].Status = types.ComponentStatusSucceeded
216229
if p.isConnectedToCluster() {
217-
if _, err := p.cluster.RecordPackageDeployment(ctx, p.cfg.Pkg, deployedComponents); err != nil {
230+
if _, err := p.cluster.RecordPackageDeployment(ctx, p.cfg.Pkg, deployedComponents, packageGeneration); err != nil {
218231
message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error())
219232
l.Debug("unable to record package deployment", "component", component.Name, "error", err.Error())
220233
}
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
3+
4+
// Package test provides e2e tests for Zarf.
5+
package test
6+
7+
import (
8+
"encoding/base64"
9+
"encoding/json"
10+
"fmt"
11+
"path/filepath"
12+
"sync"
13+
"testing"
14+
"time"
15+
16+
"github.com/stretchr/testify/require"
17+
"github.com/zarf-dev/zarf/src/types"
18+
)
19+
20+
func TestComponentStatus(t *testing.T) {
21+
t.Log("E2E: Component Status")
22+
tmpDir := t.TempDir()
23+
_, _, err := e2e.Zarf(t, "package", "create", "src/test/packages/37-component-status", "-o", tmpDir, "--confirm")
24+
require.NoError(t, err)
25+
packageName := fmt.Sprintf("zarf-package-component-status-%s.tar.zst", e2e.Arch)
26+
path := filepath.Join(tmpDir, packageName)
27+
// Stop channel getting the zarf state
28+
stop := make(chan bool)
29+
// Error channel to return any errors from the goroutine. Testify doesn't like require in a goroutine
30+
errCh := make(chan error, 1)
31+
var wg sync.WaitGroup
32+
wg.Add(1)
33+
// Goroutine to wait for the package to show "deploying" status
34+
go func() {
35+
defer wg.Done()
36+
// Give extra time to build and push the package
37+
ticker := time.NewTicker(30 * time.Second)
38+
for {
39+
select {
40+
case <-ticker.C:
41+
t.Log("Timed out waiting for package to deploy")
42+
errCh <- fmt.Errorf("timed out waiting for package to deploy")
43+
return
44+
case <-stop:
45+
return
46+
default:
47+
stdOut, _, err := e2e.Kubectl(t, "get", "secret", "zarf-package-component-status", "-n", "zarf", "-o", "jsonpath={.data.data}")
48+
if err != nil {
49+
// Wait for the secret to be created and try again
50+
time.Sleep(2 * time.Second)
51+
continue
52+
}
53+
deployedPackage, err := getDeployedPackage(stdOut)
54+
if err != nil {
55+
errCh <- err
56+
return
57+
}
58+
if len(deployedPackage.DeployedComponents) != 1 {
59+
errCh <- fmt.Errorf("expected 1 component got %d", len(deployedPackage.DeployedComponents))
60+
return
61+
}
62+
status := deployedPackage.DeployedComponents[0].Status
63+
if status != types.ComponentStatusDeploying {
64+
errCh <- fmt.Errorf("expected %s got %s", types.ComponentStatusDeploying, status)
65+
return
66+
}
67+
time.Sleep(2 * time.Second)
68+
return
69+
}
70+
}
71+
}()
72+
stdOut, stdErr, err := e2e.Zarf(t, "package", "deploy", path, "--confirm")
73+
require.NoError(t, err, stdOut, stdErr)
74+
close(stop)
75+
wg.Wait()
76+
select {
77+
case err := <-errCh:
78+
require.NoError(t, err)
79+
default:
80+
}
81+
// Verify that the component status is "succeeded"
82+
stdOut, stdErr, err = e2e.Kubectl(t, "get", "secret", "zarf-package-component-status", "-n", "zarf", "-o", "jsonpath={.data.data}")
83+
require.NoError(t, err, stdOut, stdErr)
84+
deployedPackage, err := getDeployedPackage(stdOut)
85+
require.NoError(t, err)
86+
require.Len(t, deployedPackage.DeployedComponents, 1)
87+
require.Equal(t, types.ComponentStatusSucceeded, deployedPackage.DeployedComponents[0].Status)
88+
// Remove the package
89+
t.Cleanup(func() {
90+
stdOut, stdErr, err = e2e.Zarf(t, "package", "remove", "component-status", "--confirm")
91+
require.NoError(t, err, stdOut, stdErr)
92+
})
93+
}
94+
95+
func getDeployedPackage(secret string) (*types.DeployedPackage, error) {
96+
deployedPackage := &types.DeployedPackage{}
97+
decoded, err := base64.StdEncoding.DecodeString(secret)
98+
if err != nil {
99+
return nil, err
100+
}
101+
err = json.Unmarshal(decoded, &deployedPackage)
102+
if err != nil {
103+
return nil, err
104+
}
105+
return deployedPackage, nil
106+
}

src/test/packages/22-git-data/zarf.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ components:
2626
onDeploy:
2727
before:
2828
# Check to verify the package secret has been saved for the already deployed component
29-
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 1
29+
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 2
3030
description: Check that the package secret has been updated with the deployed component
3131
maxRetries: 3
3232

@@ -41,7 +41,7 @@ components:
4141
onDeploy:
4242
before:
4343
# Check to verify the package secret has been saved for the already deployed component
44-
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 2
44+
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 3
4545
description: Check that the package secret has been updated with the deployed component
4646
maxRetries: 3
4747

@@ -56,7 +56,7 @@ components:
5656
onDeploy:
5757
before:
5858
# Check to verify the package secret has been saved for the already deployed component
59-
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 3
59+
- cmd: test $(./zarf tools kubectl get secret -n zarf zarf-package-git-data-test -o jsonpath='{.data.*}' | base64 --decode | jq -r .deployedComponents | jq '. | length') -eq 4
6060
description: Check that the package secret has been updated with the deployed component
6161
maxRetries: 3
6262
onSuccess:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: component-status
5+
spec:
6+
# Extra security to ensure the pod isn't ready before the health checks run
7+
initContainers:
8+
- name: init-wait
9+
image: ghcr.io/stefanprodan/podinfo:6.4.0
10+
command: ["sh", "-c", "sleep 10"]
11+
containers:
12+
- name: component-status
13+
image: ghcr.io/stefanprodan/podinfo:6.4.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
kind: ZarfPackageConfig
2+
metadata:
3+
name: component-status
4+
description: Test the status of components
5+
6+
components:
7+
- name: component-status
8+
required: true
9+
manifests:
10+
- name: first-component
11+
namespace: first-component
12+
files:
13+
- component-status.yaml
14+
images:
15+
- ghcr.io/stefanprodan/podinfo:6.4.0

src/types/k8s.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type DeployedPackage struct {
7272
Name string `json:"name"`
7373
Data v1alpha1.ZarfPackage `json:"data"`
7474
CLIVersion string `json:"cliVersion"`
75+
Generation int `json:"generation"`
7576
DeployedComponents []DeployedComponent `json:"deployedComponents"`
7677
ConnectStrings ConnectStrings `json:"connectStrings,omitempty"`
7778
}
@@ -89,8 +90,10 @@ type ConnectStrings map[string]ConnectString
8990

9091
// DeployedComponent contains information about a Zarf Package Component that has been deployed to a cluster.
9192
type DeployedComponent struct {
92-
Name string `json:"name"`
93-
InstalledCharts []InstalledChart `json:"installedCharts"`
93+
Name string `json:"name"`
94+
InstalledCharts []InstalledChart `json:"installedCharts"`
95+
Status ComponentStatus `json:"status"`
96+
ObservedGeneration int `json:"observedGeneration"`
9497
}
9598

9699
// InstalledChart contains information about a Helm Chart that has been deployed to a cluster.

0 commit comments

Comments
 (0)