Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit b4fbad6

Browse files
committed
Merge branch 'main' into add-hooks
2 parents 0c5d9b2 + 64b465d commit b4fbad6

File tree

19 files changed

+1009
-70
lines changed

19 files changed

+1009
-70
lines changed

.github/workflows/test.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,16 @@ jobs:
1515
with:
1616
fetch-depth: 0
1717

18-
- name: Run make deploy
18+
- name: Run controller tests
1919
run: make test
20+
21+
- name: Cache operator bin directory
22+
uses: actions/cache@v4
23+
with:
24+
path: deploy/operator/bin/
25+
key: ${{ runner.os }}-operator-bin-${{ hashFiles('deploy/operator/go.mod') }}
26+
restore-keys: |
27+
${{ runner.os }}-operator-bin-
28+
29+
- name: Run operator tests
30+
run: make -C deploy/operator test

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# jumpstarter-router
1+
# jumpstarter-controller
2+
3+
[![Build and push container image](https://github.com/jumpstarter-dev/jumpstarter-controller/actions/workflows/build.yaml/badge.svg)](https://github.com/jumpstarter-dev/jumpstarter-controller/actions/workflows/build.yaml)
4+
![GitHub Release](https://img.shields.io/github/v/release/jumpstarter-dev/jumpstarter-controller)
5+
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/jumpstarter-dev/jumpstarter-controller/total)
6+
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/jumpstarter-dev/jumpstarter-controller)
7+
28
// TODO(user): Add simple overview of use/purpose
39

410
## Description

api/v1alpha1/lease_helpers.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"k8s.io/apimachinery/pkg/api/meta"
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/labels"
17+
"k8s.io/apimachinery/pkg/selection"
1718
"k8s.io/apimachinery/pkg/types"
1819
"k8s.io/utils/ptr"
1920
kclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -73,12 +74,107 @@ func ReconcileLeaseTimeFields(beginTime, endTime **metav1.Time, duration **metav
7374
return nil
7475
}
7576

77+
// ParseLabelSelector parses a label selector string and converts it to metav1.LabelSelector.
78+
// This function supports the != operator by first parsing with labels.Parse() which supports
79+
// all label selector syntax including !=, then converting to metav1.LabelSelector format.
80+
func ParseLabelSelector(selectorStr string) (*metav1.LabelSelector, error) {
81+
// First, try to parse using labels.Parse() which supports != operator
82+
parsedSelector, err := labels.Parse(selectorStr)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to parse label selector: %w", err)
85+
}
86+
87+
// Extract requirements from the parsed selector
88+
requirements, selectable := parsedSelector.Requirements()
89+
if !selectable {
90+
return &metav1.LabelSelector{}, nil
91+
}
92+
93+
// Convert requirements to metav1.LabelSelector format
94+
matchLabels := make(map[string]string)
95+
var matchExpressions []metav1.LabelSelectorRequirement
96+
97+
// Track NotEquals requirements by key so we can combine them into NotIn
98+
notEqualsByKey := make(map[string][]string)
99+
100+
for _, req := range requirements {
101+
key := req.Key()
102+
operator := req.Operator()
103+
values := req.ValuesUnsorted()
104+
105+
switch operator {
106+
case selection.Equals:
107+
// For exact match with single value, use matchLabels
108+
if len(values) == 1 {
109+
// Check if we already have an equality requirement for this key with a different value
110+
if existingValue, exists := matchLabels[key]; exists && existingValue != values[0] {
111+
return nil, fmt.Errorf("invalid selector: label %s cannot have multiple equality requirements with different values (%s and %s)", key, existingValue, values[0])
112+
}
113+
matchLabels[key] = values[0]
114+
} else {
115+
// Multiple values should use In operator
116+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
117+
Key: key,
118+
Operator: metav1.LabelSelectorOpIn,
119+
Values: values,
120+
})
121+
}
122+
case selection.NotEquals:
123+
// Accumulate != requirements for the same key to combine into NotIn
124+
if len(values) != 1 {
125+
return nil, fmt.Errorf("invalid selector: != operator requires exactly one value")
126+
}
127+
notEqualsByKey[key] = append(notEqualsByKey[key], values[0])
128+
case selection.In:
129+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
130+
Key: key,
131+
Operator: metav1.LabelSelectorOpIn,
132+
Values: values,
133+
})
134+
case selection.NotIn:
135+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
136+
Key: key,
137+
Operator: metav1.LabelSelectorOpNotIn,
138+
Values: values,
139+
})
140+
case selection.Exists:
141+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
142+
Key: key,
143+
Operator: metav1.LabelSelectorOpExists,
144+
Values: []string{},
145+
})
146+
case selection.DoesNotExist:
147+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
148+
Key: key,
149+
Operator: metav1.LabelSelectorOpDoesNotExist,
150+
Values: []string{},
151+
})
152+
default:
153+
return nil, fmt.Errorf("unsupported label selector operator: %v", operator)
154+
}
155+
}
156+
157+
// Convert accumulated NotEquals requirements to NotIn expressions
158+
for key, vals := range notEqualsByKey {
159+
matchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{
160+
Key: key,
161+
Operator: metav1.LabelSelectorOpNotIn,
162+
Values: vals,
163+
})
164+
}
165+
166+
return &metav1.LabelSelector{
167+
MatchLabels: matchLabels,
168+
MatchExpressions: matchExpressions,
169+
}, nil
170+
}
171+
76172
func LeaseFromProtobuf(
77173
req *cpb.Lease,
78174
key types.NamespacedName,
79175
clientRef corev1.LocalObjectReference,
80176
) (*Lease, error) {
81-
selector, err := metav1.ParseToLabelSelector(req.Selector)
177+
selector, err := ParseLabelSelector(req.Selector)
82178
if err != nil {
83179
return nil, err
84180
}

0 commit comments

Comments
 (0)