Skip to content

Commit ae4f613

Browse files
committed
ci: add lint ci for helm chart.
Signed-off-by: Lan Liang <[email protected]>
1 parent a39f99b commit ae4f613

15 files changed

+130
-16
lines changed

.github/workflows/lint-chart.yaml

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# validate any chart changes under charts directory
2+
name: Chart Lint
3+
4+
env:
5+
HELM_VERSION: v3.11.2
6+
KIND_VERSION: v0.23.0
7+
KIND_NODE_IMAGE: kindest/node:v1.30.0
8+
K8S_VERSION: v1.30.0
9+
10+
on:
11+
push:
12+
# Exclude branches created by Dependabot to avoid triggering current workflow
13+
# for PRs initiated by Dependabot.
14+
branches-ignore:
15+
- 'dependabot/**'
16+
paths:
17+
- "kwok/charts/**"
18+
pull_request:
19+
paths:
20+
- "charts/**"
21+
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
chart-lint-test:
27+
runs-on: ubuntu-22.04
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Set up Helm
35+
uses: azure/setup-helm@v4
36+
with:
37+
version: ${{ env.HELM_VERSION }}
38+
39+
- name: Run chart-testing (template)
40+
run: |
41+
helm template --dependency-update ./kwok/charts --debug > /dev/null
42+
43+
# Python is required because `ct lint` runs Yamale (https://github.com/23andMe/Yamale) and
44+
# yamllint (https://github.com/adrienverge/yamllint) which require Python
45+
- name: Set up Python
46+
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1
47+
with:
48+
python-version: 3.10
49+
check-latest: true
50+
51+
- name: Set up chart-testing
52+
uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992
53+
54+
- name: Add dependency chart repos
55+
run: |
56+
helm repo add bitnami https://charts.bitnami.com/bitnami
57+
58+
- name: Run chart-testing (list-changed)
59+
id: list-changed
60+
run: |
61+
changed=$( ct list-changed )
62+
if [[ -n "$changed" ]]; then
63+
echo "changed=true" >> $GITHUB_OUTPUT
64+
fi
65+
66+
- name: Run chart-testing (lint)
67+
if: steps.list-changed.outputs.changed == 'true'
68+
run: ct lint --debug --check-version-increment=false
69+
70+
- name: Create kind cluster
71+
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
72+
if: steps.list-changed.outputs.changed == 'true'
73+
with:
74+
wait: 120s
75+
version: ${{ env.KIND_VERSION }}
76+
node_image: ${{ env.KIND_NODE_IMAGE }}
77+
kubectl_version: ${{ env.K8S_VERSION }}
78+
79+
- name: Run chart-testing (install)
80+
if: steps.list-changed.outputs.changed == 'true'
81+
run: ct install --debug --helm-extra-args "--timeout 800s"

kwok/charts/crds/karpenter.sh_nodeclaims.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ spec:
8686
properties:
8787
group:
8888
description: API version of the referent
89+
pattern: ^[^/]*$
8990
type: string
9091
kind:
9192
description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"'

kwok/charts/crds/karpenter.sh_nodepools.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ spec:
225225
properties:
226226
group:
227227
description: API version of the referent
228+
pattern: ^[^/]*$
228229
type: string
229230
kind:
230231
description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"'

kwok/charts/templates/role.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,4 @@ rules:
7575
# Write
7676
- apiGroups: ["coordination.k8s.io"]
7777
resources: ["leases"]
78-
verbs: ["delete"]
78+
verbs: ["delete"]

kwok/charts/templates/secret-webhook-cert.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ metadata:
1111
{{- toYaml . | nindent 4 }}
1212
{{- end }}
1313
# data: {} # Injected by karpenter-webhook
14-
{{- end }}
14+
{{- end }}

pkg/apis/crds/karpenter.sh_nodeclaims.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ spec:
8686
properties:
8787
group:
8888
description: API version of the referent
89+
pattern: ^[^/]*$
8990
type: string
9091
kind:
9192
description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"'

pkg/apis/crds/karpenter.sh_nodepools.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ spec:
225225
properties:
226226
group:
227227
description: API version of the referent
228+
pattern: ^[^/]*$
228229
type: string
229230
kind:
230231
description: 'Kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"'

