Skip to content

Commit 005b9f8

Browse files
authored
Merge pull request #4155 from willie-yao/clusterclass-aks-final
Add ClusterClass Support for Managed Clusters
2 parents 3b94793 + 224b7dc commit 005b9f8

File tree

73 files changed

+7216
-1525
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+7216
-1525
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2023 The Kubernetes 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 v1beta1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// AzureManagedClusterTemplateSpec defines the desired state of AzureManagedClusterTemplate.
24+
type AzureManagedClusterTemplateSpec struct {
25+
Template AzureManagedClusterTemplateResource `json:"template"`
26+
}
27+
28+
// +kubebuilder:object:root=true
29+
// +kubebuilder:resource:path=azuremanagedclustertemplates,scope=Namespaced,categories=cluster-api,shortName=amct
30+
// +kubebuilder:storageversion
31+
32+
// AzureManagedClusterTemplate is the Schema for the AzureManagedClusterTemplates API.
33+
type AzureManagedClusterTemplate struct {
34+
metav1.TypeMeta `json:",inline"`
35+
metav1.ObjectMeta `json:"metadata,omitempty"`
36+
37+
Spec AzureManagedClusterTemplateSpec `json:"spec,omitempty"`
38+
}
39+
40+
// +kubebuilder:object:root=true
41+
42+
// AzureManagedClusterTemplateList contains a list of AzureManagedClusterTemplates.
43+
type AzureManagedClusterTemplateList struct {
44+
metav1.TypeMeta `json:",inline"`
45+
metav1.ListMeta `json:"metadata,omitempty"`
46+
Items []AzureManagedClusterTemplate `json:"items"`
47+
}
48+
49+
func init() {
50+
SchemeBuilder.Register(&AzureManagedClusterTemplate{}, &AzureManagedClusterTemplateList{})
51+
}
52+
53+
// AzureManagedClusterTemplateResource describes the data needed to create an AzureManagedCluster from a template.
54+
type AzureManagedClusterTemplateResource struct {
55+
Spec AzureManagedClusterTemplateResourceSpec `json:"spec"`
56+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2023 The Kubernetes 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 v1beta1
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/runtime"
21+
"k8s.io/apimachinery/pkg/util/validation/field"
22+
"sigs.k8s.io/cluster-api-provider-azure/feature"
23+
capifeature "sigs.k8s.io/cluster-api/feature"
24+
ctrl "sigs.k8s.io/controller-runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/webhook"
26+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27+
)
28+
29+
// SetupWebhookWithManager sets up and registers the webhook with the manager.
30+
func (r *AzureManagedClusterTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error {
31+
return ctrl.NewWebhookManagedBy(mgr).
32+
For(r).
33+
Complete()
34+
}
35+
36+
// +kubebuilder:webhook:verbs=update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-azuremanagedclustertemplate,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=azuremanagedclustertemplates,versions=v1beta1,name=validation.azuremanagedclustertemplates.infrastructure.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1
37+
38+
var _ webhook.Validator = &AzureManagedClusterTemplate{}
39+
40+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
41+
func (r *AzureManagedClusterTemplate) ValidateCreate() (admission.Warnings, error) {
42+
// NOTE: AzureManagedClusterTemplate relies upon MachinePools, which is behind a feature gate flag.
43+
// The webhook must prevent creating new objects in case the feature flag is disabled.
44+
if !feature.Gates.Enabled(capifeature.MachinePool) {
45+
return nil, field.Forbidden(
46+
field.NewPath("spec"),
47+
"cannot be set if the Cluster API 'MachinePool' feature flag is not enabled",
48+
)
49+
}
50+
return nil, nil
51+
}
52+
53+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
54+
func (r *AzureManagedClusterTemplate) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) {
55+
return nil, nil
56+
}
57+
58+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
59+
func (r *AzureManagedClusterTemplate) ValidateDelete() (admission.Warnings, error) {
60+
return nil, nil
61+
}

api/v1beta1/azuremanagedcontrolplane_default.go

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package v1beta1
1919
import (
2020
"encoding/base64"
2121
"fmt"
22+
"strings"
2223

2324
"golang.org/x/crypto/ssh"
2425
"k8s.io/utils/ptr"
2526
utilSSH "sigs.k8s.io/cluster-api-provider-azure/util/ssh"
27+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
28+
ctrl "sigs.k8s.io/controller-runtime"
2629
)
2730

