Skip to content

Commit fecea3f

Browse files
committed
feat: add container sha as optional field to package
1 parent 4073db0 commit fecea3f

File tree

12 files changed

+87
-37
lines changed

12 files changed

+87
-37
lines changed

chart/templates/skyhook-crd.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ spec:
231231
The keys stored in Data must not overlap with the keys in
232232
the BinaryData field, this is enforced during validation process.
233233
type: object
234+
containerSHA:
235+
description: |-
236+
ContainerSHA is the SHA256 digest of the container image for verification purposes.
237+
When specified, this will be used instead of the version tag to pull the exact image.
238+
Format: sha256:1234567890abcdef...
239+
example: sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
240+
type: string
234241
dependsOn:
235242
additionalProperties:
236243
type: string
@@ -694,6 +701,10 @@ spec:
694701
additionalProperties:
695702
additionalProperties:
696703
properties:
704+
containerSHA:
705+
description: ContainerSHA is the SHA256 digest that was actually
706+
deployed
707+
type: string
697708
image:
698709
description: Image for the package
699710
type: string

k8s-tests/chainsaw/skyhook/simple-skyhook/assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ metadata:
3434
"name": "foobar",
3535
"version": "1.2",
3636
"image": "ghcr.io/nvidia/skyhook/agentless",
37+
"containerSHA": "sha256:a9cc86f68d73e874bf2dfbabab5e8a8f710ad78411a03764d1b42aa1b5cda7dd",
3738
"stage": "config",
3839
"state": "complete"
3940
},
@@ -74,6 +75,7 @@ status:
7475
state: complete
7576
version: '1.2'
7677
image: ghcr.io/nvidia/skyhook/agentless
78+
containerSHA: sha256:a9cc86f68d73e874bf2dfbabab5e8a8f710ad78411a03764d1b42aa1b5cda7dd
7779
stage: config
7880
spencer|3.2.3:
7981
name: spencer

k8s-tests/chainsaw/skyhook/simple-skyhook/skyhook.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ spec:
4242
foobar:
4343
version: "1.2"
4444
image: ghcr.io/nvidia/skyhook/agentless
45+
containerSHA: "sha256:a9cc86f68d73e874bf2dfbabab5e8a8f710ad78411a03764d1b42aa1b5cda7dd"
4546
dependsOn:
4647
dexter: "1.2.3"
4748
env:

