Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion hack/api-reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,76 @@ string
<p>Architecture is the CPU architecture of the machine image</p>
</td>
</tr>
<tr>
<td>
<code>capabilities</code></br>
<em>
<a href="#capabilities">Capabilities</a>
</em>
</td>
<td>
<p>Capabilities of the machine image.</p>
</td>
</tr>

</tbody>
</table>


<h3 id="machineimageflavor">MachineImageFlavor
</h3>


<p>
(<em>Appears on:</em><a href="#machineimageversion">MachineImageVersion</a>)
</p>

<p>
MachineImageFlavor groups all RegionAMIMappings for a specific set of capabilities.
</p>

<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>

<tr>
<td>
<code>regions</code></br>
<em>
<a href="#regionidmapping">RegionIDMapping</a> array
</em>
</td>
<td>
<p>Regions is a mapping to the correct Image ID for the machine image in the supported regions.</p>
</td>
</tr>
<tr>
<td>
<code>image</code></br>
<em>
string
</em>
</td>
<td>
<p>Image is the name of the image.</p>
</td>
</tr>
<tr>
<td>
<code>capabilities</code></br>
<em>
<a href="#capabilities">Capabilities</a>
</em>
</td>
<td>
<p>Capabilities that are supported by the Image ID in this set.</p>
</td>
</tr>

</tbody>
</table>
Expand Down Expand Up @@ -1266,6 +1336,17 @@ string
<p>Regions is an optional mapping to the correct Image ID for the machine image in the supported regions.</p>
</td>
</tr>
<tr>
<td>
<code>capabilityFlavors</code></br>
<em>
<a href="#machineimageflavor">MachineImageFlavor</a> array
</em>
</td>
<td>
<p>CapabilityFlavors is grouping of region AMIs by capabilities.</p>
</td>
</tr>

</tbody>
</table>
Expand Down Expand Up @@ -1635,7 +1716,7 @@ Purpose is a purpose of a resource.


<p>
(<em>Appears on:</em><a href="#machineimageversion">MachineImageVersion</a>)
(<em>Appears on:</em><a href="#machineimageflavor">MachineImageFlavor</a>, <a href="#machineimageversion">MachineImageVersion</a>)
</p>

<p>
Expand Down
85 changes: 85 additions & 0 deletions pkg/admission/mutator/cloudprofile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package mutator

import (
"context"
"fmt"
"slices"

extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"

stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1"
)

// NewCloudProfileMutator returns a new instance of a CloudProfile mutator.
func NewCloudProfileMutator(mgr manager.Manager) extensionswebhook.Mutator {
return &cloudProfile{
client: mgr.GetClient(),
decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(),
}
}

type cloudProfile struct {
client client.Client
decoder runtime.Decoder
}

// Mutate mutates the given CloudProfile object.
func (p *cloudProfile) Mutate(_ context.Context, newObj, _ client.Object) error {
profile, ok := newObj.(*gardencorev1beta1.CloudProfile)
if !ok {
return fmt.Errorf("wrong object type %T", newObj)
}

if profile.DeletionTimestamp != nil || profile.Spec.ProviderConfig == nil || len(profile.Spec.MachineCapabilities) == 0 {
return nil
}

specConfig := &stackitv1alpha1.CloudProfileConfig{}
if _, _, err := p.decoder.Decode(profile.Spec.ProviderConfig.Raw, nil, specConfig); err != nil {
return fmt.Errorf("could not decode providerConfig of cloudProfile for %q: %w", profile.Name, err)
}

overwriteMachineImageCapabilityFlavors(profile, specConfig)
return nil
}

func overwriteMachineImageCapabilityFlavors(profile *gardencorev1beta1.CloudProfile, config *stackitv1alpha1.CloudProfileConfig) {
for _, providerMachineImage := range config.MachineImages {
imageIdx := slices.IndexFunc(profile.Spec.MachineImages, func(mi gardencorev1beta1.MachineImage) bool {
return mi.Name == providerMachineImage.Name
})
if imageIdx == -1 {
continue
}

for _, providerVersion := range providerMachineImage.Versions {
versionIdx := slices.IndexFunc(profile.Spec.MachineImages[imageIdx].Versions, func(miv gardencorev1beta1.MachineImageVersion) bool {
return miv.Version == providerVersion.Version
})
if versionIdx == -1 {
continue
}

profile.Spec.MachineImages[imageIdx].Versions[versionIdx].CapabilityFlavors = convertCapabilityFlavors(providerVersion.CapabilityFlavors)
}
}
}