2831
const (
@@ -36,6 +39,15 @@ const (
3639
defaultAKSNodeSubnetCIDRForOverlay = "10.224.0.0/16"
3740
)
3841

42+
// setDefaultResourceGroupName sets the default ResourceGroupName for an AzureManagedControlPlane.
43+
func (m *AzureManagedControlPlane) setDefaultResourceGroupName() {
44+
if m.Spec.ResourceGroupName == "" {
45+
if clusterName, ok := m.Labels[clusterv1.ClusterNameLabel]; ok {
46+
m.Spec.ResourceGroupName = clusterName
47+
}
48+
}
49+
}
50+
3951
// setDefaultSSHPublicKey sets the default SSHPublicKey for an AzureManagedControlPlane.
4052
func (m *AzureManagedControlPlane) setDefaultSSHPublicKey() error {
4153
if sshKey := m.Spec.SSHPublicKey; sshKey != nil && *sshKey == "" {
@@ -86,73 +98,89 @@ func (m *AzureManagedControlPlane) setDefaultSubnet() {
8698
}
8799
}
88100

89-
func (m *AzureManagedControlPlane) setDefaultSku() {
90-
if m.Spec.SKU == nil {
91-
m.Spec.SKU = &AKSSku{
92-
Tier: FreeManagedControlPlaneTier,
93-
}
101+
func setDefaultSku(sku *AKSSku) *AKSSku {
102+
result := sku.DeepCopy()
103+
if sku == nil {
104+
result = new(AKSSku)
105+
result.Tier = FreeManagedControlPlaneTier
106+
} else if sku.Tier == PaidManagedControlPlaneTier {
107+
result.Tier = StandardManagedControlPlaneTier
108+
ctrl.Log.WithName("AzureManagedControlPlaneWebHookLogger").Info("Paid SKU tier is deprecated and has been replaced by Standard")
94109
}
110+
return result
95111
}
96112

97-
func (m *AzureManagedControlPlane) setDefaultAutoScalerProfile() {
98-
if m.Spec.AutoScalerProfile == nil {
99-
return
113+
func setDefaultVersion(version string) string {
114+
if version != "" && !strings.HasPrefix(version, "v") {
115+
normalizedVersion := "v" + version
116+
version = normalizedVersion
117+
}
118+
return version
119+
}
120+
121+
func setDefaultAutoScalerProfile(autoScalerProfile *AutoScalerProfile) *AutoScalerProfile {
122+
if autoScalerProfile == nil {
123+
return nil
100124
}
101125

126+
result := autoScalerProfile.DeepCopy()
127+
102128
// Default values are from https://learn.microsoft.com/en-us/azure/aks/cluster-autoscaler#using-the-autoscaler-profile
103129
// If any values are set, they all need to be set.
104-
if m.Spec.AutoScalerProfile.BalanceSimilarNodeGroups == nil {
105-
m.Spec.AutoScalerProfile.BalanceSimilarNodeGroups = (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsFalse)))
130+
if autoScalerProfile.BalanceSimilarNodeGroups == nil {
131+
result.BalanceSimilarNodeGroups = (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsFalse)))
106132
}
107-
if m.Spec.AutoScalerProfile.Expander == nil {
108-
m.Spec.AutoScalerProfile.Expander = (*Expander)(ptr.To(string(ExpanderRandom)))
133+
if autoScalerProfile.Expander == nil {
134+
result.Expander = (*Expander)(ptr.To(string(ExpanderRandom)))
109135
}
110-
if m.Spec.AutoScalerProfile.MaxEmptyBulkDelete == nil {
111-
m.Spec.AutoScalerProfile.MaxEmptyBulkDelete = ptr.To("10")
136+
if autoScalerProfile.MaxEmptyBulkDelete == nil {
137+
result.MaxEmptyBulkDelete = ptr.To("10")
112138
}
113-
if m.Spec.AutoScalerProfile.MaxGracefulTerminationSec == nil {
114-
m.Spec.AutoScalerProfile.MaxGracefulTerminationSec = ptr.To("600")
139+
if autoScalerProfile.MaxGracefulTerminationSec == nil {
140+
result.MaxGracefulTerminationSec = ptr.To("600")
115141
}
116-
if m.Spec.AutoScalerProfile.MaxNodeProvisionTime == nil {
117-
m.Spec.AutoScalerProfile.MaxNodeProvisionTime = ptr.To("15m")
142+
if autoScalerProfile.MaxNodeProvisionTime == nil {
143+
result.MaxNodeProvisionTime = ptr.To("15m")
118144
}
119-
if m.Spec.AutoScalerProfile.MaxTotalUnreadyPercentage == nil {
120-
m.Spec.AutoScalerProfile.MaxTotalUnreadyPercentage = ptr.To("45")
145+
if autoScalerProfile.MaxTotalUnreadyPercentage == nil {
146+
result.MaxTotalUnreadyPercentage = ptr.To("45")
121147
}
122-
if m.Spec.AutoScalerProfile.NewPodScaleUpDelay == nil {
123-
m.Spec.AutoScalerProfile.NewPodScaleUpDelay = ptr.To("0s")
148+
if autoScalerProfile.NewPodScaleUpDelay == nil {
149+
result.NewPodScaleUpDelay = ptr.To("0s")
124150
}
125-
if m.Spec.AutoScalerProfile.OkTotalUnreadyCount == nil {
126-
m.Spec.AutoScalerProfile.OkTotalUnreadyCount = ptr.To("3")
151+
if autoScalerProfile.OkTotalUnreadyCount == nil {
152+
result.OkTotalUnreadyCount = ptr.To("3")
127153
}
128-
if m.Spec.AutoScalerProfile.ScanInterval == nil {
129-
m.Spec.AutoScalerProfile.ScanInterval = ptr.To("10s")
154+
if autoScalerProfile.ScanInterval == nil {
155+
result.ScanInterval = ptr.To("10s")
130156
}
131-
if m.Spec.AutoScalerProfile.ScaleDownDelayAfterAdd == nil {
132-
m.Spec.AutoScalerProfile.ScaleDownDelayAfterAdd = ptr.To("10m")
157+
if autoScalerProfile.ScaleDownDelayAfterAdd == nil {
158+
result.ScaleDownDelayAfterAdd = ptr.To("10m")
133159
}
134-
if m.Spec.AutoScalerProfile.ScaleDownDelayAfterDelete == nil {
160+
if autoScalerProfile.ScaleDownDelayAfterDelete == nil {
135161
// Default is the same as the ScanInterval so default to that same value if it isn't set
136-
m.Spec.AutoScalerProfile.ScaleDownDelayAfterDelete = m.Spec.AutoScalerProfile.ScanInterval
162+
result.ScaleDownDelayAfterDelete = result.ScanInterval
137163
}
138-
if m.Spec.AutoScalerProfile.ScaleDownDelayAfterFailure == nil {
139-
m.Spec.AutoScalerProfile.ScaleDownDelayAfterFailure = ptr.To("3m")
164+
if autoScalerProfile.ScaleDownDelayAfterFailure == nil {
165+
result.ScaleDownDelayAfterFailure = ptr.To("3m")
140166
}
141-
if m.Spec.AutoScalerProfile.ScaleDownUnneededTime == nil {
142-
m.Spec.AutoScalerProfile.ScaleDownUnneededTime = ptr.To("10m")
167+
if autoScalerProfile.ScaleDownUnneededTime == nil {
168+
result.ScaleDownUnneededTime = ptr.To("10m")
143169
}
144-
if m.Spec.AutoScalerProfile.ScaleDownUnreadyTime == nil {
145-
m.Spec.AutoScalerProfile.ScaleDownUnreadyTime = ptr.To("20m")
170+
if autoScalerProfile.ScaleDownUnreadyTime == nil {
171+
result.ScaleDownUnreadyTime = ptr.To("20m")
146172
}
147-
if m.Spec.AutoScalerProfile.ScaleDownUtilizationThreshold == nil {
148-
m.Spec.AutoScalerProfile.ScaleDownUtilizationThreshold = ptr.To("0.5")
173+
if autoScalerProfile.ScaleDownUtilizationThreshold == nil {
174+
result.ScaleDownUtilizationThreshold = ptr.To("0.5")
149175
}
150-
if m.Spec.AutoScalerProfile.SkipNodesWithLocalStorage == nil {
151-
m.Spec.AutoScalerProfile.SkipNodesWithLocalStorage = (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageFalse)))
176+
if autoScalerProfile.SkipNodesWithLocalStorage == nil {
177+
result.SkipNodesWithLocalStorage = (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageFalse)))
152178
}
153-
if m.Spec.AutoScalerProfile.SkipNodesWithSystemPods == nil {
154-
m.Spec.AutoScalerProfile.SkipNodesWithSystemPods = (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsTrue)))
179+
if autoScalerProfile.SkipNodesWithSystemPods == nil {
180+
result.SkipNodesWithSystemPods = (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsTrue)))
155181
}
182+
183+
return result
156184
}
157185

158186
func (m *AzureManagedControlPlane) setDefaultOIDCIssuerProfile() {

api/v1beta1/azuremanagedcontrolplane_default_test.go

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -64,69 +64,77 @@ func TestSetDefaultAutoScalerProfile(t *testing.T) {
6464

6565
defaultAMP := &AzureManagedControlPlane{
6666
Spec: AzureManagedControlPlaneSpec{
67-
AutoScalerProfile: &AutoScalerProfile{
68-
BalanceSimilarNodeGroups: (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsFalse))),
69-
Expander: (*Expander)(ptr.To(string(ExpanderRandom))),
70-
MaxEmptyBulkDelete: ptr.To("10"),
71-
MaxGracefulTerminationSec: ptr.To("600"),
72-
MaxNodeProvisionTime: ptr.To("15m"),
73-
MaxTotalUnreadyPercentage: ptr.To("45"),
74-
NewPodScaleUpDelay: ptr.To("0s"),
75-
OkTotalUnreadyCount: ptr.To("3"),
76-
ScanInterval: ptr.To("10s"),
77-
ScaleDownDelayAfterAdd: ptr.To("10m"),
78-
ScaleDownDelayAfterDelete: ptr.To("10s"),
79-
ScaleDownDelayAfterFailure: ptr.To("3m"),
80-
ScaleDownUnneededTime: ptr.To("10m"),
81-
ScaleDownUnreadyTime: ptr.To("20m"),
82-
ScaleDownUtilizationThreshold: ptr.To("0.5"),
83-
SkipNodesWithLocalStorage: (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageFalse))),
84-
SkipNodesWithSystemPods: (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsTrue))),
67+
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
68+
AutoScalerProfile: &AutoScalerProfile{
69+
BalanceSimilarNodeGroups: (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsFalse))),
70+
Expander: (*Expander)(ptr.To(string(ExpanderRandom))),
71+
MaxEmptyBulkDelete: ptr.To("10"),
72+
MaxGracefulTerminationSec: ptr.To("600"),
73+
MaxNodeProvisionTime: ptr.To("15m"),
74+
MaxTotalUnreadyPercentage: ptr.To("45"),
75+
NewPodScaleUpDelay: ptr.To("0s"),
76+
OkTotalUnreadyCount: ptr.To("3"),
77+
ScanInterval: ptr.To("10s"),
78+
ScaleDownDelayAfterAdd: ptr.To("10m"),
79+
ScaleDownDelayAfterDelete: ptr.To("10s"),
80+
ScaleDownDelayAfterFailure: ptr.To("3m"),
81+
ScaleDownUnneededTime: ptr.To("10m"),
82+
ScaleDownUnreadyTime: ptr.To("20m"),
83+
ScaleDownUtilizationThreshold: ptr.To("0.5"),
84+
SkipNodesWithLocalStorage: (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageFalse))),
85+
SkipNodesWithSystemPods: (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsTrue))),
86+
},
8587
},
8688
},
8789
}
8890

8991
allFieldsAreNilTest := test{amcp: &AzureManagedControlPlane{
9092
Spec: AzureManagedControlPlaneSpec{
91-
AutoScalerProfile: &AutoScalerProfile{},
93+
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
94+
AutoScalerProfile: &AutoScalerProfile{},
95+
},
9296
},
9397
}}
9498

