Skip to content

introduce seal/reseal operations#53

Closed
bennyz wants to merge 1 commit into
centos-automotive-suite:mainfrom
bennyz:seal-reseal-v2
Closed

introduce seal/reseal operations#53
bennyz wants to merge 1 commit into
centos-automotive-suite:mainfrom
bennyz:seal-reseal-v2

Conversation

@bennyz

@bennyz bennyz commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Image resealing capability: seal bootc container images with optional seal key protection and registry credentials
    • New reseal CLI command with configurable options for builder image, storage class, and timeout settings
    • New REST API endpoints to create, list, and retrieve reseal operations
    • Real-time status tracking for reseal operations with phase information (Pending, Running, Completed, Failed) and completion details

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Feb 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

This pull request introduces ImageReseal, a new Kubernetes custom resource and controller for orchestrating container image resealing operations. It includes CRD definitions, API handlers, a CLI command, controller reconciliation logic, Tekton task workflows, and supporting scripts to manage the end-to-end reseal workflow.

Changes

Cohort / File(s) Summary
Kubernetes API Definitions
api/v1alpha1/imagereseal_types.go, api/v1alpha1/zz_generated.deepcopy.go
Introduces ImageReseal, ImageResealSpec, and ImageResealStatus types with full CRD markers, helper methods (GetAIBImage, HasSealKey, HasSealKeyPassword), and auto-generated deepcopy methods for proper Kubernetes object semantics.
CRD Configuration
config/crd/bases/automotive.sdv.cloud.redhat.com_imagereseals.yaml
Defines the CustomResourceDefinition for ImageReseal resource with spec and status schemas, required fields (sourceContainer, targetContainer), phase enum, and print columns for UI visibility.
RBAC Permissions
config/rbac/role.yaml
Extends ClusterRole ado-manager-role with permissions for imagereseals, imagereseals/finalizers, and imagereseals/status resources in the automotive.sdv.cloud.redhat.com API group.
Controller Implementation
internal/controller/imagereseal/controller.go
Implements Reconciler with reconciliation loop handling Pending→Running→Completed/Failed state transitions, TaskRun lifecycle management, secret cleanup, and SetupWithManager for Kubernetes integration.
Build API Client
internal/buildapi/client/client.go
Adds three public client methods (CreateReseal, GetReseal, ListReseals) for reseal operations via HTTP endpoints; note: implementation is duplicated in the file.
Build API Server
internal/buildapi/server.go
Implements reseal API endpoints (/v1/reseals) with handlers for create, list, and get operations; includes request validation, secret creation/management, ImageReseal CR instantiation, and owner reference setup for cascading deletion.
API Types
internal/buildapi/types.go
Introduces ResealRequest, ResealResponse, and ResealListItem types; adds BuilderImageUsed field to BuildResponse for API contract extension.
Tekton Task Generation
internal/common/tasks/tasks.go, internal/common/tasks/scripts.go
Adds GenerateResealTask function creating a full Tekton Task spec for reseal operations; exports ResealImageScript variable embedding reseal_image.sh script; note: GenerateResealTask appears duplicated.
Reseal Script
internal/common/tasks/scripts/reseal_image.sh
Implements comprehensive Bash orchestration for container image resealing: validates inputs, manages container storage/auth, handles seal key injection, pre-pulls builder images, executes aib reseal, and pushes resealed image to registry.
CLI Commands
cmd/caib/main.go
Adds reseal subcommand with full flag set (server, token, name, key, passwd, builder-image, etc.); implements runReseal handler, waitForResealCompletion poller, registry credential validation; integrates phaseCompleted constant into existing completion checks.
Manager Initialization
cmd/main.go
Registers new imagereseal.Reconciler with manager during controller setup with error handling and logging.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI<br/>(caib reseal)
    participant APIServer as Build API<br/>Server
    participant K8s as Kubernetes API
    participant Ctrl as ImageReseal<br/>Controller
    participant Tekton as Tekton<br/>TaskRun
    participant Registry as Container<br/>Registry

    User->>CLI: caib reseal --from-build build1 ...
    CLI->>APIServer: POST /v1/reseals (ResealRequest)
    APIServer->>APIServer: Validate request & credentials
    APIServer->>K8s: Create registry secrets
    APIServer->>K8s: Create seal key secrets
    APIServer->>K8s: Create ImageReseal CR (status: Pending)
    APIServer-->>CLI: ResealResponse (status: Pending)
    
    CLI->>APIServer: GET /v1/reseals/{name} (polling)
    Note over CLI,APIServer: Poll loop (if --wait)
    
    K8s->>Ctrl: ImageReseal created (Pending)
    Ctrl->>Ctrl: handlePending()
    Ctrl->>K8s: Create Tekton TaskRun
    Ctrl->>K8s: Update CR (status: Running)
    
    Tekton->>Tekton: Execute reseal_image.sh
    Tekton->>Registry: Pull source container
    Tekton->>Registry: Pull builder image (aib)
    Tekton->>Registry: Execute aib reseal
    Tekton->>Registry: Push resealed image
    
    Ctrl->>K8s: Poll TaskRun completion
    Ctrl->>K8s: Update CR (status: Completed)<br/>+ SealedContainer ref
    Ctrl->>K8s: Cleanup transient secrets
    
    CLI->>APIServer: GET /v1/reseals/{name}
    APIServer-->>CLI: ResealResponse (Completed)<br/>+ sealed image reference
    CLI-->>User: Display results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A new reseal hops into view,