func convertCapabilityFlavors(providerFlavors []stackitv1alpha1.MachineImageFlavor) []gardencorev1beta1.MachineImageFlavor {
capabilityFlavors := make([]gardencorev1beta1.MachineImageFlavor, 0, len(providerFlavors))
for _, providerFlavor := range providerFlavors {
capabilityFlavors = append(capabilityFlavors, gardencorev1beta1.MachineImageFlavor{
Capabilities: providerFlavor.GetCapabilities(),
})
}
return capabilityFlavors
}
1 change: 1 addition & 0 deletions pkg/admission/mutator/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
Name: Name,
Path: "/webhooks/mutate",
Mutators: map[extensionswebhook.Mutator][]extensionswebhook.Type{
NewCloudProfileMutator(mgr): {{Obj: &gardencorev1beta1.CloudProfile{}}},
NewShootMutator(mgr): {{Obj: &gardencorev1beta1.Shoot{}}},
NewNamespacedCloudProfileMutator(mgr): {{Obj: &gardencorev1beta1.NamespacedCloudProfile{}, Subresource: new("status")}},
},
Expand Down
24 changes: 17 additions & 7 deletions pkg/admission/mutator/namespacedcloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"

"github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/helper"
stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1"
)

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

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

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

statusConfig.MachineImages = mergeMachineImages(specConfig.MachineImages, statusConfig.MachineImages)
uniformSpecConfig := helper.TransformProviderConfigToParentFormat(specConfig, profile.Status.CloudProfileSpec.MachineCapabilities)
statusConfig.MachineImages = mergeMachineImages(uniformSpecConfig.MachineImages, statusConfig.MachineImages)

// Overwrite API endpoints from spec
if specConfig.APIEndpoints != nil {
statusConfig.APIEndpoints = specConfig.APIEndpoints
}

modifiedStatusConfig, err := json.Marshal(statusConfig)
return p.updateProfileStatus(profile, statusConfig)
}

func (p *namespacedCloudProfile) updateProfileStatus(profile *gardencorev1beta1.NamespacedCloudProfile, config *stackitv1alpha1.CloudProfileConfig) error {
modifiedStatusConfig, err := json.Marshal(config)
if err != nil {
return err
return fmt.Errorf("failed to marshal status config: %w", err)
}
profile.Status.CloudProfileSpec.ProviderConfig.Raw = modifiedStatusConfig

return nil
}

func shouldSkipMutation(profile *gardencorev1beta1.NamespacedCloudProfile) bool {
return profile.DeletionTimestamp != nil ||
profile.Generation != profile.Status.ObservedGeneration ||
profile.Spec.ProviderConfig == nil ||
profile.Status.CloudProfileSpec.ProviderConfig == nil
}

func mergeMachineImages(specMachineImages, statusMachineImages []stackitv1alpha1.MachineImages) []stackitv1alpha1.MachineImages {
specImages := utils.CreateMapFromSlice(specMachineImages, func(mi stackitv1alpha1.MachineImages) string { return mi.Name })
statusImages := utils.CreateMapFromSlice(statusMachineImages, func(mi stackitv1alpha1.MachineImages) string { return mi.Name })
Expand Down
8 changes: 7 additions & 1 deletion pkg/admission/validator/cloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"

extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
gardencorev1beta1helper "github.com/gardener/gardener/pkg/api/core/v1beta1/helper"
"github.com/gardener/gardener/pkg/apis/core"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -42,5 +43,10 @@ func (cp *cloudProfile) Validate(_ context.Context, newObj, _ client.Object) err
return err
}

return stackitvalidation.ValidateCloudProfileConfig(cpConfig, cloudProfile.Spec.MachineImages, providerConfigPath).ToAggregate()
capabilityDefinitions, err := gardencorev1beta1helper.ConvertV1beta1CapabilityDefinitions(cloudProfile.Spec.MachineCapabilities)
if err != nil {
return field.InternalError(field.NewPath("spec").Child("machineCapabilities"), err)
}

return stackitvalidation.ValidateCloudProfileConfig(cpConfig, cloudProfile.Spec.MachineImages, capabilityDefinitions, providerConfigPath).ToAggregate()
}
Loading