Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions k8s-tests/chainsaw/skyhook/config-skyhook/assert-update-glob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

---
apiVersion: v1
kind: Pod
metadata:
namespace: skyhook
labels:
skyhook.nvidia.com/name: config-skyhook
skyhook.nvidia.com/package: dexter-1.2.3
annotations:
("skyhook.nvidia.com/package" && parse_json("skyhook.nvidia.com/package")):
{
"name": "dexter",
"version": "1.2.3",
"skyhook": "config-skyhook",
"stage": "config"
}
spec:
initContainers:
- name: dexter-init
- name: dexter-config
args:
([0]): config
([1]): /root
(length(@)): 3
- name: dexter-configcheck
args:
([0]): config-check
([1]): /root
(length(@)): 3
---
apiVersion: v1
kind: Node
metadata:
labels:
skyhook.nvidia.com/test-node: skyhooke2e
skyhook.nvidia.com/status_config-skyhook: complete
annotations:
("skyhook.nvidia.com/nodeState_config-skyhook" && parse_json("skyhook.nvidia.com/nodeState_config-skyhook")):
{
"dexter|1.2.3": {
"name": "dexter",
"version": "1.2.3",
"image": "ghcr.io/nvidia/skyhook/agentless",
"stage": "post-interrupt",
"state": "complete"
}
}
skyhook.nvidia.com/status_config-skyhook: complete
status:
(conditions[?type == 'skyhook.nvidia.com/config-skyhook/NotReady']):
- reason: "Complete"
status: "False"
(conditions[?type == 'skyhook.nvidia.com/config-skyhook/Erroring']):
- reason: "Not Erroring"
status: "False"
---
apiVersion: skyhook.nvidia.com/v1alpha1
kind: Skyhook
metadata:
name: config-skyhook
status:
status: complete
nodeState:
(values(@)):
- dexter|1.2.3:
name: dexter
state: complete
version: '1.2.3'
stage: post-interrupt
image: ghcr.io/nvidia/skyhook/agentless
nodeStatus:
(values(@)):
- complete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: config-skyhook-dexter-1.2.3
namespace: skyhook
labels:
skyhook.nvidia.com/name: config-skyhook
ownerReferences:
- apiVersion: skyhook.nvidia.com/v1alpha1
blockOwnerDeletion: true
controller: true
kind: Skyhook
name: config-skyhook
data:
game.properties: |
changed via glob
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ spec:
file: update-no-interrupt.yaml
- assert:
file: assert-update-no-interrupt.yaml
- try:
- apply:
file: update-glob.yaml
- assert:
file: assert-update-glob.yaml
32 changes: 32 additions & 0 deletions k8s-tests/chainsaw/skyhook/config-skyhook/update-glob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: skyhook.nvidia.com/v1alpha1
kind: Skyhook
metadata:
name: config-skyhook
spec:
packages:
dexter:
# Add a globbed configInterrupt that matches at least one key
configInterrupts:
"*.properties":
type: service
services: [rsyslog]
# Change a key that should match the glob
configMap:
game.properties: |
changed via glob
27 changes: 24 additions & 3 deletions operator/api/v1alpha1/skyhook_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package v1alpha1
import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"

Expand Down Expand Up @@ -171,10 +172,30 @@ func (r *Skyhook) Validate() error {
}

// test to make sure that the config interrupts are for valid packages
for configMap := range v.ConfigInterrupts {
if _, exists := v.ConfigMap[configMap]; !exists {
return fmt.Errorf("error config interrupt for key that doesn't exist: %s doesn't exist as a configmap", configMap)
for pattern := range v.ConfigInterrupts {
// exact key present
if _, exists := v.ConfigMap[pattern]; exists {
continue
}

// Only '*' is supported as a glob meta character
isGlob := strings.Contains(pattern, "*")
if isGlob {
matchedAny := false
for key := range v.ConfigMap {
if ok, err := filepath.Match(pattern, key); err == nil && ok {
matchedAny = true
break
}
}
if matchedAny {
continue
}
return fmt.Errorf("error config interrupt glob %q does not match any configMap keys", pattern)
}

// not a glob and not an exact key
return fmt.Errorf("error config interrupt for key that doesn't exist: %s doesn't exist as a configmap", pattern)
}

image, version, found := strings.Cut(v.Image, ":")
Expand Down
45 changes: 45 additions & 0 deletions operator/api/v1alpha1/skyhook_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,51 @@ var _ = Describe("Skyhook Webhook", func() {
Expect(err).To(BeNil())
})

It("should allow glob patterns in configInterrupts that match at least one key", func() {
skyhook := &Skyhook{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: SkyhookSpec{
Packages: Packages{
"foo": {
PackageRef: PackageRef{Name: "foo", Version: "1.0.0"},
ConfigMap: map[string]string{
"config.sh": "abc",
"upgrade.sh": "def",
},
ConfigInterrupts: map[string]Interrupt{
"*.sh": {Type: SERVICE, Services: []string{"kubelet"}},
},
},
},
},
}

_, err := skyhookWebhook.ValidateCreate(ctx, skyhook)
Expect(err).To(BeNil())
})

It("should reject glob patterns in configInterrupts that match no keys", func() {
skyhook := &Skyhook{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: SkyhookSpec{
Packages: Packages{
"foo": {
PackageRef: PackageRef{Name: "foo", Version: "1.0.0"},
ConfigMap: map[string]string{
"config.txt": "abc",
},
ConfigInterrupts: map[string]Interrupt{
"*.sh": {Type: SERVICE, Services: []string{"kubelet"}},
},
},
},
},
}

_, err := skyhookWebhook.ValidateCreate(ctx, skyhook)
Expect(err).ToNot(BeNil())
})

It("Should deny if ambiguous version match", func() {
skyhook := &Skyhook{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Expand Down
24 changes: 19 additions & 5 deletions operator/internal/wrapper/skyhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package wrapper

import (
"fmt"
"path/filepath"
"strings"

"github.com/NVIDIA/skyhook/operator/api/v1alpha1"
"github.com/NVIDIA/skyhook/operator/internal/version"
Expand Down Expand Up @@ -140,13 +142,25 @@ func (s *Skyhook) GetConfigInterrupts() map[string][]*v1alpha1.Interrupt {
for _pkg := range s.Spec.Packages {
_package := s.Spec.Packages[_pkg]

// Track duplicates to avoid adding the same interrupt multiple times per package
seen := make(map[string]struct{})

for _, update := range s.Status.ConfigUpdates[_package.Name] {
if interrupt, exists := _package.ConfigInterrupts[update]; exists {
if interrupts[_package.Name] == nil {
interrupts[_package.Name] = make([]*v1alpha1.Interrupt, 0)
for pattern, interrupt := range _package.ConfigInterrupts {
// filepath.Match treats a non-glob pattern as a literal
if ok, err := filepath.Match(pattern, update); err == nil && ok {
if interrupts[_package.Name] == nil {
interrupts[_package.Name] = make([]*v1alpha1.Interrupt, 0)
}

key := fmt.Sprintf("%s|%s", interrupt.Type, strings.Join(interrupt.Services, ","))
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}

interrupts[_package.Name] = append(interrupts[_package.Name], &interrupt)
}

interrupts[_package.Name] = append(interrupts[_package.Name], &interrupt)
}
}
}
Expand Down