From b482f187f34ecd0e63ff6169ccf73403a2b8fd3e Mon Sep 17 00:00:00 2001 From: lubedacht <132355999+lubedacht@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:56:03 +0200 Subject: [PATCH 01/10] :bug: Reduce failure risk (#107) :warning: Breaking :warning: This PR introduces changes to resource names, which are created in IONOS Cloud. This will lead to clusters being unable to be deleted. Therefore clusters need to be deleted before using the new version. **What is the purpose of this pull request/Why do we need it?** Looks like api-server creates a label with a node name in its lease. A label name has the limit of 63 characters. If the name for the node is too long this fails with an error. As manifest names can be 253 characters long, we need to at least reduce the risk of failure **Description of changes:** * node names will now be the exact name, which is generated from the machine deployment. * Removed prefixes for resources to be consistent. **Special notes for your reviewer:** * We decided to remove the `k8s` prefix from resources. * IONOS Cloud resources will still keep a prefix as the DCD will complain if there are multiple resources with the same name **Checklist:** - [x] Unit Tests added - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning) - [x] Local test successful --- internal/service/cloud/ipblock.go | 4 ++-- internal/service/cloud/ipblock_test.go | 2 +- internal/service/cloud/network.go | 2 +- internal/service/cloud/network_test.go | 2 +- internal/service/cloud/nic.go | 2 +- internal/service/cloud/nic_test.go | 6 ++++++ internal/service/cloud/server.go | 25 +++++++------------------ internal/service/cloud/server_test.go | 18 +++++++++--------- 8 files changed, 28 insertions(+), 33 deletions(-) diff --git a/internal/service/cloud/ipblock.go b/internal/service/cloud/ipblock.go index 88e72e9d..ea1920c6 100644 --- a/internal/service/cloud/ipblock.go +++ b/internal/service/cloud/ipblock.go @@ -444,11 +444,11 @@ func (s *Service) getLatestIPBlockDeletionRequest(ctx context.Context, ipBlockID // controlPlaneEndpointIPBlockName returns the name that should be used for cluster context resources. func (*Service) controlPlaneEndpointIPBlockName(cs *scope.Cluster) string { - return fmt.Sprintf("k8s-ipb-%s-%s", cs.Cluster.Namespace, cs.Cluster.Name) + return fmt.Sprintf("ipb-%s-%s", cs.Cluster.Namespace, cs.Cluster.Name) } func (*Service) failoverIPBlockName(ms *scope.Machine) string { - return fmt.Sprintf("k8s-fo-ipb-%s-%s", + return fmt.Sprintf("fo-ipb-%s-%s", ms.IonosMachine.Namespace, ms.IonosMachine.Labels[clusterv1.MachineDeploymentNameLabel], ) diff --git a/internal/service/cloud/ipblock_test.go b/internal/service/cloud/ipblock_test.go index 43ae3f9a..39a4daa3 100644 --- a/internal/service/cloud/ipblock_test.go +++ b/internal/service/cloud/ipblock_test.go @@ -38,7 +38,7 @@ func TestIPBlockTestSuite(t *testing.T) { } const ( - exampleIPBlockName = "k8s-ipb-default-test-cluster" + exampleIPBlockName = "ipb-default-test-cluster" ) func (s *ipBlockTestSuite) TestGetControlPlaneEndpointIPBlockMultipleMatches() { diff --git a/internal/service/cloud/network.go b/internal/service/cloud/network.go index e9103423..e864b36a 100644 --- a/internal/service/cloud/network.go +++ b/internal/service/cloud/network.go @@ -37,7 +37,7 @@ import ( // lanName returns the name of the cluster LAN. func (*Service) lanName(c *clusterv1.Cluster) string { return fmt.Sprintf( - "k8s-lan-%s-%s", + "lan-%s-%s", c.Namespace, c.Name) } diff --git a/internal/service/cloud/network_test.go b/internal/service/cloud/network_test.go index 5f0a8105..2e3e2f8f 100644 --- a/internal/service/cloud/network_test.go +++ b/internal/service/cloud/network_test.go @@ -47,7 +47,7 @@ func TestLANSuite(t *testing.T) { } func (s *lanSuite) TestNetworkLANName() { - s.Equal("k8s-lan-default-test-cluster", s.service.lanName(s.clusterScope.Cluster)) + s.Equal("lan-default-test-cluster", s.service.lanName(s.clusterScope.Cluster)) } func (s *lanSuite) TestLANURL() { diff --git a/internal/service/cloud/nic.go b/internal/service/cloud/nic.go index f318c226..2b9aa9da 100644 --- a/internal/service/cloud/nic.go +++ b/internal/service/cloud/nic.go @@ -149,5 +149,5 @@ func nicHasIP(nic *sdk.Nic, expectedIP string) bool { } func (*Service) nicName(m *infrav1.IonosCloudMachine) string { - return fmt.Sprintf("k8s-nic-%s-%s", m.Namespace, m.Name) + return "nic-" + m.Name } diff --git a/internal/service/cloud/nic_test.go b/internal/service/cloud/nic_test.go index 8cdaab2b..b6ac51db 100644 --- a/internal/service/cloud/nic_test.go +++ b/internal/service/cloud/nic_test.go @@ -41,6 +41,12 @@ func TestNICSuite(t *testing.T) { suite.Run(t, new(nicSuite)) } +func (s *nicSuite) TestNICName() { + nicName := s.service.nicName(s.infraMachine) + expected := "nic-" + s.infraMachine.Name + s.Equal(expected, nicName) +} + func (s *nicSuite) TestReconcileNICConfig() { s.mockGetServerCall(exampleServerID).Return(s.defaultServer(s.infraMachine, exampleDHCPIP), nil).Once() diff --git a/internal/service/cloud/server.go b/internal/service/cloud/server.go index 256260ff..9ff5fb86 100644 --- a/internal/service/cloud/server.go +++ b/internal/service/cloud/server.go @@ -213,7 +213,7 @@ func (s *Service) getServer(ctx context.Context, ms *scope.Machine) (*sdk.Server items := ptr.Deref(serverList.Items, []sdk.Server{}) // find servers with the expected name for _, server := range items { - if server.HasProperties() && *server.Properties.Name == s.serverName(ms.IonosMachine) { + if server.HasProperties() && *server.Properties.Name == ms.IonosMachine.Name { // if the server was found, we set the provider ID and return it ms.SetProviderID(ptr.Deref(server.Id, "")) return &server, nil @@ -261,7 +261,7 @@ func (s *Service) getLatestServerCreationRequest(ctx context.Context, ms *scope. s, http.MethodPost, path.Join("datacenters", ms.DatacenterID(), "servers"), - matchByName[*sdk.Server, *sdk.ServerProperties](s.serverName(ms.IonosMachine)), + matchByName[*sdk.Server, *sdk.ServerProperties](ms.IonosMachine.Name), ) } @@ -328,13 +328,13 @@ func (s *Service) createServer(ctx context.Context, secret *corev1.Secret, ms *s } // buildServerProperties returns the server properties for the expected cloud server resource. -func (s *Service) buildServerProperties( +func (*Service) buildServerProperties( ms *scope.Machine, machineSpec *infrav1.IonosCloudMachineSpec, ) sdk.ServerProperties { props := sdk.ServerProperties{ AvailabilityZone: ptr.To(machineSpec.AvailabilityZone.String()), Cores: &machineSpec.NumCores, - Name: ptr.To(s.serverName(ms.IonosMachine)), + Name: ptr.To(ms.IonosMachine.Name), Ram: &machineSpec.MemoryMB, CpuFamily: machineSpec.CPUFamily, } @@ -400,15 +400,12 @@ func (s *Service) buildServerEntities(ms *scope.Machine, params serverEntityPara } } -func (s *Service) renderUserData(ms *scope.Machine, input string) string { - // TODO(lubedacht) update user data to include needed information - // VNC and hostname - +func (*Service) renderUserData(ms *scope.Machine, input string) string { const bootCmdFormat = `bootcmd: - echo %[1]s > /etc/hostname - hostname %[1]s ` - bootCmdString := fmt.Sprintf(bootCmdFormat, s.serverName(ms.IonosMachine)) + bootCmdString := fmt.Sprintf(bootCmdFormat, ms.IonosMachine.Name) input = fmt.Sprintf("%s\n%s", input, bootCmdString) return base64.StdEncoding.EncodeToString([]byte(input)) @@ -418,14 +415,6 @@ func (*Service) serversURL(datacenterID string) string { return path.Join("datacenters", datacenterID, "servers") } -// serverName returns a formatted name for the expected cloud server resource. -func (*Service) serverName(m *infrav1.IonosCloudMachine) string { - return fmt.Sprintf( - "k8s-%s-%s", - m.Namespace, - m.Name) -} - func (*Service) volumeName(m *infrav1.IonosCloudMachine) string { - return fmt.Sprintf("k8s-vol-%s-%s", m.Namespace, m.Name) + return "vol-" + m.Name } diff --git a/internal/service/cloud/server_test.go b/internal/service/cloud/server_test.go index 249d23e1..fa432344 100644 --- a/internal/service/cloud/server_test.go +++ b/internal/service/cloud/server_test.go @@ -40,9 +40,10 @@ func TestServerSuite(t *testing.T) { suite.Run(t, new(serverSuite)) } -func (s *serverSuite) TestServerName() { - serverName := s.service.serverName(s.infraMachine) - s.Equal("k8s-default-test-machine", serverName) +func (s *serverSuite) TestVolumeName() { + volumeName := s.service.volumeName(s.infraMachine) + expected := "vol-" + s.infraMachine.Name + s.Equal(expected, volumeName) } func (s *serverSuite) TestReconcileServerNoBootstrapSecret() { @@ -74,7 +75,7 @@ func (s *serverSuite) TestReconcileServerRequestDoneStateBusy() { State: ptr.To(sdk.Busy), }, Properties: &sdk.ServerProperties{ - Name: ptr.To(s.service.serverName(s.infraMachine)), + Name: ptr.To(s.infraMachine.Name), }, }, }}, nil).Once() @@ -93,7 +94,7 @@ func (s *serverSuite) TestReconcileServerRequestDoneStateAvailable() { State: ptr.To(sdk.Available), }, Properties: &sdk.ServerProperties{ - Name: ptr.To(s.service.serverName(s.infraMachine)), + Name: ptr.To(s.infraMachine.Name), VmState: ptr.To("RUNNING"), }, Entities: &sdk.ServerEntities{ @@ -134,7 +135,7 @@ func (s *serverSuite) TestReconcileServerRequestDoneStateAvailableTurnedOff() { State: ptr.To(sdk.Available), }, Properties: &sdk.ServerProperties{ - Name: ptr.To(s.service.serverName(s.infraMachine)), + Name: ptr.To(s.infraMachine.Name), VmState: ptr.To(sdk.Available), }, }, @@ -314,12 +315,11 @@ func (s *serverSuite) TestGetServerWithProviderIDNotFound() { } func (s *serverSuite) TestGetServerWithoutProviderIDFoundInList() { - serverName := s.service.serverName(s.infraMachine) s.machineScope.IonosMachine.Spec.ProviderID = nil s.mockListServersCall().Return(&sdk.Servers{Items: &[]sdk.Server{ { Properties: &sdk.ServerProperties{ - Name: ptr.To(serverName), + Name: ptr.To(s.infraMachine.Name), }, }, }}, nil) @@ -396,7 +396,7 @@ func (s *serverSuite) examplePostRequest(status string) sdk.Request { status: status, method: http.MethodPost, url: s.service.serversURL(s.machineScope.DatacenterID()), - body: fmt.Sprintf(`{"properties": {"name": "%s"}}`, s.service.serverName(s.infraMachine)), + body: fmt.Sprintf(`{"properties": {"name": "%s"}}`, s.infraMachine.Name), href: exampleRequestPath, targetID: exampleServerID, targetType: sdk.SERVER, From 93f2400adb0ee2d87ef17a5309b7a836ae2f69a5 Mon Sep 17 00:00:00 2001 From: Matthias Bastian Date: Tue, 30 Apr 2024 12:46:29 +0200 Subject: [PATCH 02/10] Omit empty failover IP (#109) If a `IonosCloudMachine` has no `failoverIP` set, that field shouldn't be shown after marshalling the data, e.g. in JSON or YAML output. --- api/v1alpha1/ionoscloudmachine_types.go | 4 ++-- api/v1alpha1/ionoscloudmachine_types_test.go | 24 +++++++++---------- api/v1alpha1/zz_generated.deepcopy.go | 5 ++++ ...e.cluster.x-k8s.io_ionoscloudmachines.yaml | 2 +- ...r.x-k8s.io_ionoscloudmachinetemplates.yaml | 2 +- internal/service/cloud/ipblock.go | 6 ++--- internal/service/cloud/ipblock_test.go | 10 ++++---- internal/service/cloud/network.go | 8 +++++-- internal/service/cloud/network_test.go | 6 ++--- 9 files changed, 37 insertions(+), 30 deletions(-) diff --git a/api/v1alpha1/ionoscloudmachine_types.go b/api/v1alpha1/ionoscloudmachine_types.go index 63cba88b..083b6612 100644 --- a/api/v1alpha1/ionoscloudmachine_types.go +++ b/api/v1alpha1/ionoscloudmachine_types.go @@ -146,9 +146,9 @@ type IonosCloudMachineSpec struct { // // If the machine is a control plane machine, this field will not be taken into account. //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="failoverIP is immutable" - //+kubebuilder:validation:XValidation:rule=`self == "" || self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$")`,message="failoverIP must be either 'AUTO' or a valid IPv4 address" + //+kubebuilder:validation:XValidation:rule=`self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$")`,message="failoverIP must be either 'AUTO' or a valid IPv4 address" //+optional - FailoverIP string `json:"failoverIP"` + FailoverIP *string `json:"failoverIP,omitempty"` } // Networks contains a list of additional LAN IDs diff --git a/api/v1alpha1/ionoscloudmachine_types_test.go b/api/v1alpha1/ionoscloudmachine_types_test.go index b72cd6ba..533025d4 100644 --- a/api/v1alpha1/ionoscloudmachine_types_test.go +++ b/api/v1alpha1/ionoscloudmachine_types_test.go @@ -342,24 +342,24 @@ var _ = Describe("IonosCloudMachine Tests", func() { Context("FailoverIP", func() { It("should allow setting AUTO as the value", func() { m := defaultMachine() - m.Spec.FailoverIP = CloudResourceConfigAuto + m.Spec.FailoverIP = ptr.To(CloudResourceConfigAuto) Expect(k8sClient.Create(context.Background(), m)).To(Succeed()) - Expect(m.Spec.FailoverIP).To(Equal(CloudResourceConfigAuto)) + Expect(m.Spec.FailoverIP).To(Equal(ptr.To(CloudResourceConfigAuto))) }) It("should allow setting a valid IPv4 address", func() { m := defaultMachine() - m.Spec.FailoverIP = "203.0.113.1" + m.Spec.FailoverIP = ptr.To("203.0.113.1") Expect(k8sClient.Create(context.Background(), m)).To(Succeed()) - Expect(m.Spec.FailoverIP).To(Equal("203.0.113.1")) + Expect(m.Spec.FailoverIP).To(Equal(ptr.To("203.0.113.1"))) }) - It("should allow setting empty string", func() { + It("should allow setting null", func() { m := defaultMachine() Expect(k8sClient.Create(context.Background(), m)).To(Succeed()) - Expect(m.Spec.FailoverIP).To(Equal("")) + Expect(m.Spec.FailoverIP).To(BeNil()) }) DescribeTable("should not allow setting invalid IPv4 addresses", func(ip string) { m := defaultMachine() - m.Spec.FailoverIP = ip + m.Spec.FailoverIP = &ip Expect(k8sClient.Create(context.Background(), m)).ToNot(Succeed()) }, Entry("IPv4 out of range", "203.0.113.256"), @@ -370,17 +370,17 @@ var _ = Describe("IonosCloudMachine Tests", func() { ) It("should require AUTO to be in capital letters", func() { m := defaultMachine() - m.Spec.FailoverIP = "Auto" + m.Spec.FailoverIP = ptr.To("Auto") Expect(k8sClient.Create(context.Background(), m)).ToNot(Succeed()) }) It("should be immutable", func() { m := defaultMachine() - m.Spec.FailoverIP = "AUTO" + m.Spec.FailoverIP = ptr.To(CloudResourceConfigAuto) Expect(k8sClient.Create(context.Background(), m)).To(Succeed()) - Expect(m.Spec.FailoverIP).To(Equal("AUTO")) - m.Spec.FailoverIP = "127.0.0.1" + Expect(m.Spec.FailoverIP).To(Equal(ptr.To(CloudResourceConfigAuto))) + m.Spec.FailoverIP = ptr.To("127.0.0.1") Expect(k8sClient.Update(context.Background(), m)).ToNot(Succeed()) - m.Spec.FailoverIP = "" + m.Spec.FailoverIP = ptr.To("") Expect(k8sClient.Update(context.Background(), m)).ToNot(Succeed()) }) }) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 40606f5e..9aef4f4c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -233,6 +233,11 @@ func (in *IonosCloudMachineSpec) DeepCopyInto(out *IonosCloudMachineSpec) { *out = make(Networks, len(*in)) copy(*out, *in) } + if in.FailoverIP != nil { + in, out := &in.FailoverIP, &out.FailoverIP + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IonosCloudMachineSpec. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachines.yaml index b1939417..13c837bf 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachines.yaml @@ -158,7 +158,7 @@ spec: - message: failoverIP is immutable rule: self == oldSelf - message: failoverIP must be either 'AUTO' or a valid IPv4 address - rule: self == "" || self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$") + rule: self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$") memoryMB: default: 3072 description: |- diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachinetemplates.yaml index c507efea..882c020a 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudmachinetemplates.yaml @@ -176,7 +176,7 @@ spec: rule: self == oldSelf - message: failoverIP must be either 'AUTO' or a valid IPv4 address - rule: self == "" || self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$") + rule: self == "AUTO" || self.matches("((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$") memoryMB: default: 3072 description: |- diff --git a/internal/service/cloud/ipblock.go b/internal/service/cloud/ipblock.go index ea1920c6..efbaf475 100644 --- a/internal/service/cloud/ipblock.go +++ b/internal/service/cloud/ipblock.go @@ -148,10 +148,8 @@ func (s *Service) ReconcileControlPlaneEndpointDeletion( // ReconcileFailoverIPBlockDeletion ensures that the IP block is deleted. func (s *Service) ReconcileFailoverIPBlockDeletion(ctx context.Context, ms *scope.Machine) (requeue bool, err error) { log := s.logger.WithName("ReconcileFailoverIPBlockDeletion") - if ms.IonosMachine.Spec.FailoverIP != infrav1.CloudResourceConfigAuto { - log.V(4).Info("Failover IP block is not managed by the provider, skipping deletion", - "failoverIP", ms.IonosMachine.Spec.FailoverIP, - ) + if foIP := ms.IonosMachine.Spec.FailoverIP; foIP == nil || *foIP != infrav1.CloudResourceConfigAuto { + log.V(4).Info("Failover IP block is not managed by the provider, skipping deletion", "failoverIP", foIP) return false, nil } diff --git a/internal/service/cloud/ipblock_test.go b/internal/service/cloud/ipblock_test.go index 39a4daa3..2a81525d 100644 --- a/internal/service/cloud/ipblock_test.go +++ b/internal/service/cloud/ipblock_test.go @@ -387,7 +387,7 @@ func (s *ipBlockTestSuite) TestReconcileControlPlaneEndpointDeletionRequestNewDe } func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletion() { - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) ipBlock := exampleIPBlockWithName(s.service.failoverIPBlockName(s.machineScope)) s.mockListIPBlocksCall().Return(&sdk.IpBlocks{Items: &[]sdk.IpBlock{*ipBlock}}, nil).Once() @@ -402,7 +402,7 @@ func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletion() { } func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionSkipped() { - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) ipBlock := exampleIPBlockWithName(s.service.failoverIPBlockName(s.machineScope)) lan := s.exampleLAN() lan.Properties.IpFailover = &[]sdk.IPFailover{{ @@ -421,7 +421,7 @@ func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionSkipped() { } func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionPendingCreation() { - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) s.mockListIPBlocksCall().Return(nil, nil).Once() s.mockGetIPBlocksRequestsPostCall().Return([]sdk.Request{ @@ -436,7 +436,7 @@ func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionPendingCreation() } func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionPendingDeletion() { - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) ipBlock := exampleIPBlockWithName(s.service.failoverIPBlockName(s.machineScope)) s.mockListIPBlocksCall().Return(&sdk.IpBlocks{Items: &[]sdk.IpBlock{*ipBlock}}, nil).Once() @@ -464,7 +464,7 @@ func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionPendingDeletion() } func (s *ipBlockTestSuite) TestReconcileFailoverIPBlockDeletionDeletionFinished() { - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) ipBlock := exampleIPBlockWithName(s.service.failoverIPBlockName(s.machineScope)) s.mockListIPBlocksCall().Return(&sdk.IpBlocks{Items: &[]sdk.IpBlock{*ipBlock}}, nil).Once() diff --git a/internal/service/cloud/network.go b/internal/service/cloud/network.go index e864b36a..d8660f85 100644 --- a/internal/service/cloud/network.go +++ b/internal/service/cloud/network.go @@ -281,7 +281,11 @@ func (s *Service) retrieveFailoverIPForMachine( return false, ms.ClusterScope.GetControlPlaneEndpoint().Host, nil } - failoverIP = ms.IonosMachine.Spec.FailoverIP + failoverIP = ptr.Deref(ms.IonosMachine.Spec.FailoverIP, "") + if failoverIP == "" { + const errorMessage = "failover IP contains an empty string. Provide either a valid IP address or 'AUTO'" + return false, "", errors.New(errorMessage) + } // AUTO means we have to reserve an IP address. if failoverIP == infrav1.CloudResourceConfigAuto { @@ -597,5 +601,5 @@ func (s *Service) patchLAN(ctx context.Context, ms *scope.Machine, lanID string, } func failoverRequired(ms *scope.Machine) bool { - return util.IsControlPlaneMachine(ms.Machine) || ms.IonosMachine.Spec.FailoverIP != "" + return util.IsControlPlaneMachine(ms.Machine) || ms.IonosMachine.Spec.FailoverIP != nil } diff --git a/internal/service/cloud/network_test.go b/internal/service/cloud/network_test.go index 2e3e2f8f..1fe52035 100644 --- a/internal/service/cloud/network_test.go +++ b/internal/service/cloud/network_test.go @@ -246,7 +246,7 @@ func (s *lanSuite) TestReconcileIPFailoverNICAlreadyInFailoverGroup() { func (s *lanSuite) TestReconcileIPFailoverForWorkerWithAUTOSettings() { const deploymentLabel = "test-deployment" s.infraMachine.SetLabels(map[string]string{clusterv1.MachineDeploymentNameLabel: deploymentLabel}) - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) testServer := s.defaultServer(s.infraMachine, exampleDHCPIP, exampleWorkerFailoverIP) testLAN := s.exampleLAN() @@ -285,7 +285,7 @@ func (s *lanSuite) TestReconcileIPFailoverForWorkerWithAUTOSettings() { func (s *lanSuite) TestReconcileIPFailoverReserveIPBlock() { const deploymentLabel = "test-deployment" s.infraMachine.SetLabels(map[string]string{clusterv1.MachineDeploymentNameLabel: deploymentLabel}) - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) s.mockListIPBlocksCall().Return(nil, nil).Once() s.mockGetIPBlocksRequestsPostCall().Return(nil, nil).Once() @@ -397,7 +397,7 @@ func (s *lanSuite) TestReconcileIPFailoverDeletionWorker() { labels[clusterv1.MachineDeploymentNameLabel] = deploymentLabel s.infraMachine.SetLabels(labels) - s.infraMachine.Spec.FailoverIP = infrav1.CloudResourceConfigAuto + s.infraMachine.Spec.FailoverIP = ptr.To(infrav1.CloudResourceConfigAuto) testServer := s.defaultServer(s.infraMachine, exampleDHCPIP, exampleWorkerFailoverIP) s.NoError(s.k8sClient.Update(s.ctx, s.infraMachine)) From 20d8567c4a751f43a6d817b74a7c96915755dcc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 08:07:10 +0200 Subject: [PATCH 03/10] Bump github.com/onsi/gomega from 1.33.0 to 1.33.1 (#110) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.33.0 to 1.33.1.
Release notes

Sourced from github.com/onsi/gomega's releases.

v1.33.1

1.33.1

Fixes

  • fix confusing eventually docs [3a66379]

Maintenance

  • Bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 [e9bc35a]
Changelog

Sourced from github.com/onsi/gomega's changelog.

1.33.1

Fixes

  • fix confusing eventually docs [3a66379]

Maintenance

  • Bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 [e9bc35a]
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/onsi/gomega&package-manager=go_modules&previous-version=1.33.0&new-version=1.33.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eb94d54e..ef59323e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/ionos-cloud/sdk-go/v6 v6.1.11 github.com/jarcoal/httpmock v1.3.1 github.com/onsi/ginkgo/v2 v2.17.2 - github.com/onsi/gomega v1.33.0 + github.com/onsi/gomega v1.33.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 k8s.io/api v0.29.4 diff --git a/go.sum b/go.sum index 4bef3c1c..3dc7f9fc 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= -github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= From 928107200d2ffcc6a2ca53ced160cc9375050da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 14:15:02 +0200 Subject: [PATCH 04/10] Bump sigs.k8s.io/cluster-api from 1.6.3 to 1.7.1 (#101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [sigs.k8s.io/cluster-api](https://github.com/kubernetes-sigs/cluster-api) from 1.6.3 to 1.7.1.
Release notes

Sourced from sigs.k8s.io/cluster-api's releases.

v1.7.1

👌 Kubernetes version support

  • Management Cluster: v1.26.x -> v1.30.x
  • Workload Cluster: v1.24.x -> v1.30.x

More information about version support can be found here

Highlights

  • Kubernetes v1.30 is now supported

Changes since v1.7.0

:chart_with_upwards_trend: Overview

  • 11 new commits merged
  • 2 bugs fixed 🐛

:bug: Bug Fixes

  • CAPD: Verify lb config after writing it (#10461)
  • e2e: also gather junit reports in case of errors observed from ginkgo (#10494)

:seedling: Others

  • Dependency: Bump envtest to v1.30.0 (#10481)
  • e2e: Export more func in test/e2e/common.go (#10447)
  • Testing: Bump Kubernetes in tests to v1.30.0 and claim support for v1.30 (#10465)

:book: Additionally, there have been 6 contributions to our documentation and book. (#10446, #10448, #10451, #10456, #10470, #10491)

Dependencies

Added

Nothing has changed.

Changed

Nothing has changed.

Removed

Nothing has changed.

Thanks to all our contributors! 😊

v1.7.0

👌 Kubernetes version support

  • Management Cluster: v1.26.x -> v1.29.x
  • Workload Cluster: v1.24.x -> v1.29.x

More information about version support can be found here

... (truncated)

Commits
  • ef04465 Merge pull request #10494 from k8s-infra-cherrypick-robot/cherry-pick-10493-t...
  • d7f48ec kubetest: also gather junit reports in case of errors observed from ginkgo
  • 877d16c Merge pull request #10491 from k8s-infra-cherrypick-robot/cherry-pick-10443-t...
  • 5065e48 Update docs for ClusterResourceSets
  • 1577469 Merge pull request #10481 from k8s-infra-cherrypick-robot/cherry-pick-10477-t...
  • 64730e3 Bump envtest to v1.30.0
  • b1dbd55 Merge pull request #10470 from k8s-infra-cherrypick-robot/cherry-pick-10310-t...
  • 5fc3d2a Add docs on how to get goroutine dump
  • 1291f05 Merge pull request #10465 from chrischdi/pr-cp-10454-r-1-7
  • 71da65e test: Bump kubernetes in test due to v1.30 GA
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sigs.k8s.io/cluster-api&package-manager=go_modules&previous-version=1.6.3&new-version=1.7.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jonas Riedel --- go.mod | 20 +++++++------- go.sum | 64 +++++++++++++++++++++---------------------- scope/machine_test.go | 2 +- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index ef59323e..2cdaf9c0 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( k8s.io/apimachinery v0.29.4 k8s.io/client-go v0.29.4 k8s.io/klog/v2 v2.110.1 - sigs.k8s.io/cluster-api v1.6.3 + sigs.k8s.io/cluster-api v1.7.1 sigs.k8s.io/controller-runtime v0.17.3 ) @@ -29,8 +29,8 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -78,25 +78,25 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.24.0 // indirect - golang.org/x/oauth2 v0.14.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.20.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.2 // indirect - k8s.io/apiserver v0.29.2 // indirect - k8s.io/component-base v0.29.2 // indirect + k8s.io/apiextensions-apiserver v0.29.3 // indirect + k8s.io/apiserver v0.29.3 // indirect + k8s.io/component-base v0.29.3 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect diff --git a/go.sum b/go.sum index 3dc7f9fc..41e5d0ff 100644 --- a/go.sum +++ b/go.sum @@ -41,10 +41,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= -github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -175,8 +175,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -204,14 +204,14 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= -go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= -go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= -go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= +go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= +go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= +go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= -go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= -go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js= +go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI= go.etcd.io/etcd/pkg/v3 v3.5.10 h1:WPR8K0e9kWl1gAhB5A7gEa5ZBTNkT9NdNWrR8Qpo1CM= go.etcd.io/etcd/pkg/v3 v3.5.10/go.mod h1:TKTuCKKcF1zxmfKWDkfz5qqYaE3JncKKZPFf8c1nFUs= go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= @@ -261,8 +261,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -288,8 +288,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -305,12 +305,12 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -334,18 +334,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w= k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0= -k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= -k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= +k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= +k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc= k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= -k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= -k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= +k8s.io/apiserver v0.29.3 h1:xR7ELlJ/BZSr2n4CnD3lfA4gzFivh0wwfNfz9L0WZcE= +k8s.io/apiserver v0.29.3/go.mod h1:hrvXlwfRulbMbBgmWRQlFru2b/JySDpmzvQwwk4GUOs= k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8= k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk= -k8s.io/cluster-bootstrap v0.28.4 h1:4MKNy1Qd9QY7pl47rSMGIORF+tm3CUaqC1M8U9bjn4Q= -k8s.io/cluster-bootstrap v0.28.4/go.mod h1:/c4ro/R4yf4EtJgFgFtvnHkbDOHwubeKJXh5R1c89Bc= -k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= -k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= +k8s.io/cluster-bootstrap v0.29.3 h1:DIMDZSN8gbFMy9CS2mAS2Iqq/fIUG783WN/1lqi5TF8= +k8s.io/cluster-bootstrap v0.29.3/go.mod h1:aPAg1VtXx3uRrx5qU2jTzR7p1rf18zLXWS+pGhiqPto= +k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= +k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= @@ -354,8 +354,8 @@ k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCf k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2SGwkQasmbeqDo8th5wOBA5h/AjTKA4I= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y= -sigs.k8s.io/cluster-api v1.6.3 h1:VOlPNg92PQLlhBVLc5pg+cbAuPvGOOBujeFLk9zgnoo= -sigs.k8s.io/cluster-api v1.6.3/go.mod h1:4FzfgPPiYaFq8X9F9j2SvmggH/4OOLEDgVJuWDqKLig= +sigs.k8s.io/cluster-api v1.7.1 h1:JkMAbAMzBM+WBHxXLTJXTiCisv1PAaHRzld/3qrmLYY= +sigs.k8s.io/cluster-api v1.7.1/go.mod h1:V9ZhKLvQtsDODwjXOKgbitjyCmC71yMBwDcMyNNIov0= sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/scope/machine_test.go b/scope/machine_test.go index 47d0b2f2..71b2add4 100644 --- a/scope/machine_test.go +++ b/scope/machine_test.go @@ -96,7 +96,7 @@ func TestMachineHasFailedFailureMessage(t *testing.T) { func TestMachineHasFailedFailureReason(t *testing.T) { scope, err := NewMachine(exampleParams(t)) require.NoError(t, err) - scope.IonosMachine.Status.FailureReason = capierrors.MachineStatusErrorPtr("¯\\_(ツ)_/¯") + scope.IonosMachine.Status.FailureReason = (*capierrors.MachineStatusError)(ptr.To("¯\\_(ツ)_/¯")) require.True(t, scope.HasFailed()) } From 165f07d71620a5f40669e344760677696ee901cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 15:58:26 +0200 Subject: [PATCH 05/10] Bump k8s.io/klog/v2 from 2.110.1 to 2.120.1 (#31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.110.1 to 2.120.1.
Release notes

Sourced from k8s.io/klog/v2's releases.

Prepare klog release for Kubernetes v1.30 (Take 2)

What's Changed

Full Changelog: https://github.com/kubernetes/klog/compare/v2.120.0...v2.120.1

Prepare klog release for Kubernetes v1.30 (Take 1)

What's Changed

New Contributors

Full Changelog: https://github.com/kubernetes/klog/compare/v2.110.1...v2.120.0

Commits
  • 007e661 textlogger: allow caller to override stack unwinding
  • 2d08296 Merge pull request #396 from pohly/slog-helper
  • e4deee8 slog: use main logr package instead of logr/slogr
  • 5d1d2d5 add SetSlogLogger
  • 39afdba dependencies: logr v1.4.1
  • 2086216 Merge pull request #393 from kaisoz/add-safeptr
  • 881fa0b Add SafePtr wrapper
  • 8dd3f2e Merge pull request #395 from pohly/readme-update
  • d3dd725 docs: clarify relationship between different features
  • 761b630 Merge pull request #394 from pohly/owners-update
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k8s.io/klog/v2&package-manager=go_modules&previous-version=2.110.1&new-version=2.120.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) You can trigger a rebase of this PR by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
> **Note** > Automatic rebases have been disabled on this pull request as it has been open for over 30 days. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2cdaf9c0..a2b5a9ac 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( k8s.io/api v0.29.4 k8s.io/apimachinery v0.29.4 k8s.io/client-go v0.29.4 - k8s.io/klog/v2 v2.110.1 + k8s.io/klog/v2 v2.120.1 sigs.k8s.io/cluster-api v1.7.1 sigs.k8s.io/controller-runtime v0.17.3 ) diff --git a/go.sum b/go.sum index 41e5d0ff..9228ebf1 100644 --- a/go.sum +++ b/go.sum @@ -50,7 +50,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -346,8 +345,8 @@ k8s.io/cluster-bootstrap v0.29.3 h1:DIMDZSN8gbFMy9CS2mAS2Iqq/fIUG783WN/1lqi5TF8= k8s.io/cluster-bootstrap v0.29.3/go.mod h1:aPAg1VtXx3uRrx5qU2jTzR7p1rf18zLXWS+pGhiqPto= k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= From dd3f67e8fd1367d94e16be0c2c861b49222385f4 Mon Sep 17 00:00:00 2001 From: Matthias Bastian Date: Thu, 2 May 2024 16:19:03 +0200 Subject: [PATCH 06/10] Allow FQDN as the control plane endpoint host (#108) Until now, we assumed the control plane endpoint host to be an IP, but that's an unnecessary restriction. The template now consumes a new environment variable: `CONTROL_PLANE_ENDPOINT_HOST` It's optional and defaults to the value of the existing `CONTROL_PLANE_ENDPOINT_IP` environment variable. As the host is required to resolve to that IP, we'd actually not need it anymore to be given explicitly. However, as we still render the kube-vip manifest in the template and don't have anything nice there to do the translation automatically, we keep the explicit IP for now. The controller code is not aware of the `CONTROL_PLANE_ENDPOINT_IP` value, though. It only sees the control plane endpoint host, which can now be either an IP or an FQDN, so the controller must be able to resolve the name to use the corresponding IP to find the matching IP block and to find the correct IP failover group. --- api/v1alpha1/ionoscloudcluster_types_test.go | 5 ++ docs/quickstart.md | 20 +++-- envfile.example | 1 + internal/service/cloud/ipblock.go | 27 ++++--- internal/service/cloud/network.go | 3 +- scope/cluster.go | 36 +++++++++ scope/cluster_test.go | 77 ++++++++++++++++++++ templates/cluster-template.yaml | 2 +- 8 files changed, 150 insertions(+), 21 deletions(-) diff --git a/api/v1alpha1/ionoscloudcluster_types_test.go b/api/v1alpha1/ionoscloudcluster_types_test.go index 2382821e..9e3d5b3b 100644 --- a/api/v1alpha1/ionoscloudcluster_types_test.go +++ b/api/v1alpha1/ionoscloudcluster_types_test.go @@ -73,6 +73,11 @@ var _ = Describe("IonosCloudCluster", func() { It("should allow creating valid clusters", func() { Expect(k8sClient.Create(context.Background(), defaultCluster())).To(Succeed()) }) + It("should work with a FQDN controlplane endpoint", func() { + cluster := defaultCluster() + cluster.Spec.ControlPlaneEndpoint.Host = "example.org" + Expect(k8sClient.Create(context.Background(), cluster)).To(Succeed()) + }) It("should not allow creating clusters with empty credential secret", func() { cluster := defaultCluster() cluster.Spec.CredentialsRef.Name = "" diff --git a/docs/quickstart.md b/docs/quickstart.md index 7a88a602..61bc3966 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -60,19 +60,23 @@ clusterctl init --infrastructure=ionoscloud CAPIC requires several environment variables to be set in order to create a Kubernetes cluster on IONOS Cloud. ```env -## -- Cloud specific environment variables -- ## +## -- Cloud-specific environment variables -- ## IONOS_TOKEN # The token of the IONOS Cloud account. -IONOS_API_URL # The API URL of the IONOS Cloud account. - # Defaults to https://api.ionos.com/cloudapi/v6 - -## -- Cluster API related environment variables -- ## -CONTROL_PLANE_ENDPOINT_IP # The IP address of the control plane endpoint. -CONTROL_PLANE_ENDPOINT_PORT # The port of the control plane endpoint. +IONOS_API_URL # The API URL of the IONOS Cloud account (optional). + # Defaults to https://api.ionos.com/cloudapi/v6. + +## -- Cluster API-related environment variables -- ## +CONTROL_PLANE_ENDPOINT_HOST # The control plane endpoint host (optional). + # If it's not an IP but an FQDN, the provider must be able to resolve it + # to the value for CONTROL_PLANE_ENDPOINT_IP. +CONTROL_PLANE_ENDPOINT_IP # The IPv4 address of the control plane endpoint. +CONTROL_PLANE_ENDPOINT_PORT # The port of the control plane endpoint (optional). + # Defaults to 6443. CONTROL_PLANE_ENDPOINT_LOCATION # The location of the control plane endpoint. CLUSTER_NAME # The name of the cluster. KUBERNETES_VERSION # The version of Kubernetes to be installed (can also be set via clusterctl). -## -- Kubernetes Cluster related environment variables -- ## +## -- Kubernetes Cluster-related environment variables -- ## IONOSCLOUD_CONTRACT_NUMBER # The contract number of the IONOS Cloud contract. IONOSCLOUD_DATACENTER_ID # The datacenter ID where the cluster should be created. IONOSCLOUD_MACHINE_NUM_CORES # The number of cores. diff --git a/envfile.example b/envfile.example index 33ec7eb2..83609fc9 100644 --- a/envfile.example +++ b/envfile.example @@ -6,6 +6,7 @@ export IONOS_API_URL="https://api.ionos.com/cloudapi/v6" # Cluster API related environment variables +export CONTROL_PLANE_ENDPOINT_HOST="example.org" export CONTROL_PLANE_ENDPOINT_IP="192.168.0.1" export CONTROL_PLANE_ENDPOINT_PORT=6443 export CONTROL_PLANE_ENDPOINT_LOCATION="de/txl" diff --git a/internal/service/cloud/ipblock.go b/internal/service/cloud/ipblock.go index efbaf475..8e86b549 100644 --- a/internal/service/cloud/ipblock.go +++ b/internal/service/cloud/ipblock.go @@ -251,11 +251,17 @@ func (s *Service) getControlPlaneEndpointIPBlock(ctx context.Context, cs *scope. if ipBlock != nil || ignoreNotFound(err) != nil { return ipBlock, err } + notFoundError := err s.logger.Info("IP block not found by ID, trying to find by listing IP blocks instead") - blocks, listErr := s.apiWithDepth(listIPBlocksDepth).ListIPBlocks(ctx) - if listErr != nil { - return nil, fmt.Errorf("failed to list IP blocks: %w", listErr) + blocks, err := s.apiWithDepth(listIPBlocksDepth).ListIPBlocks(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list IP blocks: %w", err) + } + + controlPlaneEndpointIP, err := cs.GetControlPlaneEndpointIP(ctx) + if err != nil { + return nil, err } var ( @@ -275,7 +281,7 @@ func (s *Service) getControlPlaneEndpointIPBlock(ctx context.Context, cs *scope. if err != nil { return nil, err } - case s.checkIfUserSetBlock(cs, props): + case s.checkIfUserSetBlock(controlPlaneEndpointIP, props): // NOTE: this is for when customers set IPs for the control plane endpoint themselves. foundBlock, err = s.cloudAPIStateInconsistencyWorkaround(ctx, &block) if err != nil { @@ -285,11 +291,11 @@ func (s *Service) getControlPlaneEndpointIPBlock(ctx context.Context, cs *scope. } if count > 1 { return nil, fmt.Errorf( - "cannot determine IP block for Control Plane Endpoint as there are multiple IP blocks with the name %s", + "cannot determine IP block for Control Plane Endpoint, as there are multiple IP blocks with the name %s", expectedName) } } - if count == 0 && cs.GetControlPlaneEndpoint().Host != "" { + if count == 0 && controlPlaneEndpointIP != "" { return nil, errUserSetIPNotFound } if foundBlock != nil { @@ -297,13 +303,12 @@ func (s *Service) getControlPlaneEndpointIPBlock(ctx context.Context, cs *scope. } // if we still can't find an IP block we return the potential // initial not found error. - return nil, err + return nil, notFoundError } -func (*Service) checkIfUserSetBlock(cs *scope.Cluster, props *sdk.IpBlockProperties) bool { - ip := cs.GetControlPlaneEndpoint().Host +func (*Service) checkIfUserSetBlock(controlPlaneEndpointIP string, props *sdk.IpBlockProperties) bool { ips := ptr.Deref(props.GetIps(), nil) - return ip != "" && slices.Contains(ips, ip) + return controlPlaneEndpointIP != "" && slices.Contains(ips, controlPlaneEndpointIP) } // cloudAPIStateInconsistencyWorkaround is a workaround for a bug where the API returns different states for the same @@ -320,7 +325,7 @@ func (s *Service) cloudAPIStateInconsistencyWorkaround(ctx context.Context, bloc func (s *Service) getIPBlockByID(ctx context.Context, ipBlockID string) (*sdk.IpBlock, error) { if ipBlockID == "" { - s.logger.Info("Could not find any IP block by ID as the provider ID is not set.") + s.logger.Info("Could not find any IP block by ID, as the provider ID is not set.") return nil, nil } ipBlock, err := s.ionosClient.GetIPBlock(ctx, ipBlockID) diff --git a/internal/service/cloud/network.go b/internal/service/cloud/network.go index d8660f85..726068f8 100644 --- a/internal/service/cloud/network.go +++ b/internal/service/cloud/network.go @@ -278,7 +278,8 @@ func (s *Service) retrieveFailoverIPForMachine( log := s.logger.WithName("retrieveFailoverIPForMachine") if util.IsControlPlaneMachine(ms.Machine) { - return false, ms.ClusterScope.GetControlPlaneEndpoint().Host, nil + ip, err := ms.ClusterScope.GetControlPlaneEndpointIP(ctx) + return false, ip, err } failoverIP = ptr.Deref(ms.IonosMachine.Spec.FailoverIP, "") diff --git a/scope/cluster.go b/scope/cluster.go index ff580fb8..b9dc68c3 100644 --- a/scope/cluster.go +++ b/scope/cluster.go @@ -21,6 +21,9 @@ import ( "context" "errors" "fmt" + "net" + "net/netip" + "slices" "time" "k8s.io/client-go/util/retry" @@ -32,9 +35,17 @@ import ( infrav1 "github.com/ionos-cloud/cluster-api-provider-ionoscloud/api/v1alpha1" ) +// resolver is able to look up IP addresses from a given host name. +// The net.Resolver type (found at net.DefaultResolver) implements this interface. +// This is intended for testing. +type resolver interface { + LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) +} + // Cluster defines a basic cluster context for primary use in IonosCloudClusterReconciler. type Cluster struct { patchHelper *patch.Helper + resolver resolver Cluster *clusterv1.Cluster IonosCluster *infrav1.IonosCloudCluster } @@ -70,6 +81,7 @@ func NewCluster(params ClusterParams) (*Cluster, error) { Cluster: params.Cluster, IonosCluster: params.IonosCluster, patchHelper: helper, + resolver: net.DefaultResolver, } return clusterScope, nil @@ -80,6 +92,30 @@ func (c *Cluster) GetControlPlaneEndpoint() clusterv1.APIEndpoint { return c.IonosCluster.Spec.ControlPlaneEndpoint } +// GetControlPlaneEndpointIP returns the endpoint IP for the IonosCloudCluster. +// If the endpoint host is unset (neither an IP nor an FQDN), it will return an empty string. +func (c *Cluster) GetControlPlaneEndpointIP(ctx context.Context) (string, error) { + host := c.GetControlPlaneEndpoint().Host + if host == "" { + return "", nil + } + + if ip, err := netip.ParseAddr(host); err == nil { + return ip.String(), nil + } + + // If the host is not an IP, try to resolve it. + ips, err := c.resolver.LookupNetIP(ctx, "ip4", host) + if err != nil { + return "", fmt.Errorf("failed to resolve control plane endpoint IP: %w", err) + } + + // Sort IPs to deal with random order intended for load balancing. + slices.SortFunc(ips, func(a, b netip.Addr) int { return a.Compare(b) }) + + return ips[0].String(), nil +} + // SetControlPlaneEndpointIPBlockID sets the IP block ID in the IonosCloudCluster status. func (c *Cluster) SetControlPlaneEndpointIPBlockID(id string) { c.IonosCluster.Status.ControlPlaneEndpointIPBlockID = id diff --git a/scope/cluster_test.go b/scope/cluster_test.go index d8165dfe..36602920 100644 --- a/scope/cluster_test.go +++ b/scope/cluster_test.go @@ -17,6 +17,9 @@ limitations under the License. package scope import ( + "context" + "net" + "net/netip" "testing" "github.com/stretchr/testify/require" @@ -81,7 +84,81 @@ func TestNewClusterMissingParams(t *testing.T) { params, err := NewCluster(test.params) require.NoError(t, err) require.NotNil(t, params) + require.Equal(t, net.DefaultResolver, params.resolver) } }) } } + +type mockResolver struct { + addrs map[string][]netip.Addr +} + +func (m *mockResolver) LookupNetIP(_ context.Context, _, host string) ([]netip.Addr, error) { + return m.addrs[host], nil +} + +func resolvesTo(ips ...string) []netip.Addr { + res := make([]netip.Addr, 0, len(ips)) + for _, ip := range ips { + res = append(res, netip.MustParseAddr(ip)) + } + return res +} + +func TestCluster_GetControlPlaneEndpointIP(t *testing.T) { + tests := []struct { + name string + host string + resolver resolver + want string + }{ + { + name: "host empty", + host: "", + want: "", + }, + { + name: "host is IP", + host: "127.0.0.1", + want: "127.0.0.1", + }, + { + name: "host is FQDN with single IP", + host: "localhost", + resolver: &mockResolver{ + addrs: map[string][]netip.Addr{ + "localhost": resolvesTo("127.0.0.1"), + }, + }, + want: "127.0.0.1", + }, + { + name: "host is FQDN with multiple IPs", + host: "example.org", + resolver: &mockResolver{ + addrs: map[string][]netip.Addr{ + "example.org": resolvesTo("2.3.4.5", "1.2.3.4"), + }, + }, + want: "1.2.3.4", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Cluster{ + resolver: tt.resolver, + IonosCluster: &infrav1.IonosCloudCluster{ + Spec: infrav1.IonosCloudClusterSpec{ + ControlPlaneEndpoint: clusterv1.APIEndpoint{ + Host: tt.host, + }, + }, + }, + } + got, err := c.GetControlPlaneEndpointIP(context.Background()) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/templates/cluster-template.yaml b/templates/cluster-template.yaml index 61045484..5960918f 100644 --- a/templates/cluster-template.yaml +++ b/templates/cluster-template.yaml @@ -31,7 +31,7 @@ metadata: name: "${CLUSTER_NAME}" spec: controlPlaneEndpoint: - host: ${CONTROL_PLANE_ENDPOINT_IP} + host: ${CONTROL_PLANE_ENDPOINT_HOST:-${CONTROL_PLANE_ENDPOINT_IP}} port: ${CONTROL_PLANE_ENDPOINT_PORT:-6443} location: ${CONTROL_PLANE_ENDPOINT_LOCATION} contractNumber: "${IONOSCLOUD_CONTRACT_NUMBER}" From ebb242e2769dde49c8c54d13b3271acf561b1d26 Mon Sep 17 00:00:00 2001 From: Jonas Riedel <138458199+jriedel-ionos@users.noreply.github.com> Date: Fri, 3 May 2024 12:01:15 +0200 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=8C=B1=20Activate=20yamllint=20PR?= =?UTF-8?q?=20workflows=20(#106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What is the purpose of this pull request/Why do we need it?** Adds yamllint in pull requests **Checklist:** - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning) --- .github/workflows/lint.yml | 17 +++++----- .github/workflows/test.yml | 7 ---- .yamllint | 34 +++++++++++++++++++ config/default/kustomization.yaml | 14 ++++---- config/default/manager_config_patch.yaml | 1 + config/manager/kustomization.yaml | 1 + config/manager/manager.yaml | 19 +++++------ ...astructure_v1alpha1_ionoscloudcluster.yaml | 1 + ...astructure_v1alpha1_ionoscloudmachine.yaml | 1 + ...re_v1alpha1_ionoscloudmachinetemplate.yaml | 1 + config/samples/kustomization.yaml | 3 +- 11 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 .yamllint diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c69ddafc..9a773796 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,15 +17,14 @@ jobs: - name: Run lint run: "make lint" - # TODO(lubedacht) include later - # yamllint: - # name: yamllint - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - uses: ibiqlik/action-yamllint@v3 - # with: - # format: github + yamllint: + name: yamllint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ibiqlik/action-yamllint@v3 + with: + format: github actionlint: name: actionlint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93b6cd37..a9ac3341 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,10 +11,3 @@ jobs: go-version-file: go.mod - name: Run tests run: "make test" - - # TODO(lubedacht) include later - # - name: SonarCloud Scan - # uses: SonarSource/sonarcloud-github-action@v2.0.2 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.yamllint b/.yamllint new file mode 100644 index 00000000..e1c09826 --- /dev/null +++ b/.yamllint @@ -0,0 +1,34 @@ +--- +extends: default + +rules: + # the default of 80 is overly-restrictive, particularly when nested + line-length: + max: 120 + level: warning + # as this repository also contains generated yaml, we only enforce + # indentation consistency within a file + indentation: + spaces: consistent + indent-sequences: consistent + level: warning + comments: + min-spaces-from-content: 1 + # comments-indentation linting has unwanted edgecases: + # https://github.com/adrienverge/yamllint/issues/141 + comments-indentation: disable + +ignore: +# generated files +- config/crd +- config/certmanager +- config/prometheus +- config/rbac +- test/e2e +- out +- .*.yaml +- .*.yml +# these are clusterctl templates, not yaml +- templates +# github actions checked by actionlint +- .github/workflows diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 79367f05..0017ee90 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,3 +1,4 @@ +--- # Adds namespace to all resources. namePrefix: capic- namespace: capic-system @@ -12,29 +13,28 @@ resources: - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- ../webhook +# - ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +# - ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus +# - ../prometheus patchesStrategicMerge: - manager_image_patch.yaml - # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- manager_webhook_patch.yaml +# - manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. # 'CERTMANAGER' needs to be enabled to use ca injection -#- webhookcainjection_patch.yaml +# - webhookcainjection_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: +# replacements: # - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs # kind: Certificate # group: cert-manager.io diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml index f6f58916..ec4ccc0b 100644 --- a/config/default/manager_config_patch.yaml +++ b/config/default/manager_config_patch.yaml @@ -1,3 +1,4 @@ +--- apiVersion: apps/v1 kind: Deployment metadata: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index ad13e96b..28684b4e 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,3 +1,4 @@ +--- resources: - manager.yaml apiVersion: kustomize.config.k8s.io/v1beta1 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 19e0249d..64cb465e 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -1,3 +1,4 @@ +--- apiVersion: v1 kind: Namespace metadata: @@ -73,14 +74,14 @@ spec: image: controller:latest name: manager ports: - - containerPort: 8443 - name: diagnostics - protocol: TCP + - containerPort: 8443 + name: diagnostics + protocol: TCP securityContext: allowPrivilegeEscalation: false capabilities: drop: - - "ALL" + - "ALL" livenessProbe: httpGet: path: /healthz @@ -122,9 +123,7 @@ spec: selector: control-plane: controller-manager ports: - - name: diagnostics-svc - protocol: TCP - port: 8443 - targetPort: diagnostics - - + - name: diagnostics-svc + protocol: TCP + port: 8443 + targetPort: diagnostics diff --git a/config/samples/infrastructure_v1alpha1_ionoscloudcluster.yaml b/config/samples/infrastructure_v1alpha1_ionoscloudcluster.yaml index 84befb1a..131aab82 100644 --- a/config/samples/infrastructure_v1alpha1_ionoscloudcluster.yaml +++ b/config/samples/infrastructure_v1alpha1_ionoscloudcluster.yaml @@ -1,3 +1,4 @@ +--- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 kind: IonosCloudCluster metadata: diff --git a/config/samples/infrastructure_v1alpha1_ionoscloudmachine.yaml b/config/samples/infrastructure_v1alpha1_ionoscloudmachine.yaml index 04ab3475..2293710e 100644 --- a/config/samples/infrastructure_v1alpha1_ionoscloudmachine.yaml +++ b/config/samples/infrastructure_v1alpha1_ionoscloudmachine.yaml @@ -1,3 +1,4 @@ +--- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 kind: IonosCloudMachine metadata: diff --git a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate.yaml b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate.yaml index 17716777..652dfcad 100644 --- a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate.yaml +++ b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate.yaml @@ -1,3 +1,4 @@ +--- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 kind: IonosCloudMachineTemplate metadata: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 995562cd..ddc5c7bd 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,6 +1,7 @@ +--- ## Append samples of your project ## resources: - infrastructure_v1alpha1_ionoscloudcluster.yaml - infrastructure_v1alpha1_ionoscloudmachine.yaml - infrastructure_v1alpha1_ionoscloudmachinetemplate.yaml -#+kubebuilder:scaffold:manifestskustomizesamples +# +kubebuilder:scaffold:manifestskustomizesamples From 7b61307c7769c2aff4b9aeadb124e0200c926579 Mon Sep 17 00:00:00 2001 From: lubedacht <132355999+lubedacht@users.noreply.github.com> Date: Fri, 3 May 2024 12:04:17 +0200 Subject: [PATCH 08/10] :bug: Harden deletion logic (#111) **What is the purpose of this pull request/Why do we need it?** Makes sure to delete resources properly when deleting all resources at once. Also fixes handling for the credential secret which so far was not properly cleaned up. **Description of changes:** * Cluster deletion will not succeed when there are still any machines left * Credential secrets cannot be deleted as long as the cluster hasn't reached the final deletion step * Credential secrets are now properly garbage collected with a controller reference **Checklist:** - [x] Unit Tests added - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning) --- api/v1alpha1/ionoscloudcluster_types.go | 4 + .../ionoscloudcluster_controller.go | 14 ++- .../ionoscloudmachine_controller.go | 2 +- internal/controller/util.go | 45 ++++++++ internal/service/cloud/network.go | 2 +- scope/cluster.go | 22 ++++ scope/cluster_test.go | 107 ++++++++++++++++++ scope/machine.go | 34 ++---- scope/machine_test.go | 4 +- 9 files changed, 203 insertions(+), 31 deletions(-) diff --git a/api/v1alpha1/ionoscloudcluster_types.go b/api/v1alpha1/ionoscloudcluster_types.go index 3709855b..103118a1 100644 --- a/api/v1alpha1/ionoscloudcluster_types.go +++ b/api/v1alpha1/ionoscloudcluster_types.go @@ -27,6 +27,10 @@ const ( // associated with the IonosCloudCluster before removing it from the API server. ClusterFinalizer = "ionoscloudcluster.infrastructure.cluster.x-k8s.io" + // ClusterCredentialsFinalizer allows cleanup of resources, which are + // associated with the IonosCloudCluster credentials before removing it from the API server. + ClusterCredentialsFinalizer = ClusterFinalizer + "/credentials" + // IonosCloudClusterReady is the condition for the IonosCloudCluster, which indicates that the cluster is ready. IonosCloudClusterReady clusterv1.ConditionType = "ClusterReady" diff --git a/internal/controller/ionoscloudcluster_controller.go b/internal/controller/ionoscloudcluster_controller.go index 5f47387c..1d0c317e 100644 --- a/internal/controller/ionoscloudcluster_controller.go +++ b/internal/controller/ionoscloudcluster_controller.go @@ -171,8 +171,15 @@ func (r *IonosCloudClusterReconciler) reconcileDelete( return ctrl.Result{RequeueAfter: defaultReconcileDuration}, nil } - // TODO(lubedacht): check if there are any more machine CRs existing. - // If there are requeue with an offset. + machines, err := clusterScope.ListMachines(ctx, nil) + if err != nil { + return ctrl.Result{}, err + } + + if len(machines) > 0 { + log.Info("Waiting for all IonosCloudMachines to be deleted", "remaining", len(machines)) + return ctrl.Result{RequeueAfter: defaultReconcileDuration}, nil + } reconcileSequence := []serviceReconcileStep[scope.Cluster]{ {"ReconcileControlPlaneEndpointDeletion", cloudService.ReconcileControlPlaneEndpointDeletion}, @@ -186,6 +193,9 @@ func (r *IonosCloudClusterReconciler) reconcileDelete( return ctrl.Result{RequeueAfter: defaultReconcileDuration}, err } } + if err := removeCredentialsFinalizer(ctx, r.Client, clusterScope.IonosCluster); err != nil { + return ctrl.Result{}, err + } controllerutil.RemoveFinalizer(clusterScope.IonosCluster, infrav1.ClusterFinalizer) return ctrl.Result{}, nil } diff --git a/internal/controller/ionoscloudmachine_controller.go b/internal/controller/ionoscloudmachine_controller.go index e9dd5580..a3ba2961 100644 --- a/internal/controller/ionoscloudmachine_controller.go +++ b/internal/controller/ionoscloudmachine_controller.go @@ -153,7 +153,7 @@ func (r *IonosCloudMachineReconciler) reconcileNormal( if controllerutil.AddFinalizer(machineScope.IonosMachine, infrav1.MachineFinalizer) { if err := machineScope.PatchObject(); err != nil { - log.Error(err, "unable to update finalizer on object") + err = fmt.Errorf("unable to update finalizer on object: %w", err) return ctrl.Result{}, err } } diff --git a/internal/controller/util.go b/internal/controller/util.go index 1c091d37..7a60a22e 100644 --- a/internal/controller/util.go +++ b/internal/controller/util.go @@ -25,6 +25,7 @@ import ( sdk "github.com/ionos-cloud/sdk-go/v6" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" infrav1 "github.com/ionos-cloud/cluster-api-provider-ionoscloud/api/v1alpha1" icc "github.com/ionos-cloud/cluster-api-provider-ionoscloud/internal/ionoscloud/client" @@ -80,6 +81,10 @@ func createServiceFromCluster( return nil, err } + if err := ensureSecretControlledByCluster(ctx, c, cluster, &authSecret); err != nil { + return nil, err + } + token := string(authSecret.Data["token"]) apiURL := string(authSecret.Data["apiURL"]) caBundle := authSecret.Data["caBundle"] @@ -91,3 +96,43 @@ func createServiceFromCluster( return cloud.NewService(ionosClient, log) } + +// ensureSecretControlledByCluster ensures that the secrets will contain a finalizer and a controller reference. +// The secret should only be deleted when there are no resources left in the IONOS Cloud environment. +func ensureSecretControlledByCluster( + ctx context.Context, c client.Client, + cluster *infrav1.IonosCloudCluster, + secret *corev1.Secret, +) error { + requireUpdate := controllerutil.AddFinalizer(secret, infrav1.ClusterCredentialsFinalizer) + + if !controllerutil.HasControllerReference(secret) { + if err := controllerutil.SetControllerReference(cluster, secret, c.Scheme()); err != nil { + return err + } + requireUpdate = true + } + + if requireUpdate { + return c.Update(ctx, secret) + } + + return nil +} + +// removeCredentialsFinalizer removes the finalizer from the credential secret. +func removeCredentialsFinalizer(ctx context.Context, c client.Client, cluster *infrav1.IonosCloudCluster) error { + secretKey := client.ObjectKey{ + Namespace: cluster.Namespace, + Name: cluster.Spec.CredentialsRef.Name, + } + var secret corev1.Secret + + if err := c.Get(ctx, secretKey, &secret); err != nil { + // If the secret does not exist anymore, there is nothing we can do. + return client.IgnoreNotFound(err) + } + + controllerutil.RemoveFinalizer(&secret, infrav1.ClusterCredentialsFinalizer) + return c.Update(ctx, &secret) +} diff --git a/internal/service/cloud/network.go b/internal/service/cloud/network.go index 726068f8..e2e25ad7 100644 --- a/internal/service/cloud/network.go +++ b/internal/service/cloud/network.go @@ -443,7 +443,7 @@ func (s *Service) getServerNICID(ctx context.Context, ms *scope.Machine) (string log.Info("Server was not found or already deleted.") return "", nil } - log.Error(err, "Unable to retrieve server") + err = fmt.Errorf("unable to retrieve server %w", err) return "", err } diff --git a/scope/cluster.go b/scope/cluster.go index b9dc68c3..daf6a16e 100644 --- a/scope/cluster.go +++ b/scope/cluster.go @@ -44,6 +44,7 @@ type resolver interface { // Cluster defines a basic cluster context for primary use in IonosCloudClusterReconciler. type Cluster struct { + client client.Client patchHelper *patch.Helper resolver resolver Cluster *clusterv1.Cluster @@ -78,6 +79,7 @@ func NewCluster(params ClusterParams) (*Cluster, error) { } clusterScope := &Cluster{ + client: params.Client, Cluster: params.Cluster, IonosCluster: params.IonosCluster, patchHelper: helper, @@ -121,6 +123,26 @@ func (c *Cluster) SetControlPlaneEndpointIPBlockID(id string) { c.IonosCluster.Status.ControlPlaneEndpointIPBlockID = id } +// ListMachines returns a list of IonosCloudMachines in the same namespace and with the same cluster label. +// With machineLabels, additional search labels can be provided. +func (c *Cluster) ListMachines( + ctx context.Context, + machineLabels client.MatchingLabels, +) ([]infrav1.IonosCloudMachine, error) { + if machineLabels == nil { + machineLabels = client.MatchingLabels{} + } + + machineLabels[clusterv1.ClusterNameLabel] = c.Cluster.Name + listOpts := []client.ListOption{client.InNamespace(c.Cluster.Namespace), machineLabels} + + machineList := &infrav1.IonosCloudMachineList{} + if err := c.client.List(ctx, machineList, listOpts...); err != nil { + return nil, err + } + return machineList.Items, nil +} + // Location is a shortcut for getting the location used by the IONOS Cloud cluster IP block. func (c *Cluster) Location() string { return c.IonosCluster.Spec.Location diff --git a/scope/cluster_test.go b/scope/cluster_test.go index 36602920..45699c32 100644 --- a/scope/cluster_test.go +++ b/scope/cluster_test.go @@ -23,8 +23,11 @@ import ( "testing" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" infrav1 "github.com/ionos-cloud/cluster-api-provider-ionoscloud/api/v1alpha1" @@ -162,3 +165,107 @@ func TestCluster_GetControlPlaneEndpointIP(t *testing.T) { }) } } + +func TestClusterListMachines(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, infrav1.AddToScheme(scheme)) + + const clusterName = "test-cluster" + + makeLabels := func(clusterName string, additionalLabels map[string]string) map[string]string { + if additionalLabels == nil { + return map[string]string{clusterv1.ClusterNameLabel: clusterName} + } + + additionalLabels[clusterv1.ClusterNameLabel] = clusterName + return additionalLabels + } + + tests := []struct { + name string + initialObjects []client.Object + searchLabels client.MatchingLabels + expectedNames sets.Set[string] + }{{ + name: "List all machines for a cluster", + initialObjects: []client.Object{ + buildMachineWithLabel("machine-1", makeLabels(clusterName, nil)), + buildMachineWithLabel("machine-2", makeLabels(clusterName, nil)), + buildMachineWithLabel("machine-3", makeLabels(clusterName, nil)), + }, + searchLabels: client.MatchingLabels{}, + expectedNames: sets.New("machine-1", "machine-2", "machine-3"), + }, { + name: "List only machines with specific labels", + initialObjects: []client.Object{ + buildMachineWithLabel("machine-1", makeLabels(clusterName, map[string]string{"foo": "bar"})), + buildMachineWithLabel("machine-2", makeLabels(clusterName, map[string]string{"foo": "bar"})), + buildMachineWithLabel("machine-3", makeLabels(clusterName, nil)), + }, + searchLabels: client.MatchingLabels{ + "foo": "bar", + }, + expectedNames: sets.New("machine-1", "machine-2"), + }, { + name: "List no machines", + initialObjects: []client.Object{ + buildMachineWithLabel("machine-1", makeLabels(clusterName, map[string]string{"foo": "notbar"})), + buildMachineWithLabel("machine-2", makeLabels(clusterName, map[string]string{"foo": "notbar"})), + buildMachineWithLabel("machine-3", makeLabels(clusterName, map[string]string{"foo": "notbar"})), + }, + searchLabels: makeLabels(clusterName, map[string]string{"foo": "bar"}), + expectedNames: sets.New[string](), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + params := ClusterParams{ + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + clusterv1.ClusterNameLabel: clusterName, + }, + }, + }, + IonosCluster: &infrav1.IonosCloudCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ionos-cluster", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + clusterv1.ClusterNameLabel: clusterName, + }, + }, + Status: infrav1.IonosCloudClusterStatus{}, + }, + } + + cl := fake.NewClientBuilder().WithScheme(scheme). + WithObjects(test.initialObjects...).Build() + + params.Client = cl + cs, err := NewCluster(params) + require.NoError(t, err) + require.NotNil(t, cs) + + machines, err := cs.ListMachines(context.Background(), test.searchLabels) + require.NoError(t, err) + require.Len(t, machines, len(test.expectedNames)) + + for _, m := range machines { + require.Contains(t, test.expectedNames, m.Name) + } + }) + } +} + +func buildMachineWithLabel(name string, labels map[string]string) *infrav1.IonosCloudMachine { + return &infrav1.IonosCloudMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + Labels: labels, + }, + } +} diff --git a/scope/machine.go b/scope/machine.go index f714460f..6c681295 100644 --- a/scope/machine.go +++ b/scope/machine.go @@ -122,24 +122,12 @@ func (m *Machine) CountMachines(ctx context.Context, machineLabels client.Matchi return len(machines), err } -// ListMachines returns a list of IonosCloudMachines in the same namespace and with the same cluster label. -// With machineLabels, additional search labels can be provided. +// ListMachines is a convenience wrapper function for the Cluster.ListMachines function. func (m *Machine) ListMachines( ctx context.Context, machineLabels client.MatchingLabels, ) ([]infrav1.IonosCloudMachine, error) { - if machineLabels == nil { - machineLabels = client.MatchingLabels{} - } - - machineLabels[clusterv1.ClusterNameLabel] = m.ClusterScope.Cluster.Name - listOpts := []client.ListOption{client.InNamespace(m.IonosMachine.Namespace), machineLabels} - - machineList := &infrav1.IonosCloudMachineList{} - if err := m.client.List(ctx, machineList, listOpts...); err != nil { - return nil, err - } - return machineList.Items, nil + return m.ClusterScope.ListMachines(ctx, machineLabels) } // FindLatestMachine returns the latest IonosCloudMachine in the same namespace @@ -151,23 +139,17 @@ func (m *Machine) FindLatestMachine( ctx context.Context, matchLabels client.MatchingLabels, ) (*infrav1.IonosCloudMachine, error) { - if matchLabels == nil { - matchLabels = client.MatchingLabels{} - } - - matchLabels[clusterv1.ClusterNameLabel] = m.ClusterScope.Cluster.Name - listOpts := []client.ListOption{client.InNamespace(m.IonosMachine.Namespace), matchLabels} - - machineList := &infrav1.IonosCloudMachineList{} - if err := m.client.List(ctx, machineList, listOpts...); err != nil { + machines, err := m.ClusterScope.ListMachines(ctx, matchLabels) + if err != nil { return nil, err } - if len(machineList.Items) <= 1 { + + if len(machines) <= 1 { return nil, nil } - latestMachine := machineList.Items[0] - for _, machine := range machineList.Items { + latestMachine := machines[0] + for _, machine := range machines { if !machine.CreationTimestamp.Before(&latestMachine.CreationTimestamp) && machine.Name != m.IonosMachine.Name { latestMachine = machine } diff --git a/scope/machine_test.go b/scope/machine_test.go index 71b2add4..68a59af1 100644 --- a/scope/machine_test.go +++ b/scope/machine_test.go @@ -37,10 +37,12 @@ func exampleParams(t *testing.T) MachineParams { if err := infrav1.AddToScheme(scheme.Scheme); err != nil { require.NoError(t, err, "could not construct params") } + cl := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() return MachineParams{ - Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build(), + Client: cl, Machine: &clusterv1.Machine{}, ClusterScope: &Cluster{ + client: cl, Cluster: &clusterv1.Cluster{}, }, IonosMachine: &infrav1.IonosCloudMachine{}, From 1d1347a920cf1fdcf4237492080c18bc1c2aacb5 Mon Sep 17 00:00:00 2001 From: Jonas Riedel <138458199+jriedel-ionos@users.noreply.github.com> Date: Fri, 3 May 2024 14:23:31 +0200 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=90=9B=20Follow=20up=20ensure=20ser?= =?UTF-8?q?ver=20is=20started=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What is the purpose of this pull request/Why do we need it?** Follow up of https://github.com/ionos-cloud/cluster-api-provider-ionoscloud/pull/86. Fixes some logic problems **Description of changes:** Now writing the request into the status, and move some logic to the correct place. **Checklist:** - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning) --- internal/service/cloud/server.go | 91 ++++++++++++++++----------- internal/service/cloud/server_test.go | 4 +- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/internal/service/cloud/server.go b/internal/service/cloud/server.go index 9ff5fb86..8b88ba33 100644 --- a/internal/service/cloud/server.go +++ b/internal/service/cloud/server.go @@ -62,43 +62,40 @@ func (s *Service) ReconcileServer(ctx context.Context, ms *scope.Machine) (reque return true, nil } - if server != nil { - // Server is available - - if !s.isServerAvailable(ms, server) { - // Server is still provisioning, checking again later - return true, nil + if server == nil { + // Server does not exist yet, create it + log.V(4).Info("No server was found. Creating new server") + if err := s.createServer(ctx, secret, ms); err != nil { + return false, err } + log.V(4).Info("Successfully initiated server creation") + // If we reach this point, we want to requeue as the request is not processed yet, + // and we will check for the status again later. + return true, nil + } - // Attach the IPs from all NICs of the server to the status - netInfo := &infrav1.MachineNetworkInfo{NICInfo: make([]infrav1.NICInfo, 0)} - - for _, nic := range ptr.Deref(server.GetEntities().GetNics().GetItems(), []sdk.Nic{}) { - netInfo.NICInfo = append(netInfo.NICInfo, infrav1.NICInfo{ - IPv4Addresses: ptr.Deref(nic.GetProperties().GetIps(), []string{}), - IPv6Addresses: ptr.Deref(nic.GetProperties().GetIpv6Ips(), []string{}), - NetworkID: ptr.Deref(nic.GetProperties().GetLan(), 0), - Primary: s.isPrimaryNIC(ms.IonosMachine, &nic), - }) - } + requeue, err = s.ensureServerAvailable(ctx, ms, server) + if requeue || err != nil { + return requeue, err + } - ms.IonosMachine.Status.MachineNetworkInfo = netInfo + // Attach the IPs from all NICs of the server to the status + netInfo := &infrav1.MachineNetworkInfo{NICInfo: make([]infrav1.NICInfo, 0)} - log.Info("Server is available", "serverID", ptr.Deref(server.GetId(), "")) - // server exists and is available. - return false, nil + for _, nic := range ptr.Deref(server.GetEntities().GetNics().GetItems(), []sdk.Nic{}) { + netInfo.NICInfo = append(netInfo.NICInfo, infrav1.NICInfo{ + IPv4Addresses: ptr.Deref(nic.GetProperties().GetIps(), []string{}), + IPv6Addresses: ptr.Deref(nic.GetProperties().GetIpv6Ips(), []string{}), + NetworkID: ptr.Deref(nic.GetProperties().GetLan(), 0), + Primary: s.isPrimaryNIC(ms.IonosMachine, &nic), + }) } - // server does not exist yet, create it - log.V(4).Info("No server was found. Creating new server") - if err := s.createServer(ctx, secret, ms); err != nil { - return false, err - } + ms.IonosMachine.Status.MachineNetworkInfo = netInfo - log.V(4).Info("successfully finished reconciling server") - // If we reach this point, we want to requeue as the request is not processed yet, - // and we will check for the status again later. - return true, nil + log.Info("Server is available", "serverID", ptr.Deref(server.GetId(), "")) + // server exists and is available. + return false, nil } // ReconcileServerDeletion ensures the server is deleted. @@ -152,23 +149,40 @@ func (*Service) FinalizeMachineProvisioning(_ context.Context, ms *scope.Machine return false, nil } -func (s *Service) isServerAvailable(ms *scope.Machine, server *sdk.Server) bool { +// isServerAvailable checks if the server is in state AVAILABLE. +func (s *Service) isServerAvailable(server *sdk.Server) bool { log := s.logger.WithName("isServerAvailable") if state := getState(server); !isAvailable(state) { log.Info("Server is not available yet", "state", state) return false } + return true +} + +// ensureServerAvailable checks the availability of the specified server. +func (s *Service) ensureServerAvailable(ctx context.Context, ms *scope.Machine, server *sdk.Server) (bool, error) { + log := s.logger.WithName("ensureServerAvailable") + + // Check if the server is available + if !s.isServerAvailable(server) { + // Server is still provisioning, checking again later + return true, nil + } + // Check the VM state; if not running, try to start it if vmState := getVMState(server); !isRunning(vmState) { - err := s.startServer(context.Background(), ms.DatacenterID(), *server.Id) + err := s.startServer(ctx, ms, *server.Id) if err != nil { log.Error(err, "Failed to start the server") - return false + return true, err } - return true + // If we reach this point, we want to requeue as the request is not processed yet, + // and we will check for the status again later. + return true, nil } - return true + // Default return path when no conditions are met (server is available and running) + return false, nil } // getServerByServerID checks if the IonosCloudMachine has a provider ID set. @@ -241,17 +255,18 @@ func (s *Service) deleteServer(ctx context.Context, ms *scope.Machine, serverID return nil } -func (s *Service) startServer(ctx context.Context, datacenterID, serverID string) error { +func (s *Service) startServer(ctx context.Context, ms *scope.Machine, serverID string) error { log := s.logger.WithName("startServer") log.V(4).Info("Starting server", "serverID", serverID) - requestLocation, err := s.ionosClient.StartServer(ctx, datacenterID, serverID) + requestLocation, err := s.ionosClient.StartServer(ctx, ms.DatacenterID(), serverID) if err != nil { return fmt.Errorf("failed to request server start: %w", err) } log.Info("Successfully requested for server start", "location", requestLocation) - log.V(4).Info("Done starting server") + ms.IonosMachine.SetCurrentRequest(http.MethodPost, sdk.RequestStatusQueued, requestLocation) + return nil } diff --git a/internal/service/cloud/server_test.go b/internal/service/cloud/server_test.go index fa432344..f0284ace 100644 --- a/internal/service/cloud/server_test.go +++ b/internal/service/cloud/server_test.go @@ -136,7 +136,7 @@ func (s *serverSuite) TestReconcileServerRequestDoneStateAvailableTurnedOff() { }, Properties: &sdk.ServerProperties{ Name: ptr.To(s.infraMachine.Name), - VmState: ptr.To(sdk.Available), + VmState: ptr.To("SHUTOFF"), }, }, }}, nil).Once() @@ -145,7 +145,7 @@ func (s *serverSuite) TestReconcileServerRequestDoneStateAvailableTurnedOff() { requeue, err := s.service.ReconcileServer(s.ctx, s.machineScope) s.NoError(err) - s.False(requeue) + s.True(requeue) } func (s *serverSuite) TestReconcileServerNoRequest() { From 1b58f35b9ed859156623111cff1219b69d716a23 Mon Sep 17 00:00:00 2001 From: Jonas Riedel <138458199+jriedel-ionos@users.noreply.github.com> Date: Fri, 3 May 2024 16:42:03 +0200 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=8C=B1=20Remove=20outdated=20commen?= =?UTF-8?q?t=20from=20Reconcile=20function=20(#115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What is the purpose of this pull request/Why do we need it?** Removes outdated comment from the Reconcile function **Checklist:** - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning) --- internal/controller/ionoscloudmachine_controller.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/internal/controller/ionoscloudmachine_controller.go b/internal/controller/ionoscloudmachine_controller.go index a3ba2961..becf72b3 100644 --- a/internal/controller/ionoscloudmachine_controller.go +++ b/internal/controller/ionoscloudmachine_controller.go @@ -52,15 +52,6 @@ type IonosCloudMachineReconciler struct { //+kubebuilder:rbac:groups="",resources=secrets;,verbs=get;list;watch //+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;update;patch -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the IonosCloudMachine object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.0/pkg/reconcile func (r *IonosCloudMachineReconciler) Reconcile( ctx context.Context, ionosCloudMachine *infrav1.IonosCloudMachine,