diff --git a/api/v1alpha1/common_types.go b/api/v1alpha1/common_types.go index adc6837..91dbca7 100644 --- a/api/v1alpha1/common_types.go +++ b/api/v1alpha1/common_types.go @@ -4,21 +4,17 @@ import ( corev1 "k8s.io/api/core/v1" ) -type Probe struct { - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:default=1 +type ContainerProbe struct { + Enabled bool `json:"enabled,omitempty"` + // +kubebuilder:validation:Minimum=0 InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"` - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:default=10 + // +kubebuilder:validation:Minimum=0 PeriodSeconds int32 `json:"periodSeconds,omitempty"` - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 SuccessThreshold int32 `json:"successThreshold,omitempty"` - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:default=3 + // +kubebuilder:validation:Minimum=0 FailureThreshold int32 `json:"failureThreshold,omitempty"` } @@ -33,3 +29,9 @@ type VolumeMountWrapper struct { Volume []corev1.Volume `json:"volume,omitempty"` MountPath []corev1.VolumeMount `json:"mountPath,omitempty"` } + +type AdminAuth struct { + AdminUsername *string `json:"adminUsername,omitempty"` + AdminPassword *string `json:"adminPassword,omitempty"` + WalletPassword *string `json:"walletPassword,omitempty"` +} diff --git a/api/v1alpha1/marklogicgroup_types.go b/api/v1alpha1/marklogicgroup_types.go index f9ea2f6..70069bc 100644 --- a/api/v1alpha1/marklogicgroup_types.go +++ b/api/v1alpha1/marklogicgroup_types.go @@ -34,17 +34,19 @@ type MarklogicGroupSpec struct { // +kubebuilder:default:="cluster.local" ClusterDomain string `json:"clusterDomain,omitempty"` - Image string `json:"image"` + // +kubebuilder:default:="marklogicdb/marklogic-db:11.2.0-ubi" + Image string `json:"image"` + // +kubebuilder:default:="IfNotPresent" ImagePullPolicy string `json:"imagePullPolicy,omitempty"` ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + Auth *AdminAuth `json:"auth,omitempty"` Storage *Storage `json:"storage,omitempty"` Resources *corev1.ResourceRequirements `json:"resources,omitempty"` TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` // +kubebuilder:validation:Enum=OnDelete;RollingUpdate // +kubebuilder:default:="OnDelete" UpdateStrategy string `json:"updateStrategy,omitempty"` - PodManagementPolicy *string `json:"podManagementPolicy,omitempty"` NetworkPolicy *networkingv1.NetworkPolicy `json:"networkPolicy,omitempty"` PodSecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` @@ -54,11 +56,13 @@ type MarklogicGroupSpec struct { TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` PriorityClassName string `json:"priorityClassName,omitempty"` - LivenessProbe *corev1.Probe `json:"livenessProbe,omitempty"` - ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` - StartupProbe *corev1.Probe `json:"startupProbe,omitempty"` + // +kubebuilder:default:={enabled: true, initialDelaySeconds: 30, timeoutSeconds: 5, periodSeconds: 30, successThreshold: 1, failureThreshold: 3} + LivenessProbe ContainerProbe `json:"livenessProbe,omitempty"` + // +kubebuilder:default:={enabled: false, initialDelaySeconds: 10, timeoutSeconds: 5, periodSeconds: 30, successThreshold: 1, failureThreshold: 3} + ReadinessProbe ContainerProbe `json:"readinessProbe,omitempty"` - GroupConfig GroupConfig `json:"groupConfigs,omitempty"` + // +kubebuilder:default:={name: "Default", enableXdqpSsl: true} + GroupConfig GroupConfig `json:"groupConfig,omitempty"` License *License `json:"license,omitempty"` EnableConverters bool `json:"enableConverters,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1e770c5..8723601 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -27,6 +27,51 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdminAuth) DeepCopyInto(out *AdminAuth) { + *out = *in + if in.AdminUsername != nil { + in, out := &in.AdminUsername, &out.AdminUsername + *out = new(string) + **out = **in + } + if in.AdminPassword != nil { + in, out := &in.AdminPassword, &out.AdminPassword + *out = new(string) + **out = **in + } + if in.WalletPassword != nil { + in, out := &in.WalletPassword, &out.WalletPassword + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdminAuth. +func (in *AdminAuth) DeepCopy() *AdminAuth { + if in == nil { + return nil + } + out := new(AdminAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerProbe) DeepCopyInto(out *ContainerProbe) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerProbe. +func (in *ContainerProbe) DeepCopy() *ContainerProbe { + if in == nil { + return nil + } + out := new(ContainerProbe) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupConfig) DeepCopyInto(out *GroupConfig) { *out = *in @@ -236,6 +281,11 @@ func (in *MarklogicGroupSpec) DeepCopyInto(out *MarklogicGroupSpec) { *out = make([]v1.LocalObjectReference, len(*in)) copy(*out, *in) } + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(AdminAuth) + (*in).DeepCopyInto(*out) + } if in.Storage != nil { in, out := &in.Storage, &out.Storage *out = new(Storage) @@ -251,11 +301,6 @@ func (in *MarklogicGroupSpec) DeepCopyInto(out *MarklogicGroupSpec) { *out = new(int64) **out = **in } - if in.PodManagementPolicy != nil { - in, out := &in.PodManagementPolicy, &out.PodManagementPolicy - *out = new(string) - **out = **in - } if in.NetworkPolicy != nil { in, out := &in.NetworkPolicy, &out.NetworkPolicy *out = new(networkingv1.NetworkPolicy) @@ -290,21 +335,8 @@ func (in *MarklogicGroupSpec) DeepCopyInto(out *MarklogicGroupSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.LivenessProbe != nil { - in, out := &in.LivenessProbe, &out.LivenessProbe - *out = new(v1.Probe) - (*in).DeepCopyInto(*out) - } - if in.ReadinessProbe != nil { - in, out := &in.ReadinessProbe, &out.ReadinessProbe - *out = new(v1.Probe) - (*in).DeepCopyInto(*out) - } - if in.StartupProbe != nil { - in, out := &in.StartupProbe, &out.StartupProbe - *out = new(v1.Probe) - (*in).DeepCopyInto(*out) - } + out.LivenessProbe = in.LivenessProbe + out.ReadinessProbe = in.ReadinessProbe out.GroupConfig = in.GroupConfig if in.License != nil { in, out := &in.License, &out.License @@ -375,21 +407,6 @@ func (in *MarklogicGroups) DeepCopy() *MarklogicGroups { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Probe) DeepCopyInto(out *Probe) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Probe. -func (in *Probe) DeepCopy() *Probe { - if in == nil { - return nil - } - out := new(Probe) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Storage) DeepCopyInto(out *Storage) { *out = *in diff --git a/config/crd/bases/database.marklogic.com_marklogicclusters.yaml b/config/crd/bases/database.marklogic.com_marklogicclusters.yaml index 6f0f062..5f5bcac 100644 --- a/config/crd/bases/database.marklogic.com_marklogicclusters.yaml +++ b/config/crd/bases/database.marklogic.com_marklogicclusters.yaml @@ -972,6 +972,15 @@ spec: type: array type: object type: object + auth: + properties: + adminPassword: + type: string + adminUsername: + type: string + walletPassword: + type: string + type: object bootstrapHost: type: string clusterDomain: @@ -1156,7 +1165,10 @@ spec: type: boolean enableConverters: type: boolean - groupConfigs: + groupConfig: + default: + enableXdqpSsl: true + name: Default properties: enableXdqpSsl: type: boolean @@ -1164,8 +1176,10 @@ spec: type: string type: object image: + default: marklogicdb/marklogic-db:11.2.0-ubi type: string imagePullPolicy: + default: IfNotPresent type: string imagePullSecrets: items: @@ -1189,157 +1203,35 @@ spec: type: string type: object livenessProbe: - description: Probe describes a health check to be performed - against a container to determine whether it is alive or - ready to receive traffic. + default: + enabled: true + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object + enabled: + type: boolean failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. format: int32 + minimum: 0 type: integer - grpc: - description: GRPC specifies an action involving a GRPC - port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service - to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name. This will - be canonicalized upon output, so case-variant - names will be understood as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object initialDelaySeconds: - description: 'Number of seconds after the container - has started before liveness probes are initiated. - More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. format: int32 + minimum: 0 type: integer successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum - value is 1. format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving - a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and - the time when the processes are forcibly halted with - a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, - the pod's terminationGracePeriodSeconds will be used. - Otherwise, this value overrides the value provided - by the pod spec. Value must be non-negative integer. - The value zero indicates stop immediately via the - kill signal (no opportunity to shut down). This is - a beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 + minimum: 0 type: integer timeoutSeconds: - description: 'Number of seconds after which the probe - times out. Defaults to 1 second. Minimum value is - 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer type: object name: @@ -1958,162 +1850,38 @@ spec: additionalProperties: type: string type: object - podManagementPolicy: - type: string priorityClassName: type: string readinessProbe: - description: Probe describes a health check to be performed - against a container to determine whether it is alive or - ready to receive traffic. + default: + enabled: false + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object + enabled: + type: boolean failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. format: int32 + minimum: 0 type: integer - grpc: - description: GRPC specifies an action involving a GRPC - port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service - to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name. This will - be canonicalized upon output, so case-variant - names will be understood as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object initialDelaySeconds: - description: 'Number of seconds after the container - has started before liveness probes are initiated. - More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. format: int32 + minimum: 0 type: integer successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum - value is 1. format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving - a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and - the time when the processes are forcibly halted with - a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, - the pod's terminationGracePeriodSeconds will be used. - Otherwise, this value overrides the value provided - by the pod spec. Value must be non-negative integer. - The value zero indicates stop immediately via the - kill signal (no opportunity to shut down). This is - a beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 + minimum: 0 type: integer timeoutSeconds: - description: 'Number of seconds after which the probe - times out. Defaults to 1 second. Minimum value is - 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer type: object replicas: @@ -2356,160 +2124,6 @@ spec: type: string type: object type: object - startupProbe: - description: Probe describes a health check to be performed - against a container to determine whether it is alive or - ready to receive traffic. - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's - filesystem. The command is simply exec'd, it is - not run inside a shell, so traditional shell instructions - ('|', etc) won't work. To use a shell, you need - to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is - unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC - port. - properties: - port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service - to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior - is defined by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in - httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name. This will - be canonicalized upon output, so case-variant - names will be understood as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container - has started before liveness probes are initiated. - More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum - value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving - a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs - to terminate gracefully upon probe failure. The grace - period is the duration in seconds after the processes - running in the pod are sent a termination signal and - the time when the processes are forcibly halted with - a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, - the pod's terminationGracePeriodSeconds will be used. - Otherwise, this value overrides the value provided - by the pod spec. Value must be non-negative integer. - The value zero indicates stop immediately via the - kill signal (no opportunity to shut down). This is - a beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe - times out. Defaults to 1 second. Minimum value is - 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object storage: properties: size: diff --git a/config/crd/bases/database.marklogic.com_marklogicgroups.yaml b/config/crd/bases/database.marklogic.com_marklogicgroups.yaml index 2e06f93..0db5862 100644 --- a/config/crd/bases/database.marklogic.com_marklogicgroups.yaml +++ b/config/crd/bases/database.marklogic.com_marklogicgroups.yaml @@ -862,6 +862,15 @@ spec: type: array type: object type: object + auth: + properties: + adminPassword: + type: string + adminUsername: + type: string + walletPassword: + type: string + type: object bootstrapHost: type: string clusterDomain: @@ -1031,7 +1040,10 @@ spec: type: boolean enableConverters: type: boolean - groupConfigs: + groupConfig: + default: + enableXdqpSsl: true + name: Default properties: enableXdqpSsl: type: boolean @@ -1039,8 +1051,10 @@ spec: type: string type: object image: + default: marklogicdb/marklogic-db:11.2.0-ubi type: string imagePullPolicy: + default: IfNotPresent type: string imagePullSecrets: items: @@ -1062,147 +1076,35 @@ spec: type: string type: object livenessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. + default: + enabled: true + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object + enabled: + type: boolean failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. format: int32 + minimum: 0 type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number must - be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to place - in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior is defined - by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name. This will be canonicalized - upon output, so case-variant names will be understood - as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port - type: object initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. format: int32 + minimum: 0 type: integer successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs to terminate - gracefully upon probe failure. The grace period is the duration - in seconds after the processes running in the pod are sent a - termination signal and the time when the processes are forcibly - halted with a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. Value must - be non-negative integer. The value zero indicates stop immediately - via the kill signal (no opportunity to shut down). This is a - beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 + minimum: 0 type: integer timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer type: object name: @@ -1742,152 +1644,38 @@ spec: additionalProperties: type: string type: object - podManagementPolicy: - type: string priorityClassName: type: string readinessProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. + default: + enabled: false + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object + enabled: + type: boolean failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. format: int32 + minimum: 0 type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number must - be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to place - in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior is defined - by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name. This will be canonicalized - upon output, so case-variant names will be understood - as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port - type: object initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. format: int32 + minimum: 0 type: integer successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs to terminate - gracefully upon probe failure. The grace period is the duration - in seconds after the processes running in the pod are sent a - termination signal and the time when the processes are forcibly - halted with a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. Value must - be non-negative integer. The value zero indicates stop immediately - via the kill signal (no opportunity to shut down). This is a - beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 + minimum: 0 type: integer timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 + minimum: 0 type: integer type: object replicas: @@ -2111,150 +1899,6 @@ spec: type: string type: object type: object - startupProbe: - description: Probe describes a health check to be performed against - a container to determine whether it is alive or ready to receive - traffic. - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command is - simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe to be - considered failed after having succeeded. Defaults to 3. Minimum - value is 1. - format: int32 - type: integer - grpc: - description: GRPC specifies an action involving a GRPC port. - properties: - port: - description: Port number of the gRPC service. Number must - be in the range 1 to 65535. - format: int32 - type: integer - service: - description: "Service is the name of the service to place - in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - \n If this is not specified, the default behavior is defined - by gRPC." - type: string - required: - - port - type: object - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to the pod - IP. You probably want to set "Host" in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. HTTP allows - repeated headers. - items: - description: HTTPHeader describes a custom header to be - used in HTTP probes - properties: - name: - description: The header field name. This will be canonicalized - upon output, so case-variant names will be understood - as the same header. - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. Defaults - to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has started - before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. Default - to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe to be - considered successful after having failed. Defaults to 1. Must - be 1 for liveness and startup. Minimum value is 1. - format: int32 - type: integer - tcpSocket: - description: TCPSocket specifies an action involving a TCP port. - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access on the container. - Number must be in the range 1 to 65535. Name must be an - IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - description: Optional duration in seconds the pod needs to terminate - gracefully upon probe failure. The grace period is the duration - in seconds after the processes running in the pod are sent a - termination signal and the time when the processes are forcibly - halted with a kill signal. Set this value longer than the expected - cleanup time for your process. If this value is nil, the pod's - terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. Value must - be non-negative integer. The value zero indicates stop immediately - via the kill signal (no opportunity to shut down). This is a - beta field and requires enabling ProbeTerminationGracePeriod - feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds - is used if unset. - format: int64 - type: integer - timeoutSeconds: - description: 'Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object storage: properties: size: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b8..f85d4b0 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,8 @@ resources: - manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: marklogic/marklogic-kubernetes-operator + newTag: 0.4.1 diff --git a/config/samples/marklogicgroup.yaml b/config/samples/marklogicgroup.yaml index 7411cb6..3933abb 100644 --- a/config/samples/marklogicgroup.yaml +++ b/config/samples/marklogicgroup.yaml @@ -9,20 +9,37 @@ metadata: app.kubernetes.io/created-by: marklogic-kubernetes-operator name: marklogicgroup-sample spec: - replicas: 1 + replicas: 2 name: marklogic image: "marklogicdb/marklogic-db:11.1.0-centos-1.1.2" + auth: + adminUsername: user + adminPassword: pass # storage: # size: 10Gi terminationGracePeriodSeconds: 9 updateStrategy: OnDelete - license: - key: "3981-CE27-75BB-9D3C-B81C-E067-1B39-DDFE-0875-C37E-D3F0-A76C-34E5-2F86-76BB-ADDD-E677-CB3F-D5FE-4773-C3CD-5EE8-87BC-36E5-3F71-0C15" - licensee: "MarkLogic - Version 9 QA Test License" + groupConfig: + name: "node" + # resources: # requests: # memory: "6Gi" # cpu: "2" # limits: # memory: "6Gi" - # cpu: "2" \ No newline at end of file + # cpu: "2" + # livenessProbe: + # enabled: true + # initialDelaySeconds: 31 + # periodSeconds: 11 + # timeoutSeconds: 6 + # successThreshold: 1 + # failureThreshold: 4 + # readinessProbe: + # enabled: true + # initialDelaySeconds: 29 + # periodSeconds: 9 + # timeoutSeconds: 4 + # successThreshold: 1 + # failureThreshold: 2 \ No newline at end of file diff --git a/go.mod b/go.mod index f47f35b..68aaf1e 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/marklogic/marklogic-kubernetes-operator go 1.20 require ( + github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 + k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 sigs.k8s.io/controller-runtime v0.16.3 @@ -17,7 +19,6 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -61,7 +62,6 @@ require ( 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/api v0.28.3 // indirect k8s.io/apiextensions-apiserver v0.28.3 // indirect k8s.io/component-base v0.28.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect diff --git a/internal/controller/marklogicgroup_controller.go b/internal/controller/marklogicgroup_controller.go index d7ed117..7a048c5 100644 --- a/internal/controller/marklogicgroup_controller.go +++ b/internal/controller/marklogicgroup_controller.go @@ -70,7 +70,6 @@ func (r *MarklogicGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reque log.IntoContext(ctx, logger) - // logger.Info("Operator Status: ", "Conditions", &operatorCR.Status.Conditions) oc, err := k8sutil.CreateOperatorContext(ctx, &req, r.Client, r.Scheme, r.Recorder) logger.Info("==== Reconciling MarklogicGroup") diff --git a/internal/controller/marklogicgroup_controller_test.go b/internal/controller/marklogicgroup_controller_test.go index e8bde20..a6f01e0 100644 --- a/internal/controller/marklogicgroup_controller_test.go +++ b/internal/controller/marklogicgroup_controller_test.go @@ -39,15 +39,20 @@ const ( interval = time.Millisecond * 250 ) -var rep = int32(1) +var replicas = int32(2) var typeNamespaceName = types.NamespacedName{Name: Name, Namespace: Namespace} -var imageName = "marklogicdb/marklogic-db:11.1.0-centos-1.1.1" + +const imageName = "marklogicdb/marklogic-db:11.1.0-centos-1.1.1" + +var groupConfig = databasev1alpha1.GroupConfig{ + Name: "dnode", + EnableXdqpSsl: true, +} var _ = Describe("MarkLogicGroup controller", func() { Context("When creating an MarklogicGroup", func() { ctx := context.Background() It("Should create a MarklogicGroup CR, StatefulSet and Service", func() { - // Create the namespace ns := corev1.Namespace{ ObjectMeta: v1.ObjectMeta{Name: Namespace}, @@ -65,9 +70,10 @@ var _ = Describe("MarkLogicGroup controller", func() { Namespace: Namespace, }, Spec: databasev1alpha1.MarklogicGroupSpec{ - Replicas: &rep, - Name: Name, - Image: "marklogicdb/marklogic-db:11.1.0-centos-1.1.1", + Replicas: &replicas, + Name: Name, + Image: imageName, + GroupConfig: groupConfig, }, } Expect(k8sClient.Create(ctx, mlGroup)).Should(Succeed()) @@ -79,17 +85,20 @@ var _ = Describe("MarkLogicGroup controller", func() { return err == nil }, timeout, interval).Should(BeTrue()) Expect(createdCR.Spec.Image).Should(Equal(imageName)) - Expect(createdCR.Spec.Replicas).Should(Equal(&rep)) + Expect(createdCR.Spec.Replicas).Should(Equal(&replicas)) Expect(createdCR.Name).Should(Equal(Name)) + Expect(createdCR.Spec.GroupConfig).Should(Equal(groupConfig)) // Validating if StatefulSet is created successfully - createdSts := &appsv1.StatefulSet{} + sts := &appsv1.StatefulSet{} Eventually(func() bool { - err := k8sClient.Get(ctx, typeNamespaceName, createdSts) + err := k8sClient.Get(ctx, typeNamespaceName, sts) return err == nil }, timeout, interval).Should(BeTrue()) - Expect(createdSts.Spec.Template.Spec.Containers[0].Image).Should(Equal(imageName)) - Expect(createdSts.Spec.Replicas).Should(Equal(&rep)) + Expect(sts.Spec.Template.Spec.Containers[0].Image).Should(Equal(imageName)) + Expect(sts.Spec.Replicas).Should(Equal(&replicas)) + Expect(sts.Name).Should(Equal(Name)) + Expect(sts.Spec.PodManagementPolicy).Should(Equal(appsv1.ParallelPodManagement)) // Validating if Service is created successfully createdSrv := &corev1.Service{} @@ -99,5 +108,25 @@ var _ = Describe("MarkLogicGroup controller", func() { }, timeout, interval).Should(BeTrue()) Expect(createdSrv.Spec.Ports[0].TargetPort).Should(Equal(intstr.FromInt(int(7997)))) }) + + It("Should create configmap for MarkLogic scripts", func() { + // Validating if ConfigMap is created successfully + configMap := &corev1.ConfigMap{} + configMapName := Name + "-scripts" + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: Namespace}, configMap) + return err == nil + }, timeout, interval).Should(BeTrue()) + }) + + It("Should create a secret for MarkLogic Admin User", func() { + // Validating if Secret is created successfully + secret := &corev1.Secret{} + secretName := Name + "-admin" + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: Namespace}, secret) + return err == nil + }, timeout, interval).Should(BeTrue()) + }) }) }) diff --git a/pkg/k8sutil/common.go b/pkg/k8sutil/common.go index 9713cfc..c4fb4b3 100644 --- a/pkg/k8sutil/common.go +++ b/pkg/k8sutil/common.go @@ -3,7 +3,9 @@ package k8sutil import ( databasev1alpha1 "github.com/marklogic/marklogic-kubernetes-operator/api/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "math/rand" "sigs.k8s.io/controller-runtime/pkg/client" + "time" ) // generateTypeMeta generates the TyeMeta @@ -80,3 +82,15 @@ func setOperatorInternalStatus(oc *OperatorContext, newState databasev1alpha1.In return nil } + +func generateRandomAlphaNumeric(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) + result := make([]byte, length) + for i := range result { + result[i] = charset[seededRand.Intn(len(charset))] + } + return string(result) +} diff --git a/pkg/k8sutil/configmap.go b/pkg/k8sutil/configmap.go new file mode 100644 index 0000000..a5f5772 --- /dev/null +++ b/pkg/k8sutil/configmap.go @@ -0,0 +1,92 @@ +package k8sutil + +import ( + "embed" + "github.com/marklogic/marklogic-kubernetes-operator/pkg/result" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +//go:embed scripts/* +var scriptsFolder embed.FS + +func (oc *OperatorContext) ReconcileConfigMap() result.ReconcileResult { + logger := oc.ReqLogger + client := oc.Client + cr := oc.MarklogicGroup + + logger.Info("Reconciling MarkLogic ConfigMap") + labels := getMarkLogicLabels(cr.Spec.Name) + annotations := map[string]string{} + configMapName := cr.Spec.Name + "-scripts" + objectMeta := generateObjectMeta(configMapName, cr.Namespace, labels, annotations) + nsName := types.NamespacedName{Name: objectMeta.Name, Namespace: objectMeta.Namespace} + configmap := &corev1.ConfigMap{} + err := client.Get(oc.Ctx, nsName, configmap) + if err != nil { + if errors.IsNotFound(err) { + logger.Info("MarkLogic sripts ConfigMap is not found, creating a new one") + configmapDef := oc.generateConfigMapDef(objectMeta, marklogicServerAsOwner(cr)) + err = oc.createConfigMap(configmapDef) + if err != nil { + logger.Info("MarkLogic scripts configmap creation is failed") + return result.Error(err) + } + logger.Info("MarkLogic scripts configmap creation is successful") + // result.Continue() + } else { + logger.Error(err, "MarkLogic scripts configmap creation is failed") + return result.Error(err) + } + } + + return result.Continue() +} + +func (oc *OperatorContext) generateConfigMapDef(configMapMeta metav1.ObjectMeta, ownerRef metav1.OwnerReference) *corev1.ConfigMap { + + configMapData := oc.getScriptsForConfigMap() + configmap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: configMapMeta, + Data: configMapData, + } + configmap.SetOwnerReferences(append(configmap.GetOwnerReferences(), ownerRef)) + return configmap +} + +func (oc *OperatorContext) createConfigMap(configMap *corev1.ConfigMap) error { + logger := oc.ReqLogger + client := oc.Client + err := client.Create(oc.Ctx, configMap) + if err != nil { + logger.Error(err, "MarkLogic script configmap creation is failed") + return err + } + logger.Info("MarkLogic script configmap creation is successful") + return nil +} + +func (oc *OperatorContext) getScriptsForConfigMap() map[string]string { + configMapData := make(map[string]string) + logger := oc.ReqLogger + files, err := scriptsFolder.ReadDir("scripts") + if err != nil { + logger.Error(err, "Error reading scripts directory") + } + for _, file := range files { + logger.Info(file.Name()) + fileName := file.Name() + fileData, err := scriptsFolder.ReadFile("scripts/" + fileName) + if err != nil { + logger.Error(err, "Error reading file") + } + configMapData[fileName] = string(fileData) + } + return configMapData +} diff --git a/pkg/k8sutil/handler.go b/pkg/k8sutil/handler.go index 3e07101..5f42e57 100644 --- a/pkg/k8sutil/handler.go +++ b/pkg/k8sutil/handler.go @@ -11,6 +11,15 @@ func (oc *OperatorContext) ReconsileHandler() (reconcile.Result, error) { return result.Output() } setOperatorInternalStatus(oc, "Created") + + if result := oc.ReconcileSecret(); result.Completed() { + return result.Output() + } + + if result := oc.ReconcileConfigMap(); result.Completed() { + return result.Output() + } + result, err := oc.ReconcileStatefulset() return result, err diff --git a/pkg/k8sutil/scripts/copy-certs.sh b/pkg/k8sutil/scripts/copy-certs.sh new file mode 100644 index 0000000..939fee3 --- /dev/null +++ b/pkg/k8sutil/scripts/copy-certs.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +MARKLOGIC_ADMIN_USERNAME="$(< /run/secrets/ml-secrets/username)" +MARKLOGIC_ADMIN_PASSWORD="$(< /run/secrets/ml-secrets/password)" +log () { + local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N") + echo "${TIMESTAMP} $@" +} +if [[ -d "/tmp/server-cert-secrets" ]]; then + certType="named" +else + certType="self-signed" +fi +log "Info: [copy-certs] Proceeding with $certType certificate flow." +host_FQDN="$POD_NAME.$MARKLOGIC_FQDN_SUFFIX" +log "Info: [copy-certs] FQDN for this server: $host_FQDN" +foundMatchingCert="false" +if [[ "$certType" == "named" ]]; then + cp -f /tmp/ca-cert-secret/* /run/secrets/marklogic-certs/; + cert_paths=$(find /tmp/server-cert-secrets/tls_*.crt) + for cert_path in $cert_paths; do + cert_cn=$(openssl x509 -noout -subject -in $cert_path | sed -n 's/.*CN = \([^,]*\).*/\1/p') + log "Info: [copy-certs] FQDN for the certificate: $cert_cn" + if [[ "$host_FQDN" == "$cert_cn" ]]; then + log "Info: [copy-certs] found certificate for the server" + foundMatchingCert="true" + cp $cert_path /run/secrets/marklogic-certs/tls.crt + pkey_path=$(echo "$cert_path" | sed "s:.crt:.key:") + cp $pkey_path /run/secrets/marklogic-certs/tls.key + if [[ ! -e "$pkey_path" ]]; then + log "Error: [copy-certs] private key tls.key for certificate $cert_cn is not found. Exiting." + exit 1 + fi + + # verify the tls.crt and cacert.pem is valid, otherwise exit + openssl verify -CAfile /run/secrets/marklogic-certs/cacert.pem /run/secrets/marklogic-certs/tls.crt + if [[ $? -ne 0 ]]; then + log "Error: [copy-certs] Server certificate tls.crt verification with cacert.pem failed. Exiting." + exit 1 + fi + # verify the tls.crt and tls.key is matching, otherwise exit + privateKeyMD5=$(openssl rsa -modulus -noout -in /run/secrets/marklogic-certs/tls.key | openssl md5) + publicKeyMD5=$(openssl x509 -modulus -noout -in /run/secrets/marklogic-certs/tls.crt | openssl md5) + if [[ -z "privateKeyMD5" ]] || [[ "$privateKeyMD5" != "$publicKeyMD5" ]]; then + log "Error: [copy-certs] private key tls.key and server certificate tls.crt are not matching. Exiting." + exit 1 + fi + log "Info: [copy-certs] certificate and private key are valid." + break + fi + done + if [[ $foundMatchingCert == "false" ]]; then + if [[ $POD_NAME = *"-0" ]]; then + log "Error: [copy-certs] Failed to find matching certificate for the bootstrap server. Exiting." + exit 1 + else + log "Error: [copy-certs] Failed to find matching certificate for the non-bootstrap server. Continuing with temporary certificate for this host. Please update the certificate for this host later." + fi + fi +elif [[ "$certType" == "self-signed" ]]; then + if [[ $POD_NAME != *"-0" ]] || [[ $MARKLOGIC_CLUSTER_TYPE == "non-bootstrap" ]]; then + log "Info: [copy-certs] Getting CA for bootstrap host" + cd /run/secrets/marklogic-certs/ + echo quit | openssl s_client -showcerts -servername "${MARKLOGIC_BOOTSTRAP_HOST}" -showcerts -connect "${MARKLOGIC_BOOTSTRAP_HOST}":8000 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > cacert.pem + fi +else + log "Error: [copy-certs] unknown certType: $certType" + exit 1 +fi \ No newline at end of file diff --git a/pkg/k8sutil/scripts/liveness-probe.sh b/pkg/k8sutil/scripts/liveness-probe.sh new file mode 100644 index 0000000..388d426 --- /dev/null +++ b/pkg/k8sutil/scripts/liveness-probe.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +log () { + local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N") + # Check to make sure pod doesn't terminate if PID value is empty for any reason + if [ -n "$pid" ]; then + echo "${TIMESTAMP} $@" > /proc/$pid/fd/1 + fi +} + +pid=$(pgrep -fn start.marklogic) + +# Check if ML service is running. Exit with 1 if it is other than running +ml_status=$(/etc/init.d/MarkLogic status) + +if [[ "$ml_status" =~ "running" ]]; then + http_code=$(curl -o /tmp/probe_response.txt -s -w "%{http_code}" "http://${HOSTNAME}:8001/admin/v1/timestamp") + curl_code=$? + http_resp=$(cat /tmp/probe_response.txt) + + if [[ $curl_code -ne 0 && $http_code -ne 401 ]]; then + log "Info: [Liveness Probe] Error with MarkLogic" + log "Info: [Liveness Probe] Curl response code: "$curl_code + log "Info: [Liveness Probe] Http response code: "$http_code + log "Info: [Liveness Probe] Http response message: "$http_resp + fi + rm -f /tmp/probe_response.txt + exit 0 +else + exit 1 +fi \ No newline at end of file diff --git a/pkg/k8sutil/scripts/poststart-hook.sh b/pkg/k8sutil/scripts/poststart-hook.sh new file mode 100644 index 0000000..309a3d0 --- /dev/null +++ b/pkg/k8sutil/scripts/poststart-hook.sh @@ -0,0 +1,674 @@ +#!/bin/bash +# Refer to https://docs.marklogic.com/guide/admin-api/cluster#id_10889 for cluster joining process + +N_RETRY=60 +RETRY_INTERVAL=1 +HOST_FQDN="$(hostname).${MARKLOGIC_FQDN_SUFFIX}" + +# HTTP_PROTOCOL could be http or https +HTTP_PROTOCOL="http" +HTTPS_OPTION="" +if [[ "$MARKLOGIC_JOIN_TLS_ENABLED" == "true" ]]; then + HTTP_PROTOCOL="https" + HTTPS_OPTION="-k" +fi + +IS_BOOTSTRAP_HOST=false +if [[ "$(hostname)" == *-0 ]]; then + echo "IS_BOOTSTRAP_HOST true" + IS_BOOTSTRAP_HOST=true +else + echo "IS_BOOTSTRAP_HOST false" +fi + +MARKLOGIC_ADMIN_USERNAME="$(< /run/secrets/ml-secrets/username)" +MARKLOGIC_ADMIN_PASSWORD="$(< /run/secrets/ml-secrets/password)" + +pid=$(pgrep -fn start.marklogic) + +############################################################### +# Logging utility +############################################################### +info() { + log "Info" "$@" +} + +error() { + log "Error" "$1" + local EXIT_STATUS="$2" + if [[ ${EXIT_STATUS} == "exit" ]] + then + exit 1 + fi +} + +log () { + local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N") + # Check to make sure pod doesn't terminate if PID value is empty for any reason + # If PID value is empty postStart hook logs are not recorded + message="${TIMESTAMP} [postStart] $@" + if [ -n "$pid" ]; then + echo $message > /proc/$pid/fd/1 + fi + + echo $message >> /tmp/script.log +} + +################################################################ +# restart_check(hostname, baseline_timestamp) +# +# Use the timestamp service to detect a server restart, given a +# a baseline timestamp. Use N_RETRY and RETRY_INTERVAL to tune +# the test length. Include authentication in the curl command +# so the function works whether or not security is initialized. +# $1 : The hostname to test against +# $2 : The baseline timestamp +# Returns 0 if restart is detected, exits with an error if not. +################################################################ +function restart_check { + local hostname=$1 + local old_timestamp=$2 + local retry_count + local last_start + + info "${hostname} - waiting for MarkLogic to restart" + + last_start=$( \ + curl -s --anyauth \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + "http://${hostname}:8001/admin/v1/timestamp" \ + ) + for ((retry_count = 0; retry_count < N_RETRY; retry_count = retry_count + 1)); do + if [ "${old_timestamp}" == "${last_start}" ] || [ -z "${last_start}" ]; then + info "${hostname} - waiting for MarkLogic to restart: ${old_timestamp} ${last_start}" + sleep ${RETRY_INTERVAL} + last_start=$( \ + curl -s --anyauth \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + "http://${hostname}:8001/admin/v1/timestamp" \ + ) + else + info "${hostname} - MarkLogic has restarted" + return 0 + fi + done + error "${hostname} - failed to restart" exit +} +################################################################ +# retry_and_timeout(target_url, expected_response_code, additional_options, return_error) +# The third argument is optional and can be used to pass additional options to curl. +# Fourth argurment is optional, default is set to true, can be used when custom error handling is required, +# if set to true means function will return error and exit if curl fails N_RETRY times +# setting to false means function will return response code instead of failing and exiting. +# Retry a curl command until it returns the expected response +# code or fails N_RETRY times. +# Use RETRY_INTERVAL to tune the test length. +# Validate that response code is the same as expected response +# code or exit with an error. +# +# $1 : The target url to test against +# $2 : The expected response code +# $3 : Additional options to pass to curl +# $4 : Option to return error or response code in case of error +################################################################ +function curl_retry_validate { + local retry_count + local return_error="${4:-true}" + for ((retry_count = 0; retry_count < N_RETRY; retry_count = retry_count + 1)); do + request="curl -m 30 -s -w '%{http_code}' $3 $1" + response_code=$(eval "${request}") + if [[ ${response_code} -eq $2 ]]; then + return "${response_code}" + fi + sleep ${RETRY_INTERVAL} + done + if [[ "${return_error}" = "false" ]] ; then + return "${response_code}" + fi + error "Expected response code ${2}, got ${response_code} from ${1}." exit +} + +################################################################ +# Function to initialize a host +# $1: The host name +# return values: 0 - successfully initialized +# 1 - host not reachable +################################################################ +function wait_until_marklogic_ready { + local host=$1 + info "wait until $host is ready" + timestamp=$( curl -s --anyauth \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + http://${host}:8001/admin/v1/timestamp ) + if [ -z "${timestamp}" ]; then + info "${host} - not responding yet" + sleep 5s + wait_until_marklogic_ready $host + else + info "${host} - responding, calling init" + out="/tmp/${host}.out" + + response_code=$( \ + curl --anyauth -m 30 -s --retry 5 \ + -w '%{http_code}' -o "${out}" \ + -i -X POST -H "Content-type:application/json" \ + -d "${LICENSE_PAYLOAD}" \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + http://${host}:8001/admin/v1/init \ + ) + if [ "${response_code}" = "202" ]; then + info "${host} - init called, restart triggered" + last_startup=$( \ + cat "${out}" | + grep "last-startup" | + sed 's%^.*\(.*\).*$%\1%' \ + ) + + restart_check "${host}" "${last_startup}" + info "${host} - restarted" + info "${host} - init complete" + elif [ "${response_code}" -eq "204" ]; then + info "${host} - init called, no restart triggered" + info "${host} - init complete" + else + info "${host} - error calling init: ${response_code}" + fi + fi +} + +################################################################ +# Function to initialize a host +# $1: The host name +# return values: 0 - successfully initialized +# 1 - host not reachable +################################################################ +function init_marklogic_host { + local hostname=$1 + info "initializing host: $hostname" + timestamp=$( \ + curl -s --anyauth \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + http://${hostname}:8001/admin/v1/timestamp \ + ) + if [ -z "${timestamp}" ]; then + info "${hostname} - not responding yet" + return 1 + fi + info "${hostname} - responding, calling init" + output_path="/tmp/${hostname}.out" + response_code=$( \ + curl --anyauth -m 30 -s --retry 5 \ + -w '%{http_code}' -o "${output_path}" \ + -i -X POST -H "Content-type:application/json" \ + -d "${LICENSE_PAYLOAD}" \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + http://${hostname}:8001/admin/v1/init \ + ) + + if [ "${response_code}" = "202" ]; then + info "${hostname} - init called, restart triggered" + last_startup=$( \ + cat "${output_path}" | + grep "last-startup" | + sed 's%^.*\(.*\).*$%\1%' \ + ) + + restart_check "${hostname}" "${last_startup}" + return 0 + elif [ "${response_code}" -eq "204" ]; then + info "${hostname} - init called, no restart triggered" + info "${hostname} - init complete" + return 0 + else + info "${hostname} - error calling init: ${response_code}" + [ -f "${out}" ] && cat "${out}" + fi +} + +################################################################ +# Function to bootstrap host is ready: +# 1. If TLS is not enabled, wait until Security DB is installed. +# 2. If TLS is enabled, wait until TLS is turned on in App Server +# return values: 0 - admin user successfully initialized +################################################################ +function wait_bootstrap_ready { + resp=$(curl -w '%{http_code}' -o /dev/null http://$MARKLOGIC_BOOTSTRAP_HOST:8001/admin/v1/timestamp ) + if [[ "$MARKLOGIC_JOIN_TLS_ENABLED" == "true" ]]; then + # return 403 if tls is enabled + if [[ $resp -eq 403 ]]; then + info "Bootstrap host is ready with TLS enabled" + else + info "Timestamp response code:$resp. Bootstrap host is not ready with TLS enabled, try again in 10s" + sleep 10s + wait_bootstrap_ready + fi + else + if [[ $resp -eq 401 ]]; then + info "Bootstrap host is ready with no TLS" + else + info "Timestamp response code:$resp. Bootstrap host is not ready, try again in 10s" + sleep 10s + wait_bootstrap_ready + fi + fi +} + +################################################################ +# Function to initialize admin user and security DB +# +# return values: 0 - admin user successfully initialized +################################################################ +function init_security_db { + info "initializing as bootstrap cluster" + + # check to see if the bootstrap host is already configured + response_code=$( \ + curl -s --anyauth \ + -w '%{http_code}' -o "/tmp/${MARKLOGIC_BOOTSTRAP_HOST}.out" \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" $HTTPS_OPTION \ + $HTTP_PROTOCOL://$MARKLOGIC_BOOTSTRAP_HOST:8002/manage/v2/hosts/$MARKLOGIC_BOOTSTRAP_HOST/properties + ) + + if [ "${response_code}" = "200" ]; then + info "${MARKLOGIC_BOOTSTRAP_HOST} - bootstrap security already initialized" + return 0 + else + info "${MARKLOGIC_BOOTSTRAP_HOST} - initializing bootstrap security" + + # Get last restart timestamp directly before instance-admin call to verify restart after + timestamp=$( \ + curl -s --anyauth \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + "http://${MARKLOGIC_BOOTSTRAP_HOST}:8001/admin/v1/timestamp" \ + ) + + curl_retry_validate "http://${MARKLOGIC_BOOTSTRAP_HOST}:8001/admin/v1/instance-admin" 202 \ + "-o /dev/null \ + -X POST -H \"Content-type:application/x-www-form-urlencoded; charset=utf-8\" \ + -d \"admin-username=${MARKLOGIC_ADMIN_USERNAME}\" --data-urlencode \"admin-password=${MARKLOGIC_ADMIN_PASSWORD}\" \ + -d \"realm=${ML_REALM}\" -d \"${MARKLOGIC_WALLET_PASSWORD_PAYLOAD}\"" + + restart_check "${MARKLOGIC_BOOTSTRAP_HOST}" "${timestamp}" + + info "${MARKLOGIC_BOOTSTRAP_HOST} - bootstrap security initialized" + return 0 + fi +} + +################################################################ +# Function to join marklogic host to cluster +# +# return values: 0 - admin user successfully initialized +################################################################ +function join_cluster { + hostname=$1 + + # check if Bootstrap Host is ready + # if server could not be reached, response_code == 000 + # if host has not join cluster, return 404 + # if bootstrap host not init, return 403 + # if Security DB not set or credential not correct return 401 + # if host is already in cluster, return 200 + response_code=$( curl -s --anyauth -o /dev/null -w '%{http_code}' \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" $HTTPS_OPTION \ + $HTTP_PROTOCOL://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/hosts/${hostname}/properties?format=xml \ + ) + + info "response_code: $response_code" + + if [ "${response_code}" = "200" ]; then + info "host has already joined the cluster" + return 0 + elif [ "${response_code}" != "404" ]; then + sleep 10s + join_cluster $hostname + else + info "Proceed to joining bootstrap host" + fi + + # process to join the host + + # Wait until the group is ready + retry_count=10 + while [ $retry_count -gt 0 ]; do + GROUP_RESP_CODE=$( curl --anyauth -m 20 -s -o /dev/null -w "%{http_code}" $HTTPS_OPTION -X GET $HTTP_PROTOCOL://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/${MARKLOGIC_GROUP} --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD} ) + if [[ ${GROUP_RESP_CODE} -eq 200 ]]; then + info "Found the group, process to join the group" + break + else + ((retry_count--)) + info "GROUP_RESP_CODE: $GROUP_RESP_CODE , retry $retry_count times to joining ${MARKLOGIC_GROUP} group in marklogic cluster" + sleep 10s + fi + done + + if [[ $retry_count -le 0 ]]; then + info "retry_count: $retry_count" + error "pass timeout to wait for the group ready" + exit 1 + fi + + info "${hostname} - joining group ${MARKLOGIC_GROUP}" + payload=\"group=${MARKLOGIC_GROUP}\" + curl_retry_validate "http://${hostname}:8001/admin/v1/server-config" 200 \ + "--anyauth --user \"${MARKLOGIC_ADMIN_USERNAME}\":\"${MARKLOGIC_ADMIN_PASSWORD}\" \ + -o /tmp/${hostname}.xml -X GET -H \"Accept: application/xml\"" + + curl_retry_validate "$HTTP_PROTOCOL://${MARKLOGIC_BOOTSTRAP_HOST}:8001/admin/v1/cluster-config" 200 \ + "--anyauth $HTTPS_OPTION --user \"${MARKLOGIC_ADMIN_USERNAME}\":\"${MARKLOGIC_ADMIN_PASSWORD}\" \ + -X POST -d \"${payload}\" \ + --data-urlencode \"server-config@/tmp/${hostname}.xml\" \ + -H \"Content-type: application/x-www-form-urlencoded\" \ + -o /tmp/${hostname}_cluster.zip" + + timestamp=$( \ + curl -s --anyauth $HTTPS_OPTION \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + "http://${hostname}:8001/admin/v1/timestamp" \ + ) + + curl_retry_validate "http://${hostname}:8001/admin/v1/cluster-config" 202 \ + "-o /dev/null --anyauth --user \"${MARKLOGIC_ADMIN_USERNAME}\":\"${MARKLOGIC_ADMIN_PASSWORD}\" \ + -X POST -H \"Content-type: application/zip\" \ + --data-binary @/tmp/${hostname}_cluster.zip" + + # 202 causes restart + info "${hostname} - restart triggered" + # restart_check "${hostname}" "${timestamp}" + + info "${hostname} - joined group ${MARKLOGIC_GROUP}" +} + +################################################################ +# Function to configure MarkLogic Group +# +# return +################################################################ +function configure_group { + + if [[ "$IS_BOOTSTRAP_HOST" == "true" ]]; then + group_cfg_template='{"group-name":"%s", "xdqp-ssl-enabled":"%s"}' + group_cfg=$(printf "$group_cfg_template" "$MARKLOGIC_GROUP" "$XDQP_SSL_ENABLED") + + # check if host is already in and get the current cluster + response_code=$( \ + curl -s --anyauth \ + -w '%{http_code}' -o "/tmp/groups.out" \ + --user "${MARKLOGIC_ADMIN_USERNAME}":"${MARKLOGIC_ADMIN_PASSWORD}" \ + http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/hosts/${HOST_FQDN}/properties?format=xml + ) + + if [ "${response_code}" = "200" ]; then + current_group=$( \ + cat "/tmp/groups.out" | + grep "group" | + sed 's%^.*\(.*\).*$%\1%' \ + ) + + info "current_group: $current_group" + info "group_cfg: $group_cfg" + + # curl retry doesn't work in the lower version + response_code=$( \ + curl -s --anyauth \ + --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD} \ + -w '%{http_code}' \ + -X PUT \ + -H "Content-type: application/json" \ + -d "${group_cfg}" \ + http://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/${current_group}/properties \ + ) + + info "response_code: $response_code" + + if [[ "${response_code}" = "204" ]]; then + info "group \"${current_group}\" updated" + elif [[ "${response_code}" = "202" ]]; then + # Note: THIS SHOULD NOT HAPPEN WITH THE CURRENT GROUP CONFIG + info "group \"${current_group}\" updated and a restart of all hosts in the group was triggered" + else + info "unexpected response when updating group \"${current_group}\": ${response_code}" + fi + + fi + + if [[ "$MARKLOGIC_CLUSTER_TYPE" == "non-bootstrap" ]]; then + info "creating group for other Helm Chart" + + # Create a group if group is not already exits + GROUP_RESP_CODE=$( curl --anyauth -m 20 -s -o /dev/null -w "%{http_code}" $HTTPS_OPTION -X GET $HTTP_PROTOCOL://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups/${MARKLOGIC_GROUP} --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD} ) + if [[ ${GROUP_RESP_CODE} -eq 200 ]]; then + info "Skipping creation of group $MARKLOGIC_GROUP as it already exists on the MarkLogic cluster." + else + res_code=$(curl --anyauth --user ${MARKLOGIC_ADMIN_USERNAME}:${MARKLOGIC_ADMIN_PASSWORD} $HTTPS_OPTION -m 20 -s -w '%{http_code}' -X POST -d "${group_cfg}" -H "Content-type: application/json" $HTTP_PROTOCOL://${MARKLOGIC_BOOTSTRAP_HOST}:8002/manage/v2/groups) + if [[ ${res_code} -eq 201 ]]; then + log "Info: [initContainer] Successfully configured group $MARKLOGIC_GROUP on the MarkLogic cluster." + else + log "Info: [initContainer] Expected response code 201, got $res_code" + fi + fi + + fi + else + info "not bootstrap host. Skip group configuration" + fi + +} + +function configure_tls { + info "Configuring TLS for App Servers" + + AUTH_CURL="curl --anyauth --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD -m 20 -s " + + cd /tmp/ + if [[ -e "/run/secrets/marklogic-certs/tls.crt" ]]; then + info "Configuring named certificates on host" + certType="named" + else + info "Configuring self-signed certificates on host" + certType="self-signed" + fi + info "certType in postStart: $certType" + + cat <<'EOF' > defaultCertificateTemplate.json +{ + "template-name": "defaultTemplate", + "template-description": "defaultTemplate", + "key-type": "rsa", + "key-options": { + "key-length": "2048" + }, + "req": { + "version": "0", + "subject": { + "organizationName": "MarkLogic" + } + } +} +EOF + +if [[ $POD_NAME == *-0 ]] && [[ $MARKLOGIC_CLUSTER_TYPE == "bootstrap" ]]; then + log "Info: creating default certificate Template" + response=$($AUTH_CURL -X POST --header "Content-Type:application/json" -d @defaultCertificateTemplate.json http://localhost:8002/manage/v2/certificate-templates) + sleep 5s + log "Info: done creating default certificate Template" + fi + + log "Info: creating insert-host-certificates.json" + cat <<'EOF' > insert-host-certificates.json + { + "operation": "insert-host-certificates", + "certificates": [ + { + "certificate": { + "cert": "CERT", + "pkey": "PKEY" + } + } + ] + } +EOF + + log "Info: creating generateCA.xqy" + cat <<'EOF' > generateCA.xqy +xquery= + xquery version "1.0-ml"; + import module namespace pki = "http://marklogic.com/xdmp/pki" + at "/MarkLogic/pki.xqy"; + let $tid := pki:template-get-id(pki:get-template-by-name("defaultTemplate")) + return + pki:generate-template-certificate-authority($tid, 365) +EOF + + log "Info: creating createTempCert.xqy" + cat <<'EOF' > createTempCert.xqy +xquery= + xquery version "1.0-ml"; + import module namespace pki = "http://marklogic.com/xdmp/pki" + at "/MarkLogic/pki.xqy"; + import module namespace admin = "http://marklogic.com/xdmp/admin" + at "/MarkLogic/admin.xqy"; + let $tid := pki:template-get-id(pki:get-template-by-name("defaultTemplate")) + let $config := admin:get-configuration() + let $hostname := admin:host-get-name($config, admin:host-get-id($config, xdmp:host-name())) + return + pki:generate-temporary-certificate-if-necessary($tid, 365, $hostname, (), ()) +EOF + + log "Info: inserting certificates $certType" + if [[ "$certType" == "named" ]]; then + log "Info: creating named certificate" + cert_path="/run/secrets/marklogic-certs/tls.crt" + pkey_path="/run/secrets/marklogic-certs/tls.key" + cp insert-host-certificates.json insert_cert_payload.json + cert="$(<$cert_path)" + cert="${cert//$'\n'/}" + pkey="$(<$pkey_path)" + pkey="${pkey//$'\n'/}" + + sed -i "s|CERT|$cert|" insert_cert_payload.json + sed -i "s|CERTIFICATE-----|CERTIFICATE-----\\\\n|" insert_cert_payload.json + sed -i "s|-----END CERTIFICATE|\\\\n-----END CERTIFICATE|" insert_cert_payload.json + sed -i "s|PKEY|$pkey|" insert_cert_payload.json + sed -i "s|PRIVATE KEY-----|PRIVATE KEY-----\\\\n|" insert_cert_payload.json + sed -i "s|-----END RSA|\\\\n-----END RSA|" insert_cert_payload.json + sed -i "s|-----END PRIVATE|\\\\n-----END PRIVATE|" insert_cert_payload.json + + log "Info: inserting following certificates for $cert_path for $MARKLOGIC_CLUSTER_TYPE" + + if [[ $POD_NAME == *-0 ]]; then + res=$($AUTH_CURL -X POST --header "Content-Type:application/json" -d @insert_cert_payload.json http://localhost:8002/manage/v2/certificate-templates/defaultTemplate 2>&1) + else + res=$($AUTH_CURL -k -X POST --header "Content-Type:application/json" -d @insert_cert_payload.json https://localhost:8002/manage/v2/certificate-templates/defaultTemplate 2>&1) + fi + log "Info: $res" + sleep 5s + fi + + if [[ $POD_NAME == *-0 ]]; then + if [[ $MARKLOGIC_CLUSTER_TYPE == "bootstrap" ]]; then + log "Info: Generating Temporary CA Certificate" + $AUTH_CURL -X POST -i -d @generateCA.xqy \ + -H "Content-type: application/x-www-form-urlencoded" \ + -H "Accept: multipart/mixed; boundary=BOUNDARY" \ + http://localhost:8000/v1/eval + resp_code=$? + info "response code for Generating Temporary CA Certificate is $resp_code" + sleep 5s + fi + + log "Info: enabling app-servers for HTTPS" + # Manage need be put in the last in the array to make sure http works for all the requests + appServers=("App-Services" "Admin" "Manage") + for appServer in ${appServers[@]}; do + log "configuring SSL for App Server $appServer" + curl --anyauth --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD \ + -X PUT -H "Content-type: application/json" -d '{"ssl-certificate-template":"defaultTemplate"}' \ + http://localhost:8002/manage/v2/servers/${appServer}/properties?group-id=${MARKLOGIC_GROUP} + sleep 5s + done + log "Info: Configure HTTPS in App Server finished" + + if [[ "$certType" == "self-signed" ]]; then + log "Info: Generate temporary certificate if necessary" + $AUTH_CURL -k -X POST -i -d @createTempCert.xqy -H "Content-type: application/x-www-form-urlencoded" \ + -H "Accept: multipart/mixed; boundary=BOUNDARY" https://localhost:8000/v1/eval + resp_code=$? + info "response code for Generate temporary certificate is $resp_code" + fi + fi + + log "Info: removing cert keys" + rm -f /run/secrets/marklogic-certs/*.key +} + +############################################################### +# Env Setup of MarkLogic +############################################################### +# Make sure username and password variables are not empty +if [[ -z "${MARKLOGIC_ADMIN_USERNAME}" ]] || [[ -z "${MARKLOGIC_ADMIN_PASSWORD}" ]]; then + error "MARKLOGIC_ADMIN_USERNAME and MARKLOGIC_ADMIN_PASSWORD must be set." exit +fi + +# generate JSON payload conditionally with license details. +if [[ -z "${LICENSE_KEY}" ]] || [[ -z "${LICENSEE}" ]]; then + LICENSE_PAYLOAD="{}" +else + info "LICENSE_KEY and LICENSEE are defined, installing MarkLogic license." + LICENSE_PAYLOAD="{\"license-key\" : \"${LICENSE_KEY}\",\"licensee\" : \"${LICENSEE}\"}" +fi + +# sets realm conditionally based on user input +if [[ -z "${REALM}" ]]; then + ML_REALM="public" +else + info "REALM is defined, setting realm." + ML_REALM="${REALM}" +fi + +if [[ -z "${MARKLOGIC_WALLET_PASSWORD}" ]]; then + MARKLOGIC_WALLET_PASSWORD_PAYLOAD="" +else + MARKLOGIC_WALLET_PASSWORD_PAYLOAD="wallet-password=${MARKLOGIC_WALLET_PASSWORD}" +fi + +############################################################### +info "Start configuring MarkLogic for $HOST_FQDN" +info "Bootstrap host: $MARKLOGIC_BOOTSTRAP_HOST" + +# Wait for current pod ready +wait_until_marklogic_ready $HOST_FQDN + +# Only do this if the bootstrap host is in the statefulset we are configuring +if [[ "${MARKLOGIC_CLUSTER_TYPE}" = "bootstrap" && "${HOST_FQDN}" = "${MARKLOGIC_BOOTSTRAP_HOST}" ]]; then + sleep 2s + init_security_db + configure_group +else + wait_bootstrap_ready + configure_group + join_cluster $HOST_FQDN +fi + +sleep 5s + +# Authentication configuration when path based is used +if [[ $POD_NAME == *-0 ]] && [[ $PATH_BASED_ROUTING == "true" ]]; then + log "Info: path based routing is set. Adapting authentication method" + resp=$(curl --anyauth -w "%{http_code}" --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD -m 20 -s -X PUT -H "Content-type: application/json" -d '{"authentication":"basic"}' http://localhost:8002/manage/v2/servers/Admin/properties?group-id=${MARKLOGIC_GROUP}) + log "Info: Admin-Servers response code: $resp" + resp=$(curl --anyauth -w "%{http_code}" --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD -m 20 -s -X PUT -H "Content-type: application/json" -d '{"authentication":"basic"}' http://localhost:8002/manage/v2/servers/App-Services/properties?group-id=${MARKLOGIC_GROUP}) + log "Info: App Service response code: $resp" + resp=$(curl --anyauth -w "%{http_code}" --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD -m 20 -s -X PUT -H "Content-type: application/json" -d '{"authentication":"basic"}' http://localhost:8002/manage/v2/servers/Manage/properties?group-id=${MARKLOGIC_GROUP}) + log "Info: Manage response code: $resp" + log "Info: Default App-Servers authentication set to basic auth" +else + log "Info: This is not the boostrap host or path based routing is not set. Skipping authentication configuration" +fi +#End of authentication configuration + +if [[ $MARKLOGIC_JOIN_TLS_ENABLED == "true" ]]; then + configure_tls +fi + +info "helm script completed" \ No newline at end of file diff --git a/pkg/k8sutil/scripts/prestop-hook.sh b/pkg/k8sutil/scripts/prestop-hook.sh new file mode 100644 index 0000000..67220fd --- /dev/null +++ b/pkg/k8sutil/scripts/prestop-hook.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +MARKLOGIC_ADMIN_USERNAME="$(< /run/secrets/ml-secrets/username)" +MARKLOGIC_ADMIN_PASSWORD="$(< /run/secrets/ml-secrets/password)" + +log () { + local TIMESTAMP=$(date +"%Y-%m-%d %T.%3N") + # Check to make sure pod doesn't terminate if PID value is empty for any reason + # If PID value is empty preStart hook logs are not recorded + if [ -n "$pid" ]; then + echo "${TIMESTAMP} $@" > /proc/$pid/fd/1 + fi +} + +pid=$(pgrep -fn start.marklogic) +log "Info: [prestop] Prestop Hook Execution" + +my_host=$(hostname -f) + +HTTP_PROTOCOL="http" +HTTPS_OPTION="" +if [[ "$MARKLOGIC_JOIN_TLS_ENABLED" == "true" ]]; then + HTTP_PROTOCOL="https" + HTTPS_OPTION="-k" +fi +log "Info: [prestop] MarkLogic Pod Hostname: "$my_host +for ((i = 0; i < 5; i = i + 1)); do + res_code=$(curl --anyauth --user $MARKLOGIC_ADMIN_USERNAME:$MARKLOGIC_ADMIN_PASSWORD \ + -o /dev/null -m 10 -s -w %{http_code} \ + -i -X POST ${HTTPS_OPTION} --data "state=shutdown&failover=true" \ + -H "Content-type: application/x-www-form-urlencoded" \ + ${HTTP_PROTOCOL}://localhost:8002/manage/v2/hosts/$my_host?format=json) + + if [[ ${res_code} -eq 202 ]]; then + log "Info: [prestop] Host shut down response code: "$res_code + + while (true) + do + ml_status=$(service MarkLogic status) + log "Info: [prestop] MarkLogic Status: "$ml_status + if [[ "$ml_status" =~ "running" ]]; then + sleep 5s + continue + else + break + fi + done + break + else + log "ERROR: [prestop] Retry Attempt: "$i + log "ERROR: [prestop] Host shut down expected response code 202, got "$res_code + sleep 10s + fi +done \ No newline at end of file diff --git a/pkg/k8sutil/secret.go b/pkg/k8sutil/secret.go new file mode 100644 index 0000000..f323b7d --- /dev/null +++ b/pkg/k8sutil/secret.go @@ -0,0 +1,94 @@ +package k8sutil + +import ( + "github.com/marklogic/marklogic-kubernetes-operator/pkg/result" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func (oc *OperatorContext) ReconcileSecret() result.ReconcileResult { + logger := oc.ReqLogger + client := oc.Client + cr := oc.MarklogicGroup + + logger.Info("Reconciling MarkLogic Secret") + labels := getMarkLogicLabels(cr.Spec.Name) + annotations := map[string]string{} + secretName := cr.Spec.Name + "-admin" + objectMeta := generateObjectMeta(secretName, cr.Namespace, labels, annotations) + nsName := types.NamespacedName{Name: objectMeta.Name, Namespace: objectMeta.Namespace} + secret := &corev1.Secret{} + err := client.Get(oc.Ctx, nsName, secret) + if err != nil { + if errors.IsNotFound(err) { + logger.Info("MarkLogic admin Secret is not found, creating a new one") + secretData := oc.generateSecretData() + secretDef := generateSecretDef(objectMeta, marklogicServerAsOwner(cr), secretData) + err = oc.createSecret(secretDef) + if err != nil { + logger.Info("MarkLogic admin Secret creation is failed") + return result.Error(err) + } + logger.Info("MarkLogic admin Secret creation is successful") + // result.Continue() + } else { + logger.Error(err, "MarkLogic admin Secret creation is failed") + return result.Error(err) + } + } + + return result.Continue() +} + +func (oc *OperatorContext) generateSecretData() map[string][]byte { + // logger := oc.ReqLogger + spec := oc.MarklogicGroup.Spec + secretData := map[string][]byte{} + if spec.Auth != nil && spec.Auth.AdminUsername != nil { + secretData["username"] = []byte(*spec.Auth.AdminUsername) + } else { + secretData["username"] = []byte(generateRandomAlphaNumeric(5)) + } + + if spec.Auth != nil && spec.Auth.AdminPassword != nil { + secretData["password"] = []byte(*spec.Auth.AdminPassword) + } else { + secretData["password"] = []byte(generateRandomAlphaNumeric(10)) + } + + if spec.Auth != nil && spec.Auth.WalletPassword != nil { + secretData["wallet-password"] = []byte(*spec.Auth.WalletPassword) + } else { + secretData["wallet-password"] = []byte(generateRandomAlphaNumeric(10)) + } + + return secretData +} + +func generateSecretDef(secretMeta metav1.ObjectMeta, ownerRef metav1.OwnerReference, secretData map[string][]byte) *corev1.Secret { + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: secretMeta, + Type: corev1.SecretTypeOpaque, + Data: secretData, + } + secret.SetOwnerReferences(append(secret.GetOwnerReferences(), ownerRef)) + return secret +} + +func (oc *OperatorContext) createSecret(secret *corev1.Secret) error { + logger := oc.ReqLogger + client := oc.Client + err := client.Create(oc.Ctx, secret) + if err != nil { + logger.Error(err, "MarkLogic admin secret creation is failed") + return err + } + logger.Info("MarkLogic script admin secret is successful") + return nil +} diff --git a/pkg/k8sutil/statefulset.go b/pkg/k8sutil/statefulset.go index ab5d4c2..a1df154 100644 --- a/pkg/k8sutil/statefulset.go +++ b/pkg/k8sutil/statefulset.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "strconv" ) type statefulSetParameters struct { @@ -37,6 +38,9 @@ type containerParameters struct { LicenseKey string Licensee string BootstrapHost string + LivenessProbe databasev1alpha1.ContainerProbe + ReadinessProbe databasev1alpha1.ContainerProbe + GroupConfig databasev1alpha1.GroupConfig } func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) { @@ -46,10 +50,11 @@ func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) { annotations := map[string]string{} objectMeta := generateObjectMeta(cr.Spec.Name, cr.Namespace, labels, annotations) sts, err := oc.GetStatefulSet(cr.Namespace, objectMeta.Name) - + containerParams := generateContainerParams(cr) + statefulSetParams := generateStatefulSetsParams(cr) if err != nil { if apierrors.IsNotFound(err) { - statefulSetDef := generateStatefulSetsDef(objectMeta, generateStatefulSetsParams(cr), marklogicServerAsOwner(cr), generateContainerParams(cr)) + statefulSetDef := generateStatefulSetsDef(objectMeta, statefulSetParams, marklogicServerAsOwner(cr), containerParams) oc.createStatefulSet(cr.Namespace, statefulSetDef, cr) oc.Recorder.Event(oc.MarklogicGroup, "Normal", "StatefulSetCreated", "MarkLogic statefulSet created successfully") return result.RequeueSoon(10).Output() @@ -148,9 +153,10 @@ func generateStatefulSetsDef(stsMeta metav1.ObjectMeta, params statefulSetParame TypeMeta: generateTypeMeta("StatefulSet", "apps/v1"), ObjectMeta: stsMeta, Spec: appsv1.StatefulSetSpec{ - Selector: LabelSelectors(stsMeta.GetLabels()), - ServiceName: stsMeta.Name, - Replicas: params.Replicas, + Selector: LabelSelectors(stsMeta.GetLabels()), + ServiceName: stsMeta.Name, + Replicas: params.Replicas, + PodManagementPolicy: appsv1.ParallelPodManagement, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: stsMeta.GetLabels(), @@ -158,6 +164,7 @@ func generateStatefulSetsDef(stsMeta metav1.ObjectMeta, params statefulSetParame Spec: corev1.PodSpec{ Containers: generateContainerDef(stsMeta.GetName(), containerParams), TerminationGracePeriodSeconds: params.TerminationGracePeriodSeconds, + Volumes: generateVolumes(stsMeta.Name), }, }, }, @@ -195,15 +202,20 @@ func generateContainerDef(name string, containerParams containerParameters) []co Image: containerParams.Image, ImagePullPolicy: containerParams.ImagePullPolicy, Env: getEnvironmentVariables(containerParams), - ReadinessProbe: getReadinessProbe(), - LivenessProbe: getLivenessProbe(), - StartupProbe: getStartupProbe(), + Lifecycle: getLifeCycle(), VolumeMounts: getVolumeMount(), }, } if containerParams.Resources != nil { containerDef[0].Resources = *containerParams.Resources } + if containerParams.LivenessProbe.Enabled == true { + containerDef[0].LivenessProbe = getLivenessProbe(containerParams.LivenessProbe) + } + + if containerParams.ReadinessProbe.Enabled == true { + containerDef[0].ReadinessProbe = getReadinessProbe(containerParams.ReadinessProbe) + } return containerDef } @@ -223,12 +235,15 @@ func generateStatefulSetsParams(cr *databasev1alpha1.MarklogicGroup) statefulSet func generateContainerParams(cr *databasev1alpha1.MarklogicGroup) containerParameters { trueProperty := true containerParams := containerParameters{ - Image: cr.Spec.Image, - Resources: cr.Spec.Resources, - Name: cr.Spec.Name, - Namespace: cr.Namespace, - ClusterDomain: cr.Spec.ClusterDomain, - BootstrapHost: cr.Spec.BootstrapHost, + Image: cr.Spec.Image, + Resources: cr.Spec.Resources, + Name: cr.Spec.Name, + Namespace: cr.Namespace, + ClusterDomain: cr.Spec.ClusterDomain, + BootstrapHost: cr.Spec.BootstrapHost, + LivenessProbe: cr.Spec.LivenessProbe, + ReadinessProbe: cr.Spec.ReadinessProbe, + GroupConfig: cr.Spec.GroupConfig, } if cr.Spec.Storage != nil { @@ -243,15 +258,54 @@ func generateContainerParams(cr *databasev1alpha1.MarklogicGroup) containerParam containerParams.LicenseKey = cr.Spec.License.Key containerParams.Licensee = cr.Spec.License.Licensee } + return containerParams } +func getLifeCycle() *corev1.Lifecycle { + return &corev1.Lifecycle{ + PostStart: &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/bash", "/tmp/helm-scripts/poststart-hook.sh"}, + }, + }, + PreStop: &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/bash", "/tmp/helm-scripts/prestop-hook.sh"}, + }, + }, + } +} + +func generateVolumes(stsName string) []corev1.Volume { + volumes := []corev1.Volume{} + volumes = append(volumes, corev1.Volume{ + Name: "helm-scripts", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: fmt.Sprintf("%s-scripts", stsName), + }, + DefaultMode: func(i int32) *int32 { return &i }(0755), + }, + }, + }, corev1.Volume{ + Name: "mladmin-secrets", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: fmt.Sprintf("%s-admin", stsName), + }, + }, + }) + return volumes +} + func generatePVCTemplate(storageSize string) corev1.PersistentVolumeClaim { pvcTemplate := corev1.PersistentVolumeClaim{} pvcTemplate.CreationTimestamp = metav1.Time{} pvcTemplate.Name = "data" pvcTemplate.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce} - // pvcTemplate.Spec.Resources.Requests.Storage().Add(resource.MustParse(storageSize)) + pvcTemplate.Spec.Resources.Requests.Storage().Add(resource.MustParse(storageSize)) pvcTemplate.Spec.Resources.Requests = corev1.ResourceList{ corev1.ResourceStorage: resource.MustParse(storageSize), } @@ -261,23 +315,29 @@ func generatePVCTemplate(storageSize string) corev1.PersistentVolumeClaim { func getEnvironmentVariables(containerParams containerParameters) []corev1.EnvVar { envVars := []corev1.EnvVar{} envVars = append(envVars, corev1.EnvVar{ - Name: "MARKLOGIC_ADMIN_USERNAME", - Value: "admin", + Name: "MARKLOGIC_ADMIN_USERNAME_FILE", + Value: "ml-secrets/username", }, corev1.EnvVar{ - Name: "MARKLOGIC_ADMIN_PASSWORD", - Value: "admin", + Name: "MARKLOGIC_ADMIN_PASSWORD_FILE", + Value: "ml-secrets/password", }, corev1.EnvVar{ Name: "MARKLOGIC_FQDN_SUFFIX", Value: fmt.Sprintf("%s.%s.svc.%s", containerParams.Name, containerParams.Namespace, containerParams.ClusterDomain), }, corev1.EnvVar{ Name: "MARKLOGIC_INIT", - Value: "true", + Value: "false", }, corev1.EnvVar{ Name: "MARKLOGIC_JOIN_CLUSTER", - Value: "true", + Value: "false", }, corev1.EnvVar{ Name: "MARKLOGIC_GROUP", - Value: "Default", + Value: containerParams.GroupConfig.Name, + }, corev1.EnvVar{ + Name: "XDQP_SSL_ENABLED", + Value: strconv.FormatBool(containerParams.GroupConfig.EnableXdqpSsl), + }, corev1.EnvVar{ + Name: "MARKLOGIC_CLUSTER_TYPE", + Value: "bootstrap", }, ) if containerParams.LicenseKey != "" { @@ -309,40 +369,46 @@ func getVolumeMount() []corev1.VolumeMount { var VolumeMounts []corev1.VolumeMount // if persistenceEnabled != nil && *persistenceEnabled { - VolumeMounts = append(VolumeMounts, corev1.VolumeMount{ - Name: "data", - MountPath: "/var/opt/MarkLogic", - }) - + VolumeMounts = append(VolumeMounts, + corev1.VolumeMount{ + Name: "data", + MountPath: "/var/opt/MarkLogic", + }, + corev1.VolumeMount{ + Name: "helm-scripts", + MountPath: "/tmp/helm-scripts", + }, + corev1.VolumeMount{ + Name: "mladmin-secrets", + MountPath: "/run/secrets/ml-secrets", + ReadOnly: true, + }, + ) return VolumeMounts } -func getLivenessProbe() *corev1.Probe { +func getLivenessProbe(probe databasev1alpha1.ContainerProbe) *corev1.Probe { return &corev1.Probe{ - InitialDelaySeconds: 30, - PeriodSeconds: 60, - FailureThreshold: 3, - TimeoutSeconds: 5, - SuccessThreshold: 1, + InitialDelaySeconds: probe.InitialDelaySeconds, + PeriodSeconds: probe.PeriodSeconds, + FailureThreshold: probe.FailureThreshold, + TimeoutSeconds: probe.TimeoutSeconds, + SuccessThreshold: probe.SuccessThreshold, ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/", - Port: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 7997, - }, + Exec: &corev1.ExecAction{ + Command: []string{"/bin/bash", "/tmp/helm-scripts/liveness-probe.sh"}, }, }, } } -func getReadinessProbe() *corev1.Probe { +func getReadinessProbe(probe databasev1alpha1.ContainerProbe) *corev1.Probe { return &corev1.Probe{ - InitialDelaySeconds: 10, - PeriodSeconds: 60, - FailureThreshold: 3, - TimeoutSeconds: 5, - SuccessThreshold: 1, + InitialDelaySeconds: probe.InitialDelaySeconds, + PeriodSeconds: probe.PeriodSeconds, + FailureThreshold: probe.FailureThreshold, + TimeoutSeconds: probe.TimeoutSeconds, + SuccessThreshold: probe.SuccessThreshold, ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Path: "/", @@ -354,18 +420,3 @@ func getReadinessProbe() *corev1.Probe { }, } } - -func getStartupProbe() *corev1.Probe { - return &corev1.Probe{ - InitialDelaySeconds: 10, - PeriodSeconds: 20, - TimeoutSeconds: 3, - SuccessThreshold: 1, - FailureThreshold: 30, - ProbeHandler: corev1.ProbeHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"ls", "/var/opt/MarkLogic/ready"}, - }, - }, - } -}