pkg/apis/v1/nodeclaim.go

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type NodeClassReference struct {
103103
// +required
104104
Name string `json:"name"`
105105
// API version of the referent
106+
// +kubebuilder:validation:Pattern=`^[^/]*$`
106107
// +required
107108
Group string `json:"group"`
108109
}

pkg/apis/v1/nodeclaim_validation_cel_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import (
2121
"strings"
2222
"time"
2323

24+
"github.com/awslabs/operatorpkg/object"
25+
26+
"sigs.k8s.io/karpenter/pkg/test/v1alpha1"
27+
2428
"sigs.k8s.io/karpenter/pkg/test"
2529

2630
"github.com/Pallinder/go-randomdata"
@@ -95,6 +99,24 @@ var _ = Describe("Validation", func() {
9599
Expect(env.Client.Create(ctx, nodeClaim)).To(Succeed())
96100
})
97101
})
102+
Context("NodeClassRef", func() {
103+
It("should succeed for valid group", func() {
104+
nodeClaim.Spec.NodeClassRef = &NodeClassReference{
105+
Kind: object.GVK(&v1alpha1.TestNodeClass{}).Kind,
106+
Name: "nodeclass-test",
107+
Group: object.GVK(&v1alpha1.TestNodeClass{}).Group,
108+
}
109+
Expect(env.Client.Create(ctx, nodeClaim)).To(Succeed())
110+
})
111+
It("should fail for invalid group", func() {
112+
nodeClaim.Spec.NodeClassRef = &NodeClassReference{
113+
Kind: object.GVK(&v1alpha1.TestNodeClass{}).Kind,
114+
Name: "nodeclass-test",
115+
Group: "karpenter.test.sh/v1",
116+
}
117+
Expect(env.Client.Create(ctx, nodeClaim)).ToNot(Succeed())
118+
})
119+
})
98120
Context("Requirements", func() {
99121
It("should allow supported ops", func() {
100122
nodeClaim.Spec.Requirements = []NodeSelectorRequirementWithMinValues{

pkg/controllers/nodeclaim/disruption/drift_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ var _ = Describe("Drift", func() {
368368
NodeClassRef: &v1.NodeClassReference{
369369
Kind: "fakeKind",
370370
Name: "fakeName",
371-
Group: "fakeGroup/fakeVerion",
371+
Group: "fakeGroup",
372372
},
373373
Taints: []corev1.Taint{
374374
{

pkg/controllers/nodeclaim/lifecycle/registration.go

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func (r *Registration) Reconcile(ctx context.Context, nodeClaim *v1.NodeClaim) (
6464
// check if sync succeeded but setting the registered status condition failed
6565
// if sync succeeded, then the label will be present and the taint will be gone
6666
if _, ok := node.Labels[v1.NodeRegisteredLabelKey]; !ok && !hasStartupTaint {
67+
nodeClaim.StatusConditions().SetFalse(v1.ConditionTypeRegistered, "UnregisteredTaintNotFound", fmt.Sprintf("Invariant violated, %s taint must be present on Karpenter-managed nodes", v1.UnregisteredTaintKey))
6768
return reconcile.Result{}, fmt.Errorf("missing required startup taint, %s", v1.UnregisteredTaintKey)
6869
}
6970
ctx = log.IntoContext(ctx, log.FromContext(ctx).WithValues("Node", klog.KRef("", node.Name)))

pkg/controllers/nodeclaim/lifecycle/registration_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ var _ = Describe("Registration", func() {
107107
node := test.Node(test.NodeOptions{ProviderID: nodeClaim.Status.ProviderID})
108108
ExpectApplied(ctx, env.Client, node)
109109
_ = ExpectObjectReconcileFailed(ctx, env.Client, nodeClaimController, nodeClaim)
110+
nodeClaim = ExpectExists(ctx, env.Client, nodeClaim)
111+
Expect(ExpectStatusConditionExists(nodeClaim, v1.ConditionTypeRegistered).Status).To(Equal(metav1.ConditionFalse))
110112
})
111113
It("should sync the labels to the Node when the Node comes online", func() {
112114
nodeClaim := test.NodeClaim(v1.NodeClaim{

pkg/controllers/provisioning/suite_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ var _ = Describe("Provisioning", func() {
12601260
Template: v1.NodeClaimTemplate{
12611261
Spec: v1.NodeClaimTemplateSpec{
12621262
NodeClassRef: &v1.NodeClassReference{
1263-
Group: "cloudprovider.karpenter.sh/v1",
1263+
Group: "cloudprovider.karpenter.sh",
12641264
Kind: "CloudProvider",
12651265
Name: "default",
12661266
},
@@ -1275,7 +1275,7 @@ var _ = Describe("Provisioning", func() {
12751275
Expect(cloudProvider.CreateCalls).To(HaveLen(1))
12761276
Expect(cloudProvider.CreateCalls[0].Spec.NodeClassRef).To(Equal(
12771277
&v1.NodeClassReference{
1278-
Group: "cloudprovider.karpenter.sh/v1",
1278+
Group: "cloudprovider.karpenter.sh",
12791279
Kind: "CloudProvider",
12801280
Name: "default",
12811281
},

pkg/scheduling/taints.go

+13-10
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,27 @@ import (
2121

2222
"github.com/samber/lo"
2323
"go.uber.org/multierr"
24-
v1 "k8s.io/api/core/v1"
24+
corev1 "k8s.io/api/core/v1"
2525
cloudproviderapi "k8s.io/cloud-provider/api"
26+
27+
v1 "sigs.k8s.io/karpenter/pkg/apis/v1"
2628
)
2729

2830
// KnownEphemeralTaints are taints that are expected to be added to a node while it's initializing
2931
// If the node is a Karpenter-managed node, we don't consider these taints while the node is uninitialized
3032
// since we expect these taints to eventually be removed
31-
var KnownEphemeralTaints = []v1.Taint{
32-
{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoSchedule},
33-
{Key: v1.TaintNodeUnreachable, Effect: v1.TaintEffectNoSchedule},
34-
{Key: cloudproviderapi.TaintExternalCloudProvider, Effect: v1.TaintEffectNoSchedule, Value: "true"},
33+
var KnownEphemeralTaints = []corev1.Taint{
34+
{Key: corev1.TaintNodeNotReady, Effect: corev1.TaintEffectNoSchedule},
35+
{Key: corev1.TaintNodeUnreachable, Effect: corev1.TaintEffectNoSchedule},
36+
{Key: cloudproviderapi.TaintExternalCloudProvider, Effect: corev1.TaintEffectNoSchedule, Value: "true"},
37+
v1.UnregisteredNoExecuteTaint,
3538
}
3639

37-
// Taints is a decorated alias type for []v1.Taint
38-
type Taints []v1.Taint
40+
// Taints is a decorated alias type for []corev1.Taint
41+
type Taints []corev1.Taint
3942

4043
// Tolerates returns true if the pod tolerates all taints.
41-
func (ts Taints) Tolerates(pod *v1.Pod) (errs error) {
44+
func (ts Taints) Tolerates(pod *corev1.Pod) (errs error) {
4245
for i := range ts {
4346
taint := ts[i]
4447
tolerates := false
@@ -54,11 +57,11 @@ func (ts Taints) Tolerates(pod *v1.Pod) (errs error) {
5457

5558
// Merge merges in taints with the passed in taints.
5659
func (ts Taints) Merge(with Taints) Taints {
57-
res := lo.Map(ts, func(t v1.Taint, _ int) v1.Taint {
60+
res := lo.Map(ts, func(t corev1.Taint, _ int) corev1.Taint {
5861
return t
5962
})
6063
for _, taint := range with {
61-
if _, ok := lo.Find(res, func(t v1.Taint) bool {
64+
if _, ok := lo.Find(res, func(t corev1.Taint) bool {
6265
return taint.MatchTaint(&t)
6366
}); !ok {
6467
res = append(res, taint)

pkg/utils/node/suite_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ var _ = Describe("NodeUtils", func() {
6969
Spec: v1.NodeClaimSpec{
7070
NodeClassRef: &v1.NodeClassReference{
7171
Kind: "NodeClassRef",
72-
Group: "test.cloudprovider/v1",
72+
Group: "test.cloudprovider",
7373
Name: "default",
7474
},
7575
},

0 commit comments

Comments
 (0)