Images sealed with keys, oh how true!
Tekton tasks prance through the air,
Controllers care, secrets with care,
From CLI to registry—a complete affair! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'introduce seal/reseal operations' accurately summarizes the main objective of the changeset, which adds comprehensive seal/reseal functionality across multiple layers including new Kubernetes CRD types, CLI commands, API endpoints, and controllers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@bennyz

bennyz commented Feb 1, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Feb 1, 2026

Copy link
Copy Markdown

@bennyz: I'll review the changes in this PR that introduce seal/reseal operations.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@api/v1alpha1/imagereseal_types.go`:
- Around line 46-48: Update the BuilderImage field to be required in the CRD
schema by adding the kubebuilder marker +kubebuilder:validation:Required above
the BuilderImage declaration and remove ",omitempty" from its json tag (change
`json:"builderImage,omitempty"` to `json:"builderImage"`), matching the
SourceContainer/TargetContainer pattern; after editing the BuilderImage field,
run `make generate manifests` to regenerate CRD manifests and DeepCopy code.

In `@cmd/caib/main.go`:
- Around line 1751-1763: The GetBuild call is using context.Background() instead
of the existing ctx; change the api.GetBuild invocation to use the function's
ctx variable so cancellation and deadlines propagate (replace
api.GetBuild(context.Background(), fromBuild) with api.GetBuild(ctx,
fromBuild)), keeping the surrounding error handling logic that checks
build.BuilderImageUsed and assigns builderImage unchanged.

In `@internal/buildapi/server.go`:
- Around line 2206-2293: The created secrets (registry secret via
createRegistrySecret, sealKeySecret/passwordSecret created before ImageReseal)
can become orphaned if k8sClient.Create(ctx, imageReseal) fails; ensure secrets
are either created after the ImageReseal exists or are cleaned up on failure by:
create the ImageReseal CR first (using empty/placeholder secret refs if needed)
or set OwnerReferences on each secret to the ImageReseal before creating them,
and if you choose to create secrets first implement a deferred cleanup that
deletes createRegistrySecret's secret and the local sealKeySecret/passwordSecret
when k8sClient.Create(ctx, imageReseal) returns an error; update the logic
around createRegistrySecret, the sealKeySecret/passwordSecret creation blocks
and the final k8sClient.Create(imageReseal) call to perform one of these two
approaches.

