Skip to content

Commit b87f84e

Browse files
authored
[GEP-33] Ensure NamespacedCloudProfile status format (#1515)
* feat: introduce namespacedcloudprofile automatic status transformation from and to capabilityformat * feat: implement RestrictToArchitectureCapability for capability validation during transition * feat: refine architecture handling in cloud profile and machine image flavor * feat: update CloudProfileConfig examples to include spec.machineCapabilities for architecture mapping * feat: refactor architecture handling and remove capability restriction functions in cloud profile transformer * feat: simplify provider config decoding in namespaced cloud profile * Update pkg/apis/aws/helper/cloudprofiletransformer.go
1 parent 7ab65c0 commit b87f84e

14 files changed

Lines changed: 807 additions & 56 deletions

docs/operations/operations.md

Lines changed: 117 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,128 @@ In this section we are describing how the configuration for `CloudProfile`s look
1313
### `CloudProfileConfig`
1414

1515
The cloud profile configuration contains information about the real machine image IDs in the AWS environment (AMIs).
16+
With the introduction of `spec.machineCapabilities` in Gardener *v1.131.0* you have to map every `capabilityFlavor` in `.spec.machineImages[].versions` here such that the AWS extension knows the AMI for every flavor you want to offer.
17+
18+
If the `spec.machineCapabilities` field is not used in the `CloudProfile`, the legacy `architectures` field in `.spec.machineImages[].versions` is used.
1619
You have to map every version that you specify in `.spec.machineImages[].versions` here such that the AWS extension knows the AMI for every version you want to offer.
1720
For each AMI an `architecture` field can be specified which specifies the CPU architecture of the machine on which given machine image can be used.
1821

1922
An example `CloudProfileConfig` for the AWS extension looks as follows:
2023

2124
```yaml
25+
## With `spec.machineCapabilities` in `CloudProfile`
26+
apiVersion: aws.provider.extensions.gardener.cloud/v1alpha1
27+
kind: CloudProfileConfig
28+
machineImages:
29+
- name: coreos
30+
versions:
31+
- version: 2135.6.0
32+
capabilityFlavors:
33+
- capabilities:
34+
architecture: [amd64]
35+
# otherCapability: [otherValue, ...] # optional
36+
regions:
37+
- name: eu-central-1
38+
ami: ami-034fd8c3f4026eb39
39+
- capabilities:
40+
architecture: [arm64]
41+
# otherCapability: [otherValue, ...] # optional
42+
regions:
43+
- name: eu-central-1
44+
ami: ami-034fd8c3f4026eb38
45+
46+
---
47+
## Without `spec.machineCapabilities` in `CloudProfile`
2248
apiVersion: aws.provider.extensions.gardener.cloud/v1alpha1
2349
kind: CloudProfileConfig
2450
machineImages:
25-
- name: coreos
26-
versions:
27-
- version: 2135.6.0
28-
regions:
29-
- name: eu-central-1
30-
ami: ami-034fd8c3f4026eb39
31-
# architecture: amd64 # optional
51+
- name: coreos
52+
versions:
53+
- version: 2135.6.0
54+
regions:
55+
- name: eu-central-1
56+
ami: ami-034fd8c3f4026eb39
57+
# architecture: amd64 # optional
3258
```
3359

3460
### Example `CloudProfile` manifest
3561

3662
Please find below an example `CloudProfile` manifest:
3763

3864
```yaml
65+
# With `spec.machineCapabilities`in ``CloudProfile`
66+
apiVersion: core.gardener.cloud/v1beta1
67+
kind: CloudProfile
68+
metadata:
69+
name: aws
70+
spec:
71+
machineCapabilities:
72+
- name: architecture
73+
values: [ amd64, arm64 ]
74+
# - name: otherCapability
75+
# values:
76+
# - otherValue
77+
# - anotherValue
78+
# - yetAnotherValue
79+
type: aws
80+
kubernetes:
81+
versions:
82+
- version: 1.32.1
83+
- version: 1.31.4
84+
expirationDate: "2022-10-31T23:59:59Z"
85+
machineImages:
86+
- name: coreos
87+
versions:
88+
- version: 2135.6.0
89+
capabilityFlavors:
90+
- architecture: [amd64]
91+
# otherCapability: [otherValue, ...]
92+
- architecture: [arm64]
93+
# otherCapability: [otherValue, ...]
94+
machineTypes:
95+
- name: m5.large
96+
cpu: "2"
97+
gpu: "0"
98+
memory: 8Gi
99+
usable: true
100+
capabilities:
101+
architecture: [amd64]
102+
# otherCapability: [otherValue, ...]
103+
volumeTypes:
104+
- name: gp2
105+
class: standard
106+
usable: true
107+
- name: io1
108+
class: premium
109+
usable: true
110+
regions:
111+
- name: eu-central-1
112+
zones:
113+
- name: eu-central-1a
114+
- name: eu-central-1b
115+
- name: eu-central-1c
116+
providerConfig:
117+
apiVersion: aws.provider.extensions.gardener.cloud/v1alpha1
118+
kind: CloudProfileConfig
119+
machineImages:
120+
- name: coreos
121+
versions:
122+
- version: 2135.6.0
123+
capabilityFlavors:
124+
- capabilities:
125+
architecture: [amd64]
126+
# otherCapability: [otherValue, ...] # optional
127+
regions:
128+
- name: eu-central-1
129+
ami: ami-034fd8c3f4026eb39
130+
- capabilities:
131+
architecture: [arm64]
132+
# otherCapability: [otherValue, ...] # optional
133+
regions:
134+
- name: eu-central-1
135+
ami: ami-034fd8c3f4026eb38
136+
137+
# Without `spec.machineCapabilities` in `CloudProfile`
39138
apiVersion: core.gardener.cloud/v1beta1
40139
kind: CloudProfile
41140
metadata:
@@ -81,6 +180,7 @@ spec:
81180
- name: eu-central-1
82181
ami: ami-034fd8c3f4026eb39
83182
# architecture: amd64 # optional
183+
84184
```
85185

86186
## `Seed` resource
@@ -201,16 +301,16 @@ Please make sure that the provided credentials have the correct privileges. You
201301
<summary>Click to expand the AWS IAM policy document!</summary>
202302

203303
```json
204-
{
205-
"Version": "2012-10-17",
206-
"Statement": [
207-
{
208-
"Effect": "Allow",
209-
"Action": "s3:*",
210-
"Resource": "*"
211-
}
212-
]
213-
}
304+
{
305+
"Version": "2012-10-17",
306+
"Statement": [
307+
{
308+
"Effect": "Allow",
309+
"Action": "s3:*",
310+
"Resource": "*"
311+
}
312+
]
313+
}
214314
```
215315

216316
</details>

pkg/admission/mutator/namespacedcloudprofile.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"sigs.k8s.io/controller-runtime/pkg/client"
2020
"sigs.k8s.io/controller-runtime/pkg/manager"
2121

22+
"github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper"
2223
"github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/v1alpha1"
2324
)
2425

@@ -42,9 +43,7 @@ func (p *namespacedCloudProfile) Mutate(_ context.Context, newObj, _ client.Obje
4243
return fmt.Errorf("wrong object type %T", newObj)
4344
}
4445

45-
// Ignore NamespacedCloudProfiles being deleted and wait for core mutator to patch the status.
46-
if profile.DeletionTimestamp != nil || profile.Generation != profile.Status.ObservedGeneration ||
47-
profile.Spec.ProviderConfig == nil || profile.Status.CloudProfileSpec.ProviderConfig == nil {
46+
if shouldSkipMutation(profile) {
4847
return nil
4948
}
5049

@@ -57,14 +56,26 @@ func (p *namespacedCloudProfile) Mutate(_ context.Context, newObj, _ client.Obje
5756
return fmt.Errorf("could not decode providerConfig of namespacedCloudProfile status for '%s': %w", profile.Name, err)
5857
}
5958

60-
statusConfig.MachineImages = mergeMachineImages(specConfig.MachineImages, statusConfig.MachineImages)
59+
// TODO(Roncossek): Remove TransformProviderConfigToParentFormat once all CloudProfiles have been migrated to use CapabilityFlavors and the Architecture fields are effectively forbidden or have been removed.
60+
uniformSpecConfig := helper.TransformProviderConfigToParentFormat(specConfig, profile.Status.CloudProfileSpec.MachineCapabilities)
61+
statusConfig.MachineImages = mergeMachineImages(uniformSpecConfig.MachineImages, statusConfig.MachineImages)
6162

62-
modifiedStatusConfig, err := json.Marshal(statusConfig)
63+
return p.updateProfileStatus(profile, statusConfig)
64+
}
65+
66+
func shouldSkipMutation(profile *gardencorev1beta1.NamespacedCloudProfile) bool {
67+
return profile.DeletionTimestamp != nil ||
68+
profile.Generation != profile.Status.ObservedGeneration ||
69+
profile.Spec.ProviderConfig == nil ||
70+
profile.Status.CloudProfileSpec.ProviderConfig == nil
71+
}
72+
73+
func (p *namespacedCloudProfile) updateProfileStatus(profile *gardencorev1beta1.NamespacedCloudProfile, config *v1alpha1.CloudProfileConfig) error {
74+
modifiedStatusConfig, err := json.Marshal(config)
6375
if err != nil {
64-
return err
76+
return fmt.Errorf("failed to marshal status config: %w", err)
6577
}
6678
profile.Status.CloudProfileSpec.ProviderConfig.Raw = modifiedStatusConfig
67-
6879
return nil
6980
}
7081

pkg/admission/mutator/namespacedcloudprofile_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
8888
"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1",
8989
"kind":"CloudProfileConfig",
9090
"machineImages":[
91-
{"name":"image-1","versions":[{"version":"1.1","regions":[{"name":"eu2","ami":"ami-124","architecture":"armhf"}]}]},
91+
{"name":"image-1","versions":[{"version":"1.1","regions":[{"name":"eu2","ami":"ami-124","architecture":"arm64"}]}]},
9292
{"name":"image-2","versions":[{"version":"2.0","regions":[{"name":"eu3","ami":"ami-125"}]}]}
9393
]}`)}
9494