95-
allFieldsAreNilTest.amcp.setDefaultAutoScalerProfile()
99+
allFieldsAreNilTest.amcp.Spec.AutoScalerProfile = setDefaultAutoScalerProfile(allFieldsAreNilTest.amcp.Spec.AutoScalerProfile)
96100

97101
g.Expect(allFieldsAreNilTest.amcp.Spec.AutoScalerProfile).To(Equal(defaultAMP.Spec.AutoScalerProfile))
98102

99103
expectedNotNil := &AzureManagedControlPlane{
100104
Spec: AzureManagedControlPlaneSpec{
101-
AutoScalerProfile: &AutoScalerProfile{
102-
BalanceSimilarNodeGroups: (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsTrue))),
103-
Expander: (*Expander)(ptr.To(string(ExpanderLeastWaste))),
104-
MaxEmptyBulkDelete: ptr.To("5"),
105-
MaxGracefulTerminationSec: ptr.To("300"),
106-
MaxNodeProvisionTime: ptr.To("10m"),
107-
MaxTotalUnreadyPercentage: ptr.To("30"),
108-
NewPodScaleUpDelay: ptr.To("30s"),
109-
OkTotalUnreadyCount: ptr.To("5"),
110-
ScanInterval: ptr.To("20s"),
111-
ScaleDownDelayAfterAdd: ptr.To("5m"),
112-
ScaleDownDelayAfterDelete: ptr.To("1m"),
113-
ScaleDownDelayAfterFailure: ptr.To("2m"),
114-
ScaleDownUnneededTime: ptr.To("5m"),
115-
ScaleDownUnreadyTime: ptr.To("10m"),
116-
ScaleDownUtilizationThreshold: ptr.To("0.4"),
117-
SkipNodesWithLocalStorage: (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageTrue))),
118-
SkipNodesWithSystemPods: (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsFalse))),
105+
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
106+
AutoScalerProfile: &AutoScalerProfile{
107+
BalanceSimilarNodeGroups: (*BalanceSimilarNodeGroups)(ptr.To(string(BalanceSimilarNodeGroupsTrue))),
108+
Expander: (*Expander)(ptr.To(string(ExpanderLeastWaste))),
109+
MaxEmptyBulkDelete: ptr.To("5"),
110+
MaxGracefulTerminationSec: ptr.To("300"),
111+
MaxNodeProvisionTime: ptr.To("10m"),
112+
MaxTotalUnreadyPercentage: ptr.To("30"),
113+
NewPodScaleUpDelay: ptr.To("30s"),
114+
OkTotalUnreadyCount: ptr.To("5"),
115+
ScanInterval: ptr.To("20s"),
116+
ScaleDownDelayAfterAdd: ptr.To("5m"),
117+
ScaleDownDelayAfterDelete: ptr.To("1m"),
118+
ScaleDownDelayAfterFailure: ptr.To("2m"),
119+
ScaleDownUnneededTime: ptr.To("5m"),
120+
ScaleDownUnreadyTime: ptr.To("10m"),
121+
ScaleDownUtilizationThreshold: ptr.To("0.4"),
122+
SkipNodesWithLocalStorage: (*SkipNodesWithLocalStorage)(ptr.To(string(SkipNodesWithLocalStorageTrue))),
123+
SkipNodesWithSystemPods: (*SkipNodesWithSystemPods)(ptr.To(string(SkipNodesWithSystemPodsFalse))),
124+
},
119125
},
120126
},
121127
}
122128

123129
allFieldsAreNotNilTest := test{amcp: &AzureManagedControlPlane{
124130
Spec: AzureManagedControlPlaneSpec{
125-
AutoScalerProfile: expectedNotNil.Spec.AutoScalerProfile,
131+
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
132+
AutoScalerProfile: ptr.To(*expectedNotNil.Spec.AutoScalerProfile),
133+
},
126134
},
127135
}}
128136

129-
allFieldsAreNotNilTest.amcp.setDefaultAutoScalerProfile()
137+
allFieldsAreNotNilTest.amcp.Spec.AutoScalerProfile = setDefaultAutoScalerProfile(allFieldsAreNotNilTest.amcp.Spec.AutoScalerProfile)
130138

131139
g.Expect(allFieldsAreNotNilTest.amcp.Spec.AutoScalerProfile).To(Equal(expectedNotNil.Spec.AutoScalerProfile))
132140
}

0 commit comments

Comments
 (0)