Skip to content

Commit 2171f05

Browse files
committed
implement ImageProvider
1 parent 1098b47 commit 2171f05

File tree

7 files changed

+614
-6
lines changed

7 files changed

+614
-6
lines changed

config/components/crds/karpenter.k8s.alicloud_ecsnodeclasses.yaml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,73 @@ spec:
4646
ECSNodeClassSpec is the top level specification for the AliCloud Karpenter Provider.
4747
This will contain configuration necessary to launch instances in AliCloud.
4848
properties:
49+
imageSelectorTerms:
50+
description: ImageSelectorTerms is a list of or image selector terms.
51+
The terms are ORed.
52+
items:
53+
description: |-
54+
ImageSelectorTerm defines selection logic for an image used by Karpenter to launch nodes.
55+
If multiple fields are used for selection, the requirements are ANDed.
56+
properties:
57+
alias:
58+
description: |-
59+
Alias specifies which ACK image to select.
60+
Each alias consists of a family and an image version, specified as "family@version".
61+
Valid families include: aliyun3.
62+
Currently only supports version pinning to the latest image release, with that images version format (ex: "aliyun3@latest").
63+
Setting the version to latest will result in drift when a new Image is released. This is **not** recommended for production environments.
64+
maxLength: 30
65+
type: string
66+
x-kubernetes-validations:
67+
- message: '''alias'' is improperly formatted, must match the
68+
format ''family@version'''
69+
rule: self.matches('^[a-zA-Z0-9]*@.*$')
70+
- message: 'family is not supported, must be one of the following:
71+
''aliyun3'''
72+
rule: self.find('^[^@]+') in ['aliyun3']
73+
id:
74+
description: ID is the image id in ECS
75+
type: string
76+
name:
77+
description: |-
78+
Name is the image name in ECS.
79+
This value is the name field, which is different from the name tag.
80+
type: string
81+
owner:
82+
description: |-
83+
Owner is the image source.
84+
Default is system | self | public. If specified, only one of the following: "self", "system", "share", "public", and "marketplace"
85+
type: string
86+
tags:
87+
additionalProperties:
88+
type: string
89+
description: |-
90+
Tags is a map of key/value tags used to select subsets
91+
Specifying '*' for a value selects all values for a given tag key.
92+
maxProperties: 20
93+
type: object
94+
x-kubernetes-validations:
95+
- message: empty tag keys aren't supported
96+
rule: self.all(k, k != '')
97+
type: object
98+
maxItems: 30
99+
minItems: 1
100+
type: array
101+
x-kubernetes-validations:
102+
- message: expected at least one, got none, ['tags', 'id', 'name',
103+
'alias']
104+
rule: self.all(x, has(x.tags) || has(x.id) || has(x.name) || has(x.alias))
105+
- message: '''id'' is mutually exclusive, cannot be set with a combination
106+
of other fields in imageSelectorTerms'
107+
rule: '!self.exists(x, has(x.id) && (has(x.alias) || has(x.tags)
108+
|| has(x.name) || has(x.owner)))'
109+
- message: '''alias'' is mutually exclusive, cannot be set with a
110+
combination of other fields in imageSelectorTerms'
111+
rule: '!self.exists(x, has(x.alias) && (has(x.id) || has(x.tags)
112+
|| has(x.name) || has(x.owner)))'
113+
- message: '''alias'' is mutually exclusive, cannot be set with a
114+
combination of other imageSelectorTerms'
115+
rule: '!(self.exists(x, has(x.alias)) && self.size() != 1)'
49116
kubeletConfiguration:
50117
description: |-
51118
KubeletConfiguration defines args to be used when configuring kubelet on provisioned nodes.
@@ -245,12 +312,70 @@ spec:
245312
of other fields in vSwitchSelectorTerms'
246313
rule: '!self.all(x, has(x.id) && has(x.tags))'
247314
required:
315+
- imageSelectorTerms
248316
- securityGroupSelectorTerms
249317
- vSwitchSelectorTerms
250318
type: object
251319
status:
252320
description: ECSNodeClassStatus contains the resolved state of the ECSNodeClass
253321
properties:
322+
conditions:
323+
description: Conditions contains signals for health and readiness
324+
items:
325+
description: Condition aliases the upstream type and adds additional
326+
helper methods
327+
properties:
328+
lastTransitionTime:
329+
description: |-
330+
lastTransitionTime is the last time the condition transitioned from one status to another.
331+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
332+
format: date-time
333+
type: string
334+
message:
335+
description: |-
336+
message is a human readable message indicating details about the transition.
337+
This may be an empty string.
338+
maxLength: 32768
339+
type: string
340+
observedGeneration:
341+
description: |-
342+
observedGeneration represents the .metadata.generation that the condition was set based upon.
343+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
344+
with respect to the current state of the instance.
345+
format: int64
346+
minimum: 0
347+
type: integer
348+
reason:
349+
description: |-
350+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
351+
Producers of specific condition types may define expected values and meanings for this field,
352+
and whether the values are considered a guaranteed API.
353+
The value should be a CamelCase string.
354+
This field may not be empty.
355+
maxLength: 1024
356+
minLength: 1
357+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
358+
type: string
359+
status:
360+
description: status of the condition, one of True, False, Unknown.
361+
enum:
362+
- "True"
363+
- "False"
364+
- Unknown
365+
type: string
366+
type:
367+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
368+
maxLength: 316
369+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
370+
type: string
371+
required:
372+
- lastTransitionTime
373+
- message
374+
- reason
375+
- status
376+
- type
377+
type: object
378+
type: array
254379
securityGroups:
255380
description: |-
256381
SecurityGroups contains the current Security Groups values that are available to the