@@ -101,7 +101,7 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
101101
"Name": Equal("image-1"),
102102
"Versions": ContainElements(
103103
api.MachineImageVersion{Version: "1.0", Regions: []api.RegionAMIMapping{{Name: "eu1", AMI: "ami-123", Architecture: ptr.To("amd64")}}},
104-
api.MachineImageVersion{Version: "1.1", Regions: []api.RegionAMIMapping{{Name: "eu2", AMI: "ami-124", Architecture: ptr.To("armhf")}}},
104+
api.MachineImageVersion{Version: "1.1", Regions: []api.RegionAMIMapping{{Name: "eu2", AMI: "ami-124", Architecture: ptr.To("arm64")}}},
105105
),
106106
}),
107107
MatchFields(IgnoreExtras, Fields{
@@ -113,7 +113,7 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
113113
It("should correctly merge extended machineImages using capabilities ", func() {
114114
namespacedCloudProfile.Status.CloudProfileSpec.MachineCapabilities = []v1beta1.CapabilityDefinition{{
115115
Name: "architecture",
116-
Values: []string{"amd64", "armhf"},
116+
Values: []string{"amd64", "arm64"},
117117
}}
118118
namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
119119
"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1",
@@ -128,7 +128,7 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
128128
"kind":"CloudProfileConfig",
129129
"machineImages":[
130130
{"name":"image-1","versions":[{"version":"1.1","capabilityFlavors":[
131-
{"capabilities":{"architecture":["armhf"]},"regions":[{"name":"eu2","ami":"ami-124"}]}
131+
{"capabilities":{"architecture":["arm64"]},"regions":[{"name":"eu2","ami":"ami-124"}]}
132132
]}]},
133133
{"name":"image-2","versions":[{"version":"2.0","capabilityFlavors":[
134134
{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"eu3","ami":"ami-125"}]}
@@ -146,13 +146,13 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
146146
api.MachineImageVersion{Version: "1.0",
147147
CapabilityFlavors: []api.MachineImageFlavor{{
148148
Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}},
149-
Regions: []api.RegionAMIMapping{{Name: "eu1", AMI: "ami-123", Architecture: ptr.To("ignore")}},
149+
Regions: []api.RegionAMIMapping{{Name: "eu1", AMI: "ami-123"}},
150150
}},
151151
},
152152
api.MachineImageVersion{Version: "1.1",
153153
CapabilityFlavors: []api.MachineImageFlavor{{
154-
Capabilities: v1beta1.Capabilities{"architecture": []string{"armhf"}},
155-
Regions: []api.RegionAMIMapping{{Name: "eu2", AMI: "ami-124", Architecture: ptr.To("ignore")}},
154+
Capabilities: v1beta1.Capabilities{"architecture": []string{"arm64"}},
155+
Regions: []api.RegionAMIMapping{{Name: "eu2", AMI: "ami-124"}},
156156
}},
157157
},
158158
),
@@ -163,7 +163,7 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
163163
api.MachineImageVersion{Version: "2.0",
164164
CapabilityFlavors: []api.MachineImageFlavor{{
165165
Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}},
166-
Regions: []api.RegionAMIMapping{{Name: "eu3", AMI: "ami-125", Architecture: ptr.To("ignore")}},
166+
Regions: []api.RegionAMIMapping{{Name: "eu3", AMI: "ami-125"}},
167167
}},
168168
}),
169169
}),