In `@internal/common/tasks/scripts/reseal_image.sh`:
- Around line 272-282: The script currently writes the Tekton result file
(/tekton/results/sealed-container) only inside the CONTAINER_PUSH branch; ensure
the result is always written. After the conditional push block that uses
push_cmd, log_command and the skopeo invocation (and also in the branch where
CONTAINER_PUSH is empty), write the value that represents the sealed image (use
the same variable used in the push branch, e.g., $CONTAINER_PUSH or
$OUTPUT_CONTAINER as appropriate) to /tekton/results/sealed-container so
downstream tasks always have the result; update reseal_image.sh to perform that
write in both branches.

In `@internal/controller/imagereseal/controller.go`:
- Around line 161-169: The code currently checks only for existence of the
"app.kubernetes.io/managed-by" label on existingTask before overwriting; change
the condition to verify the label value equals this controller's identifier
(compare existingTask.Labels["app.kubernetes.io/managed-by"] ==
"<controller-name>") instead of just presence, and if it doesn't match, do not
update and return or skip with a clear error/permission message; adjust the
block that assigns existingTask.Spec = resealTask.Spec and calls r.Update(ctx,
existingTask) to run only when the label value matches, and handle the
non-matching case (e.g., return nil with a descriptive fmt.Errorf or skip
update) so you never overwrite tasks managed by other controllers.
- Around line 288-295: The comment in cleanupTransientSecrets (function
cleanupTransientSecrets) contradicts the fact that seal key secrets have
ownerReferences set in server.go (the code that creates the seal key secrets) —
decide which behavior you want: if seal keys should be reusable, remove the
ownerReferences where seal key secrets are created in server.go so they are not
GC'd and keep/delete logic as-is (or add explicit deletion via
deleteSecretWithRetry); otherwise, keep the ownerReferences and update
cleanupTransientSecrets' comment and logic to reflect that seal key secrets are
lifecycle-managed by ownerReferences (i.e., they will be garbage collected and
should not be manually deleted). Ensure the chosen change touches either the
owner reference assignment in the seal-key creation code or the comment/cleanup
logic in cleanupTransientSecrets and reference deleteSecretWithRetry if adding
explicit deletion.
🧹 Nitpick comments (4)
api/v1alpha1/imagereseal_types.go (1)

42-44: Align the AIB default/tag across API helpers and task defaults.
This file defaults to ...:latest, while internal/common/tasks/tasks.go uses ...:1.0.0. That can lead to different images depending on the entrypoint, and the mutable latest tag hurts reproducibility. Consider pinning and sharing a single canonical value.

🔧 Suggested alignment (pick a single canonical tag)
-// +kubebuilder:default="quay.io/centos-sig-automotive/automotive-image-builder:latest"
+// +kubebuilder:default="quay.io/centos-sig-automotive/automotive-image-builder:1.0.0"
@@
-return "quay.io/centos-sig-automotive/automotive-image-builder:latest"
+return "quay.io/centos-sig-automotive/automotive-image-builder:1.0.0"

Also applies to: 112-118

config/crd/bases/automotive.sdv.cloud.redhat.com_imagereseals.yaml (1)

57-61: Consider using a versioned tag instead of :latest for the default AIB image.

The default value uses :latest which can lead to non-reproducible builds. Looking at internal/common/tasks/tasks.go:24, the codebase uses a versioned tag 1.0.0 for the same image elsewhere.

Suggested fix
              automotiveImageBuilder:
-               default: quay.io/centos-sig-automotive/automotive-image-builder:latest
+               default: quay.io/centos-sig-automotive/automotive-image-builder:1.0.0
                description: AutomotiveImageBuilder is the AIB container image to
                  use for the reseal operation
                type: string
cmd/caib/main.go (1)

1828-1871: Consider adding log streaming support for reseal operations.

The waitForResealCompletion function only polls for status updates but doesn't support log streaming, unlike waitForBuildCompletion and waitForFlashCompletion. This may make it harder for users to debug failed reseal operations.

The --follow flag is defined for the reseal command but isn't utilized in this function.

