Skip to content

feat(json): Removing Last VMClass/Contentlibrary from Namespaces VMServiceSpec using vapi #4012

Open
tribock wants to merge 2 commits into
vmware:mainfrom
tribock:main
Open

feat(json): Removing Last VMClass/Contentlibrary from Namespaces VMServiceSpec using vapi #4012
tribock wants to merge 2 commits into
vmware:mainfrom
tribock:main

Conversation

@tribock
Copy link
Copy Markdown

@tribock tribock commented May 12, 2026

Description

This PR removes the omitempty JSON tag from the namespace field in the request/response model.

The field must always be serialized so the API receives it explicitly.
With omitempty present, zero-value data could be dropped from the payload, which can cause validation or behavior mismatches on the vSphere API side.

Closes: #4010

What changed

Removed omitempty from the target struct tag in the namespace model.
No behavioral logic changes, only JSON serialization behavior.

Impact

Requests now always include the field, even when empty/zero-valued.
Improves API compatibility where explicit presence is required.
Low risk change, scoped to marshaling output only.

How Has This Been Tested?

package main

import (
	"context"
	"fmt"
	"log"
	"net/url"
	"time"

	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/pbm"
	"github.com/vmware/govmomi/pbm/types"
	"github.com/vmware/govmomi/vapi/namespace"

	"github.com/vmware/govmomi/vapi/rest"
)

