diff --git a/.prow/provider-equinix-metal.yaml b/.prow/provider-equinix-metal.yaml deleted file mode 100644 index 71e8e893e..000000000 --- a/.prow/provider-equinix-metal.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2022 The Machine Controller 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. - -presubmits: - - name: pull-machine-controller-e2e-equinix-metal - optional: true - run_if_changed: "(pkg/cloudprovider/provider/equinixmetal/|pkg/userdata)" - decorate: true - clone_uri: "ssh://git@github.com/kubermatic/machine-controller.git" - path_alias: k8c.io/machine-controller - labels: - preset-hetzner: "true" - preset-e2e-ssh: "true" - preset-equinix-metal: "true" - preset-goproxy: "true" - preset-kind-volume-mounts: "true" - preset-docker-mirror: "true" - preset-kubeconfig-ci: "true" - spec: - containers: - - image: quay.io/kubermatic/build:go-1.25-node-22-kind-0.30-8 - command: - - "./hack/ci/run-e2e-tests.sh" - args: - - "TestEquinixMetalProvisioningE2E" - env: - - name: CLOUD_PROVIDER - value: metal - securityContext: - privileged: true - resources: - requests: - memory: 7Gi - cpu: 2 - limits: - memory: 7Gi diff --git a/docs/cloud-provider.md b/docs/cloud-provider.md index 375b30a75..f00e05e19 100644 --- a/docs/cloud-provider.md +++ b/docs/cloud-provider.md @@ -322,26 +322,6 @@ tags: "kubernetesCluster": "my-cluster" ``` -## Equinix Metal - -### machine.spec.providerConfig.cloudProviderSpec -```yaml -# If empty, can be set via METAL_AUTH_TOKEN env var -token: "<< METAL_AUTH_TOKEN >>" -# instance type -instanceType: "t1.small.x86" -# Equinix Metal project ID -projectID: "<< PROJECT_ID >>" -# Equinix Metal facilities -facilities: - - "ewr1" -# Equinix Metal billingCycle -billingCycle: "" -# node tags -tags: - "kubernetesCluster": "my-cluster" -``` - ## KubeVirt ### machine.spec.providerConfig.cloudProviderSpec diff --git a/docs/operating-system.md b/docs/operating-system.md index c4b7692f6..680d8be94 100644 --- a/docs/operating-system.md +++ b/docs/operating-system.md @@ -9,7 +9,6 @@ | AWS | ✓ | ✓ | ✓ | ✓ | ✓ | | Azure | ✓ | ✓ | ✓ | x | ✓ | | Digitalocean | ✓ | x | x | x | ✓ | -| Equinix Metal | ✓ | ✓ | x | x | ✓ | | Google Cloud Platform | ✓ | ✓ | x | x | x | | Hetzner | ✓ | x | x | x | ✓ | | KubeVirt | ✓ | ✓ | ✓ | x | ✓ | diff --git a/examples/equinixmetal-machinedeployment.yaml b/examples/equinixmetal-machinedeployment.yaml deleted file mode 100644 index 9540d65e5..000000000 --- a/examples/equinixmetal-machinedeployment.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - # If you change the namespace/name, you must also - # adjust the rbac rules - name: machine-controller-equinixmetal - namespace: kube-system -type: Opaque -stringData: - token: << METAL_AUTH_TOKEN >> ---- -apiVersion: "cluster.k8s.io/v1alpha1" -kind: MachineDeployment -metadata: - name: equinixmetal-machinedeployment - namespace: kube-system -spec: - paused: false - replicas: 1 - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - minReadySeconds: 0 - selector: - matchLabels: - foo: bar - template: - metadata: - labels: - foo: bar - spec: - providerSpec: - value: - sshPublicKeys: - - "<< YOUR_PUBLIC_KEY >>" - cloudProvider: "equinixmetal" - cloudProviderSpec: - # If empty, can be set via METAL_TOKEN env var - token: - secretKeyRef: - namespace: kube-system - name: machine-controller-equinixmetal - key: token - instanceType: "t1.small.x86" - projectID: "<< PROJECT_ID >>" - facilities: - - "ewr1" - operatingSystem: "ubuntu" - operatingSystemSpec: - distUpgradeOnBoot: false - versions: - kubelet: 1.33.4 diff --git a/examples/operating-system-manager.yaml b/examples/operating-system-manager.yaml index da5a8b5e2..6233b4c45 100644 --- a/examples/operating-system-manager.yaml +++ b/examples/operating-system-manager.yaml @@ -208,7 +208,6 @@ spec: - linode - nutanix - openstack - - equinixmetal - vsphere - fake - alibaba @@ -947,7 +946,6 @@ spec: - linode - nutanix - openstack - - equinixmetal - vsphere - fake - alibaba diff --git a/go.mod b/go.mod index 09fb67071..894bf9314 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/aws/smithy-go v1.20.4 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/digitalocean/godo v1.124.0 - github.com/equinix/equinix-sdk-go v0.46.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 github.com/go-test/deep v1.1.0 diff --git a/go.sum b/go.sum index 372ad422f..06c82b79a 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9O github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= -github.com/equinix/equinix-sdk-go v0.46.0 h1:ldQo4GtXNr+0XsThQJf/pUdx5wcLFe9QpLFtAwonqH8= -github.com/equinix/equinix-sdk-go v0.46.0/go.mod h1:hEb3XLaedz7xhl/dpPIS6eOIiXNPeqNiVoyDrT6paIg= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= diff --git a/pkg/admission/machinedeployments_validation.go b/pkg/admission/machinedeployments_validation.go index 2e58d7da5..61a43eed0 100644 --- a/pkg/admission/machinedeployments_validation.go +++ b/pkg/admission/machinedeployments_validation.go @@ -120,14 +120,6 @@ func mutationsForMachineDeployment(md *clusterv1alpha1.MachineDeployment) error return fmt.Errorf("failed to read MachineDeployment.Spec.Template.Spec.ProviderSpec: %w", err) } - // Packet has been renamed to Equinix Metal - if providerConfig.CloudProvider == cloudProviderPacket { - err = migrateToEquinixMetal(providerConfig) - if err != nil { - return fmt.Errorf("failed to migrate packet to equinix metal: %w", err) - } - } - // Migrate if providerConfig.CloudProvider == providerconfigtypes.CloudProviderVMwareCloudDirector { err := migrateVMwareCloudDirector(providerConfig) diff --git a/pkg/admission/machines.go b/pkg/admission/machines.go index 50f81dd4c..a585c52ce 100644 --- a/pkg/admission/machines.go +++ b/pkg/admission/machines.go @@ -123,14 +123,6 @@ func (ad *admissionData) defaultAndValidateMachineSpec(ctx context.Context, spec return fmt.Errorf("failed to read machine.spec.providerSpec: %w", err) } - // Packet has been renamed to Equinix Metal - if providerConfig.CloudProvider == cloudProviderPacket { - err = migrateToEquinixMetal(providerConfig) - if err != nil { - return fmt.Errorf("failed to migrate packet to equinix metal: %w", err) - } - } - // For KubeVirt we need to initialize the annotations for MachineDeployment, to enable setting of the needed annotations. if providerConfig.CloudProvider == providerconfig.CloudProviderKubeVirt { if spec.Annotations == nil { diff --git a/pkg/admission/util.go b/pkg/admission/util.go index 1e49dbf26..12d6256fc 100644 --- a/pkg/admission/util.go +++ b/pkg/admission/util.go @@ -24,33 +24,6 @@ import ( providerconfigtypes "k8c.io/machine-controller/sdk/providerconfig" ) -const cloudProviderPacket = "packet" - -func migrateToEquinixMetal(providerConfig *providerconfigtypes.Config) (err error) { - providerConfig.CloudProvider = providerconfigtypes.CloudProviderEquinixMetal - - // Field .spec.providerSpec.cloudProviderSpec.apiKey has been replaced with .spec.providerSpec.cloudProviderSpec.token - // We first need to perform in-place replacement for this field - rawConfig := map[string]interface{}{} - if err := json.Unmarshal(providerConfig.CloudProviderSpec.Raw, &rawConfig); err != nil { - return fmt.Errorf("failed to unmarshal providerConfig.CloudProviderSpec.Raw: %w", err) - } - // NB: We have to set the token only if apiKey existed, otherwise, migrated - // machines will not create at all (authentication errors). - apiKey, ok := rawConfig["apiKey"] - if ok { - rawConfig["token"] = apiKey - delete(rawConfig, "apiKey") - } - - // Update original object - providerConfig.CloudProviderSpec.Raw, err = json.Marshal(rawConfig) - if err != nil { - return fmt.Errorf("failed to json marshal providerConfig.CloudProviderSpec.Raw: %w", err) - } - return nil -} - func migrateVMwareCloudDirector(providerConfig *providerconfigtypes.Config) (err error) { config, err := vcdtypes.GetConfig(*providerConfig) if err != nil { diff --git a/pkg/cloudprovider/provider.go b/pkg/cloudprovider/provider.go index c334b3a84..285fffa67 100644 --- a/pkg/cloudprovider/provider.go +++ b/pkg/cloudprovider/provider.go @@ -27,7 +27,6 @@ import ( "k8c.io/machine-controller/pkg/cloudprovider/provider/baremetal" "k8c.io/machine-controller/pkg/cloudprovider/provider/digitalocean" "k8c.io/machine-controller/pkg/cloudprovider/provider/edge" - "k8c.io/machine-controller/pkg/cloudprovider/provider/equinixmetal" "k8c.io/machine-controller/pkg/cloudprovider/provider/external" "k8c.io/machine-controller/pkg/cloudprovider/provider/fake" "k8c.io/machine-controller/pkg/cloudprovider/provider/gce" @@ -73,16 +72,6 @@ var ( providerconfig.CloudProviderAzure: func(cvr providerconfig.ConfigVarResolver) cloudprovidertypes.Provider { return azure.New(cvr) }, - providerconfig.CloudProviderEquinixMetal: func(cvr providerconfig.ConfigVarResolver) cloudprovidertypes.Provider { - return equinixmetal.New(cvr) - }, - // NB: This is explicitly left to allow old Packet machines to be deleted. - // We can handle those machines in the same way as Equinix Metal machines - // because there are no API changes. - // TODO: Remove this after deprecation period. - providerconfig.CloudProviderPacket: func(cvr providerconfig.ConfigVarResolver) cloudprovidertypes.Provider { - return equinixmetal.New(cvr) - }, providerconfig.CloudProviderFake: func(cvr providerconfig.ConfigVarResolver) cloudprovidertypes.Provider { return fake.New(cvr) }, diff --git a/pkg/cloudprovider/provider/equinixmetal/provider.go b/pkg/cloudprovider/provider/equinixmetal/provider.go deleted file mode 100644 index 871351b9c..000000000 --- a/pkg/cloudprovider/provider/equinixmetal/provider.go +++ /dev/null @@ -1,578 +0,0 @@ -/* -Copyright 2019 The Machine Controller 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. -*/ - -package equinixmetal - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "slices" - "strings" - - "github.com/equinix/equinix-sdk-go/services/metalv1" - "go.uber.org/zap" - - cloudprovidererrors "k8c.io/machine-controller/pkg/cloudprovider/errors" - "k8c.io/machine-controller/pkg/cloudprovider/instance" - cloudprovidertypes "k8c.io/machine-controller/pkg/cloudprovider/types" - "k8c.io/machine-controller/sdk/apis/cluster/common" - clusterv1alpha1 "k8c.io/machine-controller/sdk/apis/cluster/v1alpha1" - equinixmetaltypes "k8c.io/machine-controller/sdk/cloudprovider/equinixmetal" - "k8c.io/machine-controller/sdk/providerconfig" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/sets" -) - -const ( - machineUIDTag = "kubermatic-machine-controller:machine-uid" - defaultBillingCycle = "hourly" -) - -// New returns a Equinix Metal provider. -func New(configVarResolver providerconfig.ConfigVarResolver) cloudprovidertypes.Provider { - return &provider{configVarResolver: configVarResolver} -} - -type Config struct { - Token string - ProjectID string - BillingCycle string - InstanceType string - Metro string - Facilities []string - Tags []string -} - -// because we have both Config and RawConfig, we need to have func for each -// ideally, these would be merged into one. -func (c *Config) populateDefaults() { - if c.BillingCycle == "" { - c.BillingCycle = defaultBillingCycle - } -} - -func populateDefaults(c *equinixmetaltypes.RawConfig) { - if c.BillingCycle.Value == "" { - c.BillingCycle.Value = defaultBillingCycle - } -} - -type provider struct { - configVarResolver providerconfig.ConfigVarResolver -} - -func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *equinixmetaltypes.RawConfig, *providerconfig.Config, error) { - pconfig, err := providerconfig.GetConfig(provSpec) - if err != nil { - return nil, nil, nil, err - } - - rawConfig, err := equinixmetaltypes.GetConfig(*pconfig) - if err != nil { - return nil, nil, nil, err - } - - if pconfig.OperatingSystemSpec.Raw == nil { - return nil, nil, nil, errors.New("operatingSystemSpec in the MachineDeployment cannot be empty") - } - - c := Config{} - c.Token, err = p.configVarResolver.GetStringValueOrEnv(rawConfig.Token, "METAL_AUTH_TOKEN") - if err != nil || len(c.Token) == 0 { - // This retry is temporary and is only required to facilitate migration from Packet to Equinix Metal - // We look for env variable PACKET_API_KEY associated with Packet to ensure that nothing breaks during automated migration for the Machines - // TODO(@ahmedwaleedmalik) Remove this after a release period - c.Token, err = p.configVarResolver.GetStringValueOrEnv(rawConfig.Token, "PACKET_API_KEY") - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get the value of \"apiKey\" field, error = %w", err) - } - } - c.ProjectID, err = p.configVarResolver.GetStringValueOrEnv(rawConfig.ProjectID, "METAL_PROJECT_ID") - if err != nil || len(c.ProjectID) == 0 { - // This retry is temporary and is only required to facilitate migration from Packet to Equinix Metal - // We look for env variable PACKET_PROJECT_ID associated with Packet to ensure that nothing breaks during automated migration for the Machines - // TODO(@ahmedwaleedmalik) Remove this after a release period - c.ProjectID, err = p.configVarResolver.GetStringValueOrEnv(rawConfig.ProjectID, "PACKET_PROJECT_ID") - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get the value of \"apiKey\" field, error = %w", err) - } - } - c.InstanceType, err = p.configVarResolver.GetStringValue(rawConfig.InstanceType) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get the value of \"instanceType\" field, error = %w", err) - } - c.BillingCycle, err = p.configVarResolver.GetStringValue(rawConfig.BillingCycle) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get the value of \"billingCycle\" field, error = %w", err) - } - for i, tag := range rawConfig.Tags { - tagValue, err := p.configVarResolver.GetStringValue(tag) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read the value for the Tag at index %d of the \"tags\" field, error = %w", i, err) - } - c.Tags = append(c.Tags, tagValue) - } - for i, facility := range rawConfig.Facilities { - facilityValue, err := p.configVarResolver.GetStringValue(facility) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read the value for the Tag at index %d of the \"facilities\" field, error = %w", i, err) - } - c.Facilities = append(c.Facilities, facilityValue) - } - c.Metro, err = p.configVarResolver.GetStringValue(rawConfig.Metro) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get the value of \"metro\" field, error = %w", err) - } - - // ensure we have defaults - c.populateDefaults() - - return &c, rawConfig, pconfig, err -} - -func (p *provider) getMetalDevice(ctx context.Context, machine *clusterv1alpha1.Machine) (*metalv1.Device, *metalv1.APIClient, error) { - c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) - if err != nil { - return nil, nil, cloudprovidererrors.TerminalError{ - Reason: common.InvalidConfigurationMachineError, - Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), - } - } - - client := getClient(c.Token) - device, err := getDeviceByTag(ctx, client, c.ProjectID, generateTag(string(machine.UID))) - if err != nil { - return nil, nil, err - } - return device, client, nil -} - -func (p *provider) Validate(ctx context.Context, _ *zap.SugaredLogger, spec clusterv1alpha1.MachineSpec) error { - c, _, pc, err := p.getConfig(spec.ProviderSpec) - if err != nil { - return fmt.Errorf("failed to parse config: %w", err) - } - - if c.Token == "" { - return errors.New("apiKey is missing") - } - if c.InstanceType == "" { - return errors.New("instanceType is missing") - } - if c.ProjectID == "" { - return errors.New("projectID is missing") - } - - _, err = getNameForOS(pc.OperatingSystem) - if err != nil { - return fmt.Errorf("invalid/not supported operating system specified %q: %w", pc.OperatingSystem, err) - } - - client := getClient(c.Token) - - if c.Metro == "" && (len(c.Facilities) == 0 || c.Facilities[0] == "") { - return fmt.Errorf("must have at least one non-blank facility or a metro") - } - - if c.Facilities != nil && (len(c.Facilities) > 0 || c.Facilities[0] != "") { - // get all valid facilities - request := client.FacilitiesApi.FindFacilitiesByProject(ctx, c.ProjectID) - facilities, resp, err := client.FacilitiesApi.FindFacilitiesByProjectExecute(request) - if err != nil { - return fmt.Errorf("failed to list facilities: %w", err) - } - resp.Body.Close() - - expectedFacilities := sets.New(c.Facilities...) - availableFacilities := sets.New[string]() - for _, facility := range facilities.Facilities { - availableFacilities.Insert(*facility.Code) - } - - // ensure our requested facilities are in those facilities - if diff := expectedFacilities.Difference(availableFacilities); diff.Len() > 0 { - return fmt.Errorf("unknown facilities: %v", sets.List(diff)) - } - } - - if c.Metro != "" { - request := client.MetrosApi.FindMetros(ctx) - metros, resp, err := client.MetrosApi.FindMetrosExecute(request) - if err != nil { - return fmt.Errorf("failed to list metros: %w", err) - } - resp.Body.Close() - - metroExists := slices.ContainsFunc(metros.Metros, func(m metalv1.Metro) bool { - return strings.EqualFold(*m.Code, c.Metro) - }) - - if !metroExists { - return fmt.Errorf("unknown metro %q", c.Metro) - } - } - - // get all valid plans a.k.a. instance types - request := client.PlansApi.FindPlansByProject(ctx, c.ProjectID) - plans, resp, err := client.PlansApi.FindPlansByProjectExecute(request) - if err != nil { - return fmt.Errorf("failed to list instance types / plans: %w", err) - } - resp.Body.Close() - - // ensure our requested plan is in those plans - expectedPlans := sets.New(c.InstanceType) - availablePlans := sets.New[string]() - for _, plan := range plans.Plans { - availablePlans.Insert(*plan.Name) - } - - if diff := expectedPlans.Difference(availablePlans); diff.Len() > 0 { - return fmt.Errorf("unknown instance type / plan: %s, acceptable plans: %v", c.InstanceType, sets.List(availablePlans)) - } - - return nil -} - -func (p *provider) Create(ctx context.Context, _ *zap.SugaredLogger, machine *clusterv1alpha1.Machine, _ *cloudprovidertypes.ProviderData, userdata string) (instance.Instance, error) { - c, _, pc, err := p.getConfig(machine.Spec.ProviderSpec) - if err != nil { - return nil, cloudprovidererrors.TerminalError{ - Reason: common.InvalidConfigurationMachineError, - Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), - } - } - - client := getClient(c.Token) - request := client.DevicesApi.CreateDevice(ctx, c.ProjectID) - - imageName, err := getNameForOS(pc.OperatingSystem) - if err != nil { - return nil, cloudprovidererrors.TerminalError{ - Reason: common.InvalidConfigurationMachineError, - Message: fmt.Sprintf("Invalid operating system specified %q, details = %v", pc.OperatingSystem, err), - } - } - - billingCycle := metalv1.DeviceCreateInputBillingCycle(c.BillingCycle) - - if c.Metro != "" { - request = request.CreateDeviceRequest(metalv1.CreateDeviceRequest{ - DeviceCreateInMetroInput: &metalv1.DeviceCreateInMetroInput{ - Hostname: &machine.Spec.Name, - Userdata: &userdata, - Metro: c.Metro, - BillingCycle: &billingCycle, - Plan: c.InstanceType, - OperatingSystem: imageName, - Tags: []string{ - generateTag(string(machine.UID)), - }, - }, - }) - } else { - request = request.CreateDeviceRequest(metalv1.CreateDeviceRequest{ - DeviceCreateInFacilityInput: &metalv1.DeviceCreateInFacilityInput{ - Hostname: &machine.Spec.Name, - Userdata: &userdata, - Facility: c.Facilities, - BillingCycle: &billingCycle, - Plan: c.InstanceType, - OperatingSystem: imageName, - Tags: []string{ - generateTag(string(machine.UID)), - }, - }, - }) - } - - device, resp, err := client.DevicesApi.CreateDeviceExecute(request) - if err != nil { - return nil, metalErrorToTerminalError(err, resp, "failed to create server") - } - resp.Body.Close() - - return &metalDevice{device: device}, nil -} - -func (p *provider) Cleanup(ctx context.Context, log *zap.SugaredLogger, machine *clusterv1alpha1.Machine, data *cloudprovidertypes.ProviderData) (bool, error) { - instance, err := p.Get(ctx, log, machine, data) - if err != nil { - if errors.Is(err, cloudprovidererrors.ErrInstanceNotFound) { - return true, nil - } - return false, err - } - - c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) - if err != nil { - return false, cloudprovidererrors.TerminalError{ - Reason: common.InvalidConfigurationMachineError, - Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), - } - } - - client := getClient(c.Token) - request := client.DevicesApi.DeleteDevice(ctx, *instance.(*metalDevice).device.Id) - - resp, err := client.DevicesApi.DeleteDeviceExecute(request) - if err != nil { - return false, metalErrorToTerminalError(err, resp, "failed to delete the server") - } - resp.Body.Close() - - return false, nil -} - -func (p *provider) AddDefaults(_ *zap.SugaredLogger, spec clusterv1alpha1.MachineSpec) (clusterv1alpha1.MachineSpec, error) { - _, rawConfig, _, err := p.getConfig(spec.ProviderSpec) - if err != nil { - return spec, err - } - populateDefaults(rawConfig) - spec.ProviderSpec.Value, err = setProviderSpec(*rawConfig, spec.ProviderSpec) - if err != nil { - return spec, err - } - return spec, nil -} - -func (p *provider) Get(ctx context.Context, _ *zap.SugaredLogger, machine *clusterv1alpha1.Machine, _ *cloudprovidertypes.ProviderData) (instance.Instance, error) { - device, _, err := p.getMetalDevice(ctx, machine) - if err != nil { - return nil, err - } - if device != nil { - return &metalDevice{device: device}, nil - } - - return nil, cloudprovidererrors.ErrInstanceNotFound -} - -func (p *provider) MigrateUID(ctx context.Context, log *zap.SugaredLogger, machine *clusterv1alpha1.Machine, newID types.UID) error { - device, client, err := p.getMetalDevice(ctx, machine) - if err != nil { - return err - } - if device == nil { - log.Info("No instance exists for machine") - return nil - } - - // go through existing labels, make sure that no other UID label exists - tags := make([]string, 0) - for _, t := range device.Tags { - // filter out old UID tag(s) - if _, err := getTagUID(t); err != nil { - tags = append(tags, t) - } - } - - // create a new UID label - tags = append(tags, generateTag(string(newID))) - - log.Info("Setting UID label for machine") - - dur := client.DevicesApi. - UpdateDevice(ctx, *device.Id). - DeviceUpdateInput(metalv1.DeviceUpdateInput{ - Tags: tags, - }) - - _, response, err := client.DevicesApi.UpdateDeviceExecute(dur) - if err != nil { - return metalErrorToTerminalError(err, response, "failed to update UID label") - } - response.Body.Close() - - log.Info("Successfully set UID label for machine") - - return nil -} - -func (p *provider) MachineMetricsLabels(machine *clusterv1alpha1.Machine) (map[string]string, error) { - labels := make(map[string]string) - - c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) - if err == nil { - labels["size"] = c.InstanceType - labels["facilities"] = strings.Join(c.Facilities, ",") - } - - return labels, err -} - -func (p *provider) SetMetricsForMachines(_ clusterv1alpha1.MachineList) error { - return nil -} - -type metalDevice struct { - device *metalv1.Device -} - -func (s *metalDevice) Name() string { - return *s.device.Hostname -} - -func (s *metalDevice) ID() string { - return *s.device.Id -} - -func (s *metalDevice) ProviderID() string { - if s.device == nil || *s.device.Id == "" { - return "" - } - return "equinixmetal://" + *s.device.Id -} - -// Addresses returns addresses in CIDR format. -func (s *metalDevice) Addresses() map[string]corev1.NodeAddressType { - addresses := map[string]corev1.NodeAddressType{} - for _, ip := range s.device.IpAddresses { - kind := corev1.NodeInternalIP - if *ip.Public { - kind = corev1.NodeExternalIP - } - - addresses[*ip.Address] = kind - } - - return addresses -} - -func (s *metalDevice) Status() instance.Status { - if s.device.State == nil { - return instance.StatusUnknown - } - - switch *s.device.State { - case metalv1.DEVICESTATE_PROVISIONING: - return instance.StatusCreating - case metalv1.DEVICESTATE_ACTIVE: - return instance.StatusRunning - default: - return instance.StatusUnknown - } -} - -// CONVENIENCE INTERNAL FUNCTIONS. -func setProviderSpec(rawConfig equinixmetaltypes.RawConfig, s clusterv1alpha1.ProviderSpec) (*runtime.RawExtension, error) { - if s.Value == nil { - return nil, fmt.Errorf("machine.spec.providerconfig.value is nil") - } - - pconfig, err := providerconfig.GetConfig(s) - if err != nil { - return nil, err - } - - rawCloudProviderSpec, err := json.Marshal(rawConfig) - if err != nil { - return nil, err - } - - pconfig.CloudProviderSpec = runtime.RawExtension{Raw: rawCloudProviderSpec} - rawPconfig, err := json.Marshal(pconfig) - if err != nil { - return nil, err - } - - return &runtime.RawExtension{Raw: rawPconfig}, nil -} - -func getDeviceByTag(ctx context.Context, client *metalv1.APIClient, projectID, tag string) (*metalv1.Device, error) { - request := client.DevicesApi. - FindProjectDevices(ctx, projectID). - Tag(tag) - - devices, response, err := client.DevicesApi.FindProjectDevicesExecute(request) - if err != nil { - return nil, metalErrorToTerminalError(err, response, "failed to list devices") - } - response.Body.Close() - - for _, device := range devices.Devices { - if slices.Contains(device.Tags, tag) { - return &device, nil - } - } - - return nil, nil -} - -// given a defined Kubermatic constant for an operating system, return the canonical slug for Equinix Metal. -func getNameForOS(os providerconfig.OperatingSystem) (string, error) { - switch os { - case providerconfig.OperatingSystemUbuntu: - return "ubuntu_24_04", nil - case providerconfig.OperatingSystemFlatcar: - return "flatcar_stable", nil - case providerconfig.OperatingSystemRockyLinux: - return "rocky_8", nil - } - return "", providerconfig.ErrOSNotSupported -} - -func getClient(apiKey string) *metalv1.APIClient { - configuration := metalv1.NewConfiguration() - configuration.UserAgent = fmt.Sprintf("kubermatic/machine-controller %s", configuration.UserAgent) - configuration.AddDefaultHeader("X-Auth-Token", apiKey) - - return metalv1.NewAPIClient(configuration) -} - -func generateTag(ID string) string { - return fmt.Sprintf("%s:%s", machineUIDTag, ID) -} - -func getTagUID(tag string) (string, error) { - parts := strings.Split(tag, ":") - if len(parts) < 2 || parts[0] != machineUIDTag { - return "", fmt.Errorf("not a machine UID tag") - } - return parts[1], nil -} - -// metalErrorToTerminalError judges if the given error -// can be qualified as a "terminal" error, for more info see v1alpha1.MachineStatus -// -// if the given error doesn't qualify the error passed as an argument will be returned. -func metalErrorToTerminalError(err error, response *http.Response, msg string) error { - prepareAndReturnError := func() error { - return fmt.Errorf("%s: %w", msg, err) - } - - if err != nil { - if response != nil && response.StatusCode == http.StatusForbidden { - // authorization primitives come from MachineSpec - // thus we are setting InvalidConfigurationMachineError - return cloudprovidererrors.TerminalError{ - Reason: common.InvalidConfigurationMachineError, - Message: "A request has been rejected due to invalid credentials which were taken from the MachineSpec", - } - } - - return prepareAndReturnError() - } - - return err -} diff --git a/sdk/cloudprovider/equinixmetal/types.go b/sdk/cloudprovider/equinixmetal/types.go deleted file mode 100644 index a941b58a8..000000000 --- a/sdk/cloudprovider/equinixmetal/types.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2019 The Machine Controller 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. -*/ - -package equinixmetal - -import ( - "k8c.io/machine-controller/sdk/jsonutil" - "k8c.io/machine-controller/sdk/providerconfig" -) - -type RawConfig struct { - Token providerconfig.ConfigVarString `json:"token,omitempty"` - ProjectID providerconfig.ConfigVarString `json:"projectID,omitempty"` - BillingCycle providerconfig.ConfigVarString `json:"billingCycle"` - InstanceType providerconfig.ConfigVarString `json:"instanceType"` - Metro providerconfig.ConfigVarString `json:"metro,omitempty"` - Facilities []providerconfig.ConfigVarString `json:"facilities,omitempty"` - Tags []providerconfig.ConfigVarString `json:"tags,omitempty"` -} - -func GetConfig(pconfig providerconfig.Config) (*RawConfig, error) { - rawConfig := &RawConfig{} - - return rawConfig, jsonutil.StrictUnmarshal(pconfig.CloudProviderSpec.Raw, rawConfig) -} diff --git a/sdk/providerconfig/types.go b/sdk/providerconfig/types.go index b986a8a8b..6a58db844 100644 --- a/sdk/providerconfig/types.go +++ b/sdk/providerconfig/types.go @@ -58,8 +58,6 @@ const ( CloudProviderAzure CloudProvider = "azure" CloudProviderDigitalocean CloudProvider = "digitalocean" CloudProviderGoogle CloudProvider = "gce" - CloudProviderEquinixMetal CloudProvider = "equinixmetal" - CloudProviderPacket CloudProvider = "packet" CloudProviderHetzner CloudProvider = "hetzner" CloudProviderKubeVirt CloudProvider = "kubevirt" CloudProviderLinode CloudProvider = "linode" @@ -95,8 +93,6 @@ var ( CloudProviderAWS, CloudProviderAzure, CloudProviderDigitalocean, - CloudProviderEquinixMetal, - CloudProviderPacket, CloudProviderGoogle, CloudProviderHetzner, CloudProviderKubeVirt, diff --git a/test/e2e/provisioning/all_e2e_test.go b/test/e2e/provisioning/all_e2e_test.go index ad94fa78b..58343634f 100644 --- a/test/e2e/provisioning/all_e2e_test.go +++ b/test/e2e/provisioning/all_e2e_test.go @@ -59,7 +59,6 @@ const ( AzureManifest = "./testdata/machinedeployment-azure.yaml" AzureRedhatSatelliteManifest = "./testdata/machinedeployment-azure.yaml" AzureCustomImageReferenceManifest = "./testdata/machinedeployment-azure-custom-image-reference.yaml" - EquinixMetalManifest = "./testdata/machinedeployment-equinixmetal.yaml" GCEManifest = "./testdata/machinedeployment-gce.yaml" HZManifest = "./testdata/machinedeployment-hetzner.yaml" LinodeManifest = "./testdata/machinedeployment-linode.yaml" @@ -689,32 +688,6 @@ func TestHetznerProvisioningE2E(t *testing.T) { runScenarios(context.Background(), t, selector, params, HZManifest, fmt.Sprintf("hz-%s", *testRunIdentifier)) } -// TestEquinixMetalProvisioningE2E - a test suite that exercises Equinix Metal provider -// by requesting nodes with different combination of container runtime type, container runtime version and the OS flavour. -func TestEquinixMetalProvisioningE2E(t *testing.T) { - t.Parallel() - - // test data - token := os.Getenv("METAL_AUTH_TOKEN") - if len(token) == 0 { - t.Fatal("Unable to run the test suite, METAL_AUTH_TOKEN environment variable cannot be empty") - } - - projectID := os.Getenv("METAL_PROJECT_ID") - if len(projectID) == 0 { - t.Fatal("Unable to run the test suite, METAL_PROJECT_ID environment variable cannot be empty") - } - - selector := And(OsSelector("ubuntu", "rockylinux", "flatcar"), Not(NameSelector("migrateUID"))) - - // act - params := []string{ - fmt.Sprintf("<< METAL_AUTH_TOKEN >>=%s", token), - fmt.Sprintf("<< METAL_PROJECT_ID >>=%s", projectID), - } - runScenarios(context.Background(), t, selector, params, EquinixMetalManifest, fmt.Sprintf("equinixmetal-%s", *testRunIdentifier)) -} - func TestAlibabaProvisioningE2E(t *testing.T) { t.Parallel() diff --git a/test/e2e/provisioning/helper.go b/test/e2e/provisioning/helper.go index a07d3fa45..e8587e21d 100644 --- a/test/e2e/provisioning/helper.go +++ b/test/e2e/provisioning/helper.go @@ -238,20 +238,6 @@ func testScenario(ctx context.Context, t *testing.T, testCase scenario, cloudPro scenarioParams = append(scenarioParams, fmt.Sprintf("<< MAX_PRICE >>=%s", "0.023")) } - if strings.Contains(cloudProvider, string(providerconfigtypes.CloudProviderEquinixMetal)) { - switch testCase.osName { - case string(providerconfigtypes.OperatingSystemFlatcar): - scenarioParams = append(scenarioParams, fmt.Sprintf("<< INSTANCE_TYPE >>=%s", "c3.small.x86")) - scenarioParams = append(scenarioParams, fmt.Sprintf("<< METRO_CODE >>=%s", "NY")) - case string(providerconfigtypes.OperatingSystemRockyLinux): - scenarioParams = append(scenarioParams, fmt.Sprintf("<< INSTANCE_TYPE >>=%s", "m3.small.x86")) - scenarioParams = append(scenarioParams, fmt.Sprintf("<< METRO_CODE >>=%s", "AM")) - case string(providerconfigtypes.OperatingSystemUbuntu): - scenarioParams = append(scenarioParams, fmt.Sprintf("<< INSTANCE_TYPE >>=%s", "m3.small.x86")) - scenarioParams = append(scenarioParams, fmt.Sprintf("<< METRO_CODE >>=%s", "TY")) - } - } - // only used by assume role scenario, otherwise empty (disabled) scenarioParams = append(scenarioParams, fmt.Sprintf("<< AWS_ASSUME_ROLE_ARN >>=%s", os.Getenv("AWS_ASSUME_ROLE_ARN"))) scenarioParams = append(scenarioParams, fmt.Sprintf("<< AWS_ASSUME_ROLE_EXTERNAL_ID >>=%s", os.Getenv("AWS_ASSUME_ROLE_EXTERNAL_ID"))) diff --git a/test/e2e/provisioning/testdata/machinedeployment-equinixmetal.yaml b/test/e2e/provisioning/testdata/machinedeployment-equinixmetal.yaml deleted file mode 100644 index 398240b8d..000000000 --- a/test/e2e/provisioning/testdata/machinedeployment-equinixmetal.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: "cluster.k8s.io/v1alpha1" -kind: MachineDeployment -metadata: - name: << MACHINE_NAME >> - namespace: kube-system - annotations: - k8c.io/operating-system-profile: osp-<< OS_NAME >> -spec: - replicas: 1 - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - selector: - matchLabels: - name: << MACHINE_NAME >> - template: - metadata: - labels: - name: << MACHINE_NAME >> - spec: - providerSpec: - value: - sshPublicKeys: - - "<< YOUR_PUBLIC_KEY >>" - cloudProvider: "equinixmetal" - cloudProviderSpec: - token: << METAL_AUTH_TOKEN >> - projectID: << METAL_PROJECT_ID >> - instanceType: << INSTANCE_TYPE >> - metro: << METRO_CODE >> - operatingSystem: "<< OS_NAME >>" - operatingSystemSpec: - distUpgradeOnBoot: false - disableAutoUpdate: true - versions: - kubelet: "<< KUBERNETES_VERSION >>"