internal/controller/imagereseal/controller.go (1)

222-230: Consider using ptr.To(true) for cleaner pointer creation.

The inline function for creating a bool pointer can be simplified using k8s.io/utils/ptr.To(true) which is more idiomatic in Kubernetes codebases.

Suggested fix
+import "k8s.io/utils/ptr"
+
 			OwnerReferences: []metav1.OwnerReference{
 				{
 					APIVersion: automotivev1alpha1.GroupVersion.String(),
 					Kind:       "ImageReseal",
 					Name:       reseal.Name,
 					UID:        reseal.UID,
-					Controller: func(b bool) *bool { return &b }(true),
+					Controller: ptr.To(true),
 				},
 			},

Comment thread api/v1alpha1/imagereseal_types.go Outdated
Comment on lines +46 to +48
// BuilderImage is the osbuild builder container image to use
// Required for reseal operations - the builder runs rpm-ostree compose
BuilderImage string `json:"builderImage,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the imagereseal_types.go file to see full context
cat -n api/v1alpha1/imagereseal_types.go

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 5846


🏁 Script executed:

# Search for reseal task implementation to verify empty-check behavior
rg -A 5 -B 2 "BuilderImage" --type go

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 10345


🏁 Script executed:

# Check for kubebuilder validation patterns in the api/v1alpha1 directory
rg "kubebuilder:validation" api/v1alpha1/ --type go | head -20

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 1986


🏁 Script executed:

# Check Go version requirements
cat -n go.mod | head -5

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 222


Add +kubebuilder:validation:Required and remove omitempty from BuilderImage.

The field is documented as required and the API handler explicitly rejects empty values, but lacks CRD-level schema validation. This allows invalid CRs to pass admission and fail at runtime. Align it with SourceContainer and TargetContainer, which follow the required field pattern.

After making this change, run make generate manifests to regenerate manifests and DeepCopy methods.

🛠️ Suggested schema fix
 // BuilderImage is the osbuild builder container image to use
 // Required for reseal operations - the builder runs rpm-ostree compose
-BuilderImage string `json:"builderImage,omitempty"`
+// +kubebuilder:validation:Required
+// +kubebuilder:validation:MinLength=1
+BuilderImage string `json:"builderImage"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// BuilderImage is the osbuild builder container image to use
// Required for reseal operations - the builder runs rpm-ostree compose
BuilderImage string `json:"builderImage,omitempty"`
// BuilderImage is the osbuild builder container image to use
// Required for reseal operations - the builder runs rpm-ostree compose
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
BuilderImage string `json:"builderImage"`
🤖 Prompt for AI Agents
In `@api/v1alpha1/imagereseal_types.go` around lines 46 - 48, Update the
BuilderImage field to be required in the CRD schema by adding the kubebuilder
marker +kubebuilder:validation:Required above the BuilderImage declaration and
remove ",omitempty" from its json tag (change `json:"builderImage,omitempty"` to
`json:"builderImage"`), matching the SourceContainer/TargetContainer pattern;
after editing the BuilderImage field, run `make generate manifests` to
regenerate CRD manifests and DeepCopy code.

