diff --git a/cloud/services/container/clusters/reconcile.go b/cloud/services/container/clusters/reconcile.go index 22a14dd8d..69087a9b2 100644 --- a/cloud/services/container/clusters/reconcile.go +++ b/cloud/services/container/clusters/reconcile.go @@ -300,6 +300,12 @@ func (s *Service) createCluster(ctx context.Context, log *logr.Logger) error { } if !s.scope.IsAutopilotCluster() { cluster.NodePools = scope.ConvertToSdkNodePools(nodePools, machinePools, isRegional, cluster.GetName()) + if s.scope.GCPManagedControlPlane.Spec.LoggingService != nil { + cluster.LoggingService = s.scope.GCPManagedControlPlane.Spec.LoggingService.String() + } + if s.scope.GCPManagedControlPlane.Spec.MonitoringService != nil { + cluster.MonitoringService = s.scope.GCPManagedControlPlane.Spec.MonitoringService.String() + } } createClusterRequest := &containerpb.CreateClusterRequest{ @@ -434,6 +440,20 @@ func (s *Service) checkDiffAndPrepareUpdate(existingCluster *containerpb.Cluster } } + // LoggingService + if existingCluster.GetLoggingService() != s.scope.GCPManagedControlPlane.Spec.LoggingService.String() { + needUpdate = true + clusterUpdate.DesiredLoggingService = s.scope.GCPManagedControlPlane.Spec.LoggingService.String() + log.V(2).Info("LoggingService config update required", "current", existingCluster.GetLoggingService(), "desired", s.scope.GCPManagedControlPlane.Spec.LoggingService.String()) + } + + // MonitoringService + if existingCluster.GetMonitoringService() != s.scope.GCPManagedControlPlane.Spec.MonitoringService.String() { + needUpdate = true + clusterUpdate.DesiredLoggingService = s.scope.GCPManagedControlPlane.Spec.MonitoringService.String() + log.V(2).Info("MonitoringService config update required", "current", existingCluster.GetMonitoringService(), "desired", s.scope.GCPManagedControlPlane.Spec.MonitoringService.String()) + } + // DesiredMasterAuthorizedNetworksConfig // When desiredMasterAuthorizedNetworksConfig is nil, it means that the user wants to disable the feature. desiredMasterAuthorizedNetworksConfig := convertToSdkMasterAuthorizedNetworksConfig(s.scope.GCPManagedControlPlane.Spec.MasterAuthorizedNetworksConfig) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml index 2c00db076..7ffbc16ee 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml @@ -165,6 +165,12 @@ spec: Location represents the location (region or zone) in which the GKE cluster will be created. type: string + loggingService: + description: |- + LoggingService represents configuration of logging service feature of the GKE cluster. + Possible values: none, logging.googleapis.com/kubernetes (default). + Value is ignored when enableAutopilot = true. + type: string master_authorized_networks_config: description: |- MasterAuthorizedNetworksConfig represents configuration options for master authorized networks feature of the GKE cluster. @@ -193,6 +199,12 @@ spec: Public IP addresses. type: boolean type: object + monitoringService: + description: |- + MonitoringService represents configuration of monitoring service feature of the GKE cluster. + Possible values: none, monitoring.googleapis.com/kubernetes (default). + Value is ignored when enableAutopilot = true. + type: string project: description: Project is the name of the project to deploy the cluster to. diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go index 0d2e68cfe..d4c65b4c5 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go @@ -17,7 +17,11 @@ limitations under the License. package v1beta1 import ( + "fmt" + "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/strings/slices" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -151,6 +155,16 @@ type GCPManagedControlPlaneSpec struct { // This feature is disabled if this field is not specified. // +optional MasterAuthorizedNetworksConfig *MasterAuthorizedNetworksConfig `json:"master_authorized_networks_config,omitempty"` + // LoggingService represents configuration of logging service feature of the GKE cluster. + // Possible values: none, logging.googleapis.com/kubernetes (default). + // Value is ignored when enableAutopilot = true. + // +optional + LoggingService *LoggingService `json:"loggingService,omitempty"` + // MonitoringService represents configuration of monitoring service feature of the GKE cluster. + // Possible values: none, monitoring.googleapis.com/kubernetes (default). + // Value is ignored when enableAutopilot = true. + // +optional + MonitoringService *MonitoringService `json:"monitoringService,omitempty"` } // GCPManagedControlPlaneStatus defines the observed state of GCPManagedControlPlane. @@ -236,6 +250,42 @@ type MasterAuthorizedNetworksConfigCidrBlock struct { CidrBlock string `json:"cidr_block,omitempty"` } +// LoggingService is GKE logging service configuration. +type LoggingService string + +// Validate validates LoggingService value. +func (l LoggingService) Validate() error { + validValues := []string{"none", "logging.googleapis.com/kubernetes"} + if !slices.Contains(validValues, l.String()) { + return fmt.Errorf("invalid value; expect one of : %s", strings.Join(validValues, ",")) + } + + return nil +} + +// String returns a string from LoggingService. +func (l LoggingService) String() string { + return string(l) +} + +// MonitoringService is GKE logging service configuration. +type MonitoringService string + +// Validate validates MonitoringService value. +func (m MonitoringService) Validate() error { + validValues := []string{"none", "monitoring.googleapis.com/kubernetes"} + if !slices.Contains(validValues, m.String()) { + return fmt.Errorf("invalid value; expect one of : %s", strings.Join(validValues, ",")) + } + + return nil +} + +// String returns a string from MonitoringService. +func (m MonitoringService) String() string { + return string(m) +} + // GetConditions returns the control planes conditions. func (r *GCPManagedControlPlane) GetConditions() clusterv1.Conditions { return r.Status.Conditions diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go index 402ebc3bd..7835b4143 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/google/go-cmp/cmp" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/validation/field" @@ -89,6 +88,16 @@ func (r *GCPManagedControlPlane) ValidateCreate() (admission.Warnings, error) { allErrs = append(allErrs, field.Required(field.NewPath("spec", "ReleaseChannel"), "Release channel is required for an autopilot enabled cluster")) } + if r.Spec.EnableAutopilot && r.Spec.LoggingService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if r.Spec.EnableAutopilot && r.Spec.MonitoringService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + if len(allErrs) == 0 { return nil, nil } @@ -130,6 +139,32 @@ func (r *GCPManagedControlPlane) ValidateUpdate(oldRaw runtime.Object) (admissio ) } + if old.Spec.EnableAutopilot && r.Spec.LoggingService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if old.Spec.EnableAutopilot && r.Spec.MonitoringService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if r.Spec.LoggingService != nil { + err := r.Spec.LoggingService.Validate() + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.LoggingService, err.Error())) + } + } + + if r.Spec.MonitoringService != nil { + err := r.Spec.MonitoringService.Validate() + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.MonitoringService, err.Error())) + } + } + if len(allErrs) == 0 { return nil, nil } diff --git a/exp/api/v1beta1/zz_generated.deepcopy.go b/exp/api/v1beta1/zz_generated.deepcopy.go index f70cde996..521cd4510 100644 --- a/exp/api/v1beta1/zz_generated.deepcopy.go +++ b/exp/api/v1beta1/zz_generated.deepcopy.go @@ -308,6 +308,16 @@ func (in *GCPManagedControlPlaneSpec) DeepCopyInto(out *GCPManagedControlPlaneSp *out = new(MasterAuthorizedNetworksConfig) (*in).DeepCopyInto(*out) } + if in.LoggingService != nil { + in, out := &in.LoggingService, &out.LoggingService + *out = new(LoggingService) + **out = **in + } + if in.MonitoringService != nil { + in, out := &in.MonitoringService, &out.MonitoringService + *out = new(MonitoringService) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneSpec.