diff --git a/api/v1alpha5/zz_generated.conversion.go b/api/v1alpha5/zz_generated.conversion.go index 2c5a688927..7ef2f85215 100644 --- a/api/v1alpha5/zz_generated.conversion.go +++ b/api/v1alpha5/zz_generated.conversion.go @@ -486,7 +486,9 @@ func Convert_v1beta1_AddressPair_To_v1alpha5_AddressPair(in *v1beta1.AddressPair } func autoConvert_v1alpha5_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_bool_To_optional_Bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Instance requires manual conversion: does not exist in peer-type if err := optional.Convert_string_To_optional_String(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err @@ -495,7 +497,9 @@ func autoConvert_v1alpha5_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.B } func autoConvert_v1beta1_Bastion_To_v1alpha5_Bastion(in *v1beta1.Bastion, out *Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_optional_Bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Spec requires manual conversion: does not exist in peer-type if err := optional.Convert_optional_String_To_string(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err diff --git a/api/v1alpha6/openstackcluster_conversion.go b/api/v1alpha6/openstackcluster_conversion.go index 66fd5896bb..0cc60afb18 100644 --- a/api/v1alpha6/openstackcluster_conversion.go +++ b/api/v1alpha6/openstackcluster_conversion.go @@ -416,6 +416,7 @@ func restorev1beta1Bastion(previous **infrav1.Bastion, dst **infrav1.Bastion) { optional.RestoreString(&(*previous).FloatingIP, &(*dst).FloatingIP) optional.RestoreString(&(*previous).AvailabilityZone, &(*dst).AvailabilityZone) + optional.RestoreBool(&(*previous).Enabled, &(*dst).Enabled) } func Convert_v1alpha6_Bastion_To_v1beta1_Bastion(in *Bastion, out *infrav1.Bastion, s apiconversion.Scope) error { diff --git a/api/v1alpha6/zz_generated.conversion.go b/api/v1alpha6/zz_generated.conversion.go index 8e33e36df6..f1ad2538c2 100644 --- a/api/v1alpha6/zz_generated.conversion.go +++ b/api/v1alpha6/zz_generated.conversion.go @@ -510,7 +510,9 @@ func Convert_v1beta1_AddressPair_To_v1alpha6_AddressPair(in *v1beta1.AddressPair } func autoConvert_v1alpha6_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_bool_To_optional_Bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Instance requires manual conversion: does not exist in peer-type if err := optional.Convert_string_To_optional_String(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err @@ -519,7 +521,9 @@ func autoConvert_v1alpha6_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.B } func autoConvert_v1beta1_Bastion_To_v1alpha6_Bastion(in *v1beta1.Bastion, out *Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_optional_Bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Spec requires manual conversion: does not exist in peer-type if err := optional.Convert_optional_String_To_string(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err diff --git a/api/v1alpha7/openstackcluster_conversion.go b/api/v1alpha7/openstackcluster_conversion.go index 4f06e21cc4..df703c5eb7 100644 --- a/api/v1alpha7/openstackcluster_conversion.go +++ b/api/v1alpha7/openstackcluster_conversion.go @@ -384,6 +384,7 @@ func restorev1beta1Bastion(previous **infrav1.Bastion, dst **infrav1.Bastion) { optional.RestoreString(&(*previous).FloatingIP, &(*dst).FloatingIP) optional.RestoreString(&(*previous).AvailabilityZone, &(*dst).AvailabilityZone) + optional.RestoreBool(&(*previous).Enabled, &(*dst).Enabled) } } diff --git a/api/v1alpha7/zz_generated.conversion.go b/api/v1alpha7/zz_generated.conversion.go index c202a6787b..91d95656f4 100644 --- a/api/v1alpha7/zz_generated.conversion.go +++ b/api/v1alpha7/zz_generated.conversion.go @@ -573,7 +573,9 @@ func Convert_v1beta1_AddressPair_To_v1alpha7_AddressPair(in *v1beta1.AddressPair } func autoConvert_v1alpha7_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_bool_To_optional_Bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Instance requires manual conversion: does not exist in peer-type if err := optional.Convert_string_To_optional_String(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err @@ -582,7 +584,9 @@ func autoConvert_v1alpha7_Bastion_To_v1beta1_Bastion(in *Bastion, out *v1beta1.B } func autoConvert_v1beta1_Bastion_To_v1alpha7_Bastion(in *v1beta1.Bastion, out *Bastion, s conversion.Scope) error { - out.Enabled = in.Enabled + if err := optional.Convert_optional_Bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } // WARNING: in.Spec requires manual conversion: does not exist in peer-type if err := optional.Convert_optional_String_To_string(&in.AvailabilityZone, &out.AvailabilityZone, s); err != nil { return err diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go index 492546e755..bfdef09c6b 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -719,12 +719,18 @@ var ( ) // Bastion represents basic information about the bastion node. If you enable bastion, the spec has to be specified. -// +kubebuilder:validation:XValidation:rule="!self.enabled || has(self.spec)",message="you need to specify the spec if bastion is enabled" +// +kubebuilder:validation:XValidation:rule="!self.enabled || has(self.spec)",message="spec is required if bastion is enabled" type Bastion struct { - // Enabled means that bastion is enabled. Defaults to false. - // +kubebuilder:validation:Required - // +kubebuilder:default:=false - Enabled bool `json:"enabled"` + // Enabled means that bastion is enabled. The bastion is enabled by + // default if this field is not specified. Set this field to false to disable the + // bastion. + // + // It is not currently possible to remove the bastion from the cluster + // spec without first disabling it by setting this field to false and + // waiting until the bastion has been deleted. + // +kubebuilder:default:=true + // +optional + Enabled optional.Bool `json:"enabled,omitempty"` // Spec for the bastion itself Spec *OpenStackMachineSpec `json:"spec,omitempty"` @@ -741,6 +747,13 @@ type Bastion struct { FloatingIP optional.String `json:"floatingIP,omitempty"` } +func (b *Bastion) IsEnabled() bool { + if b == nil { + return false + } + return b.Enabled == nil || *b.Enabled +} + type APIServerLoadBalancer struct { // Enabled defines whether a load balancer should be created. This value // defaults to true if an APIServerLoadBalancer is given. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 088e9c46f2..69d71929a1 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -116,6 +116,11 @@ func (in *AllocationPool) DeepCopy() *AllocationPool { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Bastion) DeepCopyInto(out *Bastion) { *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } if in.Spec != nil { in, out := &in.Spec, &out.Spec *out = new(OpenStackMachineSpec) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml index 5056e60154..acdaa6eae7 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml @@ -4921,9 +4921,16 @@ spec: be used to create the Bastion Spec. type: string enabled: - default: false - description: Enabled means that bastion is enabled. Defaults to - false. + default: true + description: |- + Enabled means that bastion is enabled. The bastion is enabled by + default if this field is not specified. Set this field to false to disable the + bastion. + + + It is not currently possible to remove the bastion from the cluster + spec without first disabling it by setting this field to false and + waiting until the bastion has been deleted. type: boolean floatingIP: description: |- @@ -5639,11 +5646,9 @@ spec: - flavor - image type: object - required: - - enabled type: object x-kubernetes-validations: - - message: you need to specify the spec if bastion is enabled + - message: spec is required if bastion is enabled rule: '!self.enabled || has(self.spec)' controlPlaneAvailabilityZones: description: |- diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml index 5f25ff93c1..f01e35e574 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml @@ -2345,9 +2345,16 @@ spec: will be used to create the Bastion Spec. type: string enabled: - default: false - description: Enabled means that bastion is enabled. Defaults - to false. + default: true + description: |- + Enabled means that bastion is enabled. The bastion is enabled by + default if this field is not specified. Set this field to false to disable the + bastion. + + + It is not currently possible to remove the bastion from the cluster + spec without first disabling it by setting this field to false and + waiting until the bastion has been deleted. type: boolean floatingIP: description: |- @@ -3076,11 +3083,9 @@ spec: - flavor - image type: object - required: - - enabled type: object x-kubernetes-validations: - - message: you need to specify the spec if bastion is enabled + - message: spec is required if bastion is enabled rule: '!self.enabled || has(self.spec)' controlPlaneAvailabilityZones: description: |- diff --git a/controllers/openstackcluster_controller.go b/controllers/openstackcluster_controller.go index c9283f11b7..3823b6ec3a 100644 --- a/controllers/openstackcluster_controller.go +++ b/controllers/openstackcluster_controller.go @@ -219,7 +219,7 @@ func contains(arr []string, target string) bool { func resolveBastionResources(scope *scope.WithLogger, clusterResourceName string, openStackCluster *infrav1.OpenStackCluster) (bool, error) { // Resolve and store resources for the bastion - if openStackCluster.Spec.Bastion != nil && openStackCluster.Spec.Bastion.Enabled { + if openStackCluster.Spec.Bastion.IsEnabled() { if openStackCluster.Status.Bastion == nil { openStackCluster.Status.Bastion = &infrav1.BastionStatus{} } @@ -406,7 +406,7 @@ func reconcileBastion(scope *scope.WithLogger, cluster *clusterv1.Cluster, openS } // No Bastion defined - if openStackCluster.Spec.Bastion == nil || !openStackCluster.Spec.Bastion.Enabled { + if !openStackCluster.Spec.Bastion.IsEnabled() { // Delete any existing bastion if openStackCluster.Status.Bastion != nil { if err := deleteBastion(scope, cluster, openStackCluster); err != nil { @@ -440,6 +440,8 @@ func reconcileBastion(scope *scope.WithLogger, cluster *clusterv1.Cluster, openS return nil, fmt.Errorf("failed computing bastion hash from instance spec: %w", err) } if bastionHashHasChanged(bastionHash, openStackCluster.ObjectMeta.Annotations) { + scope.Logger().Info("Bastion instance spec has changed, deleting existing bastion") + if err := deleteBastion(scope, cluster, openStackCluster); err != nil { return nil, err } @@ -544,7 +546,7 @@ func bastionAddFloatingIP(openStackCluster *infrav1.OpenStackCluster, clusterRes func bastionToInstanceSpec(openStackCluster *infrav1.OpenStackCluster, cluster *clusterv1.Cluster) (*compute.InstanceSpec, error) { bastion := openStackCluster.Spec.Bastion if bastion == nil { - return nil, fmt.Errorf("bastion spec is nil") + bastion = &infrav1.Bastion{} } if bastion.Spec == nil { // For the case when Bastion is deleted but we don't have spec, let's use an empty one. diff --git a/controllers/openstackcluster_controller_test.go b/controllers/openstackcluster_controller_test.go index c2a602ec31..86fb45173d 100644 --- a/controllers/openstackcluster_controller_test.go +++ b/controllers/openstackcluster_controller_test.go @@ -196,10 +196,10 @@ var _ = Describe("OpenStackCluster controller", func() { Expect(err).To(MatchError(clientCreateErr)) Expect(result).To(Equal(reconcile.Result{})) }) - It("should be able to reconcile when bastion is disabled and does not exist", func() { - testCluster.SetName("no-bastion") + It("should be able to reconcile when bastion is explicitly disabled and does not exist", func() { + testCluster.SetName("no-bastion-explicit") testCluster.Spec = infrav1.OpenStackClusterSpec{ - Bastion: &infrav1.Bastion{}, + Bastion: &infrav1.Bastion{Enabled: pointer.Bool(false)}, } err := k8sClient.Create(ctx, testCluster) Expect(err).To(BeNil()) @@ -228,7 +228,7 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("adopt-existing-bastion") testCluster.Spec = infrav1.OpenStackClusterSpec{ Bastion: &infrav1.Bastion{ - Enabled: true, + Enabled: pointer.Bool(true), Spec: &bastionSpec, }, } @@ -311,7 +311,7 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("requeue-bastion") testCluster.Spec = infrav1.OpenStackClusterSpec{ Bastion: &infrav1.Bastion{ - Enabled: true, + Enabled: pointer.Bool(true), Spec: &bastionSpec, }, } @@ -393,7 +393,7 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("requeue-bastion") testCluster.Spec = infrav1.OpenStackClusterSpec{ Bastion: &infrav1.Bastion{ - Enabled: true, + Enabled: pointer.Bool(true), Spec: &bastionSpec, }, } @@ -467,9 +467,7 @@ var _ = Describe("OpenStackCluster controller", func() { }) It("should delete an existing bastion even if its uuid is not stored in status", func() { testCluster.SetName("delete-existing-bastion") - testCluster.Spec = infrav1.OpenStackClusterSpec{ - Bastion: &infrav1.Bastion{}, - } + testCluster.Spec = infrav1.OpenStackClusterSpec{} err := k8sClient.Create(ctx, testCluster) Expect(err).To(BeNil()) err = k8sClient.Create(ctx, capiCluster) @@ -516,7 +514,7 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("subnet-filtering") testCluster.Spec = infrav1.OpenStackClusterSpec{ Bastion: &infrav1.Bastion{ - Enabled: true, + Enabled: pointer.Bool(true), Spec: &bastionSpec, }, DisableAPIServerFloatingIP: pointer.Bool(true), @@ -586,7 +584,7 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("subnet-filtering") testCluster.Spec = infrav1.OpenStackClusterSpec{ Bastion: &infrav1.Bastion{ - Enabled: true, + Enabled: pointer.Bool(true), Spec: &bastionSpec, }, DisableAPIServerFloatingIP: pointer.Bool(true), diff --git a/docs/book/src/api/v1beta1/api.md b/docs/book/src/api/v1beta1/api.md index 217373dd46..b97042eec6 100644 --- a/docs/book/src/api/v1beta1/api.md +++ b/docs/book/src/api/v1beta1/api.md @@ -1084,7 +1084,13 @@ bool
Enabled means that bastion is enabled. Defaults to false.
+(Optional) +Enabled means that bastion is enabled. The bastion is enabled by +default if this field is not specified. Set this field to false to disable the +bastion.
+It is not currently possible to remove the bastion from the cluster +spec without first disabling it by setting this field to false and +waiting until the bastion has been deleted.