Comment thread cmd/caib/main.go
Comment on lines +1751 to +1763
if builderImage == "" && fromBuild != "" {
// Fetch the ImageBuild to get its builder image
fmt.Printf("Looking up builder image from build: %s\n", fromBuild)
build, err := api.GetBuild(context.Background(), fromBuild)
if err != nil {
handleError(fmt.Errorf("failed to get build %s: %w", fromBuild, err))
}
if build.BuilderImageUsed == "" {
handleError(fmt.Errorf("build %s does not have a recorded builder image", fromBuild))
}
builderImage = build.BuilderImageUsed
fmt.Printf("Using builder image from build: %s\n", builderImage)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the existing ctx instead of context.Background().

Line 1754 creates a new context.Background() for the GetBuild call, but the function already has a valid ctx that should be used for consistency and proper cancellation propagation.

Suggested fix
 	if builderImage == "" && fromBuild != "" {
 		// Fetch the ImageBuild to get its builder image
 		fmt.Printf("Looking up builder image from build: %s\n", fromBuild)
-		build, err := api.GetBuild(context.Background(), fromBuild)
+		build, err := api.GetBuild(ctx, fromBuild)
 		if err != nil {
 			handleError(fmt.Errorf("failed to get build %s: %w", fromBuild, err))
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if builderImage == "" && fromBuild != "" {
// Fetch the ImageBuild to get its builder image
fmt.Printf("Looking up builder image from build: %s\n", fromBuild)
build, err := api.GetBuild(context.Background(), fromBuild)
if err != nil {
handleError(fmt.Errorf("failed to get build %s: %w", fromBuild, err))
}
if build.BuilderImageUsed == "" {
handleError(fmt.Errorf("build %s does not have a recorded builder image", fromBuild))
}
builderImage = build.BuilderImageUsed
fmt.Printf("Using builder image from build: %s\n", builderImage)
}
if builderImage == "" && fromBuild != "" {
// Fetch the ImageBuild to get its builder image
fmt.Printf("Looking up builder image from build: %s\n", fromBuild)
build, err := api.GetBuild(ctx, fromBuild)
if err != nil {
handleError(fmt.Errorf("failed to get build %s: %w", fromBuild, err))
}
if build.BuilderImageUsed == "" {
handleError(fmt.Errorf("build %s does not have a recorded builder image", fromBuild))
}
builderImage = build.BuilderImageUsed
fmt.Printf("Using builder image from build: %s\n", builderImage)
}
🤖 Prompt for AI Agents
In `@cmd/caib/main.go` around lines 1751 - 1763, The GetBuild call is using
context.Background() instead of the existing ctx; change the api.GetBuild
invocation to use the function's ctx variable so cancellation and deadlines
propagate (replace api.GetBuild(context.Background(), fromBuild) with
api.GetBuild(ctx, fromBuild)), keeping the surrounding error handling logic that
checks build.BuilderImageUsed and assigns builderImage unchanged.

Comment thread internal/buildapi/server.go Outdated
Comment on lines +2206 to +2293
// Create registry secret if credentials provided
var envSecretRef string
if req.RegistryCredentials != nil && req.RegistryCredentials.Enabled {
envSecretRef, err = createRegistrySecret(ctx, k8sClient, namespace, req.Name+"-reseal", req.RegistryCredentials)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error creating registry secret: %v", err)})
return
}
}

// Create seal key secret if key provided directly
var sealKeySecretRef string
if req.SealKey != "" {
sealKeySecretRef = req.Name + "-seal-key"
sealKeySecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sealKeySecretRef,
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/managed-by": "build-api",
"app.kubernetes.io/part-of": "automotive-dev",
"automotive.sdv.cloud.redhat.com/resource-type": "seal-key",
"automotive.sdv.cloud.redhat.com/reseal-name": req.Name,
},
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"private-key": []byte(req.SealKey),
},
}
if err := k8sClient.Create(ctx, sealKeySecret); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error creating seal key secret: %v", err)})
return
}
}

// Create seal key password secret if password provided
var sealKeyPasswordSecretRef string
if req.SealKeyPassword != "" {
sealKeyPasswordSecretRef = req.Name + "-seal-key-password"
passwordSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sealKeyPasswordSecretRef,
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/managed-by": "build-api",
"app.kubernetes.io/part-of": "automotive-dev",
"automotive.sdv.cloud.redhat.com/resource-type": "seal-key-password",
"automotive.sdv.cloud.redhat.com/reseal-name": req.Name,
},
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"password": []byte(req.SealKeyPassword),
},
}
if err := k8sClient.Create(ctx, passwordSecret); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error creating seal key password secret: %v", err)})
return
}
}