func main() {
	ctx := context.Background()

	// vCenter connection details (update these with your actual credentials)
	vcenterURL := "myhost"
	username := "user"
	password := "password!"

	storagePolicyName := "sp-vks-global"

	namespaceName := "test-vmss-deletion"
	IngressNet := "10.11.1.0"
	namespaceNet := "192.168.101.0"
	gw := "labnsx01-t0-gw-01"
	provider := "NSXT_CONTAINER_PLUGIN"

	log.Println("=== Creating vSphere Client ===")
	client, err := NewVSphereClient(ctx, vcenterURL, username, password)
	if err != nil {
		log.Fatalf("Failed to create vSphere client: %v", err)
	}

	log.Println("=== Finding Supervisors ===")
	// Find supervisors using the namespace manager
	supervisors, err := client.NamespaceManager.GetSupervisorSummaries(ctx)
	if err != nil {
		log.Fatalf("Failed to list supervisors: %v", err)
	}

	if len(supervisors.Items) == 0 {
		log.Fatalf("No supervisors found")
	}

	if len(supervisors.Items) != 1 {
		log.Fatalf("Expected exactly one supervisor, found %d supervisors:", len(supervisors.Items))
	}

	supervisor := supervisors.Items[0]
	log.Printf("Found exactly one supervisor: %s (ID: %s)", supervisor.Supervisor, supervisor.Info.Name)

	log.Println("=== Creating Namespace Network Specification ===")
	// Define CIDR blocks
	namespaceCidrs := []namespace.CidrBlock{
		{Address: namespaceNet, Prefix: 24},
	}
	ingressCidrs := []namespace.CidrBlock{
		{Address: IngressNet, Prefix: 24},
	}
	// Only on NAT Mode
	// egressCidrs := []namespace.CidrBlock{
	// 	{Address: "10.10.2.0", Prefix: 24},
	// }

	log.Printf("=== Creating Namespace: %s ===", namespaceName)
	description := "Test namespace with network override"

	storagePolicyId, err := getStoragePolicyByName(ctx, client.StorageManager, storagePolicyName)
	if err != nil {
		log.Printf("Warning: Could not find storage policy '%s': %v", storagePolicyName, err)
	}

	namespaceSpec := namespace.NamespaceInstanceCreateSpecV2{
		// Standard govmomi fields
		Namespace:   namespaceName,
		Supervisor:  supervisor.Supervisor,
		Description: &description,
		NamespaceNetwork: &namespace.NamespaceNetworkCreateSpec{
			NetworkProvider: provider, // or another valid network provider
			Network: &namespace.NamespaceNetworkConfig{
				NamespaceNetworkCidrs: namespaceCidrs,
				IngressCidrs:          ingressCidrs,
				// EgressCidrs:           egressCidrs,
				NsxTier0Gateway:    gw, // Update with your actual NSX-T Tier-0 Gateway name
				SubnetPrefixLength: 24,
				RoutedMode:         true,
				LoadBalancerSize:   "SMALL",
			},
		},
		VmServiceSpec: &namespace.VmServiceSpec{
			VmClasses: []string{
				"best-effort-small",
			},
			ContentLibraries: []string{
				"acc85cd4-119f-4e23-bfce-3348fd49498d",
			},
		},
	}

	// Test the extended struct with vSphere API
	err = client.NamespaceManager.CreateNamespaceV2(ctx, namespaceSpec)
	if err != nil {
		log.Printf("Warning: Failed to create namespace: %v", err)
	}

	time.Sleep(10 * time.Second) // Wait for a bit to allow namespace creation to propagate before verification

	// log.Printf("Namespace '%s' created successfully with network override!", namespaceSpec.Namespace)

	// Verify the namespace was created
	log.Println("=== Verifying Namespace Creation ===")
	nsInfo, err := client.NamespaceManager.GetNamespaceV2(ctx, namespaceSpec.Namespace)
	if err != nil {
		log.Printf("Warning: Could not verify namespace creation: %v", err)
	} else {
		verifyNamespace(nsInfo, namespaceSpec)
	}

	// ------------------------------------------------------------------------
	// 	Update Namespace - Remove VmClasse (all policies)
	// ------------------------------------------------------------------------
	log.Println("================================================================================ ")
	log.Println("================================================================================ ")

	// Patch Namespace with empty VmServiceSpec to verify that empty VmServiceSpec is respected and removes all specified VM classes from the namespace
	log.Printf("=== Patching Namespace '%s' with empty VM service spec to remove all VM classes ===", namespaceSpec.Namespace)
	updateSpec := namespace.NamespacesInstanceUpdateSpec{
		VmServiceSpec: namespace.VmServiceSpec{
			VmClasses: []string{},
		},
	}

	err = client.NamespaceManager.UpdateNamespace(ctx, namespaceName, updateSpec)
	if err != nil {
		log.Printf("Warning: Failed to update namespace: %v", err)
	}

	time.Sleep(10 * time.Second) // Wait for a bit to allow namespace creation to propagate before verification

	// Verify the namespace was updated
	log.Println("=== Verifying Namespace Update ===")
	nsInfo, err = client.NamespaceManager.GetNamespaceV2(ctx, namespaceSpec.Namespace)
	if err != nil {
		log.Printf("Warning: Could not verify namespace update: %v", err)
	} else {
		verifyNamespace(nsInfo, namespaceSpec)
	}

	// ------------------------------------------------------------------------
	// Re-Add VmClasse to Namespace
	// ------------------------------------------------------------------------
	log.Println("================================================================================ ")
	log.Println("================================================================================ ")
	// Patch Namespace with a VmServiceSpec to verify that the new VmClasse is added
	// should have same output as the create operation and the VmClasse should be added back to the namespace
	log.Printf("=== Patching Namespace '%s' with old VmServiceSpec ===", namespaceSpec.Namespace)
	updateSpec = namespace.NamespacesInstanceUpdateSpec{
		VmServiceSpec: namespace.VmServiceSpec{
			VmClasses: []string{
				"best-effort-small",
			},
		},
	}

	err = client.NamespaceManager.UpdateNamespace(ctx, namespaceName, updateSpec)
	if err != nil {
		log.Printf("Warning: Failed to update namespace: %v", err)
	}

	time.Sleep(10 * time.Second) // Wait for a bit to allow namespace creation to propagate before verification

	// Verify the namespace was updated
	log.Println("=== Verifying Namespace Update ===")
	nsInfo, err = client.NamespaceManager.GetNamespaceV2(ctx, namespaceSpec.Namespace)
	if err != nil {
		log.Printf("Warning: Could not verify namespace update: %v", err)
	} else {
		verifyNamespace(nsInfo, namespaceSpec)
	}

	// ------------------------------------------------------------------------
	// 	Update Namespace without touching the VmServiceSpec
	// ------------------------------------------------------------------------
	log.Println("================================================================================ ")
	log.Println("================================================================================ ")
	log.Printf("=== Patching Namespace '%s' with new storage spec without touching VmServiceSpec ===", namespaceSpec.Namespace)
	updateSpec = namespace.NamespacesInstanceUpdateSpec{
		StorageSpecs: []namespace.StorageSpec{
			{
				Policy: storagePolicyId.ID,
			},
		},
	}

	err = client.NamespaceManager.UpdateNamespace(ctx, namespaceName, updateSpec)
	if err != nil {
		log.Printf("Warning: Failed to update namespace: %v", err)
	}
	time.Sleep(10 * time.Second) // Wait for a bit to allow namespace creation to propagate before verification
	// Verify the namespace was updated
	log.Println("=== Verifying Namespace Update ===")
	nsInfo, err = client.NamespaceManager.GetNamespaceV2(ctx, namespaceSpec.Namespace)
	if err != nil {
		log.Printf("Warning: Could not verify namespace update: %v", err)
	} else {
		verifyNamespace(nsInfo, namespaceSpec)
	}

	// ------------------------------------------------------------------------
	// 	Update Namespace without touching the VmClasses but by removing the content library
	// ------------------------------------------------------------------------
	log.Println("================================================================================ ")
	log.Println("================================================================================ ")
	log.Printf("=== Patching Namespace '%s' with empty storage spec without touching the VmClasses but by removing the content library ===", namespaceSpec.Namespace)

	err = client.NamespaceManager.UpdateNamespace(ctx, namespaceName, namespace.NamespacesInstanceUpdateSpec{
		StorageSpecs: []namespace.StorageSpec{},
		VmServiceSpec: namespace.VmServiceSpec{
			ContentLibraries: []string{
				// empty to remove all content libraries from the namespace
			},
		},
	})

	if err != nil {
		log.Printf("Warning: Failed to update namespace: %v", err)
	}
	time.Sleep(10 * time.Second) // Wait for a bit to allow namespace creation to propagate before verification
	// Verify the namespace was updated
	log.Println("=== Verifying Namespace Update ===")
	nsInfo, err = client.NamespaceManager.GetNamespaceV2(ctx, namespaceSpec.Namespace)
	if err != nil {
		log.Printf("Warning: Could not verify namespace update: %v", err)
	} else {
		verifyNamespace(nsInfo, namespaceSpec)
	}

	// ------------------------------------------------------------------------
	// 								Cleanup - Delete Namespace
	// ------------------------------------------------------------------------

	// Verify by key press enter to continue to deletion
	fmt.Println("Press Enter to continue to namespace deletion...")
	fmt.Scanln()
	// Cleanup
	log.Printf("=== Deleting Namespace: %s ===", namespaceName)
	err = client.NamespaceManager.DeleteNamespace(ctx, namespaceName)
	if err != nil {
		log.Fatalf("Failed to delete namespace: %v", err)
	}
	log.Printf("Namespace '%s' deleted successfully!", namespaceName)
}

