From a279a7856a7c631c09d56d38818af3bad7dfaa00 Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Thu, 22 Jan 2026 14:35:59 +0100 Subject: [PATCH 1/3] refactor code into multiple files Signed-off-by: Felix Breuer --- pkg/client/helper.go | 57 +++ pkg/{provider/sdk_client.go => client/sdk.go} | 6 +- .../sdk_client_test.go => client/sdk_test.go} | 2 +- .../stackit_client.go => client/stackit.go} | 7 +- pkg/provider/core.go | 476 ------------------ pkg/provider/core_mocks_test.go | 31 +- pkg/provider/create.go | 273 ++++++++++ ...ine_basic_test.go => create_basic_test.go} | 9 +- ...e_config_test.go => create_config_test.go} | 73 +-- ...king_test.go => create_networking_test.go} | 73 +-- ...storage_test.go => create_storage_test.go} | 31 +- ...erdata_test.go => create_userdata_test.go} | 31 +- pkg/provider/delete.go | 91 ++++ ..._delete_machine_test.go => delete_test.go} | 3 +- pkg/provider/helpers.go | 58 --- pkg/provider/list.go | 75 +++ ...ore_list_machines_test.go => list_test.go} | 15 +- pkg/provider/provider.go | 11 +- pkg/provider/status.go | 84 ++++ ..._machine_status_test.go => status_test.go} | 15 +- 20 files changed, 734 insertions(+), 687 deletions(-) create mode 100644 pkg/client/helper.go rename pkg/{provider/sdk_client.go => client/sdk.go} (98%) rename pkg/{provider/sdk_client_test.go => client/sdk_test.go} (99%) rename pkg/{provider/stackit_client.go => client/stackit.go} (95%) create mode 100644 pkg/provider/create.go rename pkg/provider/{core_create_machine_basic_test.go => create_basic_test.go} (95%) rename pkg/provider/{core_create_machine_config_test.go => create_config_test.go} (84%) rename pkg/provider/{core_create_machine_networking_test.go => create_networking_test.go} (90%) rename pkg/provider/{core_create_machine_storage_test.go => create_storage_test.go} (89%) rename pkg/provider/{core_create_machine_userdata_test.go => create_userdata_test.go} (87%) create mode 100644 pkg/provider/delete.go rename pkg/provider/{core_delete_machine_test.go => delete_test.go} (96%) create mode 100644 pkg/provider/list.go rename pkg/provider/{core_list_machines_test.go => list_test.go} (91%) create mode 100644 pkg/provider/status.go rename pkg/provider/{core_get_machine_status_test.go => status_test.go} (93%) diff --git a/pkg/client/helper.go b/pkg/client/helper.go new file mode 100644 index 00000000..4ca29941 --- /dev/null +++ b/pkg/client/helper.go @@ -0,0 +1,57 @@ +package client + +// ptr returns a pointer to the given value +// This helper is needed because the STACKIT SDK uses pointers for optional fields +func ptr[T any](v T) *T { + return &v +} + +// convertLabelsToSDK converts map[string]string to *map[string]interface{} for SDK +// +//nolint:gocritic // SDK requires *map +func convertLabelsToSDK(labels map[string]string) *map[string]interface{} { + if labels == nil { + return nil + } + + result := make(map[string]interface{}, len(labels)) + for k, v := range labels { + result[k] = v + } + return &result +} + +// convertLabelsFromSDK converts *map[string]interface{} from SDK to map[string]string +// +//nolint:gocritic // SDK requires *map +func convertLabelsFromSDK(labels *map[string]interface{}) map[string]string { + if labels == nil { + return nil + } + + result := make(map[string]string, len(*labels)) + for k, v := range *labels { + if strVal, ok := v.(string); ok { + result[k] = strVal + } + } + return result +} + +// convertStringSliceToSDK converts []string to *[]string for SDK +func convertStringSliceToSDK(slice []string) *[]string { + if slice == nil { + return nil + } + return &slice +} + +// convertMetadataToSDK converts map[string]interface{} to *map[string]interface{} for SDK +// +//nolint:gocritic // SDK requires *map +func convertMetadataToSDK(metadata map[string]interface{}) *map[string]interface{} { + if metadata == nil { + return nil + } + return &metadata +} diff --git a/pkg/provider/sdk_client.go b/pkg/client/sdk.go similarity index 98% rename from pkg/provider/sdk_client.go rename to pkg/client/sdk.go index e63c0cc5..4584d665 100644 --- a/pkg/provider/sdk_client.go +++ b/pkg/client/sdk.go @@ -1,8 +1,4 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -package provider +package client import ( "context" diff --git a/pkg/provider/sdk_client_test.go b/pkg/client/sdk_test.go similarity index 99% rename from pkg/provider/sdk_client_test.go rename to pkg/client/sdk_test.go index 3b1da0a8..7ea5f403 100644 --- a/pkg/provider/sdk_client_test.go +++ b/pkg/client/sdk_test.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package provider +package client import ( "errors" diff --git a/pkg/provider/stackit_client.go b/pkg/client/stackit.go similarity index 95% rename from pkg/provider/stackit_client.go rename to pkg/client/stackit.go index 0fe44be9..ad83be08 100644 --- a/pkg/provider/stackit_client.go +++ b/pkg/client/stackit.go @@ -1,8 +1,4 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -package provider +package client import ( "context" @@ -18,7 +14,6 @@ import ( // // Note: region parameter is required by STACKIT SDK v1.0.0+ // It must be extracted from the Secret (e.g., "eu01-1", "eu01-2") -// nolint:dupl // the duplicates are mock functions type StackitClient interface { // CreateServer creates a new server in STACKIT CreateServer(ctx context.Context, projectID, region string, req *CreateServerRequest) (*Server, error) diff --git a/pkg/provider/core.go b/pkg/provider/core.go index 0dcad840..f4f8cab4 100644 --- a/pkg/provider/core.go +++ b/pkg/provider/core.go @@ -7,17 +7,10 @@ package provider import ( "context" - "encoding/base64" - "errors" - "fmt" - "slices" - "strings" "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" - api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" - "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis/validation" "k8s.io/klog/v2" ) @@ -28,475 +21,6 @@ const ( StackitRoleLabel = "mcm.gardener.cloud/role" ) -// CreateMachine handles a machine creation request by creating a STACKIT server -// -// This method creates a new server in STACKIT infrastructure based on the ProviderSpec -// configuration in the MachineClass. It assigns MCM-specific labels to the server for -// tracking and orphan VM detection. -// -// Returns: -// - ProviderID: Unique identifier in format "stackit:///" -// - NodeName: Name that the VM will register with in Kubernetes (matches Machine name) -// -// Error codes: -// - InvalidArgument: Invalid ProviderSpec or missing required fields -// - Internal: Failed to create server or communicate with STACKIT API -// -//nolint:gocyclo,funlen//TODO:refactor -func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineRequest) (*driver.CreateMachineResponse, error) { - // Log messages to track request - klog.V(2).Infof("Machine creation request has been received for %q", req.Machine.Name) - defer klog.V(2).Infof("Machine creation request has been processed for %q", req.Machine.Name) - - // Check if incoming provider in the MachineClass is a provider we support - if req.MachineClass.Provider != StackitProviderName { - err := fmt.Errorf("requested for Provider '%s', we only support '%s'", req.MachineClass.Provider, StackitProviderName) - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - // Decode ProviderSpec from MachineClass - providerSpec, err := decodeProviderSpec(req.MachineClass) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - // Validate ProviderSpec and Secret - validationErrs := validation.ValidateProviderSpecNSecret(providerSpec, req.Secret) - if len(validationErrs) > 0 { - return nil, status.Error(codes.InvalidArgument, validationErrs[0].Error()) - } - - // Extract credentials from Secret - projectID := string(req.Secret.Data["project-id"]) - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) - - // Initialize client on first use (lazy initialization) - if err := p.ensureClient(serviceAccountKey); err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) - } - - // Build labels: merge ProviderSpec labels with MCM-specific labels - labels := make(map[string]string) - // Start with user-provided labels from ProviderSpec - if providerSpec.Labels != nil { - for k, v := range providerSpec.Labels { - labels[k] = v - } - } - // Add MCM-specific labels for server identification and orphan VM detection - labels[StackitMachineLabel] = req.Machine.Name - labels[StackitMachineClassLabel] = req.MachineClass.Name - labels[StackitRoleLabel] = "node" - - // Create server request - createReq := &CreateServerRequest{ - Name: req.Machine.Name, - MachineType: providerSpec.MachineType, - ImageID: providerSpec.ImageID, - Labels: labels, - } - - // Add networking configuration (required in v2 API) - // If not specified in ProviderSpec, try to use networkId from Secret, or use empty - if providerSpec.Networking != nil { - createReq.Networking = &ServerNetworkingRequest{ - NetworkID: providerSpec.Networking.NetworkID, - NICIDs: providerSpec.Networking.NICIDs, - } - } else { - // v2 API requires networking field - use networkId from Secret if available - // This allows tests/deployments to specify a default network without modifying each MachineClass - networkID := string(req.Secret.Data["networkId"]) - createReq.Networking = &ServerNetworkingRequest{ - NetworkID: networkID, // Can be empty string if not in Secret - } - } - - // Add security groups if specified - if len(providerSpec.SecurityGroups) > 0 { - createReq.SecurityGroups = providerSpec.SecurityGroups - } - - // Add userData for VM bootstrapping - // Priority: ProviderSpec.UserData > Secret.userData - // Note: IAAS API requires base64-encoded userData (OpenAPI spec: format=byte) - var userDataPlain string - if providerSpec.UserData != "" { - userDataPlain = providerSpec.UserData - } else if userData, ok := req.Secret.Data["userData"]; ok && len(userData) > 0 { - userDataPlain = string(userData) - } - - if userDataPlain != "" { - createReq.UserData = base64.StdEncoding.EncodeToString([]byte(userDataPlain)) - } - - // Add boot volume configuration if specified - if providerSpec.BootVolume != nil { - createReq.BootVolume = &BootVolumeRequest{ - DeleteOnTermination: providerSpec.BootVolume.DeleteOnTermination, - PerformanceClass: providerSpec.BootVolume.PerformanceClass, - Size: providerSpec.BootVolume.Size, - } - - // Add boot volume source if specified - if providerSpec.BootVolume.Source != nil { - createReq.BootVolume.Source = &BootVolumeSourceRequest{ - Type: providerSpec.BootVolume.Source.Type, - ID: providerSpec.BootVolume.Source.ID, - } - } - } - - // Add additional volumes if specified - if len(providerSpec.Volumes) > 0 { - createReq.Volumes = providerSpec.Volumes - } - - // Add keypair name if specified - if providerSpec.KeypairName != "" { - createReq.KeypairName = providerSpec.KeypairName - } - - // Add availability zone if specified - if providerSpec.AvailabilityZone != "" { - createReq.AvailabilityZone = providerSpec.AvailabilityZone - } - - // Add affinity group if specified - if providerSpec.AffinityGroup != "" { - createReq.AffinityGroup = providerSpec.AffinityGroup - } - - // Add service account mails if specified - if len(providerSpec.ServiceAccountMails) > 0 { - createReq.ServiceAccountMails = providerSpec.ServiceAccountMails - } - - // Add agent configuration if specified - if providerSpec.Agent != nil { - createReq.Agent = &AgentRequest{ - Provisioned: providerSpec.Agent.Provisioned, - } - } - - // Add metadata if specified - if len(providerSpec.Metadata) > 0 { - createReq.Metadata = providerSpec.Metadata - } - - // check if server already exists - server, err := p.getServerByName(ctx, projectID, providerSpec.Region, req.Machine.Name) - if err != nil { - klog.Errorf("Failed to fetch server for machine %q: %v", req.Machine.Name, err) - return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to fetch server: %v", err)) - } - - if server == nil { - // Call STACKIT API to create server - server, err = p.client.CreateServer(ctx, projectID, providerSpec.Region, createReq) - if err != nil { - klog.Errorf("Failed to create server for machine %q: %v", req.Machine.Name, err) - return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to create server: %v", err)) - } - } - - if err := p.patchNetworkInterface(ctx, projectID, server.ID, providerSpec); err != nil { - klog.Errorf("Failed to patch network interface for server %q: %v", req.Machine.Name, err) - return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to patch network interface for server: %v", err)) - } - - // Generate ProviderID in format: stackit:/// - providerID := fmt.Sprintf("%s://%s/%s", StackitProviderName, projectID, server.ID) - klog.V(2).Infof("Successfully created server %q with ID %q for machine %q", server.Name, server.ID, req.Machine.Name) - - return &driver.CreateMachineResponse{ - ProviderID: providerID, - NodeName: req.Machine.Name, - }, nil -} - -func (p *Provider) getServerByName(ctx context.Context, projectID, region, serverName string) (*Server, error) { - // Check if the server got already created - labelSelector := map[string]string{ - StackitMachineLabel: serverName, - } - servers, err := p.client.ListServers(ctx, projectID, region, labelSelector) - if err != nil { - return nil, fmt.Errorf("SDK ListServers with labelSelector: %v failed: %w", labelSelector, err) - } - - if len(servers) > 1 { - return nil, fmt.Errorf("%v servers found for server name %v", len(servers), serverName) - } - - if len(servers) == 1 { - return servers[0], nil - } - - // no servers found len == 0 - return nil, nil -} - -func (p *Provider) patchNetworkInterface(ctx context.Context, projectID, serverID string, providerSpec *api.ProviderSpec) error { - if len(providerSpec.AllowedAddresses) == 0 { - return nil - } - - nics, err := p.client.GetNICsForServer(ctx, projectID, providerSpec.Region, serverID) - if err != nil { - return fmt.Errorf("failed to get NICs for server %q: %w", serverID, err) - } - - if len(nics) == 0 { - return fmt.Errorf("failed to find NIC for server %q", serverID) - } - - for _, nic := range nics { - // if networking is not set, server is inside the default network - // just patch the interface since the server should only have one - if providerSpec.Networking != nil { - // only process interfaces that are either in the configured network (NetworkID) or are defined in NICIDs - if providerSpec.Networking.NetworkID != nic.NetworkID && !slices.Contains(providerSpec.Networking.NICIDs, nic.ID) { - continue - } - } - - updateNic := false - // check if every cidr in providerspec.allowedAddresses is inside the nic allowedAddresses - for _, allowedAddress := range providerSpec.AllowedAddresses { - if !slices.Contains(nic.AllowedAddresses, allowedAddress) { - nic.AllowedAddresses = append(nic.AllowedAddresses, allowedAddress) - updateNic = true - } - } - - if !updateNic { - continue - } - - if _, err := p.client.UpdateNIC(ctx, projectID, providerSpec.Region, nic.NetworkID, nic.ID, nic.AllowedAddresses); err != nil { - return fmt.Errorf("failed to update allowed addresses for NIC %s: %w", nic.ID, err) - } - - klog.V(2).Infof("Updated allowed addresses for NIC %s to %v", nic.ID, nic.AllowedAddresses) - } - - return nil -} - -// DeleteMachine handles a machine deletion request by deleting the STACKIT server -// -// This method deletes the server identified by the ProviderID from STACKIT infrastructure. -// It is idempotent - if the server is already deleted (404), it returns success. -// -// Error codes: -// - InvalidArgument: Missing or invalid ProviderID -// - Internal: Failed to delete server or communicate with STACKIT API -func (p *Provider) DeleteMachine(ctx context.Context, req *driver.DeleteMachineRequest) (*driver.DeleteMachineResponse, error) { - // Log messages to track delete request - klog.V(2).Infof("Machine deletion request has been received for %q", req.Machine.Name) - defer klog.V(2).Infof("Machine deletion request has been processed for %q", req.Machine.Name) - - // Extract credentials from Secret - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) - // Initialize client on first use (lazy initialization) - if err := p.ensureClient(serviceAccountKey); err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) - } - - var projectID, serverID string - var err error - if req.Machine.Spec.ProviderID != "" { - if !strings.HasPrefix(req.Machine.Spec.ProviderID, StackitProviderName) { - return nil, status.Error(codes.InvalidArgument, "providerID is not empty and does not start with stackit://") - } - - // Parse ProviderID to extract projectID and serverID - projectID, serverID, err = parseProviderID(req.Machine.Spec.ProviderID) - if err != nil { - klog.V(2).Infof("invalid ProviderID format: %v", err) - } - } - - if projectID == "" { - projectID = string(req.Secret.Data["project-id"]) - } - - providerSpec, err := decodeProviderSpec(req.MachineClass) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - if serverID == "" { - server, err := p.getServerByName(ctx, projectID, providerSpec.Region, req.Machine.Name) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to find server by name: %v", err)) - } - - if server != nil { - serverID = server.ID - } - } - - if serverID == "" { - klog.V(2).Infof("Server is already deleted for machine %q", req.Machine.Name) - return &driver.DeleteMachineResponse{}, nil - } - - // Call STACKIT API to delete server - err = p.client.DeleteServer(ctx, projectID, providerSpec.Region, serverID) - if err != nil { - // Check if server was not found (404) - this is OK for idempotency - if errors.Is(err, ErrServerNotFound) { - klog.V(2).Infof("Server %q already deleted for machine %q (idempotent)", serverID, req.Machine.Name) - return &driver.DeleteMachineResponse{}, nil - } - // All other errors are internal errors - klog.Errorf("Failed to delete server for machine %q: %v", req.Machine.Name, err) - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to delete server: %v", err)) - } - - klog.V(2).Infof("Successfully deleted server %q for machine %q", serverID, req.Machine.Name) - - return &driver.DeleteMachineResponse{}, nil -} - -// GetMachineStatus retrieves the current status of a STACKIT server -// -// This method queries STACKIT API to get the current state of the server identified -// by the Machine's ProviderID. If the ProviderID is empty (machine not created yet) -// or the server doesn't exist, it returns NotFound error. -// -// Returns: -// - ProviderID: The machine's ProviderID -// - NodeName: Name that the VM registered with in Kubernetes -// -// Error codes: -// - NotFound: Machine has no ProviderID yet, or server not found in STACKIT -// - InvalidArgument: Invalid ProviderID format -// - Internal: Failed to get server status or communicate with STACKIT API -func (p *Provider) GetMachineStatus(ctx context.Context, req *driver.GetMachineStatusRequest) (*driver.GetMachineStatusResponse, error) { - // Log messages to track start and end of request - klog.V(2).Infof("Get request has been received for %q", req.Machine.Name) - defer klog.V(2).Infof("Machine get request has been processed successfully for %q", req.Machine.Name) - - // When ProviderID is empty, the machine doesn't exist yet - // Return NotFound so MCM knows to call CreateMachine - if req.Machine.Spec.ProviderID == "" { - klog.V(2).Infof("Machine %q has no ProviderID, returning NotFound", req.Machine.Name) - return nil, status.Error(codes.NotFound, "machine does not have a ProviderID yet") - } - - // Extract credentials from Secret - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) - - // Initialize client on first use (lazy initialization) - if err := p.ensureClient(serviceAccountKey); err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) - } - - // Parse ProviderID to extract projectID and serverID - // Expected format: stackit:/// - projectID, serverID, err := parseProviderID(req.Machine.Spec.ProviderID) - if projectID == "" { - projectID = string(req.Secret.Data["project-id"]) - } - if err != nil { - return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid ProviderID format: %v", err)) - } - - // Decode ProviderSpec from MachineClass - providerSpec, err := decodeProviderSpec(req.MachineClass) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - // Call STACKIT API to get server status - server, err := p.client.GetServer(ctx, projectID, providerSpec.Region, serverID) - if err != nil { - // Check if server was not found (404) - if errors.Is(err, ErrServerNotFound) { - klog.V(2).Infof("Server %q not found for machine %q", serverID, req.Machine.Name) - return nil, status.Error(codes.NotFound, fmt.Sprintf("server %q not found", serverID)) - } - // All other errors are internal errors - klog.Errorf("Failed to get server status for machine %q: %v", req.Machine.Name, err) - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get server status: %v", err)) - } - - klog.V(2).Infof("Retrieved server status for machine %q: status=%s", req.Machine.Name, server.Status) - - return &driver.GetMachineStatusResponse{ - ProviderID: req.Machine.Spec.ProviderID, - NodeName: req.Machine.Name, - }, nil -} - -// ListMachines lists all STACKIT servers that belong to the specified MachineClass -// -// This method retrieves all servers in the STACKIT project and filters them based on -// the "mcm.gardener.cloud/machineclass" label. This enables the MCM safety controller -// to detect and clean up orphan VMs that are not backed by Machine CRs. -// -// Returns: -// - MachineList: Map of ProviderID to MachineName for all servers matching the MachineClass -// -// Error codes: -// - Internal: Failed to list servers or communicate with STACKIT API -func (p *Provider) ListMachines(ctx context.Context, req *driver.ListMachinesRequest) (*driver.ListMachinesResponse, error) { - // Log messages to track start and end of request - klog.V(2).Infof("List machines request has been received for %q", req.MachineClass.Name) - defer klog.V(2).Infof("List machines request has been processed for %q", req.MachineClass.Name) - - // Extract credentials from Secret - projectID := string(req.Secret.Data["project-id"]) - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) - - // Initialize client on first use (lazy initialization) - if err := p.ensureClient(serviceAccountKey); err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) - } - - // Decode ProviderSpec from MachineClass - providerSpec, err := decodeProviderSpec(req.MachineClass) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - // Call STACKIT API to list all servers - labelSelector := map[string]string{ - StackitMachineClassLabel: req.MachineClass.Name, - } - servers, err := p.client.ListServers(ctx, projectID, providerSpec.Region, labelSelector) - if err != nil { - klog.Errorf("Failed to list servers for MachineClass %q: %v", req.MachineClass.Name, err) - return nil, status.Error(codes.Internal, fmt.Sprintf("failed to list servers: %v", err)) - } - - // Filter servers by MachineClass label - // We use the "mcm.gardener.cloud/machineclass" label to identify which servers belong to this MachineClass - machineList := make(map[string]string) - for _, server := range servers { - // Generate ProviderID in format: stackit:/// - providerID := fmt.Sprintf("stackit://%s/%s", projectID, server.ID) - - // Get machine name from labels (fallback to server name if not found) - machineName := server.Name - if machineLabel, ok := server.Labels[StackitMachineLabel]; ok { - machineName = machineLabel - } - - machineList[providerID] = machineName - } - - klog.V(2).Infof("Found %d machines for MachineClass %q", len(machineList), req.MachineClass.Name) - - return &driver.ListMachinesResponse{ - MachineList: machineList, - }, nil -} - // GetVolumeIDs extracts volume IDs from PersistentVolume specs // // This method is used by MCM to get volume IDs for persistent volumes. diff --git a/pkg/provider/core_mocks_test.go b/pkg/provider/core_mocks_test.go index ce4388c8..8c0af24f 100644 --- a/pkg/provider/core_mocks_test.go +++ b/pkg/provider/core_mocks_test.go @@ -7,36 +7,37 @@ package provider import ( "context" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" ) // mockStackitClient is a mock implementation of StackitClient for testing // Note: Single-tenant design - each client is bound to one set of credentials type mockStackitClient struct { - createServerFunc func(ctx context.Context, projectID, region string, req *CreateServerRequest) (*Server, error) - getServerFunc func(ctx context.Context, projectID, region, serverID string) (*Server, error) + createServerFunc func(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) + getServerFunc func(ctx context.Context, projectID, region, serverID string) (*client.Server, error) deleteServerFunc func(ctx context.Context, projectID, region, serverID string) error - listServersFunc func(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*Server, error) - getNICsFunc func(ctx context.Context, projectID, region, serverID string) ([]*NIC, error) - updateNICFunc func(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*NIC, error) + listServersFunc func(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) + getNICsFunc func(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) + updateNICFunc func(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) } -func (m *mockStackitClient) CreateServer(ctx context.Context, projectID, region string, req *CreateServerRequest) (*Server, error) { +func (m *mockStackitClient) CreateServer(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) { if m.createServerFunc != nil { return m.createServerFunc(ctx, projectID, region, req) } - return &Server{ + return &client.Server{ ID: "550e8400-e29b-41d4-a716-446655440000", Name: req.Name, Status: "CREATING", }, nil } -func (m *mockStackitClient) GetServer(ctx context.Context, projectID, region, serverID string) (*Server, error) { +func (m *mockStackitClient) GetServer(ctx context.Context, projectID, region, serverID string) (*client.Server, error) { if m.getServerFunc != nil { return m.getServerFunc(ctx, projectID, region, serverID) } - return &Server{ + return &client.Server{ ID: serverID, Name: "test-machine", Status: "ACTIVE", @@ -50,25 +51,25 @@ func (m *mockStackitClient) DeleteServer(ctx context.Context, projectID, region, return nil } -func (m *mockStackitClient) ListServers(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*Server, error) { +func (m *mockStackitClient) ListServers(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) { if m.listServersFunc != nil { return m.listServersFunc(ctx, projectID, region, labelSelector) } - return []*Server{}, nil + return []*client.Server{}, nil } -func (m *mockStackitClient) GetNICsForServer(ctx context.Context, projectID, region, serverID string) ([]*NIC, error) { +func (m *mockStackitClient) GetNICsForServer(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) { if m.getNICsFunc != nil { return m.getNICsFunc(ctx, projectID, region, serverID) } - return []*NIC{}, nil + return []*client.NIC{}, nil } -func (m *mockStackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*NIC, error) { +func (m *mockStackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) { if m.updateNICFunc != nil { return m.updateNICFunc(ctx, projectID, region, networkID, nicID, allowedAddresses) } - return &NIC{}, nil + return &client.NIC{}, nil } // UpdateNIC updates a network interface diff --git a/pkg/provider/create.go b/pkg/provider/create.go new file mode 100644 index 00000000..ad6fdc01 --- /dev/null +++ b/pkg/provider/create.go @@ -0,0 +1,273 @@ +package provider + +import ( + "context" + "encoding/base64" + "fmt" + "slices" + + "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis/validation" + "k8s.io/klog/v2" +) + +// CreateMachine handles a machine creation request by creating a STACKIT server +// +// This method creates a new server in STACKIT infrastructure based on the ProviderSpec +// configuration in the MachineClass. It assigns MCM-specific labels to the server for +// tracking and orphan VM detection. +// +// Returns: +// - ProviderID: Unique identifier in format "stackit:///" +// - NodeName: Name that the VM will register with in Kubernetes (matches Machine name) +// +// Error codes: +// - InvalidArgument: Invalid ProviderSpec or missing required fields +// - Internal: Failed to create server or communicate with STACKIT API +// +//nolint:gocyclo,funlen//TODO:refactor +func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineRequest) (*driver.CreateMachineResponse, error) { + // Log messages to track request + klog.V(2).Infof("Machine creation request has been received for %q", req.Machine.Name) + defer klog.V(2).Infof("Machine creation request has been processed for %q", req.Machine.Name) + + // Check if incoming provider in the MachineClass is a provider we support + if req.MachineClass.Provider != StackitProviderName { + err := fmt.Errorf("requested for Provider '%s', we only support '%s'", req.MachineClass.Provider, StackitProviderName) + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // Decode ProviderSpec from MachineClass + providerSpec, err := decodeProviderSpec(req.MachineClass) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + // Validate ProviderSpec and Secret + validationErrs := validation.ValidateProviderSpecNSecret(providerSpec, req.Secret) + if len(validationErrs) > 0 { + return nil, status.Error(codes.InvalidArgument, validationErrs[0].Error()) + } + + // Extract credentials from Secret + projectID := string(req.Secret.Data["project-id"]) + serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + + // Initialize client on first use (lazy initialization) + if err := p.ensureClient(serviceAccountKey); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) + } + + // Build labels: merge ProviderSpec labels with MCM-specific labels + labels := make(map[string]string) + // Start with user-provided labels from ProviderSpec + if providerSpec.Labels != nil { + for k, v := range providerSpec.Labels { + labels[k] = v + } + } + // Add MCM-specific labels for server identification and orphan VM detection + labels[StackitMachineLabel] = req.Machine.Name + labels[StackitMachineClassLabel] = req.MachineClass.Name + labels[StackitRoleLabel] = "node" + + // Create server request + createReq := &client.CreateServerRequest{ + Name: req.Machine.Name, + MachineType: providerSpec.MachineType, + ImageID: providerSpec.ImageID, + Labels: labels, + } + + // Add networking configuration (required in v2 API) + // If not specified in ProviderSpec, try to use networkId from Secret, or use empty + if providerSpec.Networking != nil { + createReq.Networking = &client.ServerNetworkingRequest{ + NetworkID: providerSpec.Networking.NetworkID, + NICIDs: providerSpec.Networking.NICIDs, + } + } else { + // v2 API requires networking field - use networkId from Secret if available + // This allows tests/deployments to specify a default network without modifying each MachineClass + networkID := string(req.Secret.Data["networkId"]) + createReq.Networking = &client.ServerNetworkingRequest{ + NetworkID: networkID, // Can be empty string if not in Secret + } + } + + // Add security groups if specified + if len(providerSpec.SecurityGroups) > 0 { + createReq.SecurityGroups = providerSpec.SecurityGroups + } + + // Add userData for VM bootstrapping + // Priority: ProviderSpec.UserData > Secret.userData + // Note: IAAS API requires base64-encoded userData (OpenAPI spec: format=byte) + var userDataPlain string + if providerSpec.UserData != "" { + userDataPlain = providerSpec.UserData + } else if userData, ok := req.Secret.Data["userData"]; ok && len(userData) > 0 { + userDataPlain = string(userData) + } + + if userDataPlain != "" { + createReq.UserData = base64.StdEncoding.EncodeToString([]byte(userDataPlain)) + } + + // Add boot volume configuration if specified + if providerSpec.BootVolume != nil { + createReq.BootVolume = &client.BootVolumeRequest{ + DeleteOnTermination: providerSpec.BootVolume.DeleteOnTermination, + PerformanceClass: providerSpec.BootVolume.PerformanceClass, + Size: providerSpec.BootVolume.Size, + } + + // Add boot volume source if specified + if providerSpec.BootVolume.Source != nil { + createReq.BootVolume.Source = &client.BootVolumeSourceRequest{ + Type: providerSpec.BootVolume.Source.Type, + ID: providerSpec.BootVolume.Source.ID, + } + } + } + + // Add additional volumes if specified + if len(providerSpec.Volumes) > 0 { + createReq.Volumes = providerSpec.Volumes + } + + // Add keypair name if specified + if providerSpec.KeypairName != "" { + createReq.KeypairName = providerSpec.KeypairName + } + + // Add availability zone if specified + if providerSpec.AvailabilityZone != "" { + createReq.AvailabilityZone = providerSpec.AvailabilityZone + } + + // Add affinity group if specified + if providerSpec.AffinityGroup != "" { + createReq.AffinityGroup = providerSpec.AffinityGroup + } + + // Add service account mails if specified + if len(providerSpec.ServiceAccountMails) > 0 { + createReq.ServiceAccountMails = providerSpec.ServiceAccountMails + } + + // Add agent configuration if specified + if providerSpec.Agent != nil { + createReq.Agent = &client.AgentRequest{ + Provisioned: providerSpec.Agent.Provisioned, + } + } + + // Add metadata if specified + if len(providerSpec.Metadata) > 0 { + createReq.Metadata = providerSpec.Metadata + } + + // check if server already exists + server, err := p.getServerByName(ctx, projectID, providerSpec.Region, req.Machine.Name) + if err != nil { + klog.Errorf("Failed to fetch server for machine %q: %v", req.Machine.Name, err) + return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to fetch server: %v", err)) + } + + if server == nil { + // Call STACKIT API to create server + server, err = p.client.CreateServer(ctx, projectID, providerSpec.Region, createReq) + if err != nil { + klog.Errorf("Failed to create server for machine %q: %v", req.Machine.Name, err) + return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to create server: %v", err)) + } + } + + if err := p.patchNetworkInterface(ctx, projectID, server.ID, providerSpec); err != nil { + klog.Errorf("Failed to patch network interface for server %q: %v", req.Machine.Name, err) + return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to patch network interface for server: %v", err)) + } + + // Generate ProviderID in format: stackit:/// + providerID := fmt.Sprintf("%s://%s/%s", StackitProviderName, projectID, server.ID) + klog.V(2).Infof("Successfully created server %q with ID %q for machine %q", server.Name, server.ID, req.Machine.Name) + + return &driver.CreateMachineResponse{ + ProviderID: providerID, + NodeName: req.Machine.Name, + }, nil +} + +func (p *Provider) getServerByName(ctx context.Context, projectID, region, serverName string) (*client.Server, error) { + // Check if the server got already created + labelSelector := map[string]string{ + StackitMachineLabel: serverName, + } + servers, err := p.client.ListServers(ctx, projectID, region, labelSelector) + if err != nil { + return nil, fmt.Errorf("SDK ListServers with labelSelector: %v failed: %w", labelSelector, err) + } + + if len(servers) > 1 { + return nil, fmt.Errorf("%v servers found for server name %v", len(servers), serverName) + } + + if len(servers) == 1 { + return servers[0], nil + } + + // no servers found len == 0 + return nil, nil +} + +func (p *Provider) patchNetworkInterface(ctx context.Context, projectID, serverID string, providerSpec *api.ProviderSpec) error { + if len(providerSpec.AllowedAddresses) == 0 { + return nil + } + + nics, err := p.client.GetNICsForServer(ctx, projectID, providerSpec.Region, serverID) + if err != nil { + return fmt.Errorf("failed to get NICs for server %q: %w", serverID, err) + } + + if len(nics) == 0 { + return fmt.Errorf("failed to find NIC for server %q", serverID) + } + + for _, nic := range nics { + // if networking is not set, server is inside the default network + // just patch the interface since the server should only have one + if providerSpec.Networking != nil { + // only process interfaces that are either in the configured network (NetworkID) or are defined in NICIDs + if providerSpec.Networking.NetworkID != nic.NetworkID && !slices.Contains(providerSpec.Networking.NICIDs, nic.ID) { + continue + } + } + + updateNic := false + // check if every cidr in providerspec.allowedAddresses is inside the nic allowedAddresses + for _, allowedAddress := range providerSpec.AllowedAddresses { + if !slices.Contains(nic.AllowedAddresses, allowedAddress) { + nic.AllowedAddresses = append(nic.AllowedAddresses, allowedAddress) + updateNic = true + } + } + + if !updateNic { + continue + } + + if _, err := p.client.UpdateNIC(ctx, projectID, providerSpec.Region, nic.NetworkID, nic.ID, nic.AllowedAddresses); err != nil { + return fmt.Errorf("failed to update allowed addresses for NIC %s: %w", nic.ID, err) + } + + klog.V(2).Infof("Updated allowed addresses for NIC %s to %v", nic.ID, nic.AllowedAddresses) + } + + return nil +} diff --git a/pkg/provider/core_create_machine_basic_test.go b/pkg/provider/create_basic_test.go similarity index 95% rename from pkg/provider/core_create_machine_basic_test.go rename to pkg/provider/create_basic_test.go index 1fc72358..532d12ec 100644 --- a/pkg/provider/core_create_machine_basic_test.go +++ b/pkg/provider/create_basic_test.go @@ -14,6 +14,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -93,13 +94,13 @@ var _ = Describe("CreateMachine", func() { }) It("should call STACKIT API with correct parameters", func() { - var capturedReq *CreateServerRequest + var capturedReq *client.CreateServerRequest var capturedProjectID string - mockClient.createServerFunc = func(_ context.Context, projectID, _ string, req *CreateServerRequest) (*Server, error) { + mockClient.createServerFunc = func(_ context.Context, projectID, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedProjectID = projectID capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -175,7 +176,7 @@ var _ = Describe("CreateMachine", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.createServerFunc = func(_ context.Context, _, _ string, _ *CreateServerRequest) (*Server, error) { + mockClient.createServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) { return nil, fmt.Errorf("API connection failed") } diff --git a/pkg/provider/core_create_machine_config_test.go b/pkg/provider/create_config_test.go similarity index 84% rename from pkg/provider/core_create_machine_config_test.go rename to pkg/provider/create_config_test.go index 7a9c7469..362a0eee 100644 --- a/pkg/provider/core_create_machine_config_test.go +++ b/pkg/provider/create_config_test.go @@ -11,6 +11,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -90,10 +91,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -108,10 +109,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send KeypairName when empty", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -137,10 +138,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -155,10 +156,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send AvailabilityZone when empty", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -184,10 +185,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -202,10 +203,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send AffinityGroup when empty", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -231,10 +232,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -251,10 +252,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send ServiceAccountMails when empty", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -281,10 +282,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -300,10 +301,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send Agent when nil", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -331,10 +332,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -354,10 +355,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send Metadata when nil", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", diff --git a/pkg/provider/core_create_machine_networking_test.go b/pkg/provider/create_networking_test.go similarity index 90% rename from pkg/provider/core_create_machine_networking_test.go rename to pkg/provider/create_networking_test.go index b8649d93..9fc1886c 100644 --- a/pkg/provider/core_create_machine_networking_test.go +++ b/pkg/provider/create_networking_test.go @@ -11,6 +11,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -80,10 +81,10 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -128,10 +129,10 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -178,18 +179,18 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*NIC, error) { - return []*NIC{{ + mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + return []*client.NIC{{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: []string{}, @@ -197,11 +198,11 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*NIC, error) { + mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true Expect(addresses).To(HaveLen(1)) Expect(addresses[0]).To(Equal("10.0.0.1/8")) - return &NIC{ + return &client.NIC{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: addresses, @@ -249,18 +250,18 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*NIC, error) { - return []*NIC{{ + mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + return []*client.NIC{{ ID: "880e8400-e29b-41d4-a716-446655440001", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: []string{}, @@ -268,11 +269,11 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*NIC, error) { + mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true Expect(addresses).To(HaveLen(1)) Expect(addresses[0]).To(Equal("10.0.0.1/8")) - return &NIC{ + return &client.NIC{ ID: "880e8400-e29b-41d4-a716-446655440001", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: addresses, @@ -317,18 +318,18 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*NIC, error) { - return []*NIC{{ + mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + return []*client.NIC{{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: []string{"10.0.0.1/8"}, @@ -336,9 +337,9 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*NIC, error) { + mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true - return &NIC{ + return &client.NIC{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", AllowedAddresses: addresses, @@ -383,10 +384,10 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -426,10 +427,10 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, // No networkId in secret } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -476,10 +477,10 @@ var _ = Describe("CreateMachine - Networking", func() { Secret: secret, } - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", diff --git a/pkg/provider/core_create_machine_storage_test.go b/pkg/provider/create_storage_test.go similarity index 89% rename from pkg/provider/core_create_machine_storage_test.go rename to pkg/provider/create_storage_test.go index 1f81dd0b..8b71ce46 100644 --- a/pkg/provider/core_create_machine_storage_test.go +++ b/pkg/provider/create_storage_test.go @@ -11,6 +11,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -99,10 +100,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -134,10 +135,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -165,10 +166,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -200,10 +201,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -220,10 +221,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send volumes when not specified", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", diff --git a/pkg/provider/core_create_machine_userdata_test.go b/pkg/provider/create_userdata_test.go similarity index 87% rename from pkg/provider/core_create_machine_userdata_test.go rename to pkg/provider/create_userdata_test.go index c0d90c7a..52d0e19f 100644 --- a/pkg/provider/core_create_machine_userdata_test.go +++ b/pkg/provider/create_userdata_test.go @@ -12,6 +12,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -91,10 +92,10 @@ var _ = Describe("CreateMachine", func() { providerSpecRaw, _ := encodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -112,10 +113,10 @@ var _ = Describe("CreateMachine", func() { It("should pass userData from Secret to API when ProviderSpec.UserData is empty", func() { secret.Data["userData"] = []byte("#cloud-config\nruncmd:\n - echo 'Hello from Secret'") - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -141,10 +142,10 @@ var _ = Describe("CreateMachine", func() { req.MachineClass.ProviderSpec.Raw = providerSpecRaw secret.Data["userData"] = []byte("#cloud-config from Secret") - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -160,10 +161,10 @@ var _ = Describe("CreateMachine", func() { }) It("should not send userData when neither ProviderSpec nor Secret have it", func() { - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", @@ -180,10 +181,10 @@ var _ = Describe("CreateMachine", func() { It("should handle empty userData in Secret gracefully", func() { secret.Data["userData"] = []byte("") - var capturedReq *CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *CreateServerRequest) (*Server, error) { + var capturedReq *client.CreateServerRequest + mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req - return &Server{ + return &client.Server{ ID: "test-server-id", Name: req.Name, Status: "CREATING", diff --git a/pkg/provider/delete.go b/pkg/provider/delete.go new file mode 100644 index 00000000..1379fbdc --- /dev/null +++ b/pkg/provider/delete.go @@ -0,0 +1,91 @@ +package provider + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "k8s.io/klog/v2" +) + +// DeleteMachine handles a machine deletion request by deleting the STACKIT server +// +// This method deletes the server identified by the ProviderID from STACKIT infrastructure. +// It is idempotent - if the server is already deleted (404), it returns success. +// +// Error codes: +// - InvalidArgument: Missing or invalid ProviderID +// - Internal: Failed to delete server or communicate with STACKIT API +func (p *Provider) DeleteMachine(ctx context.Context, req *driver.DeleteMachineRequest) (*driver.DeleteMachineResponse, error) { + // Log messages to track delete request + klog.V(2).Infof("Machine deletion request has been received for %q", req.Machine.Name) + defer klog.V(2).Infof("Machine deletion request has been processed for %q", req.Machine.Name) + + // Extract credentials from Secret + serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + // Initialize client on first use (lazy initialization) + if err := p.ensureClient(serviceAccountKey); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) + } + + var projectID, serverID string + var err error + if req.Machine.Spec.ProviderID != "" { + if !strings.HasPrefix(req.Machine.Spec.ProviderID, StackitProviderName) { + return nil, status.Error(codes.InvalidArgument, "providerID is not empty and does not start with stackit://") + } + + // Parse ProviderID to extract projectID and serverID + projectID, serverID, err = parseProviderID(req.Machine.Spec.ProviderID) + if err != nil { + klog.V(2).Infof("invalid ProviderID format: %v", err) + } + } + + if projectID == "" { + projectID = string(req.Secret.Data["project-id"]) + } + + providerSpec, err := decodeProviderSpec(req.MachineClass) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + if serverID == "" { + server, err := p.getServerByName(ctx, projectID, providerSpec.Region, req.Machine.Name) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to find server by name: %v", err)) + } + + if server != nil { + serverID = server.ID + } + } + + if serverID == "" { + klog.V(2).Infof("Server is already deleted for machine %q", req.Machine.Name) + return &driver.DeleteMachineResponse{}, nil + } + + // Call STACKIT API to delete server + err = p.client.DeleteServer(ctx, projectID, providerSpec.Region, serverID) + if err != nil { + // Check if server was not found (404) - this is OK for idempotency + if errors.Is(err, client.ErrServerNotFound) { + klog.V(2).Infof("Server %q already deleted for machine %q (idempotent)", serverID, req.Machine.Name) + return &driver.DeleteMachineResponse{}, nil + } + // All other errors are internal errors + klog.Errorf("Failed to delete server for machine %q: %v", req.Machine.Name, err) + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to delete server: %v", err)) + } + + klog.V(2).Infof("Successfully deleted server %q for machine %q", serverID, req.Machine.Name) + + return &driver.DeleteMachineResponse{}, nil +} diff --git a/pkg/provider/core_delete_machine_test.go b/pkg/provider/delete_test.go similarity index 96% rename from pkg/provider/core_delete_machine_test.go rename to pkg/provider/delete_test.go index a748d062..361abe63 100644 --- a/pkg/provider/core_delete_machine_test.go +++ b/pkg/provider/delete_test.go @@ -14,6 +14,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -138,7 +139,7 @@ var _ = Describe("DeleteMachine", func() { Context("when machine not found", func() { It("should return success if machine does not exist (idempotent)", func() { mockClient.deleteServerFunc = func(_ context.Context, _, _, _ string) error { - return fmt.Errorf("%w: status 404", ErrServerNotFound) + return fmt.Errorf("%w: status 404", client.ErrServerNotFound) } resp, err := provider.DeleteMachine(ctx, req) diff --git a/pkg/provider/helpers.go b/pkg/provider/helpers.go index da6eb7a2..3d07a9da 100644 --- a/pkg/provider/helpers.go +++ b/pkg/provider/helpers.go @@ -55,61 +55,3 @@ func parseProviderID(providerID string) (projectID, serverID string, err error) return parts[0], parts[1], nil } - -// ========== SDK Conversion Helpers ========== - -// ptr returns a pointer to the given value -// This helper is needed because the STACKIT SDK uses pointers for optional fields -func ptr[T any](v T) *T { - return &v -} - -// convertLabelsToSDK converts map[string]string to *map[string]interface{} for SDK -// -//nolint:gocritic // SDK requires *map -func convertLabelsToSDK(labels map[string]string) *map[string]interface{} { - if labels == nil { - return nil - } - - result := make(map[string]interface{}, len(labels)) - for k, v := range labels { - result[k] = v - } - return &result -} - -// convertLabelsFromSDK converts *map[string]interface{} from SDK to map[string]string -// -//nolint:gocritic // SDK requires *map -func convertLabelsFromSDK(labels *map[string]interface{}) map[string]string { - if labels == nil { - return nil - } - - result := make(map[string]string, len(*labels)) - for k, v := range *labels { - if strVal, ok := v.(string); ok { - result[k] = strVal - } - } - return result -} - -// convertStringSliceToSDK converts []string to *[]string for SDK -func convertStringSliceToSDK(slice []string) *[]string { - if slice == nil { - return nil - } - return &slice -} - -// convertMetadataToSDK converts map[string]interface{} to *map[string]interface{} for SDK -// -//nolint:gocritic // SDK requires *map -func convertMetadataToSDK(metadata map[string]interface{}) *map[string]interface{} { - if metadata == nil { - return nil - } - return &metadata -} diff --git a/pkg/provider/list.go b/pkg/provider/list.go new file mode 100644 index 00000000..6367b8b2 --- /dev/null +++ b/pkg/provider/list.go @@ -0,0 +1,75 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + "k8s.io/klog/v2" +) + +// ListMachines lists all STACKIT servers that belong to the specified MachineClass +// +// This method retrieves all servers in the STACKIT project and filters them based on +// the "mcm.gardener.cloud/machineclass" label. This enables the MCM safety controller +// to detect and clean up orphan VMs that are not backed by Machine CRs. +// +// Returns: +// - MachineList: Map of ProviderID to MachineName for all servers matching the MachineClass +// +// Error codes: +// - Internal: Failed to list servers or communicate with STACKIT API +func (p *Provider) ListMachines(ctx context.Context, req *driver.ListMachinesRequest) (*driver.ListMachinesResponse, error) { + // Log messages to track start and end of request + klog.V(2).Infof("List machines request has been received for %q", req.MachineClass.Name) + defer klog.V(2).Infof("List machines request has been processed for %q", req.MachineClass.Name) + + // Extract credentials from Secret + projectID := string(req.Secret.Data["project-id"]) + serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + + // Initialize client on first use (lazy initialization) + if err := p.ensureClient(serviceAccountKey); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) + } + + // Decode ProviderSpec from MachineClass + providerSpec, err := decodeProviderSpec(req.MachineClass) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + // Call STACKIT API to list all servers + labelSelector := map[string]string{ + StackitMachineClassLabel: req.MachineClass.Name, + } + servers, err := p.client.ListServers(ctx, projectID, providerSpec.Region, labelSelector) + if err != nil { + klog.Errorf("Failed to list servers for MachineClass %q: %v", req.MachineClass.Name, err) + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to list servers: %v", err)) + } + + // Filter servers by MachineClass label + // We use the "mcm.gardener.cloud/machineclass" label to identify which servers belong to this MachineClass + machineList := make(map[string]string) + for _, server := range servers { + // Generate ProviderID in format: stackit:/// + providerID := fmt.Sprintf("stackit://%s/%s", projectID, server.ID) + + // Get machine name from labels (fallback to server name if not found) + machineName := server.Name + if machineLabel, ok := server.Labels[StackitMachineLabel]; ok { + machineName = machineLabel + } + + machineList[providerID] = machineName + } + + klog.V(2).Infof("Found %d machines for MachineClass %q", len(machineList), req.MachineClass.Name) + + return &driver.ListMachinesResponse{ + MachineList: machineList, + }, nil +} diff --git a/pkg/provider/core_list_machines_test.go b/pkg/provider/list_test.go similarity index 91% rename from pkg/provider/core_list_machines_test.go rename to pkg/provider/list_test.go index 0e201372..ed651908 100644 --- a/pkg/provider/core_list_machines_test.go +++ b/pkg/provider/list_test.go @@ -14,6 +14,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,10 +73,10 @@ var _ = Describe("ListMachines", func() { Context("with valid inputs", func() { It("should list machines filtered by MachineClass label", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*Server, error) { + mockClient.listServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*client.Server, error) { Expect(selector["mcm.gardener.cloud/machineclass"]).To(Equal("test-machine-class")) - return []*Server{ + return []*client.Server{ { ID: "server-1", Name: "machine-1", @@ -106,8 +107,8 @@ var _ = Describe("ListMachines", func() { }) It("should return empty list when no servers match", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*Server, error) { - return []*Server{}, nil + mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { + return []*client.Server{}, nil } resp, err := provider.ListMachines(ctx, req) @@ -118,8 +119,8 @@ var _ = Describe("ListMachines", func() { }) It("should return empty list when no servers exist", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*Server, error) { - return []*Server{}, nil + mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { + return []*client.Server{}, nil } resp, err := provider.ListMachines(ctx, req) @@ -132,7 +133,7 @@ var _ = Describe("ListMachines", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*Server, error) { + mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { return nil, fmt.Errorf("API connection failed") } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 17e6ba29..98de2ba5 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + client2 "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/spi" "k8s.io/klog/v2" ) @@ -24,10 +25,10 @@ import ( // - Credential rotation requires pod restart (standard Kubernetes pattern) type Provider struct { SPI spi.SessionProviderInterface - client StackitClient // STACKIT API client (can be mocked for testing) - clientOnce sync.Once // Ensures client is initialized exactly once - clientErr error // Stores initialization error if any - capturedCredentials string // Service account key used for initialization (for defensive checks) + client client2.StackitClient // STACKIT API client (can be mocked for testing) + clientOnce sync.Once // Ensures client is initialized exactly once + clientErr error // Stores initialization error if any + capturedCredentials string // Service account key used for initialization (for defensive checks) } // NewProvider returns an empty provider object @@ -53,7 +54,7 @@ func (p *Provider) ensureClient(serviceAccountKey string) error { } p.clientOnce.Do(func() { - client, err := NewStackitClient(serviceAccountKey) + client, err := client2.NewStackitClient(serviceAccountKey) if err != nil { p.clientErr = fmt.Errorf("failed to initialize STACKIT client: %w", err) return diff --git a/pkg/provider/status.go b/pkg/provider/status.go new file mode 100644 index 00000000..885ffe15 --- /dev/null +++ b/pkg/provider/status.go @@ -0,0 +1,84 @@ +package provider + +import ( + "context" + "errors" + "fmt" + + "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" + "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "k8s.io/klog/v2" +) + +// GetMachineStatus retrieves the current status of a STACKIT server +// +// This method queries STACKIT API to get the current state of the server identified +// by the Machine's ProviderID. If the ProviderID is empty (machine not created yet) +// or the server doesn't exist, it returns NotFound error. +// +// Returns: +// - ProviderID: The machine's ProviderID +// - NodeName: Name that the VM registered with in Kubernetes +// +// Error codes: +// - NotFound: Machine has no ProviderID yet, or server not found in STACKIT +// - InvalidArgument: Invalid ProviderID format +// - Internal: Failed to get server status or communicate with STACKIT API +func (p *Provider) GetMachineStatus(ctx context.Context, req *driver.GetMachineStatusRequest) (*driver.GetMachineStatusResponse, error) { + // Log messages to track start and end of request + klog.V(2).Infof("Get request has been received for %q", req.Machine.Name) + defer klog.V(2).Infof("Machine get request has been processed successfully for %q", req.Machine.Name) + + // When ProviderID is empty, the machine doesn't exist yet + // Return NotFound so MCM knows to call CreateMachine + if req.Machine.Spec.ProviderID == "" { + klog.V(2).Infof("Machine %q has no ProviderID, returning NotFound", req.Machine.Name) + return nil, status.Error(codes.NotFound, "machine does not have a ProviderID yet") + } + + // Extract credentials from Secret + serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + + // Initialize client on first use (lazy initialization) + if err := p.ensureClient(serviceAccountKey); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) + } + + // Parse ProviderID to extract projectID and serverID + // Expected format: stackit:/// + projectID, serverID, err := parseProviderID(req.Machine.Spec.ProviderID) + if projectID == "" { + projectID = string(req.Secret.Data["project-id"]) + } + if err != nil { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid ProviderID format: %v", err)) + } + + // Decode ProviderSpec from MachineClass + providerSpec, err := decodeProviderSpec(req.MachineClass) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + // Call STACKIT API to get server status + server, err := p.client.GetServer(ctx, projectID, providerSpec.Region, serverID) + if err != nil { + // Check if server was not found (404) + if errors.Is(err, client.ErrServerNotFound) { + klog.V(2).Infof("Server %q not found for machine %q", serverID, req.Machine.Name) + return nil, status.Error(codes.NotFound, fmt.Sprintf("server %q not found", serverID)) + } + // All other errors are internal errors + klog.Errorf("Failed to get server status for machine %q: %v", req.Machine.Name, err) + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get server status: %v", err)) + } + + klog.V(2).Infof("Retrieved server status for machine %q: status=%s", req.Machine.Name, server.Status) + + return &driver.GetMachineStatusResponse{ + ProviderID: req.Machine.Spec.ProviderID, + NodeName: req.Machine.Name, + }, nil +} diff --git a/pkg/provider/core_get_machine_status_test.go b/pkg/provider/status_test.go similarity index 93% rename from pkg/provider/core_get_machine_status_test.go rename to pkg/provider/status_test.go index 4470d700..49c321fd 100644 --- a/pkg/provider/core_get_machine_status_test.go +++ b/pkg/provider/status_test.go @@ -14,6 +14,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -86,8 +87,8 @@ var _ = Describe("GetMachineStatus", func() { Context("with valid inputs", func() { It("should successfully get machine status when server exists", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, serverID string) (*Server, error) { - return &Server{ + mockClient.getServerFunc = func(_ context.Context, _, _, serverID string) (*client.Server, error) { + return &client.Server{ ID: serverID, Name: "test-machine", Status: "ACTIVE", @@ -106,10 +107,10 @@ var _ = Describe("GetMachineStatus", func() { var capturedProjectID string var capturedServerID string - mockClient.getServerFunc = func(_ context.Context, projectID, _, serverID string) (*Server, error) { + mockClient.getServerFunc = func(_ context.Context, projectID, _, serverID string) (*client.Server, error) { capturedProjectID = projectID capturedServerID = serverID - return &Server{ + return &client.Server{ ID: serverID, Name: "test-machine", Status: "ACTIVE", @@ -161,8 +162,8 @@ var _ = Describe("GetMachineStatus", func() { Context("when server does not exist", func() { It("should return NotFound when server is not found", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*Server, error) { - return nil, fmt.Errorf("%w: status 404", ErrServerNotFound) + mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { + return nil, fmt.Errorf("%w: status 404", client.ErrServerNotFound) } _, err := provider.GetMachineStatus(ctx, req) @@ -176,7 +177,7 @@ var _ = Describe("GetMachineStatus", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*Server, error) { + mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { return nil, fmt.Errorf("API connection failed") } From b07743b39eb8ee62ea5abf69ecf7387abd36f56f Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Thu, 22 Jan 2026 14:47:28 +0100 Subject: [PATCH 2/3] remove license comments Signed-off-by: Felix Breuer --- OWNERS_ALIASES | 1 + cmd/machine-controller/main.go | 21 ------------------- hack/rename-project | 4 ---- pkg/client/sdk_test.go | 4 ---- pkg/provider/apis/provider_spec.go | 4 ---- pkg/provider/apis/validation/validation.go | 5 ----- .../validation/validation_core_labels_test.go | 4 ---- .../apis/validation/validation_fields_test.go | 4 ---- .../validation/validation_networking_test.go | 4 ---- .../validation/validation_secgroup_test.go | 4 ---- .../apis/validation/validation_secret_test.go | 4 ---- .../validation/validation_volumes_test.go | 4 ---- pkg/provider/core.go | 5 ----- pkg/provider/core_mocks_test.go | 4 ---- pkg/provider/create_basic_test.go | 4 ---- pkg/provider/create_config_test.go | 4 ---- pkg/provider/create_networking_test.go | 4 ---- pkg/provider/create_storage_test.go | 4 ---- pkg/provider/create_userdata_test.go | 4 ---- pkg/provider/delete_test.go | 4 ---- pkg/provider/helpers.go | 4 ---- pkg/provider/helpers_test.go | 4 ---- pkg/provider/list_test.go | 4 ---- pkg/provider/provider.go | 5 ----- pkg/provider/provider_suite_test.go | 4 ---- pkg/provider/status_test.go | 4 ---- pkg/spi/spi.go | 4 ---- scripts/Dockerfile_build | 3 --- scripts/build-docker.sh | 3 --- scripts/build.sh | 2 -- test/e2e/common.go | 4 ---- test/e2e/e2e_affinity_test.go | 4 ---- test/e2e/e2e_agent_test.go | 4 ---- test/e2e/e2e_az_test.go | 4 ---- test/e2e/e2e_basic_test.go | 4 ---- test/e2e/e2e_keypair_test.go | 4 ---- test/e2e/e2e_labels_test.go | 4 ---- test/e2e/e2e_lifecycle_test.go | 4 ---- test/e2e/e2e_metadata_test.go | 4 ---- test/e2e/e2e_networking_test.go | 4 ---- test/e2e/e2e_service_accounts_test.go | 4 ---- test/e2e/e2e_suite_test.go | 4 ---- test/e2e/e2e_userdata_test.go | 4 ---- test/e2e/e2e_volumes_test.go | 4 ---- test/utils/utils.go | 16 -------------- 45 files changed, 1 insertion(+), 204 deletions(-) diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index c23cc234..1a6eae4f 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -18,6 +18,7 @@ aliases: - jamand - breuerfelix - aniruddha2000 + machine-controller-manager-provider-stackit-approvers: - dergeberl - JuliusSte diff --git a/cmd/machine-controller/main.go b/cmd/machine-controller/main.go index f15032b8..0686b96c 100644 --- a/cmd/machine-controller/main.go +++ b/cmd/machine-controller/main.go @@ -1,24 +1,3 @@ -/* -Copyright 2014 The Kubernetes Authors. - -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. - -This file was copied and modified from the kubernetes/kubernetes project -https://github.com/kubernetes/kubernetes/release-1.8/cmd/kube-controller-manager/controller_manager.go - -Modifications Copyright (c) 2017 SAP SE or an SAP affiliate company. All rights reserved. -*/ - package main import ( diff --git a/hack/rename-project b/hack/rename-project index fc0300bd..265e2ffa 100755 --- a/hack/rename-project +++ b/hack/rename-project @@ -1,8 +1,4 @@ #!/bin/bash -eu -# -# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -# -# SPDX-License-Identifier: Apache-2.0 project_name=$1 provider_name=$2 diff --git a/pkg/client/sdk_test.go b/pkg/client/sdk_test.go index 7ea5f403..40f9da68 100644 --- a/pkg/client/sdk_test.go +++ b/pkg/client/sdk_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package client import ( diff --git a/pkg/provider/apis/provider_spec.go b/pkg/provider/apis/provider_spec.go index cb1822d4..e05d82b4 100644 --- a/pkg/provider/apis/provider_spec.go +++ b/pkg/provider/apis/provider_spec.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package api // ProviderSpec is the spec to be used while parsing the calls. diff --git a/pkg/provider/apis/validation/validation.go b/pkg/provider/apis/validation/validation.go index bc7a7f55..a9ecbf2d 100644 --- a/pkg/provider/apis/validation/validation.go +++ b/pkg/provider/apis/validation/validation.go @@ -1,8 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -// Package validation - validation is used to validate cloud specific ProviderSpec package validation import ( diff --git a/pkg/provider/apis/validation/validation_core_labels_test.go b/pkg/provider/apis/validation/validation_core_labels_test.go index 2e59f1bc..eb5a926f 100644 --- a/pkg/provider/apis/validation/validation_core_labels_test.go +++ b/pkg/provider/apis/validation/validation_core_labels_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/apis/validation/validation_fields_test.go b/pkg/provider/apis/validation/validation_fields_test.go index 7ceb3aba..4c3a1812 100644 --- a/pkg/provider/apis/validation/validation_fields_test.go +++ b/pkg/provider/apis/validation/validation_fields_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/apis/validation/validation_networking_test.go b/pkg/provider/apis/validation/validation_networking_test.go index beb22110..fc7cf93c 100644 --- a/pkg/provider/apis/validation/validation_networking_test.go +++ b/pkg/provider/apis/validation/validation_networking_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/apis/validation/validation_secgroup_test.go b/pkg/provider/apis/validation/validation_secgroup_test.go index 5477366b..89d97e83 100644 --- a/pkg/provider/apis/validation/validation_secgroup_test.go +++ b/pkg/provider/apis/validation/validation_secgroup_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/apis/validation/validation_secret_test.go b/pkg/provider/apis/validation/validation_secret_test.go index 89cb9773..dc4dda88 100644 --- a/pkg/provider/apis/validation/validation_secret_test.go +++ b/pkg/provider/apis/validation/validation_secret_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/apis/validation/validation_volumes_test.go b/pkg/provider/apis/validation/validation_volumes_test.go index 4576c4be..9c74f595 100644 --- a/pkg/provider/apis/validation/validation_volumes_test.go +++ b/pkg/provider/apis/validation/validation_volumes_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package validation_test import ( diff --git a/pkg/provider/core.go b/pkg/provider/core.go index f4f8cab4..d5020049 100644 --- a/pkg/provider/core.go +++ b/pkg/provider/core.go @@ -1,8 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -// Package provider contains the cloud provider specific implementations to manage machines package provider import ( diff --git a/pkg/provider/core_mocks_test.go b/pkg/provider/core_mocks_test.go index 8c0af24f..05228afb 100644 --- a/pkg/provider/core_mocks_test.go +++ b/pkg/provider/core_mocks_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/create_basic_test.go b/pkg/provider/create_basic_test.go index 532d12ec..0fef401d 100644 --- a/pkg/provider/create_basic_test.go +++ b/pkg/provider/create_basic_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/create_config_test.go b/pkg/provider/create_config_test.go index 362a0eee..df4062b6 100644 --- a/pkg/provider/create_config_test.go +++ b/pkg/provider/create_config_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/create_networking_test.go b/pkg/provider/create_networking_test.go index 9fc1886c..f361e886 100644 --- a/pkg/provider/create_networking_test.go +++ b/pkg/provider/create_networking_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/create_storage_test.go b/pkg/provider/create_storage_test.go index 8b71ce46..f83b03a9 100644 --- a/pkg/provider/create_storage_test.go +++ b/pkg/provider/create_storage_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/create_userdata_test.go b/pkg/provider/create_userdata_test.go index 52d0e19f..44321e1b 100644 --- a/pkg/provider/create_userdata_test.go +++ b/pkg/provider/create_userdata_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/delete_test.go b/pkg/provider/delete_test.go index 361abe63..ec291eda 100644 --- a/pkg/provider/delete_test.go +++ b/pkg/provider/delete_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/helpers.go b/pkg/provider/helpers.go index 3d07a9da..14127ff4 100644 --- a/pkg/provider/helpers.go +++ b/pkg/provider/helpers.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/helpers_test.go b/pkg/provider/helpers_test.go index 71cc35e9..70355525 100644 --- a/pkg/provider/helpers_test.go +++ b/pkg/provider/helpers_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/list_test.go b/pkg/provider/list_test.go index ed651908..ff145f64 100644 --- a/pkg/provider/list_test.go +++ b/pkg/provider/list_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 98de2ba5..992d9496 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -1,8 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -// Package provider contains the cloud provider specific implementations to manage machines package provider import ( diff --git a/pkg/provider/provider_suite_test.go b/pkg/provider/provider_suite_test.go index b4da7a44..25a87a60 100644 --- a/pkg/provider/provider_suite_test.go +++ b/pkg/provider/provider_suite_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/provider/status_test.go b/pkg/provider/status_test.go index 49c321fd..dbd7c1c2 100644 --- a/pkg/provider/status_test.go +++ b/pkg/provider/status_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package provider import ( diff --git a/pkg/spi/spi.go b/pkg/spi/spi.go index d50330de..c8eacaff 100644 --- a/pkg/spi/spi.go +++ b/pkg/spi/spi.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package spi // SessionProviderInterface provides an interface to deal with cloud provider session diff --git a/scripts/Dockerfile_build b/scripts/Dockerfile_build index 0c8698bd..e9dbd040 100644 --- a/scripts/Dockerfile_build +++ b/scripts/Dockerfile_build @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -# SPDX-License-Identifier: Apache-2.0 -# # Dockerfile for building binary inside Docker (exports to host) FROM golang:1.25.5 AS builder diff --git a/scripts/build-docker.sh b/scripts/build-docker.sh index 751c2af8..c4f66982 100755 --- a/scripts/build-docker.sh +++ b/scripts/build-docker.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -# SPDX-License-Identifier: Apache-2.0 -# # Build binary inside Docker container (no local Go required) set -euo pipefail diff --git a/scripts/build.sh b/scripts/build.sh index 1a39eea9..e33f33c8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,6 +1,4 @@ #!/usr/bin/env bash -# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -# SPDX-License-Identifier: Apache-2.0 set -euo pipefail diff --git a/test/e2e/common.go b/test/e2e/common.go index e289efc8..7ff60ff6 100644 --- a/test/e2e/common.go +++ b/test/e2e/common.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_affinity_test.go b/test/e2e/e2e_affinity_test.go index 5977be63..ff9b0559 100644 --- a/test/e2e/e2e_affinity_test.go +++ b/test/e2e/e2e_affinity_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_agent_test.go b/test/e2e/e2e_agent_test.go index 0437adef..ae544c06 100644 --- a/test/e2e/e2e_agent_test.go +++ b/test/e2e/e2e_agent_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_az_test.go b/test/e2e/e2e_az_test.go index 2f260c5c..1eb7aeb9 100644 --- a/test/e2e/e2e_az_test.go +++ b/test/e2e/e2e_az_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_basic_test.go b/test/e2e/e2e_basic_test.go index 5ab77f41..19363742 100644 --- a/test/e2e/e2e_basic_test.go +++ b/test/e2e/e2e_basic_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_keypair_test.go b/test/e2e/e2e_keypair_test.go index 46ac7193..1e29f907 100644 --- a/test/e2e/e2e_keypair_test.go +++ b/test/e2e/e2e_keypair_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_labels_test.go b/test/e2e/e2e_labels_test.go index 4b686a28..a1d25e36 100644 --- a/test/e2e/e2e_labels_test.go +++ b/test/e2e/e2e_labels_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_lifecycle_test.go b/test/e2e/e2e_lifecycle_test.go index 01981428..e3cab91c 100644 --- a/test/e2e/e2e_lifecycle_test.go +++ b/test/e2e/e2e_lifecycle_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_metadata_test.go b/test/e2e/e2e_metadata_test.go index 9cf2dd11..1fe6623a 100644 --- a/test/e2e/e2e_metadata_test.go +++ b/test/e2e/e2e_metadata_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_networking_test.go b/test/e2e/e2e_networking_test.go index 24d5cba2..b2c6d782 100644 --- a/test/e2e/e2e_networking_test.go +++ b/test/e2e/e2e_networking_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_service_accounts_test.go b/test/e2e/e2e_service_accounts_test.go index a9a5791a..e332b4c8 100644 --- a/test/e2e/e2e_service_accounts_test.go +++ b/test/e2e/e2e_service_accounts_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index cfa50009..ed587d6b 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_userdata_test.go b/test/e2e/e2e_userdata_test.go index 1722dbab..1f475411 100644 --- a/test/e2e/e2e_userdata_test.go +++ b/test/e2e/e2e_userdata_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/e2e/e2e_volumes_test.go b/test/e2e/e2e_volumes_test.go index 9dcc30a9..430228aa 100644 --- a/test/e2e/e2e_volumes_test.go +++ b/test/e2e/e2e_volumes_test.go @@ -1,7 +1,3 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - package e2e import ( diff --git a/test/utils/utils.go b/test/utils/utils.go index be705a56..d6c8b573 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -1,19 +1,3 @@ -/* -Copyright 2025. - -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 ( From da64b9ac955ecdd86601fceeab89142cff7ff749 Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Thu, 22 Jan 2026 16:16:36 +0100 Subject: [PATCH 3/3] move mock client to its own package Signed-off-by: Felix Breuer --- pkg/client/mock/client.go | 77 ++++++++++++++++++++++++++ pkg/provider/core_mocks_test.go | 76 ------------------------- pkg/provider/create_basic_test.go | 15 ++--- pkg/provider/create_config_test.go | 43 +++++++------- pkg/provider/create_networking_test.go | 51 ++++++++--------- pkg/provider/create_storage_test.go | 25 +++++---- pkg/provider/create_userdata_test.go | 21 +++---- pkg/provider/delete_test.go | 15 ++--- pkg/provider/list_test.go | 15 ++--- pkg/provider/status_test.go | 15 ++--- 10 files changed, 181 insertions(+), 172 deletions(-) create mode 100644 pkg/client/mock/client.go delete mode 100644 pkg/provider/core_mocks_test.go diff --git a/pkg/client/mock/client.go b/pkg/client/mock/client.go new file mode 100644 index 00000000..bb623de3 --- /dev/null +++ b/pkg/client/mock/client.go @@ -0,0 +1,77 @@ +package mock + +import ( + "context" + "encoding/json" + + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" +) + +// StackitClient is a mock implementation of StackitClient for testing +// Note: Single-tenant design - each client is bound to one set of credentials +type StackitClient struct { + CreateServerFunc func(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) + GetServerFunc func(ctx context.Context, projectID, region, serverID string) (*client.Server, error) + DeleteServerFunc func(ctx context.Context, projectID, region, serverID string) error + ListServersFunc func(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) + GetNICsFunc func(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) + UpdateNICFunc func(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) +} + +func (m *StackitClient) CreateServer(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) { + if m.CreateServerFunc != nil { + return m.CreateServerFunc(ctx, projectID, region, req) + } + return &client.Server{ + ID: "550e8400-e29b-41d4-a716-446655440000", + Name: req.Name, + Status: "CREATING", + }, nil +} + +func (m *StackitClient) GetServer(ctx context.Context, projectID, region, serverID string) (*client.Server, error) { + if m.GetServerFunc != nil { + return m.GetServerFunc(ctx, projectID, region, serverID) + } + return &client.Server{ + ID: serverID, + Name: "test-machine", + Status: "ACTIVE", + }, nil +} + +func (m *StackitClient) DeleteServer(ctx context.Context, projectID, region, serverID string) error { + if m.DeleteServerFunc != nil { + return m.DeleteServerFunc(ctx, projectID, region, serverID) + } + return nil +} + +func (m *StackitClient) ListServers(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) { + if m.ListServersFunc != nil { + return m.ListServersFunc(ctx, projectID, region, labelSelector) + } + return []*client.Server{}, nil +} + +func (m *StackitClient) GetNICsForServer(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) { + if m.GetNICsFunc != nil { + return m.GetNICsFunc(ctx, projectID, region, serverID) + } + return []*client.NIC{}, nil +} + +func (m *StackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) { + if m.UpdateNICFunc != nil { + return m.UpdateNICFunc(ctx, projectID, region, networkID, nicID, allowedAddresses) + } + return &client.NIC{}, nil +} + +// UpdateNIC updates a network interface + +// encodeProviderSpec is a helper function to encode ProviderSpec for tests +func EncodeProviderSpec(spec *api.ProviderSpec) ([]byte, error) { + return json.Marshal(spec) +} diff --git a/pkg/provider/core_mocks_test.go b/pkg/provider/core_mocks_test.go deleted file mode 100644 index 05228afb..00000000 --- a/pkg/provider/core_mocks_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package provider - -import ( - "context" - - "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" - api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" -) - -// mockStackitClient is a mock implementation of StackitClient for testing -// Note: Single-tenant design - each client is bound to one set of credentials -type mockStackitClient struct { - createServerFunc func(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) - getServerFunc func(ctx context.Context, projectID, region, serverID string) (*client.Server, error) - deleteServerFunc func(ctx context.Context, projectID, region, serverID string) error - listServersFunc func(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) - getNICsFunc func(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) - updateNICFunc func(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) -} - -func (m *mockStackitClient) CreateServer(ctx context.Context, projectID, region string, req *client.CreateServerRequest) (*client.Server, error) { - if m.createServerFunc != nil { - return m.createServerFunc(ctx, projectID, region, req) - } - return &client.Server{ - ID: "550e8400-e29b-41d4-a716-446655440000", - Name: req.Name, - Status: "CREATING", - }, nil -} - -func (m *mockStackitClient) GetServer(ctx context.Context, projectID, region, serverID string) (*client.Server, error) { - if m.getServerFunc != nil { - return m.getServerFunc(ctx, projectID, region, serverID) - } - return &client.Server{ - ID: serverID, - Name: "test-machine", - Status: "ACTIVE", - }, nil -} - -func (m *mockStackitClient) DeleteServer(ctx context.Context, projectID, region, serverID string) error { - if m.deleteServerFunc != nil { - return m.deleteServerFunc(ctx, projectID, region, serverID) - } - return nil -} - -func (m *mockStackitClient) ListServers(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error) { - if m.listServersFunc != nil { - return m.listServersFunc(ctx, projectID, region, labelSelector) - } - return []*client.Server{}, nil -} - -func (m *mockStackitClient) GetNICsForServer(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error) { - if m.getNICsFunc != nil { - return m.getNICsFunc(ctx, projectID, region, serverID) - } - return []*client.NIC{}, nil -} - -func (m *mockStackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) { - if m.updateNICFunc != nil { - return m.updateNICFunc(ctx, projectID, region, networkID, nicID, allowedAddresses) - } - return &client.NIC{}, nil -} - -// UpdateNIC updates a network interface - -// encodeProviderSpec is a helper function to encode ProviderSpec for tests -func encodeProviderSpec(spec *api.ProviderSpec) ([]byte, error) { - return encodeProviderSpecForResponse(spec) -} diff --git a/pkg/provider/create_basic_test.go b/pkg/provider/create_basic_test.go index 0fef401d..952ad3f3 100644 --- a/pkg/provider/create_basic_test.go +++ b/pkg/provider/create_basic_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,7 +22,7 @@ var _ = Describe("CreateMachine", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.CreateMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -30,7 +31,7 @@ var _ = Describe("CreateMachine", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -50,7 +51,7 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -93,7 +94,7 @@ var _ = Describe("CreateMachine", func() { var capturedReq *client.CreateServerRequest var capturedProjectID string - mockClient.createServerFunc = func(_ context.Context, projectID, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, projectID, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedProjectID = projectID capturedReq = req return &client.Server{ @@ -120,7 +121,7 @@ var _ = Describe("CreateMachine", func() { MachineType: "", ImageID: "12345678-1234-1234-1234-123456789abc", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw _, err := provider.CreateMachine(ctx, req) @@ -136,7 +137,7 @@ var _ = Describe("CreateMachine", func() { MachineType: "c2i.2", ImageID: "", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw _, err := provider.CreateMachine(ctx, req) @@ -172,7 +173,7 @@ var _ = Describe("CreateMachine", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.createServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) { return nil, fmt.Errorf("API connection failed") } diff --git a/pkg/provider/create_config_test.go b/pkg/provider/create_config_test.go index df4062b6..686b37ac 100644 --- a/pkg/provider/create_config_test.go +++ b/pkg/provider/create_config_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,7 +19,7 @@ var _ = Describe("CreateMachine", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.CreateMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -27,7 +28,7 @@ var _ = Describe("CreateMachine", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -47,7 +48,7 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -84,11 +85,11 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", KeypairName: "my-ssh-key", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -106,7 +107,7 @@ var _ = Describe("CreateMachine", func() { It("should not send KeypairName when empty", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -131,11 +132,11 @@ var _ = Describe("CreateMachine", func() { Region: "eu01", AvailabilityZone: "eu01-1", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -153,7 +154,7 @@ var _ = Describe("CreateMachine", func() { It("should not send AvailabilityZone when empty", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -178,11 +179,11 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", AffinityGroup: "880e8400-e29b-41d4-a716-446655440000", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -200,7 +201,7 @@ var _ = Describe("CreateMachine", func() { It("should not send AffinityGroup when empty", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -225,11 +226,11 @@ var _ = Describe("CreateMachine", func() { "my-service@sa.stackit.cloud", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -249,7 +250,7 @@ var _ = Describe("CreateMachine", func() { It("should not send ServiceAccountMails when empty", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -275,11 +276,11 @@ var _ = Describe("CreateMachine", func() { Provisioned: &provisioned, }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -298,7 +299,7 @@ var _ = Describe("CreateMachine", func() { It("should not send Agent when nil", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -325,11 +326,11 @@ var _ = Describe("CreateMachine", func() { "count": 42, }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -352,7 +353,7 @@ var _ = Describe("CreateMachine", func() { It("should not send Metadata when nil", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", diff --git a/pkg/provider/create_networking_test.go b/pkg/provider/create_networking_test.go index f361e886..ce886fb7 100644 --- a/pkg/provider/create_networking_test.go +++ b/pkg/provider/create_networking_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,7 +19,7 @@ var _ = Describe("CreateMachine - Networking", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.CreateMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -27,7 +28,7 @@ var _ = Describe("CreateMachine - Networking", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -59,7 +60,7 @@ var _ = Describe("CreateMachine - Networking", func() { NetworkID: "770e8400-e29b-41d4-a716-446655440000", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -78,7 +79,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -107,7 +108,7 @@ var _ = Describe("CreateMachine - Networking", func() { }, }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -126,7 +127,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -157,7 +158,7 @@ var _ = Describe("CreateMachine - Networking", func() { "10.0.0.1/8", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -176,7 +177,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -185,7 +186,7 @@ var _ = Describe("CreateMachine - Networking", func() { }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + mockClient.GetNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { return []*client.NIC{{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", @@ -194,7 +195,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { + mockClient.UpdateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true Expect(addresses).To(HaveLen(1)) Expect(addresses[0]).To(Equal("10.0.0.1/8")) @@ -228,7 +229,7 @@ var _ = Describe("CreateMachine - Networking", func() { "10.0.0.1/8", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -247,7 +248,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -256,7 +257,7 @@ var _ = Describe("CreateMachine - Networking", func() { }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + mockClient.GetNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { return []*client.NIC{{ ID: "880e8400-e29b-41d4-a716-446655440001", NetworkID: "770e8400-e29b-41d4-a716-446655440000", @@ -265,7 +266,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { + mockClient.UpdateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true Expect(addresses).To(HaveLen(1)) Expect(addresses[0]).To(Equal("10.0.0.1/8")) @@ -296,7 +297,7 @@ var _ = Describe("CreateMachine - Networking", func() { "10.0.0.1/8", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -315,7 +316,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -324,7 +325,7 @@ var _ = Describe("CreateMachine - Networking", func() { }, nil } - mockClient.getNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { + mockClient.GetNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) { return []*client.NIC{{ ID: "990e8400-e29b-41d4-a716-446655440002", NetworkID: "770e8400-e29b-41d4-a716-446655440000", @@ -333,7 +334,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var called = false - mockClient.updateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { + mockClient.UpdateNICFunc = func(_ context.Context, _, _, _, _ string, addresses []string) (*client.NIC, error) { called = true return &client.NIC{ ID: "990e8400-e29b-41d4-a716-446655440002", @@ -362,7 +363,7 @@ var _ = Describe("CreateMachine - Networking", func() { Region: "eu01", // Networking is nil - should fall back to Secret } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -381,7 +382,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -405,7 +406,7 @@ var _ = Describe("CreateMachine - Networking", func() { Region: "eu01", // Networking is nil } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -424,7 +425,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -455,7 +456,7 @@ var _ = Describe("CreateMachine - Networking", func() { NetworkID: "990e8400-e29b-41d4-a716-446655440002", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ @@ -474,7 +475,7 @@ var _ = Describe("CreateMachine - Networking", func() { } var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -503,7 +504,7 @@ var _ = Describe("CreateMachine - Networking", func() { // Both NetworkID and NICIDs are empty - should fail validation }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) machineClass = &v1alpha1.MachineClass{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/provider/create_storage_test.go b/pkg/provider/create_storage_test.go index f83b03a9..aae0dd3f 100644 --- a/pkg/provider/create_storage_test.go +++ b/pkg/provider/create_storage_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,7 +19,7 @@ var _ = Describe("CreateMachine", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.CreateMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -27,7 +28,7 @@ var _ = Describe("CreateMachine", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -47,7 +48,7 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -93,11 +94,11 @@ var _ = Describe("CreateMachine", func() { }, }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -128,11 +129,11 @@ var _ = Describe("CreateMachine", func() { Size: 50, }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -159,11 +160,11 @@ var _ = Describe("CreateMachine", func() { "660e8400-e29b-41d4-a716-446655440001", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -194,11 +195,11 @@ var _ = Describe("CreateMachine", func() { "550e8400-e29b-41d4-a716-446655440000", }, } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -218,7 +219,7 @@ var _ = Describe("CreateMachine", func() { It("should not send volumes when not specified", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", diff --git a/pkg/provider/create_userdata_test.go b/pkg/provider/create_userdata_test.go index 44321e1b..5176ad65 100644 --- a/pkg/provider/create_userdata_test.go +++ b/pkg/provider/create_userdata_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,7 +20,7 @@ var _ = Describe("CreateMachine", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.CreateMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -28,7 +29,7 @@ var _ = Describe("CreateMachine", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -48,7 +49,7 @@ var _ = Describe("CreateMachine", func() { ImageID: "12345678-1234-1234-1234-123456789abc", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -85,11 +86,11 @@ var _ = Describe("CreateMachine", func() { UserData: "#cloud-config\nruncmd:\n - echo 'Hello from ProviderSpec'", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -110,7 +111,7 @@ var _ = Describe("CreateMachine", func() { secret.Data["userData"] = []byte("#cloud-config\nruncmd:\n - echo 'Hello from Secret'") var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -134,12 +135,12 @@ var _ = Describe("CreateMachine", func() { UserData: "#cloud-config from ProviderSpec", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) req.MachineClass.ProviderSpec.Raw = providerSpecRaw secret.Data["userData"] = []byte("#cloud-config from Secret") var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -158,7 +159,7 @@ var _ = Describe("CreateMachine", func() { It("should not send userData when neither ProviderSpec nor Secret have it", func() { var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", @@ -178,7 +179,7 @@ var _ = Describe("CreateMachine", func() { secret.Data["userData"] = []byte("") var capturedReq *client.CreateServerRequest - mockClient.createServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { + mockClient.CreateServerFunc = func(_ context.Context, _, _ string, req *client.CreateServerRequest) (*client.Server, error) { capturedReq = req return &client.Server{ ID: "test-server-id", diff --git a/pkg/provider/delete_test.go b/pkg/provider/delete_test.go index ec291eda..746f76fb 100644 --- a/pkg/provider/delete_test.go +++ b/pkg/provider/delete_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,7 +22,7 @@ var _ = Describe("DeleteMachine", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.DeleteMachineRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -30,7 +31,7 @@ var _ = Describe("DeleteMachine", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -50,7 +51,7 @@ var _ = Describe("DeleteMachine", func() { ImageID: "image-uuid-123", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -83,7 +84,7 @@ var _ = Describe("DeleteMachine", func() { Context("with valid inputs", func() { It("should successfully delete a machine", func() { - mockClient.deleteServerFunc = func(_ context.Context, _, _, _ string) error { + mockClient.DeleteServerFunc = func(_ context.Context, _, _, _ string) error { return nil } @@ -97,7 +98,7 @@ var _ = Describe("DeleteMachine", func() { var capturedProjectID string var capturedServerID string - mockClient.deleteServerFunc = func(_ context.Context, projectID, _, serverID string) error { + mockClient.DeleteServerFunc = func(_ context.Context, projectID, _, serverID string) error { capturedProjectID = projectID capturedServerID = serverID return nil @@ -134,7 +135,7 @@ var _ = Describe("DeleteMachine", func() { Context("when machine not found", func() { It("should return success if machine does not exist (idempotent)", func() { - mockClient.deleteServerFunc = func(_ context.Context, _, _, _ string) error { + mockClient.DeleteServerFunc = func(_ context.Context, _, _, _ string) error { return fmt.Errorf("%w: status 404", client.ErrServerNotFound) } @@ -147,7 +148,7 @@ var _ = Describe("DeleteMachine", func() { Context("when STACKIT API fails", func() { It("should return error when API call fails", func() { - mockClient.deleteServerFunc = func(_ context.Context, _, _, _ string) error { + mockClient.DeleteServerFunc = func(_ context.Context, _, _, _ string) error { return fmt.Errorf("API connection failed") } diff --git a/pkg/provider/list_test.go b/pkg/provider/list_test.go index ff145f64..cbaf18df 100644 --- a/pkg/provider/list_test.go +++ b/pkg/provider/list_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,7 +22,7 @@ var _ = Describe("ListMachines", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.ListMachinesRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -29,7 +30,7 @@ var _ = Describe("ListMachines", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -48,7 +49,7 @@ var _ = Describe("ListMachines", func() { ImageID: "image-uuid-123", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -69,7 +70,7 @@ var _ = Describe("ListMachines", func() { Context("with valid inputs", func() { It("should list machines filtered by MachineClass label", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*client.Server, error) { + mockClient.ListServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*client.Server, error) { Expect(selector["mcm.gardener.cloud/machineclass"]).To(Equal("test-machine-class")) return []*client.Server{ @@ -103,7 +104,7 @@ var _ = Describe("ListMachines", func() { }) It("should return empty list when no servers match", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { + mockClient.ListServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { return []*client.Server{}, nil } @@ -115,7 +116,7 @@ var _ = Describe("ListMachines", func() { }) It("should return empty list when no servers exist", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { + mockClient.ListServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { return []*client.Server{}, nil } @@ -129,7 +130,7 @@ var _ = Describe("ListMachines", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.listServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { + mockClient.ListServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) { return nil, fmt.Errorf("API connection failed") } diff --git a/pkg/provider/status_test.go b/pkg/provider/status_test.go index dbd7c1c2..e52440af 100644 --- a/pkg/provider/status_test.go +++ b/pkg/provider/status_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client/mock" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,7 +22,7 @@ var _ = Describe("GetMachineStatus", func() { var ( ctx context.Context provider *Provider - mockClient *mockStackitClient + mockClient *mock.StackitClient req *driver.GetMachineStatusRequest secret *corev1.Secret machineClass *v1alpha1.MachineClass @@ -30,7 +31,7 @@ var _ = Describe("GetMachineStatus", func() { BeforeEach(func() { ctx = context.Background() - mockClient = &mockStackitClient{} + mockClient = &mock.StackitClient{} provider = &Provider{ client: mockClient, } @@ -50,7 +51,7 @@ var _ = Describe("GetMachineStatus", func() { ImageID: "image-uuid-123", Region: "eu01", } - providerSpecRaw, _ := encodeProviderSpec(providerSpec) + providerSpecRaw, _ := mock.EncodeProviderSpec(providerSpec) // Create MachineClass machineClass = &v1alpha1.MachineClass{ @@ -83,7 +84,7 @@ var _ = Describe("GetMachineStatus", func() { Context("with valid inputs", func() { It("should successfully get machine status when server exists", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, serverID string) (*client.Server, error) { + mockClient.GetServerFunc = func(_ context.Context, _, _, serverID string) (*client.Server, error) { return &client.Server{ ID: serverID, Name: "test-machine", @@ -103,7 +104,7 @@ var _ = Describe("GetMachineStatus", func() { var capturedProjectID string var capturedServerID string - mockClient.getServerFunc = func(_ context.Context, projectID, _, serverID string) (*client.Server, error) { + mockClient.GetServerFunc = func(_ context.Context, projectID, _, serverID string) (*client.Server, error) { capturedProjectID = projectID capturedServerID = serverID return &client.Server{ @@ -158,7 +159,7 @@ var _ = Describe("GetMachineStatus", func() { Context("when server does not exist", func() { It("should return NotFound when server is not found", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { + mockClient.GetServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { return nil, fmt.Errorf("%w: status 404", client.ErrServerNotFound) } @@ -173,7 +174,7 @@ var _ = Describe("GetMachineStatus", func() { Context("when STACKIT API fails", func() { It("should return Internal error on API failure", func() { - mockClient.getServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { + mockClient.GetServerFunc = func(_ context.Context, _, _, _ string) (*client.Server, error) { return nil, fmt.Errorf("API connection failed") }