Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
43a89b3
patch capabilities to api cloudprofiles
Vincinator Oct 31, 2025
e186b38
test: convert to DescribeTableSubtree
Vincinator Nov 3, 2025
1ba9e7a
feat: admission validation for cloudprofiles with capabilities
Vincinator Nov 3, 2025
d1311d2
test: port cloudprofile validation tests to cap
Vincinator Nov 3, 2025
b792b95
feat: validate machine image arch in NamespacedCloudProfile with capa…
Vincinator Nov 3, 2025
ccb0ff2
test: capabilities enabled test case - override existing machine image
Vincinator Nov 3, 2025
26c5ec2
feat: simulate transformation to check for validation errors
Vincinator Nov 3, 2025
5a26609
refactor: Validate machineImages on original provider config
Vincinator Nov 4, 2025
3529219
test: fix more tests for nscp with capabilities
Vincinator Nov 4, 2025
04d0af3
feat: capability mutator for cloudprofile
Vincinator Nov 4, 2025
6883129
refactor: extract check to shouldSkipMutation function
Vincinator Nov 4, 2025
6226ded
refactor: simplify provider config decoding in namespacedCloudProfile
Vincinator Nov 4, 2025
cb7b005
fix: need to include version.Image in transformed machineImage version
Vincinator Nov 27, 2025
eae34de
feat: simplify provider config decoding in namespaced cloud profile
Vincinator Nov 27, 2025
c6d47bd
fix: typos in comments
Vincinator Nov 27, 2025
af0cb05
test: nscp mutator: should correctly merge extended machineImages usi…
Vincinator Nov 27, 2025
3d1afd4
wip: worker node capability enablement
Vincinator Nov 27, 2025
fa51cb1
make generate
Vincinator Dec 1, 2025
4e275f8
worker node selection with capabilities
Vincinator Dec 1, 2025
91fe95a
clean up: apply feedback from verify pipeline
Vincinator Dec 11, 2025
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
79 changes: 79 additions & 0 deletions hack/api-reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,71 @@ string
<p>Architecture is the CPU architecture of the machine image</p>
</td>
</tr>
<tr>
<td>
<code>capabilities</code></br>
<em>
github.com/gardener/gardener/pkg/apis/core/v1beta1.Capabilities
</em>
</td>
<td>
<p>Capabilities of the machine image.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageFlavor">MachineImageFlavor
</h3>
<p>
(<em>Appears on:</em>
<a href="#openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageVersion">MachineImageVersion</a>)
</p>
<p>
<p>MachineImageFlavor groups all RegionAMIMappings for a specific set of capabilities.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>regions</code></br>
<em>
<a href="#openstack.provider.extensions.gardener.cloud/v1alpha1.RegionIDMapping">
[]RegionIDMapping
</a>
</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>
github.com/gardener/gardener/pkg/apis/core/v1beta1.Capabilities
</em>
</td>
<td>
<p>Capabilities that are supported by the Image ID in this set.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageVersion">MachineImageVersion
Expand Down Expand Up @@ -1129,6 +1194,19 @@ 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="#openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageFlavor">
[]MachineImageFlavor
</a>
</em>
</td>
<td>
<p>CapabilityFlavors is grouping of region AMIs by capabilities.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImages">MachineImages
Expand Down Expand Up @@ -1446,6 +1524,7 @@ string
</h3>
<p>
(<em>Appears on:</em>
<a href="#openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageFlavor">MachineImageFlavor</a>,
<a href="#openstack.provider.extensions.gardener.cloud/v1alpha1.MachineImageVersion">MachineImageVersion</a>)
</p>
<p>
Expand Down
91 changes: 91 additions & 0 deletions pkg/admission/mutator/cloudprofile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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"

"github.com/gardener/gardener-extension-provider-openstack/pkg/apis/openstack/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)
}

// Skip mutation if CloudProfile is being deleted or when no capabilities used in that profile
if profile.DeletionTimestamp != nil || profile.Spec.ProviderConfig == nil || len(profile.Spec.MachineCapabilities) == 0 {
return nil
}

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

overwriteMachineImageCapabilityFlavors(profile, specConfig)
return nil
}

// overwriteMachineImageCapabilityFlavors updates the capability flavors of machine images in the CloudProfile
func overwriteMachineImageCapabilityFlavors(profile *gardencorev1beta1.CloudProfile, config *v1alpha1.CloudProfileConfig) {
for _, providerMachineImage := range config.MachineImages {
// Find the corresponding machine image in the CloudProfile
imageIdx := slices.IndexFunc(profile.Spec.MachineImages, func(mi gardencorev1beta1.MachineImage) bool {
return mi.Name == providerMachineImage.Name
})
if imageIdx == -1 {
continue
}

// Iterate over versions in the provider's machine image
for _, providerVersion := range providerMachineImage.Versions {
// Find the corresponding version in the CloudProfile's machine image
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)
}
}
}

// convertCapabilityFlavors converts provider capability flavors to CloudProfile capability flavors
func convertCapabilityFlavors(providerFlavors []v1alpha1.MachineImageFlavor) []gardencorev1beta1.MachineImageFlavor {
capabilityFlavors := make([]gardencorev1beta1.MachineImageFlavor, 0, len(providerFlavors))
for _, providerFlavor := range providerFlavors {
capabilityFlavors = append(capabilityFlavors, gardencorev1beta1.MachineImageFlavor{
Capabilities: providerFlavor.GetCapabilities(),
})
}
return capabilityFlavors
}
Loading
Loading