func verifyNamespace(nsInfo namespace.NamespaceInstanceInfoV2, namespaceSpec namespace.NamespaceInstanceCreateSpecV2) {
	log.Printf("Namespace verified after update: %s on supervisor: %s", namespaceSpec.Namespace, nsInfo.Supervisor)
	log.Printf("   Status: %s", nsInfo.ConfigStatus)
	log.Printf("   Policies:")
	if len(nsInfo.StorageSpecs) == 0 {
		log.Printf("   - No storage policies assigned to this namespace")
	} else {
		for _, spec := range nsInfo.StorageSpecs {
			log.Printf("   -  %s", spec.Policy)
		}
	}
	log.Printf("   VM Classes:")
	if len(nsInfo.VmServiceSpec.VmClasses) == 0 {
		log.Printf("   - No VM classes assigned to this namespace")
	} else {
		for _, vmClass := range nsInfo.VmServiceSpec.VmClasses {
			log.Printf("   -  %s", vmClass)
		}
	}
	log.Printf("   Content Libraries:")
	if len(nsInfo.VmServiceSpec.ContentLibraries) == 0 {
		log.Printf("   - No content libraries assigned to this namespace")
	} else {
		for _, contentLibrary := range nsInfo.VmServiceSpec.ContentLibraries {
			log.Printf("   -  %s", contentLibrary)
		}
	}
}