// Create the ImageReseal CR
imageReseal := &automotivev1alpha1.ImageReseal{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/managed-by": "build-api",
"app.kubernetes.io/part-of": "automotive-dev",
},
},
Spec: automotivev1alpha1.ImageResealSpec{
SourceContainer: req.SourceContainer,
TargetContainer: req.TargetContainer,
SealKeySecretRef: sealKeySecretRef,
SealKeyPasswordSecretRef: sealKeyPasswordSecretRef,
AutomotiveImageBuilder: req.AutomotiveImageBuilder,
BuilderImage: req.BuilderImage,
EnvSecretRef: envSecretRef,
StorageClass: req.StorageClass,
},
}

if err := k8sClient.Create(ctx, imageReseal); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error creating ImageReseal: %v", err)})
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Secrets may be orphaned if ImageReseal CR creation fails.

If createRegistrySecret, sealKeySecret, or passwordSecret succeeds but the subsequent k8sClient.Create(ctx, imageReseal) fails, the created secrets will be orphaned since owner references are only set after the CR is created.

Consider wrapping the secret creation in a cleanup handler or creating secrets after the CR exists.

Suggested approach
+	// Track created secrets for cleanup on failure
+	var createdSecrets []string
+	defer func() {
+		if err != nil {
+			// Cleanup orphaned secrets
+			for _, secretName := range createdSecrets {
+				_ = k8sClient.Delete(ctx, &corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace},
+				})
+			}
+		}
+	}()
+
 	// Create registry secret if credentials provided
 	var envSecretRef string
 	if req.RegistryCredentials != nil && req.RegistryCredentials.Enabled {
 		envSecretRef, err = createRegistrySecret(ctx, k8sClient, namespace, req.Name+"-reseal", req.RegistryCredentials)
 		if err != nil {
 			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error creating registry secret: %v", err)})
 			return
 		}
+		createdSecrets = append(createdSecrets, envSecretRef)
 	}
🤖 Prompt for AI Agents
In `@internal/buildapi/server.go` around lines 2206 - 2293, The created secrets
(registry secret via createRegistrySecret, sealKeySecret/passwordSecret created
before ImageReseal) can become orphaned if k8sClient.Create(ctx, imageReseal)
fails; ensure secrets are either created after the ImageReseal exists or are
cleaned up on failure by: create the ImageReseal CR first (using
empty/placeholder secret refs if needed) or set OwnerReferences on each secret
to the ImageReseal before creating them, and if you choose to create secrets
first implement a deferred cleanup that deletes createRegistrySecret's secret
and the local sealKeySecret/passwordSecret when k8sClient.Create(ctx,
imageReseal) returns an error; update the logic around createRegistrySecret, the
sealKeySecret/passwordSecret creation blocks and the final
k8sClient.Create(imageReseal) call to perform one of these two approaches.

Comment on lines +272 to +282
# Push resealed container to registry if target is specified
if [ -n "$CONTAINER_PUSH" ]; then
echo "Pushing resealed container to registry: $CONTAINER_PUSH"
push_cmd=(skopeo copy --authfile="$REGISTRY_AUTH_FILE" "containers-storage:$OUTPUT_CONTAINER" "docker://$CONTAINER_PUSH")
log_command "${push_cmd[@]}"
"${push_cmd[@]}"
echo "Resealed container pushed successfully to $CONTAINER_PUSH"

# Write result for Tekton
echo "$CONTAINER_PUSH" > /tekton/results/sealed-container
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Always write the sealed-container Tekton result.
The result file is only written when container-push is set; for local-only reseals the result is missing and downstream consumers can fail. Write the result in both branches.

🛠️ Suggested fix
 if [ -n "$CONTAINER_PUSH" ]; then
   echo "Pushing resealed container to registry: $CONTAINER_PUSH"
   push_cmd=(skopeo copy --authfile="$REGISTRY_AUTH_FILE" "containers-storage:$OUTPUT_CONTAINER" "docker://$CONTAINER_PUSH")
   log_command "${push_cmd[@]}"
   "${push_cmd[@]}"
   echo "Resealed container pushed successfully to $CONTAINER_PUSH"

   # Write result for Tekton
   echo "$CONTAINER_PUSH" > /tekton/results/sealed-container