pkg/admission/validator/namespacedcloudprofile.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
gardencoreapi "github.com/gardener/gardener/pkg/api"
1414
"github.com/gardener/gardener/pkg/apis/core"
1515
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
16-
"github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
16+
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
1717
gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
1818
gutil "github.com/gardener/gardener/pkg/utils/gardener"
1919
"k8s.io/apimachinery/pkg/runtime"
@@ -24,6 +24,7 @@ import (
2424
"sigs.k8s.io/controller-runtime/pkg/manager"
2525

2626
api "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws"
27+
"github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper"
2728
"github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/validation"
2829
)
2930

@@ -61,14 +62,19 @@ func (p *namespacedCloudProfile) Validate(ctx context.Context, newObj, _ client.
6162
}
6263

6364
parentCloudProfile := cloudProfile.Spec.Parent
64-
if parentCloudProfile.Kind != constants.CloudProfileReferenceKindCloudProfile {
65+
if parentCloudProfile.Kind != v1beta1constants.CloudProfileReferenceKindCloudProfile {
6566
return fmt.Errorf("parent reference must be of kind CloudProfile (unsupported kind: %s)", parentCloudProfile.Kind)
6667
}
6768
parentProfile := &gardencorev1beta1.CloudProfile{}
6869
if err := p.client.Get(ctx, client.ObjectKey{Name: parentCloudProfile.Name}, parentProfile); err != nil {
6970
return err
7071
}
7172

73+
// TODO(Roncossek): Remove TransformSpecToParentFormat once all CloudProfiles have been migrated to use CapabilityFlavors and the Architecture fields are effectively forbidden or have been removed.
74+
if err := helper.SimulateTransformToParentFormat(cloudProfileConfig, cloudProfile, parentProfile.Spec.MachineCapabilities); err != nil {
75+
return err
76+
}
77+
7278
return p.validateMachineImages(cloudProfileConfig, cloudProfile.Spec.MachineImages, parentProfile.Spec).ToAggregate()
7379
}
7480