func NewVSphereClient(ctx context.Context, vcenterURL, username, password string) (*VSphereClient, error) {

	client, err := newGovmomiClient(ctx, vcenterURL, username, password)
	if err != nil {
		return nil, fmt.Errorf("failed to create vSphere client: %w", err)
	}

	// Create REST client for namespace management
	restClient := rest.NewClient(client.Client)

	// Login to REST API with the same credentials
	err = restClient.Login(ctx, url.UserPassword(username, password))
	if err != nil {
		return nil, fmt.Errorf("error logging into REST API: %w", err)
	}

	// Get namespace manager
	namespaceManager := namespace.NewManager(restClient)

	storageManager, err := NewStorageManager(client, restClient)
	if err != nil {
		return nil, fmt.Errorf("failed to create storage manager: %w", err)
	}

	return &VSphereClient{
		Client:           client,
		NamespaceManager: namespaceManager,
		StorageManager:   storageManager,
	}, nil
}

type VSphereClient struct {
	Client           *govmomi.Client
	NamespaceManager *namespace.Manager
	StorageManager   *StorageManager
}

func newGovmomiClient(ctx context.Context, vcenterURL, username, password string) (*govmomi.Client, error) {
	vCenterURL := fmt.Sprintf("https://%s:%s@%s/sdk", username, password, vcenterURL)

	// Parse the vCenter URL
	u, err := url.Parse(vCenterURL)
	if err != nil {
		return nil, fmt.Errorf("error parsing URL: %w", err)
	}

	u.User = url.UserPassword(username, password)

	// Create the client
	client, err := govmomi.NewClient(ctx, u, true)
	if err != nil {
		return nil, fmt.Errorf("error creating client: %w", err)
	}
	if !client.IsVC() {
		return nil, fmt.Errorf("not connected to vCenter Server")
	}
	if !client.Client.IsVC() {
		return nil, fmt.Errorf("not connected to vCenter Server")
	}

	return client, nil

}

// NewStorageManager creates a new storage manager
func NewStorageManager(client *govmomi.Client, restClient *rest.Client) (*StorageManager, error) {
	// Create PBM (Policy Based Management) client for storage policies
	pbmClient, err := pbm.NewClient(context.Background(), client.Client)
	if err != nil {
		return nil, fmt.Errorf("failed to create PBM client: %w", err)
	}

	return &StorageManager{
		PBMClient:  pbmClient,
		RestClient: restClient,
	}, nil
}

func listVirtualMachineClasses(ctx context.Context, mgr *namespace.Manager) ([]string, error) {
	// List all virtual machine classes
	vmClasses, err := mgr.ListVmClasses(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list virtual machine classes: %w", err)
	}

	var classNames []string
	for _, class := range vmClasses {
		classNames = append(classNames, class.Id)
	}

	return classNames, nil
}

func (v *VSphereClient) ListNamespaces(ctx context.Context) ([]string, error) {
	// List all supervisor namespaces
	nsList, err := v.NamespaceManager.ListNamespaces(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list namespaces: %w", err)
	}

	var namespaceNames []string
	for _, ns := range nsList {
		namespaceNames = append(namespaceNames, ns.Namespace)
	}

	return namespaceNames, nil
}

type StoragePolicy struct {
	Name        string
	ID          string
	Description string
	ProfileID   string
}

func getStoragePolicyByName(ctx context.Context, storageManager *StorageManager, storagePolicyName string) (*StoragePolicy, error) { // List all storage policies (equivalent to your curl command)
	list, err := storageManager.ListStoragePolicies(ctx)
	if err != nil {
		return nil, fmt.Errorf("error listing storage policies: %v", err)
	}

	storagePolicy := StoragePolicy{}

	for _, policy := range list {
		if policy.Name == storagePolicyName {
			fmt.Printf("Using storage policy: %s (ID: %s)\n", policy.Name, policy.ID)
			storagePolicy = policy
			break
		}

	}

	if storagePolicy.Name == "" {
		fmt.Printf("Storage policy '%s' not found - aborting\n", storagePolicyName)
		return nil, fmt.Errorf("storage policy '%s' not found", storagePolicyName)
	}

	return &storagePolicy, nil
}

// StorageManager handles storage policy operations
type StorageManager struct {
	PBMClient  *pbm.Client
	RestClient *rest.Client
}

// ListStoragePolicies lists all storage policies using PBM client
func (sm *StorageManager) ListStoragePolicies(ctx context.Context) ([]StoragePolicy, error) {
	var policies []StoragePolicy

	// Using PBM client to list storage policies
	pbmPolicies, err := sm.listPBMPolicies(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list storage policies: %w", err)
	}

	policies = append(policies, pbmPolicies...)
	return policies, nil
}

