+
Skip to content

add labels and annotations to service (httpSpec) #39925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/guides/operator/advanced-configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -473,4 +473,25 @@ They need to access {project_name} to scrape the available metrics.

Check the https://kubernetes.io/docs/concepts/services-networking/network-policies/[Kubernetes Network Policies documentation] for more information about NetworkPolicies.

=== Parameterizing service labels and annotations

If you need to set custom labels or annotations to keycloak service you can do that through `spec.http.labels` and `spec.http.annotations`

.Custom service labels and annotations
[source,yaml]
----
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: example-kc
spec:
http:
labels:
label1: label-value1
label2: label-value2
annotations:
annotation1: annotation-value1
annotation2: annotation-value2
----

</@tmpl.guide>
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
package org.keycloak.operator.controllers;

import java.util.Optional;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Service;
Expand Down Expand Up @@ -76,11 +79,19 @@ private ServiceSpec getServiceSpec(Keycloak keycloak) {

@Override
protected Service desired(Keycloak primary, Context<Keycloak> context) {

Map<String,String> labels = Utils.allInstanceLabels(primary);
var optionalSpec = Optional.ofNullable(primary.getSpec().getHttpSpec());
optionalSpec.map(HttpSpec::getLabels).ifPresent(labels::putAll);

Map<String,String> annotations = optionalSpec.map(HttpSpec::getAnnotations).orElse(new HashMap<>());

Service service = new ServiceBuilder()
.withNewMetadata()
.withName(getServiceName(primary))
.withNamespace(primary.getMetadata().getNamespace())
.addToLabels(Utils.allInstanceLabels(primary))
.addToLabels(labels)
.addToAnnotations(annotations)
.endMetadata()
.withSpec(getServiceSpec(primary))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
package org.keycloak.operator.crds.v2alpha1.deployment.spec;

import java.util.Optional;
import java.util.Map;

import org.keycloak.operator.Constants;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import io.sundr.builder.annotations.Buildable;
import org.keycloak.operator.Constants;
import org.keycloak.operator.crds.v2alpha1.CRDUtils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
Expand All @@ -45,6 +48,12 @@ public class HttpSpec {
@JsonPropertyDescription("The used HTTPS port.")
private Integer httpsPort = Constants.KEYCLOAK_HTTPS_PORT;

@JsonPropertyDescription("Annotations to be appended to the Service object")
Map<String, String> annotations;

@JsonPropertyDescription("Labels to be appended to the Service object")
Map<String, String> labels;
Comment on lines +51 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue we should probably add a service spec instead of putting it under http. But leaving the room for @shawkins to chime in as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this but I think everything inside spec.http is about service layer so I don't have sure if make sense have a service spec. I mean, if we had a service spec where will we put httpEnabled, httpPort and so on?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing http spec is a good enough place for this - it's ok to let the cr have some abstraction. If we go down the path of adding a service stanza arguably that will need to be split between the discovery and regular service.


public String getTlsSecret() {
return tlsSecret;
}
Expand Down Expand Up @@ -95,4 +104,20 @@ private static Optional<HttpSpec> httpSpec(Keycloak keycloak) {
.map(KeycloakSpec::getHttpSpec);
}

public Map<String, String> getAnnotations() {
return annotations;
}

public void setAnnotations(Map<String, String> annotations) {
this.annotations = annotations;
}

public Map<String, String> getLabels() {
return labels;
}

public void setLabels(Map<String, String> labels) {
this.labels = labels;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

@QuarkusTest
public class KeycloakServicesTest extends BaseOperatorTest {
@Test
public void testMainServiceDurability() {
var kc = getTestKeycloakDeployment(true);
kc.getSpec().getHttpSpec().setLabels(Map.of("foo","bar"));
K8sUtils.deployKeycloak(k8sclient, kc, true);
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
var serviceSelector = k8sclient.services().inNamespace(namespace).withName(serviceName);
Expand Down Expand Up @@ -71,6 +74,7 @@ public void testMainServiceDurability() {
.untilAsserted(() -> {
var s = serviceSelector.get();
assertThat(s.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())).isTrue(); // additional labels should not be overwritten
assertEquals("bar", s.getMetadata().getLabels().get("foo"));
// ignoring assigned IP/s and generated config
s.getSpec().setClusterIP(null);
s.getSpec().setClusterIPs(null);
Expand All @@ -79,6 +83,60 @@ public void testMainServiceDurability() {
});
}

@Test
public void testCustomServiceAnnotations() {
var kc = getTestKeycloakDeployment(true);

// set 'a'
kc.getSpec().getHttpSpec().setAnnotations(Map.of("a", "b"));
K8sUtils.deployKeycloak(k8sclient, kc, true);

String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
var serviceSelector = k8sclient.services().inNamespace(namespace).withName(serviceName);

Awaitility.await()
.ignoreExceptions()
.untilAsserted(() -> {
var s = serviceSelector.get();
assertEquals("b", s.getMetadata().getAnnotations().get("a"));
});

// update 'a'
kc.getSpec().getHttpSpec().setAnnotations(Map.of("a", "bb"));
K8sUtils.deployKeycloak(k8sclient, kc, true);

Awaitility.await()
.ignoreExceptions()
.untilAsserted(() -> {
var s = serviceSelector.get();
assertEquals("bb", s.getMetadata().getAnnotations().get("a"));
});

// remove 'a' and add 'c'
kc.getSpec().getHttpSpec().setAnnotations(Map.of("c", "d"));
K8sUtils.deployKeycloak(k8sclient, kc, true);

Awaitility.await()
.ignoreExceptions()
.untilAsserted(() -> {
var s = serviceSelector.get();
assertFalse(s.getMetadata().getAnnotations().containsKey("a"));
assertEquals("d", s.getMetadata().getAnnotations().get("c"));
});

// remove all
kc.getSpec().getHttpSpec().setAnnotations(null);
K8sUtils.deployKeycloak(k8sclient, kc, true);

Awaitility.await()
.ignoreExceptions()
.untilAsserted(() -> {
var s = serviceSelector.get();
assertFalse(s.getMetadata().getAnnotations().containsKey("a"));
assertFalse(s.getMetadata().getAnnotations().containsKey("c"));
});
}

@Test
public void testDiscoveryServiceDurability() {
var kc = getTestKeycloakDeployment(true);
Expand Down
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载