@@ -219,7 +225,7 @@ func validateMachineImageArchitectures(machineImage core.MachineImage, version c
219225
regionsArchitectureMap := map[string][]string{}
220226

221227
for _, regionMapping := range providerImageVersion.Regions {
222-
providerConfigArchitecture := ptr.Deref(regionMapping.Architecture, constants.ArchitectureAMD64)
228+
providerConfigArchitecture := ptr.Deref(regionMapping.Architecture, v1beta1constants.ArchitectureAMD64)
223229
if !slices.Contains(version.Architectures, providerConfigArchitecture) {
224230
allErrs = append(allErrs, field.Forbidden(
225231
field.NewPath("spec.providerConfig.machineImages"),

pkg/admission/validator/namespacedcloudprofile_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,46 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili
342342
"Detail": Equal("machine image image-3 is not defined in the NamespacedCloudProfile providerConfig"),
343343
}))))
344344
})
345+
346+
It("should succeed if parent and NamespacedCloudProfile use different format regarding architecture and capabilities", func() {
347+
// parent cloudprofile uses regions only & namespaced cloudprofile uses capabilities
348+
amiMappings := `"regions":[{"name":"eu1","ami":"ami-123"}]`
349+
namespacedAmiMappings := `{"name":"image-1","versions":[{"version":"1.1","capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]},
350+
{"name":"image-2","versions":[{"version":"2.0","capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]}`
351+
if isCapabilitiesCloudProfile {
352+
// parent cloudprofile uses capabilities & namespaced cloudprofile uses regions only
353+
amiMappings = `"capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]`
354+
namespacedAmiMappings = `{"name":"image-1","versions":[{"version":"1.1","regions":[{"name":"eu1","ami":"ami-123"}]}]},
355+
{"name":"image-2","versions":[{"version":"2.0","regions":[{"name":"eu1","ami":"ami-123"}]}]}`
356+
}
357+
358+
cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{
359+
"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1",
360+
"kind":"CloudProfileConfig",
361+
"machineImages":[{"name":"image-1","versions":[{"version":"1.0",%s}]}]
362+
}`, amiMappings))}
363+
namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{
364+
"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1",
365+
"kind":"CloudProfileConfig",
366+
"machineImages":[%s]
367+
}`, namespacedAmiMappings))}
368+
namespacedCloudProfile.Spec.MachineImages = []core.MachineImage{
369+
{
370+
Name: "image-1",
371+
Versions: []core.MachineImageVersion{{ExpirableVersion: core.ExpirableVersion{Version: "1.1"}, Architectures: []string{"amd64"}}},
372+
},
373+
{
374+
Name: "image-2",
375+
Versions: []core.MachineImageVersion{{ExpirableVersion: core.ExpirableVersion{Version: "2.0"}, Architectures: []string{"amd64"}}},
376+
},
377+
}
378+
namespacedCloudProfile.Spec.MachineTypes = []core.MachineType{
379+
{Name: "type-2"},
380+
}
381+
Expect(fakeClient.Create(ctx, cloudProfile)).To(Succeed())
382+
383+
Expect(namespacedCloudProfileValidator.Validate(ctx, namespacedCloudProfile, nil)).To(Succeed())
384+
})
345385
})
346386
},
347387
Entry("CloudProfile uses regions only", false),

0 commit comments

Comments
 (0)