pkg/apis/v1alpha1/ecsnodeclass.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
"log"
21+
"strings"
22+
23+
"github.com/samber/lo"
2024
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2125
)
2226

@@ -38,6 +42,15 @@ type ECSNodeClassSpec struct {
3842
// +kubebuilder:validation:MaxItems:=30
3943
// +required
4044
SecurityGroupSelectorTerms []SecurityGroupSelectorTerm `json:"securityGroupSelectorTerms" hash:"ignore"`
45+
// ImageSelectorTerms is a list of or image selector terms. The terms are ORed.
46+
// +kubebuilder:validation:XValidation:message="expected at least one, got none, ['tags', 'id', 'name', 'alias']",rule="self.all(x, has(x.tags) || has(x.id) || has(x.name) || has(x.alias))"
47+
// +kubebuilder:validation:XValidation:message="'id' is mutually exclusive, cannot be set with a combination of other fields in imageSelectorTerms",rule="!self.exists(x, has(x.id) && (has(x.alias) || has(x.tags) || has(x.name) || has(x.owner)))"
48+
// +kubebuilder:validation:XValidation:message="'alias' is mutually exclusive, cannot be set with a combination of other fields in imageSelectorTerms",rule="!self.exists(x, has(x.alias) && (has(x.id) || has(x.tags) || has(x.name) || has(x.owner)))"
49+
// +kubebuilder:validation:XValidation:message="'alias' is mutually exclusive, cannot be set with a combination of other imageSelectorTerms",rule="!(self.exists(x, has(x.alias)) && self.size() != 1)"
50+
// +kubebuilder:validation:MinItems:=1
51+
// +kubebuilder:validation:MaxItems:=30
52+
// +required
53+
ImageSelectorTerms []ImageSelectorTerm `json:"imageSelectorTerms" hash:"ignore"`
4154
// KubeletConfiguration defines args to be used when configuring kubelet on provisioned nodes.
4255
// They are a subset of the upstream types, recognizing not all options may be supported.
4356
// Wherever possible, the types and names should reflect the upstream kubelet types.
@@ -80,6 +93,38 @@ type SecurityGroupSelectorTerm struct {
8093
Name string `json:"name,omitempty"`
8194
}
8295

96+
// ImageSelectorTerm defines selection logic for an image used by Karpenter to launch nodes.
97+
// If multiple fields are used for selection, the requirements are ANDed.
98+
type ImageSelectorTerm struct {
99+
// Alias specifies which ACK image to select.
100+
// Each alias consists of a family and an image version, specified as "family@version".
101+
// Valid families include: aliyun3.
102+
// Currently only supports version pinning to the latest image release, with that images version format (ex: "aliyun3@latest").
103+
// Setting the version to latest will result in drift when a new Image is released. This is **not** recommended for production environments.
104+
// +kubebuilder:validation:XValidation:message="'alias' is improperly formatted, must match the format 'family@version'",rule="self.matches('^[a-zA-Z0-9]*@.*$')"
105+
// +kubebuilder:validation:XValidation:message="family is not supported, must be one of the following: 'aliyun3'",rule="self.find('^[^@]+') in ['aliyun3']"
106+
// +kubebuilder:validation:MaxLength=30
107+
// +optional
108+
Alias string `json:"alias,omitempty"`
109+
// Tags is a map of key/value tags used to select subsets
110+
// Specifying '*' for a value selects all values for a given tag key.
111+
// +kubebuilder:validation:XValidation:message="empty tag keys aren't supported",rule="self.all(k, k != '')"
112+
// +kubebuilder:validation:MaxProperties:=20
113+
// +optional
114+
Tags map[string]string `json:"tags,omitempty"`
115+
// ID is the image id in ECS
116+
// +optional
117+
ID string `json:"id,omitempty"`
118+
// Name is the image name in ECS.
119+
// This value is the name field, which is different from the name tag.
120+
// +optional
121+
Name string `json:"name,omitempty"`
122+
// Owner is the image source.
123+
// Default is system | self | public. If specified, only one of the following: "self", "system", "share", "public", and "marketplace"
124+
// +optional
125+
Owner string `json:"owner,omitempty"`
126+
}
127+
83128
// KubeletConfiguration defines args to be used when configuring kubelet on provisioned nodes.
84129
// They are a subset of the upstream types, recognizing not all options may be supported.
85130
// Wherever possible, the types and names should reflect the upstream kubelet types.
@@ -161,10 +206,45 @@ type ECSNodeClass struct {
161206
Status ECSNodeClassStatus `json:"status,omitempty"`
162207
}
163208

209+
// ImageFamily If an alias is specified, return alias, or be 'Custom' (enforced via validation).
210+
func (in *ECSNodeClass) ImageFamily() string {
211+
if term, ok := lo.Find(in.Spec.ImageSelectorTerms, func(t ImageSelectorTerm) bool {
212+
return t.Alias != ""
213+
}); ok {
214+
return ImageFamilyFromAlias(term.Alias)
215+
}
216+
// Unreachable: validation enforces that one of the above conditions must be met
217+
return ImageFamilyCustom
218+
}
219+
164220
// ECSNodeClassList contains a list of ECSNodeClass
165221
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
166222
type ECSNodeClassList struct {
167223
metav1.TypeMeta `json:",inline"`
168224
metav1.ListMeta `json:"metadata,omitempty"`
169225
Items []ECSNodeClass `json:"items"`
170226
}
227+
228+
func ImageFamilyFromAlias(alias string) string {
229+
components := strings.Split(alias, "@")
230+
if len(components) != 2 {
231+
log.Fatalf("failed to parse image alias %q, invalid format", alias)
232+
}
233+
family, ok := lo.Find([]string{
234+
ImageFamilyAliyun3,
235+
}, func(family string) bool {
236+
return strings.ToLower(family) == components[0]
237+
})
238+
if !ok {
239+
log.Fatalf("%q is an invalid alias family", components[0])
240+
}
241+
return family
242+
}
243+
244+
func ImageVersionFromAlias(alias string) string {
245+
components := strings.Split(alias, "@")
246+
if len(components) != 2 {
247+
log.Fatalf("failed to parse image alias %q, invalid format", alias)
248+
}
249+
return components[1]
250+
}

pkg/apis/v1alpha1/labels.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ func init() {
5656
}
5757

5858
var (
59-
TerminationFinalizer = apis.Group + "/termination"
60-
AWSToKubeArchitectures = map[string]string{
61-
"x86_64": karpv1.ArchitectureAmd64,
62-
karpv1.ArchitectureArm64: karpv1.ArchitectureArm64,
59+
TerminationFinalizer = apis.Group + "/termination"
60+
AlibabaCloudToKubeArchitectures = map[string]string{
61+
"x86_64": karpv1.ArchitectureAmd64,
62+
"arm64": karpv1.ArchitectureArm64,
6363
}
6464
WellKnownArchitectures = sets.NewString(
6565
karpv1.ArchitectureAmd64,
@@ -79,11 +79,11 @@ var (
7979
}
8080
AMIFamilyBottlerocket = "Bottlerocket"
8181
AMIFamilyAL2 = "AL2"
82-
AMIFamilyAL2023 = "AL2023"
82+
ImageFamilyAliyun3 = "Aliyun3"
8383
AMIFamilyUbuntu = "Ubuntu"
8484
AMIFamilyWindows2019 = "Windows2019"
8585
AMIFamilyWindows2022 = "Windows2022"
86-
AMIFamilyCustom = "Custom"
86+
ImageFamilyCustom = "Custom"
8787
Windows2019 = "2019"
8888
Windows2022 = "2022"
8989
WindowsCore = "Core"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2024 The CloudPilot AI Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package imagefamily
18+
19+
import "context"
20+
21+
type Aliyun3 struct {
22+
}
23+
24+
func (a Aliyun3) DescribeImageQuery(ctx context.Context, k8sVersion string, imageVersion string) ([]DescribeImageQuery, error) {
25+
//TODO implement me
26+
panic("implement me")
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2024 The CloudPilot AI Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package imagefamily
18+
19+
import (
20+
"context"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
"sigs.k8s.io/karpenter/pkg/cloudprovider"
24+
25+
"github.com/cloudpilot-ai/karpenter-provider-alicloud/pkg/apis/v1alpha1"
26+
)
27+
28+
type Custom struct {
29+
}
30+
31+
// UserData returns the default userdata script for the Image Family
32+
func (c Custom) UserData(_ *v1alpha1.KubeletConfiguration, _ []corev1.Taint, _ map[string]string, _ *string, _ []*cloudprovider.InstanceType, customUserData *string) {
33+
//TODO implement me
34+
panic("implement me")
35+
}
36+
37+
func (c Custom) DescribeImageQuery(_ context.Context, _ string, _ string) ([]DescribeImageQuery, error) {
38+
return []DescribeImageQuery{}, nil
39+
}

0 commit comments

Comments
 (0)