// listPBMPolicies lists storage policies using PBM client
func (sm *StorageManager) listPBMPolicies(ctx context.Context) ([]StoragePolicy, error) {
	var policies []StoragePolicy

	// category := types.PbmProfileCategoryEnumREQUIREMENT
	// Query all storage profiles - using nil for category to get all types
	profileIDs, err := sm.PBMClient.QueryProfile(ctx, types.PbmProfileResourceType{
		ResourceType: string(types.PbmProfileResourceTypeEnumSTORAGE),
	}, "") // Pass empty filter string to get all categories
	if err != nil {
		return nil, fmt.Errorf("failed to query storage profiles: %w", err)
	}

	if len(profileIDs) == 0 {
		fmt.Println("No storage profiles found")
		return policies, nil
	}

	// Retrieve profile content
	profiles, err := sm.PBMClient.RetrieveContent(ctx, profileIDs)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve profile content: %w", err)
	}

	// Convert to our storage policy format
	for _, profileContent := range profiles {
		// Get the base profile interface
		profile := profileContent.GetPbmProfile()
		if profile == nil {
			fmt.Printf("Warning: profile content is nil for profile")
			continue
		}

		// Create our storage policy from the PBM profile
		policy := StoragePolicy{
			ID:          profile.ProfileId.UniqueId,
			Name:        profile.Name,
			Description: profile.Description,
			ProfileID:   profile.ProfileId.UniqueId,
		}
		policies = append(policies, policy)
	}

	return policies, nil
}

Temporary edit on upateFunc to print rendered request:

// UpdateNamespace https://developer.vmware.com/apis/vsphere-automation/v7.0U3/vcenter/api/vcenter/namespaces/instances/namespace/patch/
func (c *Manager) UpdateNamespace(ctx context.Context, namespace string, spec NamespacesInstanceUpdateSpec) error {
	resource := c.Resource(internal.NamespacesPath).WithSubpath(namespace)
	request := resource.Request(http.MethodPatch, spec)
	command, _ := http2curl.GetCurlCommand(request)
	fmt.Println("Equivalent curl command:")
	fmt.Println(command)
	return c.Do(ctx, request, nil)
}

Output