+else
+  # Write result for Tekton (local-only reseal)
+  echo "$OUTPUT_CONTAINER" > /tekton/results/sealed-container
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Push resealed container to registry if target is specified
if [ -n "$CONTAINER_PUSH" ]; then
echo "Pushing resealed container to registry: $CONTAINER_PUSH"
push_cmd=(skopeo copy --authfile="$REGISTRY_AUTH_FILE" "containers-storage:$OUTPUT_CONTAINER" "docker://$CONTAINER_PUSH")
log_command "${push_cmd[@]}"
"${push_cmd[@]}"
echo "Resealed container pushed successfully to $CONTAINER_PUSH"
# Write result for Tekton
echo "$CONTAINER_PUSH" > /tekton/results/sealed-container
fi
# Push resealed container to registry if target is specified
if [ -n "$CONTAINER_PUSH" ]; then
echo "Pushing resealed container to registry: $CONTAINER_PUSH"
push_cmd=(skopeo copy --authfile="$REGISTRY_AUTH_FILE" "containers-storage:$OUTPUT_CONTAINER" "docker://$CONTAINER_PUSH")
log_command "${push_cmd[@]}"
"${push_cmd[@]}"
echo "Resealed container pushed successfully to $CONTAINER_PUSH"
# Write result for Tekton
echo "$CONTAINER_PUSH" > /tekton/results/sealed-container
else
# Write result for Tekton (local-only reseal)
echo "$OUTPUT_CONTAINER" > /tekton/results/sealed-container
fi
🤖 Prompt for AI Agents
In `@internal/common/tasks/scripts/reseal_image.sh` around lines 272 - 282, The
script currently writes the Tekton result file
(/tekton/results/sealed-container) only inside the CONTAINER_PUSH branch; ensure
the result is always written. After the conditional push block that uses
push_cmd, log_command and the skopeo invocation (and also in the branch where
CONTAINER_PUSH is empty), write the value that represents the sealed image (use
the same variable used in the push branch, e.g., $CONTAINER_PUSH or
$OUTPUT_CONTAINER as appropriate) to /tekton/results/sealed-container so
downstream tasks always have the result; update reseal_image.sh to perform that
write in both branches.

