pkg/utils/utils.go (113 lines of code) (raw):
/*
Portions Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/karpenter-provider-azure/pkg/apis/v1alpha2"
"github.com/Azure/karpenter-provider-azure/pkg/consts"
"github.com/samber/lo"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/cloud-provider-azure/pkg/provider"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// GetVMName parses the provider ID stored on the node to get the vmName
// associated with a node
func GetVMName(providerID string) (string, error) {
// standalone VMs have providerID in the format: azure:///subscriptions/<subscriptionID>/resourceGroups/<resourceGroup>/providers/Microsoft.Compute/virtualMachines/<instanceID>
r := regexp.MustCompile(`azure:///subscriptions/.*/resourceGroups/.*/providers/Microsoft.Compute/virtualMachines/(?P<InstanceID>.*)`)
matches := r.FindStringSubmatch(providerID)
if matches == nil {
return "", fmt.Errorf("parsing vm name %s", providerID)
}
for i, name := range r.SubexpNames() {
if name == "InstanceID" {
return matches[i], nil
}
}
return "", fmt.Errorf("parsing vm name %s", providerID)
}
func ResourceIDToProviderID(ctx context.Context, id string) string {
providerID := fmt.Sprintf("azure://%s", id)
// for historical reasons Azure providerID has the resource group name in lower case
providerIDLowerRG, err := provider.ConvertResourceGroupNameToLower(providerID)
if err != nil {
log.FromContext(ctx).Info(fmt.Sprintf("WARN: Failed to convert resource group name to lower case in providerID %s: %v", providerID, err))
// fallback to original providerID
return providerID
}
return providerIDLowerRG
}
func MkVMID(resourceGroupName string, vmName string) string {
const idFormat = "/subscriptions/subscriptionID/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s"
return fmt.Sprintf(idFormat, resourceGroupName, vmName)
}
// WithDefaultFloat64 returns the float64 value of the supplied environment variable or, if not present,
// the supplied default value. If the float64 conversion fails, returns the default
func WithDefaultFloat64(key string, def float64) float64 {
val, ok := os.LookupEnv(key)
if !ok {
return def
}
f, err := strconv.ParseFloat(val, 64)
if err != nil {
return def
}
return f
}
func ImageReferenceToString(imageRef *armcompute.ImageReference) string {
// Check for Custom Image
if imageRef.ID != nil && *imageRef.ID != "" {
return *imageRef.ID
}
// Check for Community Image
if imageRef.CommunityGalleryImageID != nil && *imageRef.CommunityGalleryImageID != "" {
return *imageRef.CommunityGalleryImageID
}
// Check for Shared Gallery Image
if imageRef.SharedGalleryImageID != nil && *imageRef.SharedGalleryImageID != "" {
return *imageRef.SharedGalleryImageID
}
// Check for Platform Image and use standard string representation
if imageRef.Publisher != nil && imageRef.Offer != nil && imageRef.SKU != nil && imageRef.Version != nil {
// Use the standard format: Publisher:Offer:Sku:Version
return fmt.Sprintf("%s:%s:%s:%s",
*imageRef.Publisher, *imageRef.Offer, *imageRef.SKU, *imageRef.Version)
}
return ""
}
func IsVMDeleting(vm armcompute.VirtualMachine) bool {
if vm.Properties != nil && vm.Properties.ProvisioningState != nil {
return *vm.Properties.ProvisioningState == "Deleting"
}
return false
}
// StringMap returns the string map representation of the resource list
func StringMap(list v1.ResourceList) map[string]string {
if list == nil {
return nil
}
m := make(map[string]string)
for k, v := range list {
m[k.String()] = v.String()
}
return m
}
// PrettySlice truncates a slice after a certain number of max items to ensure
// that the Slice isn't too long
func PrettySlice[T any](s []T, maxItems int) string {
var sb strings.Builder
for i, elem := range s {
if i > maxItems-1 {
fmt.Fprintf(&sb, " and %d other(s)", len(s)-i)
break
} else if i > 0 {
fmt.Fprint(&sb, ", ")
}
fmt.Fprint(&sb, elem)
}
return sb.String()
}
// GetMaxPods resolves what we should set max pods to for a given nodeclass.
// If not specified, defaults based on network-plugin. 30 for "azure", 110 for "kubenet",
// or 250 for "none" and network plugin mode overlay.
func GetMaxPods(nodeClass *v1alpha2.AKSNodeClass, networkPlugin, networkPluginMode string) int32 {
if nodeClass.Spec.MaxPods != nil {
return lo.FromPtr(nodeClass.Spec.MaxPods)
}
switch {
case networkPlugin == consts.NetworkPluginNone:
return consts.DefaultNetPluginNoneMaxPods
case networkPlugin == consts.NetworkPluginAzure && networkPluginMode == consts.NetworkPluginModeOverlay:
return consts.DefaultOverlayMaxPods
case networkPlugin == consts.NetworkPluginAzure && networkPluginMode == consts.NetworkPluginModeNone:
return consts.DefaultNodeSubnetMaxPods
default:
return consts.DefaultKubernetesMaxPods
}
}