operator/api/v1alpha1/skyhook_types.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ type Package struct {
196196
//+kubebuilder:validation:Required
197197
Image string `json:"image"`
198198

199+
// ContainerSHA is the SHA256 digest of the container image for verification purposes.
200+
// When specified, this will be used instead of the version tag to pull the exact image.
201+
// Format: sha256:1234567890abcdef...
202+
//+kubebuilder:example="sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
203+
//+optional
204+
ContainerSHA string `json:"containerSHA,omitempty"`
205+
199206
// Agent Image Override is the container image to override at the package level. Full qualified image with tag.
200207
// This overrides the image provided via ENV to the operator.
201208
//+kubebuilder:example="alpine:3.21.0"
@@ -365,19 +372,20 @@ type SkyhookStatus struct {
365372
type NodeState map[string]PackageStatus
366373

367374
// Upsert adds or updates specified state for package in the node state
368-
func (ns *NodeState) Upsert(_package PackageRef, image string, state State, stage Stage, restarts int32) bool {
375+
func (ns *NodeState) Upsert(_package PackageRef, image string, state State, stage Stage, restarts int32, containerSHA string) bool {
369376

370377
if *ns == nil {
371378
*ns = make(map[string]PackageStatus)
372379
}
373380

374381
status := PackageStatus{
375-
Name: _package.Name,
376-
Version: _package.Version,
377-
State: state,
378-
Image: image,
379-
Stage: stage,
380-
Restarts: restarts,
382+
Name: _package.Name,
383+
Version: _package.Version,
384+
State: state,
385+
Image: image,
386+
Stage: stage,
387+
Restarts: restarts,
388+
ContainerSHA: containerSHA,
381389
}
382390

383391
existing, ok := (*ns)[_package.GetUniqueName()]
@@ -567,6 +575,10 @@ type PackageStatus struct {
567575
//+kubebuilder:validation:Required
568576
Image string `json:"image"`
569577

578+
// ContainerSHA is the SHA256 digest that was actually deployed
579+
//+optional
580+
ContainerSHA string `json:"containerSHA,omitempty"`
581+
570582
// Stage is where in the package install process is currently for a node.
571583
// these stages encapsulate checks. Both Apply and PostInterrupt also run checks,
572584
// these are all or nothing, meaning both need to be successful in order to transition
@@ -583,10 +595,11 @@ type PackageStatus struct {
583595
Restarts int32 `json:"restarts,omitempty"`
584596
}
585597

586-
// Equal checks name, version, state, state (not restarts)
598+
// Equal checks name, version, image, containerSHA, stage, state, and restarts
587599
func (left *PackageStatus) Equal(right *PackageStatus) bool {
588600
return left.Name == right.Name &&
589601
left.Version == right.Version &&
602+
left.ContainerSHA == right.ContainerSHA &&
590603
left.Stage == right.Stage &&
591604
left.State == right.State &&
592605
left.Restarts == right.Restarts

operator/api/v1alpha1/skyhook_types_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,19 +298,19 @@ var _ = Describe("Skyhook Types", func() {
298298
Expect(nodeState.Upsert(PackageRef{
299299
Name: "foo",
300300
Version: "1.2.3",
301-
}, "", StateComplete, stage, 2)).To(BeTrue())
301+
}, "", StateComplete, stage, 2, "")).To(BeTrue())
302302
Expect(nodeState.Upsert(PackageRef{
303303
Name: "bar",
304304
Version: "2.3",
305-
}, "", StateComplete, stage, 2)).To(BeTrue())
305+
}, "", StateComplete, stage, 2, "")).To(BeTrue())
306306
Expect(nodeState.Upsert(PackageRef{ // replace
307307
Name: "bar",
308308
Version: "2",
309-
}, "", StateComplete, stage, 2)).To(BeTrue())
309+
}, "", StateComplete, stage, 2, "")).To(BeTrue())
310310
Expect(nodeState.Upsert(PackageRef{ // exists
311311
Name: "bar",
312312
Version: "2",
313-
}, "", StateComplete, stage, 2)).To(BeFalse())
313+
}, "", StateComplete, stage, 2, "")).To(BeFalse())
314314

315315
interrupts := map[string][]*Interrupt{}
316316
configUpdates := map[string][]string{}

operator/config/crd/bases/skyhook.nvidia.com_skyhooks.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@ spec:
225225
The keys stored in Data must not overlap with the keys in
226226
the BinaryData field, this is enforced during validation process.
227227
type: object
228+
containerSHA:
229+
description: |-
230+
ContainerSHA is the SHA256 digest of the container image for verification purposes.
231+
When specified, this will be used instead of the version tag to pull the exact image.
232+
Format: sha256:1234567890abcdef...
233+
example: sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
234+
type: string
228235
dependsOn:
229236
additionalProperties:
230237
type: string
@@ -695,6 +702,10 @@ spec:
695702
additionalProperties:
696703
additionalProperties:
697704
properties:
705+
containerSHA:
706+
description: ContainerSHA is the SHA256 digest that was actually
707+
deployed
708+
type: string
698709
image:
699710
description: Image for the package
700711
type: string

operator/internal/controller/annotations.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type PackageSkyhook struct {
3131
Skyhook string `json:"skyhook"`
3232
Stage v1alpha1.Stage `json:"stage"`
3333
Image string `json:"image"`
34+
ContainerSHA string `json:"containerSHA,omitempty"`
3435
Invalid bool `json:"invalid,omitempty"`
3536
}
3637

@@ -59,10 +60,11 @@ func SetPackages(pod *corev1.Pod, skyhook *v1alpha1.Skyhook, image string, stage
5960
}
6061

6162
strk := &PackageSkyhook{
62-
Skyhook: skyhook.Name,
63-
Stage: stage,
64-
PackageRef: _package.PackageRef,
65-
Image: image,
63+
Skyhook: skyhook.Name,
64+
Stage: stage,
65+
PackageRef: _package.PackageRef,
66+
Image: image,
67+
ContainerSHA: _package.ContainerSHA,
6668
}
6769

6870
data, err := json.Marshal(strk)