Comment on lines +161 to +169
} else {
// Task exists - check if it's managed by us before updating
if _, managed := existingTask.Labels["app.kubernetes.io/managed-by"]; managed {
existingTask.Spec = resealTask.Spec
if err := r.Update(ctx, existingTask); err != nil {
return nil, fmt.Errorf("failed to update reseal task: %w", err)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Task update check should verify the label value, not just presence.

The condition if _, managed := existingTask.Labels["app.kubernetes.io/managed-by"]; managed only checks if the label exists, but doesn't verify it was set by this controller. This could unintentionally overwrite a Task managed by another component.

Suggested fix
 	} else {
 		// Task exists - check if it's managed by us before updating
-		if _, managed := existingTask.Labels["app.kubernetes.io/managed-by"]; managed {
+		if existingTask.Labels["app.kubernetes.io/managed-by"] == "automotive-dev-operator" {
 			existingTask.Spec = resealTask.Spec
 			if err := r.Update(ctx, existingTask); err != nil {
 				return nil, fmt.Errorf("failed to update reseal task: %w", err)
 			}
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
// Task exists - check if it's managed by us before updating
if _, managed := existingTask.Labels["app.kubernetes.io/managed-by"]; managed {
existingTask.Spec = resealTask.Spec
if err := r.Update(ctx, existingTask); err != nil {
return nil, fmt.Errorf("failed to update reseal task: %w", err)
}
}
}
} else {
// Task exists - check if it's managed by us before updating
if existingTask.Labels["app.kubernetes.io/managed-by"] == "automotive-dev-operator" {
existingTask.Spec = resealTask.Spec
if err := r.Update(ctx, existingTask); err != nil {
return nil, fmt.Errorf("failed to update reseal task: %w", err)
}
}
}
🤖 Prompt for AI Agents
In `@internal/controller/imagereseal/controller.go` around lines 161 - 169, The
code currently checks only for existence of the "app.kubernetes.io/managed-by"
label on existingTask before overwriting; change the condition to verify the
label value equals this controller's identifier (compare
existingTask.Labels["app.kubernetes.io/managed-by"] == "<controller-name>")
instead of just presence, and if it doesn't match, do not update and return or
skip with a clear error/permission message; adjust the block that assigns
existingTask.Spec = resealTask.Spec and calls r.Update(ctx, existingTask) to run
only when the label value matches, and handle the non-matching case (e.g.,
return nil with a descriptive fmt.Errorf or skip update) so you never overwrite
tasks managed by other controllers.

Comment on lines +288 to +295
// cleanupTransientSecrets deletes any transient secrets created for this reseal
func (r *Reconciler) cleanupTransientSecrets(ctx context.Context, reseal *automotivev1alpha1.ImageReseal, log logr.Logger) {
// Cleanup env secret if it was created transiently
if reseal.Spec.EnvSecretRef != "" {
r.deleteSecretWithRetry(ctx, reseal.Namespace, reseal.Spec.EnvSecretRef, "registry auth", log)
}
// Note: We don't delete seal key secrets as they may be reused
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check for owner reference setup related to seal secrets
rg -n 'setResealSecretOwnerRef.*seal|OwnerReferences.*seal' internal/buildapi/server.go

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 433


🏁 Script executed:

# Also search more broadly for owner reference setups in the buildapi package
rg -n 'OwnerReferences' internal/buildapi/server.go | head -20

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 317


🏁 Script executed:

# Let's check the actual lines mentioned in the review (2296-2310)
sed -n '2296,2310p' internal/buildapi/server.go

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 858


🏁 Script executed:

# Check the controller file to understand the context better
sed -n '280,300p' internal/controller/imagereseal/controller.go

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 934


🏁 Script executed:

# Search for where seal key secrets are created or handled
rg -n 'seal.*secret|sealKeySecret' internal/buildapi/server.go -A 3 -B 3 | head -50

Repository: centos-automotive-suite/automotive-dev-operator

Length of output: 2016


Resolve the inconsistency between seal key secret owner references and the cleanup comment.

Owner references are set on seal key secrets (lines 2297, 2302 in server.go), which means they will be garbage collected when the ImageReseal is deleted. This contradicts the comment stating "they may be reused." Either:

  1. Remove owner references from seal key secrets if they're intended for reuse across multiple ImageReseals, or
  2. Update the comment and cleanup logic to reflect that all transient secrets will be garbage collected, or
  3. If selective cleanup is necessary, clarify the distinction in the comment (e.g., "sealed key secrets are lifecycle-managed via owner references" vs "env secrets are transiently created and must be manually deleted").
🤖 Prompt for AI Agents
In `@internal/controller/imagereseal/controller.go` around lines 288 - 295, The
comment in cleanupTransientSecrets (function cleanupTransientSecrets)
contradicts the fact that seal key secrets have ownerReferences set in server.go
(the code that creates the seal key secrets) — decide which behavior you want:
if seal keys should be reusable, remove the ownerReferences where seal key
secrets are created in server.go so they are not GC'd and keep/delete logic
as-is (or add explicit deletion via deleteSecretWithRetry); otherwise, keep the
ownerReferences and update cleanupTransientSecrets' comment and logic to reflect
that seal key secrets are lifecycle-managed by ownerReferences (i.e., they will
be garbage collected and should not be manually deleted). Ensure the chosen
change touches either the owner reference assignment in the seal-key creation
code or the comment/cleanup logic in cleanupTransientSecrets and reference
deleteSecretWithRetry if adding explicit deletion.

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant