mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-21 06:20:05 -06:00
Create default ServiceMonitor with Operator
Closes #40406 Signed-off-by: Ryan Emerson <remerson@ibm.com>
This commit is contained in:
67
.github/workflows/operator-ci.yml
vendored
67
.github/workflows/operator-ci.yml
vendored
@@ -151,6 +151,11 @@ jobs:
|
||||
source: github
|
||||
opm: 1.21.0
|
||||
|
||||
- name: Install OC
|
||||
uses: redhat-actions/openshift-tools-installer@144527c7d98999f2652264c048c7a9bd103f8a82 # v1.13.1
|
||||
with:
|
||||
oc: 4
|
||||
|
||||
- name: Install Yq
|
||||
run: sudo snap install yq
|
||||
|
||||
@@ -173,25 +178,15 @@ jobs:
|
||||
REGISTRY=$(minikube ip):5000 ./scripts/olm-testing.sh ${GITHUB_SHA::6}
|
||||
|
||||
- name: Deploy an example Keycloak and wait for it to be ready
|
||||
working-directory: operator
|
||||
working-directory: operator/scripts
|
||||
run: |
|
||||
kubectl apply -f src/test/resources/example-postgres.yaml
|
||||
./scripts/check-crds-installed.sh
|
||||
kubectl apply -f src/test/resources/example-db-secret.yaml
|
||||
kubectl apply -f src/test/resources/example-tls-secret.yaml
|
||||
kubectl apply -f src/test/resources/example-keycloak.yaml
|
||||
kubectl apply -f src/test/resources/example-realm.yaml
|
||||
# Wait for the CRs to be ready
|
||||
./scripts/check-examples-installed.sh
|
||||
./check-crd-installed.sh keycloaks
|
||||
./check-crd-installed.sh keycloakrealmimports
|
||||
./deploy-examples.sh
|
||||
|
||||
- name: Single namespace cleanup
|
||||
working-directory: operator
|
||||
run: |
|
||||
kubectl delete -f src/test/resources/example-postgres.yaml
|
||||
kubectl delete -f src/test/resources/example-db-secret.yaml
|
||||
kubectl delete -f src/test/resources/example-tls-secret.yaml
|
||||
kubectl delete -f src/test/resources/example-keycloak.yaml
|
||||
kubectl delete -f src/test/resources/example-realm.yaml
|
||||
working-directory: operator/scripts
|
||||
run: ./undeploy-examples.sh
|
||||
|
||||
- name: Arrange OLM test installation for all namespaces
|
||||
working-directory: operator
|
||||
@@ -200,16 +195,40 @@ jobs:
|
||||
kubectl patch operatorgroup og --type json --patch '[{"op":"remove","path":"/spec/targetNamespaces"}]'
|
||||
|
||||
- name: Deploy an example Keycloak in a different namespace and wait for it to be ready
|
||||
working-directory: operator
|
||||
working-directory: operator/scripts
|
||||
run: |
|
||||
kubectl create ns keycloak
|
||||
kubectl apply -f src/test/resources/example-postgres.yaml -n keycloak
|
||||
kubectl apply -f src/test/resources/example-db-secret.yaml -n keycloak
|
||||
kubectl apply -f src/test/resources/example-tls-secret.yaml -n keycloak
|
||||
kubectl apply -f src/test/resources/example-keycloak.yaml -n keycloak
|
||||
kubectl apply -f src/test/resources/example-realm.yaml -n keycloak
|
||||
# Wait for the CRs to be ready
|
||||
./scripts/check-examples-installed.sh keycloak
|
||||
./deploy-examples.sh keycloak
|
||||
./undeploy-examples.sh keycloak
|
||||
|
||||
- name: Install ServiceMonitor CRD
|
||||
working-directory: operator
|
||||
run: |
|
||||
kubectl apply -f src/test/resources/service-monitor-crds.yml
|
||||
./scripts/check-crd-installed.sh servicemonitors
|
||||
|
||||
- name: Deploy an example Keycloak with ServiceMonitor
|
||||
working-directory: operator/scripts
|
||||
run: |
|
||||
./deploy-examples.sh keycloak
|
||||
kubectl -n keycloak wait servicemonitor/example-kc --for=jsonpath='{.metadata.name}' --timeout=60s
|
||||
|
||||
- name: Debug Custom Resources
|
||||
if: failure()
|
||||
run: |
|
||||
kubectl get keycloaks -A -o yaml
|
||||
kubectl get keycloakrealmimports -A -o yaml
|
||||
|
||||
- name: Gather inspect report
|
||||
if: failure()
|
||||
run: oc adm inspect ns
|
||||
|
||||
- name: Upload inspect report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: oc-inspect
|
||||
path: inspect.*
|
||||
|
||||
check:
|
||||
name: Status Check - Keycloak Operator CI
|
||||
|
||||
@@ -45,12 +45,12 @@ For Prometheus, this is a https://prometheus.io/docs/prometheus/latest/feature_f
|
||||
|
||||
. Scrape the metrics using the `OpenMetricsText1.0.0` protocol, which is not enabled by default in Prometheus.
|
||||
+
|
||||
If you are using `PodMonitors` or similar in a Kubernetes environment, this can be achieved by adding it to the spec of the custom resource:
|
||||
If you are using a `ServiceMonitor` or similar in a Kubernetes environment, this can be achieved by adding it to the spec of the custom resource:
|
||||
+
|
||||
[source]
|
||||
----
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
...
|
||||
spec:
|
||||
|
||||
@@ -100,7 +100,7 @@ spec:
|
||||
----
|
||||
NOTE: The name format of options defined in this way is identical to the key format of options specified in the configuration file.
|
||||
For details on various configuration formats, see <@links.server id="configuration"/>.
|
||||
|
||||
|
||||
==== Custom environment variables
|
||||
|
||||
You may find a need to set custom environment variables - such as for theme properties or `kc.[sh|bat]` script variables.
|
||||
@@ -108,8 +108,8 @@ The `spec.env` field of the Keycloak CR allows you to directly set any environme
|
||||
Logic in the operator based upon looking for value of a particular setting does not consult `spec.env`,
|
||||
therefore do not use `spec.env` for anything that has a first-class configuration in the CR or may be specified as an `additionalOption`.
|
||||
|
||||
Here's an example setting JAVA_OPTS_APPEND:
|
||||
|
||||
Here's an example setting JAVA_OPTS_APPEND:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
@@ -124,7 +124,7 @@ spec:
|
||||
----
|
||||
|
||||
Similar to `additionalOptions` you may specify either a value or reference a Secret.
|
||||
|
||||
|
||||
=== Secret References
|
||||
|
||||
Secret References are used by some dedicated options in the Keycloak CR, such as a `tlsSecret`, or as a value in `additionalOptions`.
|
||||
@@ -526,4 +526,78 @@ spec:
|
||||
annotation2: annotation-value2
|
||||
----
|
||||
|
||||
=== ServiceMonitor
|
||||
A `ServiceMonitor` resource is used to define how a service's metrics are discovered and scraped by Prometheus.
|
||||
The {project_name} Operator automatically generates a `ServiceMonitor` resource for your deployment. In order for the
|
||||
`ServiceMonitor` resource to be created, the `monitoring.coreos.com/v1:ServiceMonitor` Custom Resource Definition (CRD)
|
||||
must be installed on your {kubernetes} cluster.
|
||||
|
||||
The Operator generates a `ServiceMonitor` with the following spec:
|
||||
|
||||
.Default ServiceMonitor
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: example-kc # <1>
|
||||
namespace: example-namespace # <2>
|
||||
spec:
|
||||
endpoints:
|
||||
- interval: 30s
|
||||
path: /metrics # <3>
|
||||
port: management
|
||||
scheme: https # <4>
|
||||
scrapeTimeout: 10s
|
||||
tlsConfig:
|
||||
insecureSkipVerify: true
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- example-namespace # <2>
|
||||
selector:
|
||||
matchLabels:
|
||||
app: keycloak
|
||||
app.kubernetes.io/instance: example-kc # <2>
|
||||
app.kubernetes.io/managed-by: keycloak-operator
|
||||
scrapeProtocols:
|
||||
- OpenMetricsText1.0.0
|
||||
----
|
||||
<1> The `ServiceMonitor` is created with the same name as the Keycloak CR.
|
||||
<2> The `ServiceMonitor` is always deployed to the same namespace as the Keycloak CR and will only match a Service
|
||||
in that namespace.
|
||||
<3> The configured path defaults to `/metrics`, but respects the `http-management-relative-path` value if configured.
|
||||
<4> The configured scheme is changed automatically depending on whether TLS is enabled or not.
|
||||
|
||||
|
||||
==== Modify ServiceMonitor configuration
|
||||
You can configure the `interval` and `scrapeTimeout` fields of the generated `ServiceMonitor` by modifying the Keycloak CR spec.
|
||||
|
||||
.Modified ServiceMonitor
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
kind: Keycloak
|
||||
metadata:
|
||||
name: example-kc
|
||||
spec:
|
||||
serviceMonitor:
|
||||
interval: 10s
|
||||
scrapeTimeout: 5s
|
||||
----
|
||||
|
||||
==== Disable ServiceMonitor creation
|
||||
You can prevent the Operator from creating a ServiceMonitor by configuring the Keycloak CR as follows:
|
||||
|
||||
.Disabled ServiceMonitor
|
||||
[source,yaml]
|
||||
----
|
||||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
kind: Keycloak
|
||||
metadata:
|
||||
name: example-kc
|
||||
spec:
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
----
|
||||
|
||||
</@tmpl.guide>
|
||||
|
||||
@@ -56,6 +56,11 @@
|
||||
<artifactId>kubernetes-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>openshift-model-monitoring</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus -->
|
||||
<dependency>
|
||||
|
||||
@@ -2,4 +2,4 @@ ARG IMAGE=keycloak
|
||||
ARG VERSION=latest
|
||||
FROM $IMAGE:$VERSION
|
||||
|
||||
RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true --features=rolling-updates
|
||||
RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true --metrics-enabled=true --features=rolling-updates
|
||||
|
||||
12
operator/scripts/check-crd-installed.sh
Executable file
12
operator/scripts/check-crd-installed.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#! /bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
CRD=$1
|
||||
max_retries=240
|
||||
c=0
|
||||
while ! kubectl get "${CRD}"
|
||||
do
|
||||
echo "$(date +"%T") Waiting for ${CRD} CRD"
|
||||
((c++)) && ((c==max_retries)) && exit -1
|
||||
sleep 1
|
||||
done
|
||||
@@ -1,19 +0,0 @@
|
||||
#! /bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
max_retries=240
|
||||
c=0
|
||||
while ! kubectl get keycloaks
|
||||
do
|
||||
echo "$(date +"%T") Waiting for Keycloak CRD"
|
||||
((c++)) && ((c==max_retries)) && exit -1
|
||||
sleep 1
|
||||
done
|
||||
|
||||
c=0
|
||||
while ! kubectl get keycloakrealmimports
|
||||
do
|
||||
echo "$(date +"%T") Waiting for Keycloak Realm Import CRD"
|
||||
((c++)) && ((c==max_retries)) && exit -1
|
||||
sleep 1
|
||||
done
|
||||
@@ -69,6 +69,9 @@ yq ea -i '.spec.install.spec.deployments[0].spec.template.metadata.labels.name =
|
||||
yq ea -i '.spec.install.spec.deployments[0].spec.template.spec.containers[0].env += [{"name": "POD_NAME", "valueFrom": {"fieldRef": {"fieldPath": "metadata.name"}}}]' "$CSV_PATH"
|
||||
yq ea -i '.spec.install.spec.deployments[0].spec.template.spec.containers[0].env += [{"name": "OPERATOR_NAME", "value": "keycloak-operator"}]' "$CSV_PATH"
|
||||
|
||||
# Remove ServiceMonitors GVK from nativeAPIS to allow CSV installation when CRDs not present
|
||||
yq ea -i 'del(.spec.nativeAPIs[] | select(.kind == "ServiceMonitor"))' "$CSV_PATH"
|
||||
|
||||
{ set +x; } 2>/dev/null
|
||||
echo ""
|
||||
echo "Created OLM bundle ok!"
|
||||
|
||||
14
operator/scripts/deploy-examples.sh
Executable file
14
operator/scripts/deploy-examples.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#! /bin/bash
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
NAMESPACE=${1:-default}
|
||||
|
||||
kubectl apply -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-postgres.yaml"
|
||||
kubectl apply -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-db-secret.yaml"
|
||||
kubectl apply -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-tls-secret.yaml"
|
||||
kubectl apply -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-keycloak.yaml"
|
||||
kubectl apply -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-realm.yaml"
|
||||
|
||||
# Wait for the CRs to be ready
|
||||
"${SCRIPT_DIR}"/check-examples-installed.sh ${NAMESPACE}
|
||||
|
||||
9
operator/scripts/undeploy-examples.sh
Executable file
9
operator/scripts/undeploy-examples.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#! /bin/bash
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
NAMESPACE=${1:-default}
|
||||
kubectl delete -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-postgres.yaml"
|
||||
kubectl delete -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-db-secret.yaml"
|
||||
kubectl delete -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-tls-secret.yaml"
|
||||
kubectl delete -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-keycloak.yaml"
|
||||
kubectl delete -n "${NAMESPACE}" -f "${SCRIPT_DIR}/../src/test/resources/example-realm.yaml"
|
||||
@@ -34,6 +34,7 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.workflow.CRDPresentActivationCondition;
|
||||
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
|
||||
import io.quarkus.logging.Log;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -63,7 +64,12 @@ import java.util.concurrent.TimeUnit;
|
||||
@Dependent(type = KeycloakIngressDependentResource.class, reconcilePrecondition = KeycloakIngressDependentResource.EnabledCondition.class),
|
||||
@Dependent(type = KeycloakServiceDependentResource.class),
|
||||
@Dependent(type = KeycloakDiscoveryServiceDependentResource.class),
|
||||
@Dependent(type = KeycloakNetworkPolicyDependentResource.class, reconcilePrecondition = KeycloakNetworkPolicyDependentResource.EnabledCondition.class)
|
||||
@Dependent(type = KeycloakNetworkPolicyDependentResource.class, reconcilePrecondition = KeycloakNetworkPolicyDependentResource.EnabledCondition.class),
|
||||
@Dependent(
|
||||
type = KeycloakServiceMonitorDependentResource.class,
|
||||
activationCondition = CRDPresentActivationCondition.class,
|
||||
reconcilePrecondition = KeycloakServiceMonitorDependentResource.ReconcilePrecondition.class
|
||||
),
|
||||
})
|
||||
public class KeycloakController implements Reconciler<Keycloak> {
|
||||
|
||||
|
||||
@@ -356,36 +356,22 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
// Set bind address as this is required for JGroups to form a cluster in IPv6 environments
|
||||
containerBuilder.addToArgs(0, "-Djgroups.bind.address=$(%s)".formatted(POD_IP));
|
||||
|
||||
boolean tls = isTlsConfigured(keycloakCR);
|
||||
String protocol = tls ? "HTTPS" : "HTTP";
|
||||
int port = -1;
|
||||
|
||||
if (readConfigurationValue(HTTP_MANAGEMENT_HEALTH_ENABLED, keycloakCR, context).map(Boolean::valueOf).orElse(true)) {
|
||||
port = HttpManagementSpec.managementPort(keycloakCR);
|
||||
if (readConfigurationValue(HTTP_MANAGEMENT_SCHEME, keycloakCR, context).filter("http"::equals).isPresent()) {
|
||||
protocol = "HTTP";
|
||||
}
|
||||
} else {
|
||||
port = tls ? HttpSpec.httpsPort(keycloakCR) : HttpSpec.httpPort(keycloakCR);
|
||||
}
|
||||
var healthEnabled = readConfigurationValue(HTTP_MANAGEMENT_HEALTH_ENABLED, keycloakCR, context).map(Boolean::valueOf).orElse(true);
|
||||
ManagementEndpoint endpoint = managementEndpoint(keycloakCR, context, healthEnabled);
|
||||
|
||||
// probes
|
||||
var readinessOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getReadinessProbeSpec());
|
||||
var livenessOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getLivenessProbeSpec());
|
||||
var startupOptionalSpec = Optional.ofNullable(keycloakCR.getSpec().getStartupProbeSpec());
|
||||
var relativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY, keycloakCR, context)
|
||||
.or(() -> readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context))
|
||||
.map(path -> !path.endsWith("/") ? path + "/" : path)
|
||||
.orElse("/");
|
||||
|
||||
if (!containerBuilder.hasReadinessProbe()) {
|
||||
containerBuilder.withNewReadinessProbe()
|
||||
.withPeriodSeconds(readinessOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(10))
|
||||
.withFailureThreshold(readinessOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(3))
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/ready")
|
||||
.withScheme(endpoint.protocol)
|
||||
.withNewPort(endpoint.port)
|
||||
.withPath(endpoint.relativePath + "health/ready")
|
||||
.endHttpGet()
|
||||
.endReadinessProbe();
|
||||
}
|
||||
@@ -394,9 +380,9 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
.withPeriodSeconds(livenessOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(10))
|
||||
.withFailureThreshold(livenessOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(3))
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/live")
|
||||
.withScheme(endpoint.protocol)
|
||||
.withNewPort(endpoint.port)
|
||||
.withPath(endpoint.relativePath + "health/live")
|
||||
.endHttpGet()
|
||||
.endLivenessProbe();
|
||||
}
|
||||
@@ -405,9 +391,9 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
.withPeriodSeconds(startupOptionalSpec.map(ProbeSpec::getProbePeriodSeconds).orElse(1))
|
||||
.withFailureThreshold(startupOptionalSpec.map(ProbeSpec::getProbeFailureThreshold).orElse(600))
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/started")
|
||||
.withScheme(endpoint.protocol)
|
||||
.withNewPort(endpoint.port)
|
||||
.withPath(endpoint.relativePath + "health/started")
|
||||
.endHttpGet()
|
||||
.endStartupProbe();
|
||||
}
|
||||
@@ -606,7 +592,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
return keycloak.getMetadata().getName();
|
||||
}
|
||||
|
||||
protected Optional<String> readConfigurationValue(String key, Keycloak keycloakCR, Context<Keycloak> context) {
|
||||
private static Optional<String> readConfigurationValue(String key, Keycloak keycloakCR, Context<Keycloak> context) {
|
||||
return Optional.ofNullable(keycloakCR.getSpec()).map(KeycloakSpec::getAdditionalOptions)
|
||||
.flatMap(l -> l.stream().filter(sc -> sc.getName().equals(key)).findFirst().map(serverConfigValue -> {
|
||||
if (serverConfigValue.getValue() != null) {
|
||||
@@ -658,4 +644,27 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
||||
toUpdate.getMetadata().getAnnotations().put(Constants.KEYCLOAK_UPDATE_REVISION_ANNOTATION, revision);
|
||||
}
|
||||
|
||||
record ManagementEndpoint(String relativePath, String protocol, int port) {}
|
||||
|
||||
static ManagementEndpoint managementEndpoint(Keycloak keycloakCR, Context<Keycloak> context, boolean useMgmtProtocolPort) {
|
||||
boolean tls = isTlsConfigured(keycloakCR);
|
||||
String protocol = tls ? "HTTPS" : "HTTP";
|
||||
int port;
|
||||
|
||||
if (useMgmtProtocolPort) {
|
||||
port = HttpManagementSpec.managementPort(keycloakCR);
|
||||
if (readConfigurationValue(HTTP_MANAGEMENT_SCHEME, keycloakCR, context).filter("http"::equals).isPresent()) {
|
||||
protocol = "HTTP";
|
||||
}
|
||||
} else {
|
||||
port = tls ? HttpSpec.httpsPort(keycloakCR) : HttpSpec.httpPort(keycloakCR);
|
||||
}
|
||||
|
||||
var relativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY, keycloakCR, context)
|
||||
.or(() -> readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context))
|
||||
.map(path -> !path.endsWith("/") ? path + "/" : path)
|
||||
.orElse("/");
|
||||
|
||||
return new ManagementEndpoint(relativePath, protocol, port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import static org.keycloak.operator.controllers.KeycloakDeploymentDependentResource.managementEndpoint;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.LEGACY_MANAGEMENT_ENABLED;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.METRICS_ENABLED;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.configuredOptions;
|
||||
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.Utils;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ServiceMonitorSpec;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
|
||||
import io.fabric8.openshift.api.model.monitoring.v1.ServiceMonitor;
|
||||
import io.fabric8.openshift.api.model.monitoring.v1.ServiceMonitorBuilder;
|
||||
import io.javaoperatorsdk.operator.api.config.informer.Informer;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
|
||||
|
||||
@KubernetesDependent(
|
||||
informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING)
|
||||
)
|
||||
public class KeycloakServiceMonitorDependentResource extends CRUDKubernetesDependentResource<ServiceMonitor, Keycloak> {
|
||||
|
||||
public static class ReconcilePrecondition implements Condition<Ingress, Keycloak> {
|
||||
@Override
|
||||
public boolean isMet(DependentResource<Ingress, Keycloak> dependentResource, Keycloak primary,
|
||||
Context<Keycloak> context) {
|
||||
var opts = configuredOptions(primary);
|
||||
if (Boolean.parseBoolean(opts.get(LEGACY_MANAGEMENT_ENABLED)) || !Boolean.parseBoolean(opts.getOrDefault(METRICS_ENABLED, "false")))
|
||||
return false;
|
||||
|
||||
return ServiceMonitorSpec.get(primary).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServiceMonitor desired(Keycloak primary, Context<Keycloak> context) {
|
||||
var endpoint = managementEndpoint(primary, context, true);
|
||||
var meta = primary.getMetadata();
|
||||
var spec = ServiceMonitorSpec.get(primary);
|
||||
return new ServiceMonitorBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(meta.getName())
|
||||
.withNamespace(meta.getNamespace())
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withNewNamespaceSelector()
|
||||
.addToMatchNames(meta.getNamespace())
|
||||
.endNamespaceSelector()
|
||||
.withNewSelector()
|
||||
.addToMatchLabels(Utils.allInstanceLabels(primary))
|
||||
.endSelector()
|
||||
.withScrapeProtocols("OpenMetricsText1.0.0")
|
||||
.addNewEndpoint()
|
||||
.withInterval(spec.getInterval())
|
||||
.withPath(endpoint.relativePath() + "metrics")
|
||||
.withPort(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
|
||||
.withScheme(endpoint.protocol().toLowerCase())
|
||||
.withScrapeTimeout(spec.getScrapeTimeout())
|
||||
.withNewTlsConfig()
|
||||
.withInsecureSkipVerify(true)
|
||||
.endTlsConfig()
|
||||
.endEndpoint()
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -26,28 +26,29 @@ import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.Volume;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public final class CRDUtils {
|
||||
private static final String METRICS_ENABLED = "metrics-enabled";
|
||||
private static final String HEALTH_ENABLED = "health-enabled";
|
||||
private static final String LEGACY_MANAGEMENT_ENABLED = "legacy-observability-interface";
|
||||
public static final String METRICS_ENABLED = "metrics-enabled";
|
||||
public static final String LEGACY_MANAGEMENT_ENABLED = "legacy-observability-interface";
|
||||
|
||||
public static boolean isTlsConfigured(Keycloak keycloakCR) {
|
||||
var tlsSecret = keycloakSpecOf(keycloakCR).map(KeycloakSpec::getHttpSpec).map(HttpSpec::getTlsSecret);
|
||||
@@ -64,17 +65,7 @@ public final class CRDUtils {
|
||||
}
|
||||
|
||||
public static boolean isManagementEndpointEnabled(Keycloak keycloak) {
|
||||
Map<String, String> options = new HashMap<>();
|
||||
// add default options
|
||||
Constants.DEFAULT_DIST_CONFIG_LIST
|
||||
.forEach(valueOrSecret -> options.put(valueOrSecret.getName(), valueOrSecret.getValue()));
|
||||
// overwrite the configured ones
|
||||
keycloakSpecOf(keycloak)
|
||||
.map(KeycloakSpec::getAdditionalOptions)
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(valueOrSecret -> options.put(valueOrSecret.getName(), valueOrSecret.getValue()));
|
||||
|
||||
var options = configuredOptions(keycloak);
|
||||
// Legacy management enabled
|
||||
if (Boolean.parseBoolean(options.get(LEGACY_MANAGEMENT_ENABLED))) {
|
||||
return false;
|
||||
@@ -87,6 +78,20 @@ public final class CRDUtils {
|
||||
.anyMatch(Boolean::parseBoolean);
|
||||
}
|
||||
|
||||
public static Map<String, String> configuredOptions(Keycloak keycloak) {
|
||||
Map<String, String> options = new HashMap<>();
|
||||
// add default options
|
||||
Constants.DEFAULT_DIST_CONFIG_LIST
|
||||
.forEach(valueOrSecret -> options.put(valueOrSecret.getName(), valueOrSecret.getValue()));
|
||||
// overwrite the configured ones
|
||||
keycloakSpecOf(keycloak)
|
||||
.map(KeycloakSpec::getAdditionalOptions)
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(valueOrSecret -> options.put(valueOrSecret.getName(), valueOrSecret.getValue()));
|
||||
return options;
|
||||
}
|
||||
|
||||
public static Optional<KeycloakSpec> keycloakSpecOf(Keycloak keycloak) {
|
||||
return Optional.ofNullable(keycloak)
|
||||
.map(Keycloak::getSpec);
|
||||
@@ -107,16 +112,6 @@ public final class CRDUtils {
|
||||
return kubernetesSerialization.convertValue(value, JsonNode.class);
|
||||
}
|
||||
|
||||
public static Stream<Volume> volumesFromStatefulSet(StatefulSet statefulSet) {
|
||||
return Optional.of(statefulSet)
|
||||
.map(StatefulSet::getSpec)
|
||||
.map(StatefulSetSpec::getTemplate)
|
||||
.map(PodTemplateSpec::getSpec)
|
||||
.map(PodSpec::getVolumes)
|
||||
.stream()
|
||||
.flatMap(Collection::stream);
|
||||
}
|
||||
|
||||
public static Optional<Boolean> fetchIsRecreateUpdate(StatefulSet statefulSet) {
|
||||
var value = statefulSet.getMetadata().getAnnotations().get(Constants.KEYCLOAK_RECREATE_UPDATE_ANNOTATION);
|
||||
return Optional.ofNullable(value).map(Boolean::parseBoolean);
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.NetworkPolicySpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProbeSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.SchedulingSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ServiceMonitorSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
|
||||
@@ -157,6 +158,9 @@ public class KeycloakSpec {
|
||||
@JsonPropertyDescription("Configuration for startup probe, by default it is 1 for periodSeconds and 600 for failureThreshold")
|
||||
private ProbeSpec startupProbeSpec;
|
||||
|
||||
@JsonProperty("serviceMonitor")
|
||||
@JsonPropertyDescription("Configuration related to the generated ServiceMonitor")
|
||||
private ServiceMonitorSpec serviceMonitorSpec;
|
||||
|
||||
public HttpSpec getHttpSpec() {
|
||||
return httpSpec;
|
||||
@@ -374,4 +378,12 @@ public class KeycloakSpec {
|
||||
this.importSpec = importSpec;
|
||||
}
|
||||
|
||||
|
||||
public ServiceMonitorSpec getServiceMonitorSpec() {
|
||||
return serviceMonitorSpec;
|
||||
}
|
||||
|
||||
public void setServiceMonitorSpec(ServiceMonitorSpec serviceMonitorSpec) {
|
||||
this.serviceMonitorSpec = serviceMonitorSpec;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.keycloak.operator.crds.v2alpha1.deployment.spec;
|
||||
|
||||
import org.keycloak.operator.crds.v2alpha1.CRDUtils;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
import io.fabric8.generator.annotation.Default;
|
||||
import io.sundr.builder.annotations.Buildable;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
|
||||
public class ServiceMonitorSpec {
|
||||
|
||||
public static final String DEFAULT_INTERVAL = "30s";
|
||||
public static final String DEFAULT_SCRAPE_TIMEOUT = "10s";
|
||||
|
||||
@JsonPropertyDescription("Enables or disables the creation of the ServiceMonitor.")
|
||||
@Default("true")
|
||||
private boolean enabled = true;
|
||||
|
||||
@JsonPropertyDescription("Interval at which metrics should be scraped")
|
||||
@Default(DEFAULT_INTERVAL)
|
||||
private String interval = DEFAULT_INTERVAL;
|
||||
|
||||
@JsonPropertyDescription("Timeout after which the scrape is ended")
|
||||
@Default(DEFAULT_SCRAPE_TIMEOUT)
|
||||
private String scrapeTimeout = DEFAULT_SCRAPE_TIMEOUT;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(String interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
public String getScrapeTimeout() {
|
||||
return scrapeTimeout;
|
||||
}
|
||||
|
||||
public void setScrapeTimeout(String scrapeTimeout) {
|
||||
this.scrapeTimeout = scrapeTimeout;
|
||||
}
|
||||
|
||||
public static ServiceMonitorSpec get(Keycloak keycloak) {
|
||||
return CRDUtils.keycloakSpecOf(keycloak)
|
||||
.map(KeycloakSpec::getServiceMonitorSpec)
|
||||
.orElse(new ServiceMonitorSpec());
|
||||
}
|
||||
}
|
||||
@@ -72,12 +72,29 @@ rules:
|
||||
- delete
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- servicemonitors
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: keycloak-operator-clusterrole
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- config.openshift.io
|
||||
resources:
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.fabric8.kubernetes.client.dsl.Loggable;
|
||||
import io.fabric8.kubernetes.client.dsl.PodResource;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.utils.Serialization;
|
||||
import io.fabric8.openshift.api.model.monitoring.v1.ServiceMonitor;
|
||||
import io.javaoperatorsdk.operator.Operator;
|
||||
import io.javaoperatorsdk.operator.api.config.BaseConfigurationService;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
|
||||
@@ -212,9 +213,11 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
|
||||
public static void createCRDs(KubernetesClient client) throws FileNotFoundException {
|
||||
K8sUtils.set(client, new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER + "keycloaks.k8s.keycloak.org-v1.yml"));
|
||||
K8sUtils.set(client, new FileInputStream(TARGET_KUBERNETES_GENERATED_YML_FOLDER + "keycloakrealmimports.k8s.keycloak.org-v1.yml"));
|
||||
K8sUtils.set(client, BaseOperatorTest.class.getResourceAsStream("/service-monitor-crds.yml"));
|
||||
|
||||
Awaitility.await().pollInterval(100, TimeUnit.MILLISECONDS).untilAsserted(() -> client.resources(Keycloak.class).list());
|
||||
Awaitility.await().pollInterval(100, TimeUnit.MILLISECONDS).untilAsserted(() -> client.resources(KeycloakRealmImport.class).list());
|
||||
Awaitility.await().pollInterval(100, TimeUnit.MILLISECONDS).untilAsserted(() -> client.resources(ServiceMonitor.class).list());
|
||||
}
|
||||
|
||||
private static void createOperator() {
|
||||
@@ -540,18 +543,22 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback {
|
||||
* @return
|
||||
*/
|
||||
public static Keycloak getTestKeycloakDeployment(boolean disableProbes) {
|
||||
Keycloak kc = K8sUtils.getDefaultKeycloakDeployment();
|
||||
kc.getMetadata().setNamespace(getCurrentNamespace());
|
||||
String image = getTestCustomImage();
|
||||
if (image != null) {
|
||||
kc.getSpec().setImage(image);
|
||||
}
|
||||
if (disableProbes) {
|
||||
return disableProbes(kc);
|
||||
}
|
||||
return kc;
|
||||
return getTestKeycloakDeployment(disableProbes, true);
|
||||
}
|
||||
|
||||
public static Keycloak getTestKeycloakDeployment(boolean disableProbes, boolean setCustomImage) {
|
||||
Keycloak kc = K8sUtils.getDefaultKeycloakDeployment();
|
||||
kc.getMetadata().setNamespace(getCurrentNamespace());
|
||||
String image = getTestCustomImage();
|
||||
if (setCustomImage && image != null) {
|
||||
kc.getSpec().setImage(image);
|
||||
}
|
||||
if (disableProbes) {
|
||||
return disableProbes(kc);
|
||||
}
|
||||
return kc;
|
||||
}
|
||||
|
||||
public static Keycloak disableProbes(Keycloak keycloak) {
|
||||
KeycloakSpecBuilder specBuilder = new KeycloakSpecBuilder(keycloak.getSpec());
|
||||
var podTemplateSpecBuilder = specBuilder.editOrNewUnsupported().editOrNewPodTemplate().editOrNewSpec();
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.keycloak.operator.testsuite.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ServiceMonitorSpecBuilder;
|
||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.openshift.api.model.monitoring.v1.ServiceMonitor;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
|
||||
@Tag(BaseOperatorTest.SLOW)
|
||||
@QuarkusTest
|
||||
public class ServiceMonitorTest extends BaseOperatorTest {
|
||||
|
||||
@Test
|
||||
public void testServiceMonitorDisabledNoMetrics() {
|
||||
Assumptions.assumeTrue(isServiceMonitorAvailable(k8sclient));
|
||||
var kc = getTestKeycloakDeployment(true, false);;
|
||||
kc.getSpec().setAdditionalOptions(List.of(new ValueOrSecret("metrics-enabled", "false")));
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
ServiceMonitor sm = getServiceMonitor(kc);
|
||||
assertThat(sm).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceMonitorCreatedWithMetricsEnabled() {
|
||||
Assumptions.assumeTrue(isServiceMonitorAvailable(k8sclient));
|
||||
var kc = getTestKeycloakDeployment(true, false);;
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await().untilAsserted(() -> {
|
||||
var sm = getServiceMonitor(kc);
|
||||
assertThat(sm).isNotNull();
|
||||
assertThat(sm.getSpec().getEndpoints()).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceMonitorDisabledExplicitly() {
|
||||
Assumptions.assumeTrue(isServiceMonitorAvailable(k8sclient));
|
||||
var kc = getTestKeycloakDeployment(true, false);;
|
||||
kc.getSpec().setServiceMonitorSpec(
|
||||
new ServiceMonitorSpecBuilder()
|
||||
.withEnabled(false)
|
||||
.build()
|
||||
);
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
ServiceMonitor sm = getServiceMonitor(kc);
|
||||
assertThat(sm).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceMonitorDisabledLegacyManagement() {
|
||||
Assumptions.assumeTrue(isServiceMonitorAvailable(k8sclient));
|
||||
var kc = getTestKeycloakDeployment(true, false);;
|
||||
kc.getSpec().setAdditionalOptions(List.of(new ValueOrSecret("legacy-observability-interface", "true")));
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
ServiceMonitor sm = getServiceMonitor(kc);
|
||||
assertThat(sm).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceMonitorConfigProperties() {
|
||||
Assumptions.assumeTrue(isServiceMonitorAvailable(k8sclient));
|
||||
var kc = getTestKeycloakDeployment(true, false);;
|
||||
kc.getSpec().setServiceMonitorSpec(
|
||||
new ServiceMonitorSpecBuilder()
|
||||
.withInterval("1s")
|
||||
.withScrapeTimeout("2s")
|
||||
.build()
|
||||
);
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await().untilAsserted(() -> {
|
||||
var sm = getServiceMonitor(kc);
|
||||
assertThat(sm).isNotNull();
|
||||
assertThat(sm.getSpec().getEndpoints()).hasSize(1);
|
||||
assertThat(sm.getSpec().getEndpoints().get(0).getInterval()).isEqualTo("1s");
|
||||
assertThat(sm.getSpec().getEndpoints().get(0).getScrapeTimeout()).isEqualTo("2s");
|
||||
});
|
||||
}
|
||||
|
||||
private ServiceMonitor getServiceMonitor(Keycloak kc) {
|
||||
return k8sclient.resources(ServiceMonitor.class)
|
||||
.inNamespace(kc.getMetadata().getNamespace())
|
||||
.withName(kc.getMetadata().getName())
|
||||
.get();
|
||||
}
|
||||
|
||||
private boolean isServiceMonitorAvailable(KubernetesClient client) {
|
||||
return client
|
||||
.apiextensions()
|
||||
.v1()
|
||||
.customResourceDefinitions()
|
||||
.withName(new ServiceMonitor().getFullResourceName())
|
||||
.get() != null;
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ServiceMonitorSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
@@ -292,6 +293,18 @@ public class CRSerializationTest {
|
||||
assertEquals("1", revision);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serviceMonitorSpecification() {
|
||||
Keycloak keycloak = Serialization.unmarshal(this.getClass().getResourceAsStream("/test-serialization-keycloak-cr.yml"), Keycloak.class);
|
||||
|
||||
ServiceMonitorSpec serviceMonitorSpec = keycloak.getSpec().getServiceMonitorSpec();
|
||||
assertThat(serviceMonitorSpec, notNullValue());
|
||||
|
||||
assertThat(serviceMonitorSpec.isEnabled(), is(true));
|
||||
assertThat(serviceMonitorSpec.getInterval(), is(ServiceMonitorSpec.DEFAULT_INTERVAL));
|
||||
assertThat(serviceMonitorSpec.getScrapeTimeout(), is(ServiceMonitorSpec.DEFAULT_SCRAPE_TIMEOUT));
|
||||
}
|
||||
|
||||
private static void assertNetworkPolicyRules(Collection<NetworkPolicyPeer> rules) {
|
||||
assertNotNull(rules);
|
||||
assertEquals(3, rules.size());
|
||||
|
||||
@@ -144,7 +144,8 @@ public class NetworkPolicyLogicTest {
|
||||
namespaceSelectorWithMatchLabel("kubernetes.io/name", "keycloak")
|
||||
));
|
||||
var networkPolicy = assertEnabledAndGet(kc);
|
||||
CRAssert.assertIngressRules(networkPolicy, kc, -1, Constants.KEYCLOAK_HTTPS_PORT, -1);
|
||||
var mgmtPort = legacyOption ? -1 : Constants.KEYCLOAK_MANAGEMENT_PORT;
|
||||
CRAssert.assertIngressRules(networkPolicy, kc, -1, Constants.KEYCLOAK_HTTPS_PORT, mgmtPort);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,23 +17,6 @@
|
||||
|
||||
package org.keycloak.operator.testsuite.utils;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.PodBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.dsl.ExecWatch;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.utils.Serialization;
|
||||
import io.quarkus.logging.Log;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpecBuilder;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.NetworkPolicySpecBuilder;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
@@ -48,6 +31,23 @@ import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpecBuilder;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.NetworkPolicySpecBuilder;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.PodBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.dsl.ExecWatch;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.utils.Serialization;
|
||||
import io.quarkus.logging.Log;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
|
||||
@@ -18,4 +18,7 @@ spec:
|
||||
hostname:
|
||||
hostname: example.com
|
||||
proxy:
|
||||
headers: xforwarded # default nginx ingress sets x-forwarded
|
||||
headers: xforwarded # default nginx ingress sets x-forwarded
|
||||
additionalOptions:
|
||||
- name: metrics-enabled
|
||||
value: "true"
|
||||
711
operator/src/test/resources/service-monitor-crds.yml
Normal file
711
operator/src/test/resources/service-monitor-crds.yml
Normal file
@@ -0,0 +1,711 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.18.0
|
||||
operator.prometheus.io/version: 0.85.0
|
||||
name: servicemonitors.monitoring.coreos.com
|
||||
spec:
|
||||
group: monitoring.coreos.com
|
||||
names:
|
||||
categories:
|
||||
- prometheus-operator
|
||||
kind: ServiceMonitor
|
||||
listKind: ServiceMonitorList
|
||||
plural: servicemonitors
|
||||
shortNames:
|
||||
- smon
|
||||
singular: servicemonitor
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
attachMetadata:
|
||||
properties:
|
||||
node:
|
||||
type: boolean
|
||||
type: object
|
||||
bodySizeLimit:
|
||||
pattern: (^0|([0-9]*[.])?[0-9]+((K|M|G|T|E|P)i?)?B)$
|
||||
type: string
|
||||
convertClassicHistogramsToNHCB:
|
||||
type: boolean
|
||||
endpoints:
|
||||
items:
|
||||
properties:
|
||||
authorization:
|
||||
properties:
|
||||
credentials:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
basicAuth:
|
||||
properties:
|
||||
password:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
username:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
bearerTokenFile:
|
||||
type: string
|
||||
bearerTokenSecret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
enableHttp2:
|
||||
type: boolean
|
||||
filterRunning:
|
||||
type: boolean
|
||||
followRedirects:
|
||||
type: boolean
|
||||
honorLabels:
|
||||
type: boolean
|
||||
honorTimestamps:
|
||||
type: boolean
|
||||
interval:
|
||||
pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$
|
||||
type: string
|
||||
metricRelabelings:
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
default: replace
|
||||
enum:
|
||||
- replace
|
||||
- Replace
|
||||
- keep
|
||||
- Keep
|
||||
- drop
|
||||
- Drop
|
||||
- hashmod
|
||||
- HashMod
|
||||
- labelmap
|
||||
- LabelMap
|
||||
- labeldrop
|
||||
- LabelDrop
|
||||
- labelkeep
|
||||
- LabelKeep
|
||||
- lowercase
|
||||
- Lowercase
|
||||
- uppercase
|
||||
- Uppercase
|
||||
- keepequal
|
||||
- KeepEqual
|
||||
- dropequal
|
||||
- DropEqual
|
||||
type: string
|
||||
modulus:
|
||||
format: int64
|
||||
type: integer
|
||||
regex:
|
||||
type: string
|
||||
replacement:
|
||||
type: string
|
||||
separator:
|
||||
type: string
|
||||
sourceLabels:
|
||||
items:
|
||||
pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
|
||||
type: string
|
||||
type: array
|
||||
targetLabel:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
noProxy:
|
||||
type: string
|
||||
oauth2:
|
||||
properties:
|
||||
clientId:
|
||||
properties:
|
||||
configMap:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
secret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
clientSecret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
endpointParams:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
noProxy:
|
||||
type: string
|
||||
proxyConnectHeader:
|
||||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
proxyFromEnvironment:
|
||||
type: boolean
|
||||
proxyUrl:
|
||||
pattern: ^(http|https|socks5)://.+$
|
||||
type: string
|
||||
scopes:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tlsConfig:
|
||||
properties:
|
||||
ca:
|
||||
properties:
|
||||
configMap:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
secret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
cert:
|
||||
properties:
|
||||
configMap:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
secret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
keySecret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
maxVersion:
|
||||
enum:
|
||||
- TLS10
|
||||
- TLS11
|
||||
- TLS12
|
||||
- TLS13
|
||||
type: string
|
||||
minVersion:
|
||||
enum:
|
||||
- TLS10
|
||||
- TLS11
|
||||
- TLS12
|
||||
- TLS13
|
||||
type: string
|
||||
serverName:
|
||||
type: string
|
||||
type: object
|
||||
tokenUrl:
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- clientId
|
||||
- clientSecret
|
||||
- tokenUrl
|
||||
type: object
|
||||
params:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
path:
|
||||
type: string
|
||||
port:
|
||||
type: string
|
||||
proxyConnectHeader:
|
||||
additionalProperties:
|
||||
items:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
proxyFromEnvironment:
|
||||
type: boolean
|
||||
proxyUrl:
|
||||
pattern: ^(http|https|socks5)://.+$
|
||||
type: string
|
||||
relabelings:
|
||||
items:
|
||||
properties:
|
||||
action:
|
||||
default: replace
|
||||
enum:
|
||||
- replace
|
||||
- Replace
|
||||
- keep
|
||||
- Keep
|
||||
- drop
|
||||
- Drop
|
||||
- hashmod
|
||||
- HashMod
|
||||
- labelmap
|
||||
- LabelMap
|
||||
- labeldrop
|
||||
- LabelDrop
|
||||
- labelkeep
|
||||
- LabelKeep
|
||||
- lowercase
|
||||
- Lowercase
|
||||
- uppercase
|
||||
- Uppercase
|
||||
- keepequal
|
||||
- KeepEqual
|
||||
- dropequal
|
||||
- DropEqual
|
||||
type: string
|
||||
modulus:
|
||||
format: int64
|
||||
type: integer
|
||||
regex:
|
||||
type: string
|
||||
replacement:
|
||||
type: string
|
||||
separator:
|
||||
type: string
|
||||
sourceLabels:
|
||||
items:
|
||||
pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
|
||||
type: string
|
||||
type: array
|
||||
targetLabel:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
scheme:
|
||||
enum:
|
||||
- http
|
||||
- https
|
||||
type: string
|
||||
scrapeTimeout:
|
||||
pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$
|
||||
type: string
|
||||
targetPort:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
x-kubernetes-int-or-string: true
|
||||
tlsConfig:
|
||||
properties:
|
||||
ca:
|
||||
properties:
|
||||
configMap:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
secret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
caFile:
|
||||
type: string
|
||||
cert:
|
||||
properties:
|
||||
configMap:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
secret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
certFile:
|
||||
type: string
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
keyFile:
|
||||
type: string
|
||||
keySecret:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
maxVersion:
|
||||
enum:
|
||||
- TLS10
|
||||
- TLS11
|
||||
- TLS12
|
||||
- TLS13
|
||||
type: string
|
||||
minVersion:
|
||||
enum:
|
||||
- TLS10
|
||||
- TLS11
|
||||
- TLS12
|
||||
- TLS13
|
||||
type: string
|
||||
serverName:
|
||||
type: string
|
||||
type: object
|
||||
trackTimestampsStaleness:
|
||||
type: boolean
|
||||
type: object
|
||||
type: array
|
||||
fallbackScrapeProtocol:
|
||||
enum:
|
||||
- PrometheusProto
|
||||
- OpenMetricsText0.0.1
|
||||
- OpenMetricsText1.0.0
|
||||
- PrometheusText0.0.4
|
||||
- PrometheusText1.0.0
|
||||
type: string
|
||||
jobLabel:
|
||||
type: string
|
||||
keepDroppedTargets:
|
||||
format: int64
|
||||
type: integer
|
||||
labelLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
labelNameLengthLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
labelValueLengthLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
namespaceSelector:
|
||||
properties:
|
||||
any:
|
||||
type: boolean
|
||||
matchNames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
nativeHistogramBucketLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
nativeHistogramMinBucketFactor:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
podTargetLabels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
sampleLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
scrapeClass:
|
||||
minLength: 1
|
||||
type: string
|
||||
scrapeClassicHistograms:
|
||||
type: boolean
|
||||
scrapeProtocols:
|
||||
items:
|
||||
enum:
|
||||
- PrometheusProto
|
||||
- OpenMetricsText0.0.1
|
||||
- OpenMetricsText1.0.0
|
||||
- PrometheusText0.0.4
|
||||
- PrometheusText1.0.0
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: set
|
||||
selector:
|
||||
properties:
|
||||
matchExpressions:
|
||||
items:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
values:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
selectorMechanism:
|
||||
enum:
|
||||
- RelabelConfig
|
||||
- RoleSelector
|
||||
type: string
|
||||
targetLabels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
targetLimit:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- endpoints
|
||||
- selector
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
bindings:
|
||||
items:
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
observedGeneration:
|
||||
format: int64
|
||||
type: integer
|
||||
reason:
|
||||
type: string
|
||||
status:
|
||||
minLength: 1
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- Accepted
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
group:
|
||||
enum:
|
||||
- monitoring.coreos.com
|
||||
type: string
|
||||
name:
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
minLength: 1
|
||||
type: string
|
||||
resource:
|
||||
enum:
|
||||
- prometheuses
|
||||
- prometheusagents
|
||||
type: string
|
||||
required:
|
||||
- group
|
||||
- name
|
||||
- namespace
|
||||
- resource
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -145,4 +145,8 @@ spec:
|
||||
podTemplate:
|
||||
metadata:
|
||||
labels:
|
||||
my-label: "foo"
|
||||
my-label: "foo"
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
|
||||
Reference in New Issue
Block a user