operator/internal/controller/pod_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (r *SkyhookReconciler) UpdateNodeState(ctx context.Context, pod *corev1.Pod
164164
}
165165

166166
if !updated {
167-
err = skyhookNode.Upsert(packagePtr.PackageRef, packagePtr.Image, state, packagePtr.Stage, restarts)
167+
err = skyhookNode.Upsert(packagePtr.PackageRef, packagePtr.Image, state, packagePtr.Stage, restarts, packagePtr.ContainerSHA)
168168
if err != nil {
169169
return false, err
170170
}
@@ -240,7 +240,7 @@ func (r *SkyhookReconciler) HandleCompletePod(ctx context.Context, skyhookNode w
240240
if exists {
241241
// If the uninstall was caused by a version changed progress forward the new version that was waiting
242242
// on the uninstall to finish
243-
err = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateComplete, v1alpha1.StageUninstall, 0)
243+
err = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateComplete, v1alpha1.StageUninstall, 0, _package.ContainerSHA)
244244
if err != nil {
245245
return false, fmt.Errorf("error updating node status: %w", err)
246246
}

operator/internal/controller/skyhook_controller.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ func HandleVersionChange(skyhook SkyhookNodes) ([]*v1alpha1.Package, error) {
707707

708708
if !exists && packageStatus.Stage != v1alpha1.StageUninstall {
709709
// Start uninstall of old package
710-
err := node.Upsert(packageStatusRef, packageStatus.Image, v1alpha1.StateInProgress, v1alpha1.StageUninstall, 0)
710+
err := node.Upsert(packageStatusRef, packageStatus.Image, v1alpha1.StateInProgress, v1alpha1.StageUninstall, 0, "")
711711
if err != nil {
712712
return nil, fmt.Errorf("error updating node status: %w", err)
713713
}
@@ -724,21 +724,21 @@ func HandleVersionChange(skyhook SkyhookNodes) ([]*v1alpha1.Package, error) {
724724
}
725725

726726
// start upgrade of package
727-
err := node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageUpgrade, 0)
727+
err := node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageUpgrade, 0, _package.ContainerSHA)
728728
if err != nil {
729729
return nil, fmt.Errorf("error updating node status: %w", err)
730730
}
731731

732732
upgrade = true
733733
} else if comparison == -1 && packageStatus.Stage != v1alpha1.StageUninstall {
734734
// Start uninstall of old package
735-
err := node.Upsert(packageStatusRef, packageStatus.Image, v1alpha1.StateInProgress, v1alpha1.StageUninstall, 0)
735+
err := node.Upsert(packageStatusRef, packageStatus.Image, v1alpha1.StateInProgress, v1alpha1.StageUninstall, 0, "")
736736
if err != nil {
737737
return nil, fmt.Errorf("error updating node status: %w", err)
738738
}
739739

740740
// If version changed then update new version to wait
741-
err = node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateSkipped, v1alpha1.StageUninstall, 0)
741+
err = node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateSkipped, v1alpha1.StageUninstall, 0, _package.ContainerSHA)
742742
if err != nil {
743743
return nil, fmt.Errorf("error updating node status: %w", err)
744744
}
@@ -949,7 +949,7 @@ func (r *SkyhookReconciler) HandleConfigUpdates(ctx context.Context, clusterStat
949949
skyhook.GetSkyhook().AddConfigUpdates(_package.Name, newConfigUpdates...)
950950

951951
for _, node := range skyhook.GetNodes() {
952-
err := node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageConfig, 0)
952+
err := node.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageConfig, 0, _package.ContainerSHA)
953953
if err != nil {
954954
return false, fmt.Errorf("error upserting node status [%s]: %w", node.GetNode().Name, err)
955955
}
@@ -1291,7 +1291,7 @@ func (r *SkyhookReconciler) Interrupt(ctx context.Context, skyhookNode wrapper.S
12911291
return fmt.Errorf("error creating interruption pod: %w", err)
12921292
}
12931293

1294-
_ = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageInterrupt, 0)
1294+
_ = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, v1alpha1.StageInterrupt, 0, _package.ContainerSHA)
12951295

