Skip to content

Commit ec53bff

Browse files
mclasmeierMoritz Clasmeier
andauthored
Fix: don't delete Central resources during SecuredCluster teardown (#110)
Co-authored-by: Moritz Clasmeier <mclasmeier@redhat.com>
1 parent ff8ef23 commit ec53bff

13 files changed

Lines changed: 486 additions & 211 deletions

internal/deployer/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package deployer
33
import "fmt"
44

55
var (
6+
centralCrName = "stackrox-central-services"
7+
securedClusterCrName = "stackrox-secured-cluster-services"
8+
69
centralDbPVCSizeSmall = "30Gi"
710

811
centralResourcesSmall = map[string]interface{}{

internal/deployer/crs.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"errors"
66
"fmt"
77
"strings"
8+
9+
"github.com/stackrox/roxie/internal/k8s"
810
)
911

1012
// generateCRS generates the Central Resource Secret using roxctl
@@ -43,7 +45,7 @@ func (d *Deployer) generateCRS(ctx context.Context, clusterName string) (string,
4345
func (d *Deployer) applyCRS(ctx context.Context, crsContent string) error {
4446
d.logger.Info("Applying CRS to sensor namespace")
4547

46-
result, err := d.runKubectl(ctx, KubectlOptions{
48+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
4749
Args: []string{"apply", "-n", d.sensorNamespace, "-f", "-"},
4850
Stdin: strings.NewReader(crsContent),
4951
})

internal/deployer/deploy_via_operator.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/stackrox/roxie/internal/env"
1313
"github.com/stackrox/roxie/internal/helpers"
14+
"github.com/stackrox/roxie/internal/k8s"
1415
"gopkg.in/yaml.v3"
1516
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1617
)
@@ -167,7 +168,7 @@ func (d *Deployer) isOperatorVersionCorrect(ctx context.Context) bool {
167168

168169
// getDeployedOperatorImage gets the image of the currently deployed operator
169170
func (d *Deployer) getDeployedOperatorImage(ctx context.Context) (string, error) {
170-
result, err := d.runKubectl(ctx, KubectlOptions{
171+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
171172
Args: []string{"get", "deployment", operatorDeploymentName, "-n", operatorNamespace,
172173
"-o", "jsonpath={.spec.template.spec.containers[0].image}"},
173174
})
@@ -200,7 +201,7 @@ func (d *Deployer) ensurePullSecretExists(ctx context.Context, namespace string)
200201
// Assemble pull secret YAML from pre-verified credentials
201202
pullSecretYAML := d.dockerAuth.CreatePullSecretYAMLFromCredentials(d.dockerCreds, namespace)
202203

203-
_, err := d.runKubectl(ctx, KubectlOptions{
204+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{
204205
Args: []string{"apply", "-f", "-"},
205206
Stdin: strings.NewReader(pullSecretYAML),
206207
})
@@ -231,7 +232,7 @@ func (d *Deployer) createAdminPasswordSecret(ctx context.Context) error {
231232
return fmt.Errorf("failed to marshal secret: %w", err)
232233
}
233234

234-
_, err = d.runKubectl(ctx, KubectlOptions{
235+
_, err = d.runKubectl(ctx, k8s.KubectlOptions{
235236
Args: []string{"apply", "-f", "-"},
236237
Stdin: bytes.NewReader(yamlData),
237238
})
@@ -438,7 +439,7 @@ func (d *Deployer) applyCentralCR(ctx context.Context, cr map[string]interface{}
438439
}
439440
}
440441

441-
result, err := d.runKubectl(ctx, KubectlOptions{
442+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
442443
Args: []string{"apply", "-f", "-"},
443444
Stdin: bytes.NewReader(yamlData),
444445
})
@@ -473,7 +474,7 @@ func (d *Deployer) waitForCentralReady(ctx context.Context, timeout time.Duratio
473474
}
474475

475476
// Check if central deployment is ready
476-
result, err := d.runKubectl(ctx, KubectlOptions{
477+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
477478
Args: []string{"get", "deployment", "central", "-n", d.centralNamespace, "-o", "jsonpath={.status.readyReplicas}"},
478479
})
479480
if err == nil && result.Stdout != "" {
@@ -508,7 +509,7 @@ func (d *Deployer) waitForLoadBalancer(ctx context.Context, namespace, serviceNa
508509

509510
start := time.Now()
510511
for time.Since(start) < time.Duration(timeout)*time.Second {
511-
result, err := d.runKubectl(ctx, KubectlOptions{
512+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
512513
Args: []string{"get", "svc", serviceName, "-n", namespace, "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}"},
513514
})
514515
if err == nil && result.Stdout != "" {
@@ -524,7 +525,7 @@ func (d *Deployer) waitForLoadBalancer(ctx context.Context, namespace, serviceNa
524525
}
525526

526527
// Also check for hostname (some cloud providers use hostname instead of IP)
527-
result, err = d.runKubectl(ctx, KubectlOptions{
528+
result, err = d.runKubectl(ctx, k8s.KubectlOptions{
528529
Args: []string{"get", "svc", serviceName, "-n", namespace, "-o", "jsonpath={.status.loadBalancer.ingress[0].hostname}"},
529530
})
530531
if err == nil && result.Stdout != "" {
@@ -549,7 +550,7 @@ func (d *Deployer) waitForLoadBalancer(ctx context.Context, namespace, serviceNa
549550
func (d *Deployer) fetchCentralCACert(ctx context.Context) error {
550551
d.logger.Info("Fetching Central CA certificate...")
551552

552-
result, err := d.runKubectl(ctx, KubectlOptions{
553+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
553554
Args: []string{"get", "secret", "central-tls", "-n", d.centralNamespace, "-o", "jsonpath={.data.ca\\.pem}"},
554555
})
555556
if err != nil {
@@ -771,7 +772,7 @@ func (d *Deployer) applySecuredClusterCR(ctx context.Context, cr map[string]inte
771772
}
772773
}
773774

774-
result, err := d.runKubectl(ctx, KubectlOptions{
775+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
775776
Args: []string{"apply", "-n", d.sensorNamespace, "-f", "-"},
776777
Stdin: bytes.NewReader(yamlData),
777778
})
@@ -805,7 +806,7 @@ func (d *Deployer) waitForSecuredClusterReady(ctx context.Context, timeout time.
805806
allReady := true
806807

807808
// Check sensor deployment
808-
result, err := d.runKubectl(ctx, KubectlOptions{
809+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
809810
Args: []string{"get", "deployment", "sensor", "-n", d.sensorNamespace, "-o", "jsonpath={.status.readyReplicas}"},
810811
})
811812
if err != nil || result.Stdout == "" {
@@ -820,7 +821,7 @@ func (d *Deployer) waitForSecuredClusterReady(ctx context.Context, timeout time.
820821
// Only check additional workloads if early-readiness is not enabled
821822
if !d.earlyReadiness {
822823
// Check admission-control deployment
823-
result, err = d.runKubectl(ctx, KubectlOptions{
824+
result, err = d.runKubectl(ctx, k8s.KubectlOptions{
824825
Args: []string{"get", "deployment", "admission-control", "-n", d.sensorNamespace, "-o", "jsonpath={.status.readyReplicas}"},
825826
})
826827
if err != nil || result.Stdout == "" {

internal/deployer/deployer.go

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/stackrox/roxie/internal/env"
2121
"github.com/stackrox/roxie/internal/helpers"
2222
"github.com/stackrox/roxie/internal/imagecache"
23+
"github.com/stackrox/roxie/internal/k8s"
2324
"github.com/stackrox/roxie/internal/logger"
2425
"github.com/stackrox/roxie/internal/portforward"
2526
)
@@ -107,7 +108,6 @@ type Deployer struct {
107108
imageCache *imagecache.ImageCache
108109
portForward *portforward.Manager
109110
clusterDefaults *clusterdefaults.Manager
110-
kubectl string
111111
roxctlVersion string
112112
centralNamespace string
113113
sensorNamespace string
@@ -135,9 +135,10 @@ type Deployer struct {
135135
clusterResourceKinds map[string]struct{}
136136
}
137137

138-
type ResourceKindWithName struct {
139-
Kind string
140-
Name string
138+
type ResourceToDelete struct {
139+
Kind string
140+
Name string
141+
OwnerName string
141142
}
142143

143144
func (d *Deployer) filterResourceKinds(resourceKinds []string) []string {
@@ -165,12 +166,12 @@ func (d *Deployer) deleteResources(ctx context.Context, namespace string, resour
165166
"--grace-period=0",
166167
}
167168
finalArgs = append(finalArgs, args...)
168-
_, err := d.runKubectl(ctx, KubectlOptions{Args: finalArgs})
169+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{Args: finalArgs})
169170
return err
170171
}
171172

172173
func (d *Deployer) deleteFinalizers(ctx context.Context, namespace, resourceType, resourceName string) error {
173-
_, err := d.runKubectl(ctx, KubectlOptions{
174+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{
174175
Args: []string{
175176
"-n", namespace, "patch", resourceType, resourceName,
176177
"-p", `{"metadata":{"finalizers":null}}`,
@@ -215,13 +216,30 @@ func (d *Deployer) deleteCentralResources(ctx context.Context, wait bool) error
215216
return err
216217
}
217218

218-
for _, resource := range []ResourceKindWithName{
219-
{Name: "central-db", Kind: "pvc"},
220-
{Name: "central-db-backup", Kind: "pvc"},
219+
for _, resource := range []ResourceToDelete{
220+
{Name: "central-db", Kind: "pvc", OwnerName: centralCrName},
221+
{Name: "central-db-backup", Kind: "pvc", OwnerName: centralCrName},
221222
{Name: "admin-password", Kind: "secret"},
223+
{Name: "scanner-db-password", Kind: "secret", OwnerName: centralCrName},
222224
} {
223-
err := d.deleteResource(ctx, d.centralNamespace, resource.Kind, resource.Name)
224-
if err != nil {
225+
d.logger.Dimf("Attempting to delete %s/%s", resource.Kind, resource.Name)
226+
if resource.OwnerName != "" {
227+
// Avoid deletion if the resource does not have the expected owner.
228+
// (e.g. in case central and secured cluster are deployed into the same namespace).
229+
obj, err := k8s.RetrieveResourceFromCluster(ctx, d.logger, d.centralNamespace, resource.Kind, resource.Name)
230+
if err != nil {
231+
if !k8s.IsResourceNotFound(err) {
232+
d.logger.Warningf("Failed to retrieve %s/%s for owner checking: %v. Skipping deletion. Deployment might be affected.", resource.Kind, resource.Name, err)
233+
}
234+
continue
235+
}
236+
if k8s.ResourceNotOwnedByName(obj, resource.OwnerName) {
237+
d.logger.Dimf("Skipping deletion of %s/%s: not owned by %s", resource.Kind, resource.Name, resource.OwnerName)
238+
continue
239+
}
240+
}
241+
242+
if err := d.deleteResource(ctx, d.centralNamespace, resource.Kind, resource.Name); err != nil {
225243
return fmt.Errorf("failed to delete %s/%s: %w", resource.Kind, resource.Name, err)
226244
}
227245
}
@@ -249,7 +267,7 @@ func (d *Deployer) preventCABundleInjection(ctx context.Context) error {
249267
}
250268

251269
d.logger.Info("Removing CNO label from injected-cabundle ConfigMap to prevent CNO from injecting the CA bundle during cleanup")
252-
_, err := d.runKubectl(ctx, KubectlOptions{
270+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{
253271
Args: []string{
254272
"label", "configmap", configMapName, "-n", d.centralNamespace,
255273
"config.openshift.io/inject-trusted-cabundle-",
@@ -290,12 +308,29 @@ func (d *Deployer) deleteSecuredClusterResources(ctx context.Context, wait bool)
290308
return err
291309
}
292310

293-
for _, resource := range []ResourceKindWithName{
311+
for _, resource := range []ResourceToDelete{
294312
{Name: "cluster-registration-secret", Kind: "secret"},
295-
{Name: "scanner-db-password", Kind: "secret"},
313+
// We need to make sure that don't accidentally delete a scanner-db-password belonging to the central CR,
314+
// when both are deployed into the same namespace.
315+
{Name: "scanner-db-password", Kind: "secret", OwnerName: securedClusterCrName},
296316
} {
297-
err := d.deleteResource(ctx, d.sensorNamespace, resource.Kind, resource.Name)
298-
if err != nil {
317+
d.logger.Dimf("Attempting to delete %s/%s", resource.Kind, resource.Name)
318+
if resource.OwnerName != "" {
319+
// Avoid deletion if the resource does not have the expected owner.
320+
// (e.g. in case central and secured cluster are deployed into the same namespace).
321+
obj, err := k8s.RetrieveResourceFromCluster(ctx, d.logger, d.sensorNamespace, resource.Kind, resource.Name)
322+
if err != nil {
323+
if !k8s.IsResourceNotFound(err) {
324+
d.logger.Warningf("Failed to retrieve %s/%s for owner checking: %v. Skipping deletion. Deployment might be affected.", resource.Kind, resource.Name, err)
325+
}
326+
continue
327+
}
328+
if k8s.ResourceNotOwnedByName(obj, resource.OwnerName) {
329+
d.logger.Dimf("Skipping deletion of %s/%s: not owned by %s", resource.Kind, resource.Name, resource.OwnerName)
330+
continue
331+
}
332+
}
333+
if err := d.deleteResource(ctx, d.sensorNamespace, resource.Kind, resource.Name); err != nil {
299334
return fmt.Errorf("failed to delete %s/%s: %w", resource.Kind, resource.Name, err)
300335
}
301336
}
@@ -477,12 +512,9 @@ func New(log *logger.Logger) (*Deployer, error) {
477512
return nil, err
478513
}
479514

480-
kubectl := getKubectl()
481-
482515
d := &Deployer{
483516
logger: log,
484517
startTime: time.Now(),
485-
kubectl: kubectl,
486518
roxctlVersion: roxctlVersion,
487519
centralNamespace: centralNamespace,
488520
sensorNamespace: sensorNamespace,
@@ -494,7 +526,7 @@ func New(log *logger.Logger) (*Deployer, error) {
494526

495527
d.dockerAuth = dockerauth.New(log)
496528
d.imageCache = imagecache.New(log, "", 20)
497-
d.portForward = portforward.New(kubectl, log)
529+
d.portForward = portforward.New(k8s.GetKubectl(), log)
498530
d.clusterDefaults = clusterdefaults.NewManager(log)
499531

500532
if password := os.Getenv("ROX_ADMIN_PASSWORD"); password != "" {
@@ -511,11 +543,7 @@ func New(log *logger.Logger) (*Deployer, error) {
511543
d.roxCACertFile = caCert
512544
}
513545

514-
ctx, err := getCurrentContext(kubectl)
515-
if err != nil {
516-
return nil, err
517-
}
518-
d.kubeContext = ctx
546+
d.kubeContext = env.GetCurrentContext()
519547

520548
clusterResourceKinds, err := d.getClusterResourceKinds()
521549
if err != nil {
@@ -530,7 +558,7 @@ func New(log *logger.Logger) (*Deployer, error) {
530558
}
531559

532560
func (d *Deployer) getClusterResourceKinds() (map[string]struct{}, error) {
533-
result, err := d.runKubectl(context.Background(), KubectlOptions{
561+
result, err := d.runKubectl(context.Background(), k8s.KubectlOptions{
534562
Args: []string{"api-resources", "-o", "name"},
535563
})
536564
if err != nil {
@@ -754,15 +782,15 @@ func (d *Deployer) ensureNamespaceExists(namespace string) error {
754782
}
755783

756784
d.logger.Infof("Creating namespace %s", namespace)
757-
_, err := d.runKubectl(context.Background(), KubectlOptions{
785+
_, err := d.runKubectl(context.Background(), k8s.KubectlOptions{
758786
Args: []string{"create", "namespace", namespace},
759787
})
760788
if err != nil {
761789
return fmt.Errorf("failed to create namespace: %w", err)
762790
}
763791

764792
// Label namespace as managed by roxie since we just created it
765-
_, err = d.runKubectl(context.Background(), KubectlOptions{
793+
_, err = d.runKubectl(context.Background(), k8s.KubectlOptions{
766794
Args: []string{"label", "namespace", namespace,
767795
"app.kubernetes.io/managed-by=roxie", "--overwrite"},
768796
})
@@ -774,7 +802,7 @@ func (d *Deployer) ensureNamespaceExists(namespace string) error {
774802
}
775803

776804
func (d *Deployer) namespaceExists(namespace string) bool {
777-
_, err := d.runKubectl(context.Background(), KubectlOptions{
805+
_, err := d.runKubectl(context.Background(), k8s.KubectlOptions{
778806
Args: []string{"get", "namespace", namespace},
779807
})
780808
return err == nil
@@ -824,13 +852,6 @@ func checkRequiredTools() error {
824852
return nil
825853
}
826854

827-
func getKubectl() string {
828-
if kubectl := os.Getenv("ORCH_CMD"); kubectl != "" {
829-
return kubectl
830-
}
831-
return "kubectl"
832-
}
833-
834855
func getRoxctlVersion() (string, error) {
835856
cmd := exec.Command("roxctl", "version")
836857
output, err := cmd.Output()
@@ -852,16 +873,6 @@ func getRoxctlVersion() (string, error) {
852873
return version, nil
853874
}
854875

855-
func getCurrentContext(kubectl string) (string, error) {
856-
cmd := exec.Command(kubectl, "config", "current-context")
857-
output, err := cmd.Output()
858-
if err != nil {
859-
return "", fmt.Errorf("failed to get current context: %w", err)
860-
}
861-
862-
return strings.TrimSpace(string(output)), nil
863-
}
864-
865876
func generatePassword() string {
866877
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
867878
const passwordLength = 20
@@ -948,7 +959,7 @@ func (d *Deployer) maybeAddPauseReconcileAnnotation(ctx context.Context, resourc
948959
}
949960

950961
func (d *Deployer) doesResourceExist(ctx context.Context, resourceType, resourceName, namespace string) bool {
951-
_, err := d.runKubectl(ctx, KubectlOptions{
962+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{
952963
Args: []string{
953964
"get", resourceType, resourceName,
954965
"-n", namespace,
@@ -958,7 +969,7 @@ func (d *Deployer) doesResourceExist(ctx context.Context, resourceType, resource
958969
}
959970

960971
func (d *Deployer) addPauseReconcileAnnotation(ctx context.Context, resourceType, resourceName, namespace string) error {
961-
_, err := d.runKubectl(ctx, KubectlOptions{
972+
_, err := d.runKubectl(ctx, k8s.KubectlOptions{
962973
Args: []string{
963974
"annotate", resourceType, resourceName,
964975
"-n", namespace,
@@ -1155,7 +1166,7 @@ func (d *Deployer) PrintCentralDeploymentSummary() {
11551166

11561167
// checkDeploymentProgressInNamespace checks for deployment state changes in a specific namespace and reports them
11571168
func (d *Deployer) checkDeploymentProgressInNamespace(ctx context.Context, namespace string, seenDeployments map[string]string) {
1158-
result, err := d.runKubectl(ctx, KubectlOptions{
1169+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
11591170
Args: []string{"get", "deployments", "-n", namespace, "-o", "jsonpath={range .items[*]}{.metadata.name}{'|'}{.status.replicas}{'|'}{.status.readyReplicas}{'|'}{.status.availableReplicas}{'\\n'}{end}"},
11601171
})
11611172
if err != nil {
@@ -1200,7 +1211,7 @@ func (d *Deployer) checkDeploymentProgressInNamespace(ctx context.Context, names
12001211

12011212
// checkPodProgressInNamespace checks for pod state changes in a specific namespace and reports them
12021213
func (d *Deployer) checkPodProgressInNamespace(ctx context.Context, namespace string, seenPods map[string]string) {
1203-
result, err := d.runKubectl(ctx, KubectlOptions{
1214+
result, err := d.runKubectl(ctx, k8s.KubectlOptions{
12041215
Args: []string{"get", "pods", "-n", namespace, "-o", "jsonpath={range .items[*]}{.metadata.name}{'|'}{.status.phase}{'|'}{.status.containerStatuses[0].ready}{'\\n'}{end}"},
12051216
})
12061217
if err != nil {

0 commit comments

Comments
 (0)