2026/05/12 06:33:25 === Creating vSphere Client ===
2026/05/12 06:33:26 === Finding Supervisors ===
2026/05/12 06:33:26 Found exactly one supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a (ID: cl-lab-e3)
2026/05/12 06:33:26 === Creating Namespace Network Specification ===
2026/05/12 06:33:26 === Creating Namespace: test-vmss-deletion ===
Using storage policy: sp-vks-global (ID: f9e82f63-6988-4139-9b0d-b201740ba943)
2026/05/12 06:33:36 === Verifying Namespace Creation ===
2026/05/12 06:33:36 Namespace verified after update: test-vmss-deletion on supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a
2026/05/12 06:33:36    Status: RUNNING
2026/05/12 06:33:36    Policies:
2026/05/12 06:33:36    - No storage policies assigned to this namespace
2026/05/12 06:33:36    VM Classes:
2026/05/12 06:33:36    -  best-effort-small
2026/05/12 06:33:36    Content Libraries:
2026/05/12 06:33:36    -  acc85cd4-119f-4e23-bfce-3348fd49498d
2026/05/12 06:33:36 ================================================================================ 
2026/05/12 06:33:36 ================================================================================ 
2026/05/12 06:33:36 === Patching Namespace 'test-vmss-deletion' with empty VM service spec to remove all VM classes ===
Equivalent curl command:
curl -X 'PATCH' -d '{"vm_service_spec":{"content_libraries":null,"vm_classes":[]},"storage_specs":null}
' 'https://labvce3.soultec.lab/api/vcenter/namespaces/instances/test-vmss-deletion'
2026/05/12 06:33:46 === Verifying Namespace Update ===
2026/05/12 06:33:47 Namespace verified after update: test-vmss-deletion on supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a
2026/05/12 06:33:47    Status: RUNNING
2026/05/12 06:33:47    Policies:
2026/05/12 06:33:47    - No storage policies assigned to this namespace
2026/05/12 06:33:47    VM Classes:
2026/05/12 06:33:47    - No VM classes assigned to this namespace
2026/05/12 06:33:47    Content Libraries:
2026/05/12 06:33:47    -  acc85cd4-119f-4e23-bfce-3348fd49498d
2026/05/12 06:33:47 ================================================================================ 
2026/05/12 06:33:47 ================================================================================ 
2026/05/12 06:33:47 === Patching Namespace 'test-vmss-deletion' with old VmServiceSpec ===
Equivalent curl command:
curl -X 'PATCH' -d '{"vm_service_spec":{"content_libraries":null,"vm_classes":["best-effort-small"]},"storage_specs":null}
' 'https://labvce3.soultec.lab/api/vcenter/namespaces/instances/test-vmss-deletion'
2026/05/12 06:33:57 === Verifying Namespace Update ===
2026/05/12 06:33:57 Namespace verified after update: test-vmss-deletion on supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a
2026/05/12 06:33:57    Status: RUNNING
2026/05/12 06:33:57    Policies:
2026/05/12 06:33:57    - No storage policies assigned to this namespace
2026/05/12 06:33:57    VM Classes:
2026/05/12 06:33:57    -  best-effort-small
2026/05/12 06:33:57    Content Libraries:
2026/05/12 06:33:57    -  acc85cd4-119f-4e23-bfce-3348fd49498d
2026/05/12 06:33:57 ================================================================================ 
2026/05/12 06:33:57 ================================================================================ 
2026/05/12 06:33:57 === Patching Namespace 'test-vmss-deletion' with new storage spec without touching VmServiceSpec ===
Equivalent curl command:
curl -X 'PATCH' -d '{"vm_service_spec":{"content_libraries":null,"vm_classes":null},"storage_specs":[{"policy":"f9e82f63-6988-4139-9b0d-b201740ba943"}]}
' 'https://labvce3.soultec.lab/api/vcenter/namespaces/instances/test-vmss-deletion'
2026/05/12 06:34:07 === Verifying Namespace Update ===
2026/05/12 06:34:07 Namespace verified after update: test-vmss-deletion on supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a
2026/05/12 06:34:07    Status: RUNNING
2026/05/12 06:34:07    Policies:
2026/05/12 06:34:07    -  f9e82f63-6988-4139-9b0d-b201740ba943
2026/05/12 06:34:07    VM Classes:
2026/05/12 06:34:07    -  best-effort-small
2026/05/12 06:34:07    Content Libraries:
2026/05/12 06:34:07    -  acc85cd4-119f-4e23-bfce-3348fd49498d
2026/05/12 06:34:07 ================================================================================ 
2026/05/12 06:34:07 ================================================================================ 
2026/05/12 06:34:07 === Patching Namespace 'test-vmss-deletion' with empty storage spec without touching the VmClasses but by removing the content library ===
Equivalent curl command:
curl -X 'PATCH' -d '{"vm_service_spec":{"content_libraries":[],"vm_classes":null},"storage_specs":[]}
' 'https://labvce3.soultec.lab/api/vcenter/namespaces/instances/test-vmss-deletion'
2026/05/12 06:34:17 === Verifying Namespace Update ===
2026/05/12 06:34:17 Namespace verified after update: test-vmss-deletion on supervisor: 0b5781e9-9089-4922-b8a6-1f72ac8eb26a
2026/05/12 06:34:17    Status: RUNNING
2026/05/12 06:34:17    Policies:
2026/05/12 06:34:17    - No storage policies assigned to this namespace
2026/05/12 06:34:17    VM Classes:
2026/05/12 06:34:17    -  best-effort-small
2026/05/12 06:34:17    Content Libraries:
2026/05/12 06:34:17    - No content libraries assigned to this namespace
Press Enter to continue to namespace deletion...

2026/05/12 06:34:46 === Deleting Namespace: test-vmss-deletion ===
2026/05/12 06:34:47 Namespace 'test-vmss-deletion' deleted successfully!

lba-soultec and others added 2 commits May 12, 2026 05:58
… it always rendered Signed-off-by: tribock <louis.baumann93@gmail.com>

Signed-off-by: tribock <louis.baumann@soultec.ch>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Removing Last VMClass/Contentlibrary from Namespaces VMServiceSpec using vapi

2 participants