12961296
r.recorder.Eventf(skyhookNode.GetSkyhook().Skyhook, EventTypeNormal, EventsReasonSkyhookInterrupt,
12971297
"Interrupting node [%s] package [%s:%s] from [skyhook:%s]",
@@ -1603,6 +1603,16 @@ func getAgentImage(opts SkyhookOperatorOptions, _package *v1alpha1.Package) stri
16031603
return opts.AgentImage
16041604
}
16051605

1606+
// getPackageImage returns the full image reference for a package, using the digest if specified
1607+
func getPackageImage(_package *v1alpha1.Package) string {
1608+
if _package.ContainerSHA != "" {
1609+
// When containerSHA is specified, use it instead of the version tag for immutable image reference
1610+
return fmt.Sprintf("%s@%s", _package.Image, _package.ContainerSHA)
1611+
}
1612+
// Fall back to version tag
1613+
return fmt.Sprintf("%s:%s", _package.Image, _package.Version)
1614+
}
1615+
16061616
func getAgentConfigEnvVars(opts SkyhookOperatorOptions, packageName string, packageVersion string, resourceID string, skyhookName string) []corev1.EnvVar {
16071617
return []corev1.EnvVar{
16081618
{
@@ -1712,7 +1722,7 @@ func createPodFromPackage(opts SkyhookOperatorOptions, _package *v1alpha1.Packag
17121722
InitContainers: []corev1.Container{
17131723
{
17141724
Name: fmt.Sprintf("%s-init", trunstr(_package.Name, 43)),
1715-
Image: fmt.Sprintf("%s:%s", _package.Image, _package.Version),
1725+
Image: getPackageImage(_package),
17161726
ImagePullPolicy: "Always",
17171727
Command: []string{"/bin/sh"},
17181728
Args: []string{
@@ -2070,7 +2080,7 @@ func (r *SkyhookReconciler) ProcessInterrupt(ctx context.Context, skyhookNode wr
20702080

20712081
//skipping
20722082
if stage == v1alpha1.StageInterrupt && !runInterrupt {
2073-
err := skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateSkipped, stage, 0)
2083+
err := skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateSkipped, stage, 0, _package.ContainerSHA)
20742084
if err != nil {
20752085
return false, fmt.Errorf("error upserting to skip interrupt: %w", err)
20762086
}
@@ -2185,7 +2195,7 @@ func (r *SkyhookReconciler) ApplyPackage(ctx context.Context, logger logr.Logger
21852195
return fmt.Errorf("error creating pod: %w", err)
21862196
}
21872197

2188-
if err = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, stage, 0); err != nil {
2198+
if err = skyhookNode.Upsert(_package.PackageRef, _package.Image, v1alpha1.StateInProgress, stage, 0, _package.ContainerSHA); err != nil {
21892199
err = fmt.Errorf("error upserting package: %w", err) // want to keep going in this case, but don't want to lose the err
21902200
}
21912201

operator/internal/wrapper/node.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type SkyhookNodeOnly interface {
6262
State() (v1alpha1.NodeState, error)
6363
SetState(state v1alpha1.NodeState) error
6464
RemoveState(_package v1alpha1.PackageRef) error
65-
Upsert(_package v1alpha1.PackageRef, image string, state v1alpha1.State, stage v1alpha1.Stage, restarts int32) error
65+
Upsert(_package v1alpha1.PackageRef, image string, state v1alpha1.State, stage v1alpha1.Stage, restarts int32, containerSHA string) error
6666
GetNode() *corev1.Node
6767
Taint(key string)
6868
RemoveTaint(key string)
@@ -283,8 +283,8 @@ func (node *skyhookNode) RemoveState(_package v1alpha1.PackageRef) error {
283283
return nil
284284
}
285285

286-
func (node *skyhookNode) Upsert(_package v1alpha1.PackageRef, image string, state v1alpha1.State, stage v1alpha1.Stage, restarts int32) error {
287-
changed := node.nodeState.Upsert(_package, image, state, stage, restarts)
286+
func (node *skyhookNode) Upsert(_package v1alpha1.PackageRef, image string, state v1alpha1.State, stage v1alpha1.Stage, restarts int32, containerSHA string) error {
287+
changed := node.nodeState.Upsert(_package, image, state, stage, restarts, containerSHA)
288288
if changed {
289289
if node.skyhook != nil {
290290
node.skyhook.Updated = true

0 commit comments

Comments
 (0)