Skip to content

Commit 9e5009f

Browse files
metacosmxstefank
authored andcommitted
fix: use infrastructure client if no operator client is set up
Signed-off-by: Chris Laprun <[email protected]>
1 parent 4d461f9 commit 9e5009f

File tree

8 files changed

+228
-6
lines changed

8 files changed

+228
-6
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ private static IllegalStateException noSpecException(
180180

181181
public static <T> T loadYaml(Class<T> clazz, Class loader, String yaml) {
182182
try (InputStream is = loader.getResourceAsStream(yaml)) {
183+
if (is == null) {
184+
throw new IllegalStateException("Cannot find yaml on classpath: " + yaml);
185+
}
183186
if (Builder.class.isAssignableFrom(clazz)) {
184187
return BuilderUtils.newBuilder(
185188
clazz, Serialization.unmarshal(is, BuilderUtils.builderTargetType(clazz)));

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ protected AbstractOperatorExtension(
6060
KubernetesClient infrastructureKubernetesClient,
6161
Function<ExtensionContext, String> namespaceNameSupplier,
6262
Function<ExtensionContext, String> perClassNamespaceNameSupplier) {
63-
this.kubernetesClient =
64-
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build();
6563
this.infrastructureKubernetesClient =
6664
infrastructureKubernetesClient != null
6765
? infrastructureKubernetesClient
6866
: new KubernetesClientBuilder().build();
67+
this.kubernetesClient =
68+
kubernetesClient != null ? kubernetesClient : this.infrastructureKubernetesClient;
6969
this.infrastructure = infrastructure;
7070
this.infrastructureTimeout = infrastructureTimeout;
7171
this.oneNamespacePerClass = oneNamespacePerClass;

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/ClusterDeployedOperatorExtension.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ public Builder withKubernetesClient(KubernetesClient kubernetesClient) {
157157
}
158158

159159
public ClusterDeployedOperatorExtension build() {
160+
infrastructureKubernetesClient =
161+
infrastructureKubernetesClient != null
162+
? infrastructureKubernetesClient
163+
: new KubernetesClientBuilder().build();
164+
kubernetesClient =
165+
kubernetesClient != null ? kubernetesClient : infrastructureKubernetesClient;
160166
return new ClusterDeployedOperatorExtension(
161167
operatorDeployment,
162168
deploymentTimeout,
@@ -165,10 +171,8 @@ public ClusterDeployedOperatorExtension build() {
165171
preserveNamespaceOnError,
166172
waitForNamespaceDeletion,
167173
oneNamespacePerClass,
168-
kubernetesClient != null ? kubernetesClient : new KubernetesClientBuilder().build(),
169-
infrastructureKubernetesClient != null
170-
? infrastructureKubernetesClient
171-
: new KubernetesClientBuilder().build(),
174+
kubernetesClient,
175+
infrastructureKubernetesClient,
172176
namespaceNameSupplier,
173177
perClassNamespaceNameSupplier);
174178
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package io.javaoperatorsdk.operator.baseapi.infrastructureclient;
2+
3+
import java.util.HashMap;
4+
import java.util.UUID;
5+
import java.util.concurrent.TimeUnit;
6+
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
13+
import io.fabric8.kubernetes.api.model.rbac.ClusterRole;
14+
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding;
15+
import io.fabric8.kubernetes.client.ConfigBuilder;
16+
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
17+
import io.fabric8.kubernetes.client.KubernetesClientException;
18+
import io.javaoperatorsdk.operator.ReconcilerUtils;
19+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
import static org.awaitility.Awaitility.await;
24+
25+
class InfrastructureClientIT {
26+
27+
private static final String RBAC_TEST_ROLE = "rbac-test-role.yaml";
28+
private static final String RBAC_TEST_ROLE_BINDING = "rbac-test-role-binding.yaml";
29+
private static final String RBAC_TEST_USER = "rbac-test-user";
30+
31+
@RegisterExtension
32+
LocallyRunOperatorExtension operator =
33+
LocallyRunOperatorExtension.builder()
34+
.withReconciler(new InfrastructureClientTestReconciler())
35+
.withKubernetesClient(
36+
new KubernetesClientBuilder()
37+
.withConfig(new ConfigBuilder().withImpersonateUsername(RBAC_TEST_USER).build())
38+
.build())
39+
.withInfrastructureKubernetesClient(
40+
new KubernetesClientBuilder().build()) // no limitations
41+
.build();
42+
43+
/**
44+
* We need to apply the cluster role also before the CRD deployment so the rbac-test-user is
45+
* permitted to deploy it
46+
*/
47+
public InfrastructureClientIT() {
48+
applyClusterRole(RBAC_TEST_ROLE);
49+
applyClusterRoleBinding(RBAC_TEST_ROLE_BINDING);
50+
}
51+
52+
@BeforeEach
53+
void setup() {
54+
applyClusterRole(RBAC_TEST_ROLE);
55+
applyClusterRoleBinding(RBAC_TEST_ROLE_BINDING);
56+
}
57+
58+
@AfterEach
59+
void cleanup() {
60+
removeClusterRoleBinding(RBAC_TEST_ROLE_BINDING);
61+
removeClusterRole(RBAC_TEST_ROLE);
62+
}
63+
64+
@Test
65+
void canCreateInfrastructure() {
66+
var resource = new InfrastructureClientTestCustomResource();
67+
resource.setMetadata(
68+
new ObjectMetaBuilder()
69+
.withName("infrastructure-client-resource")
70+
.withUid(UUID.randomUUID().toString())
71+
.withGeneration(1L)
72+
.build());
73+
resource.getMetadata().setAnnotations(new HashMap<>());
74+
resource.setKind("InfrastructureClientTestCustomResource");
75+
operator.create(resource);
76+
77+
await()
78+
.atMost(5, TimeUnit.SECONDS)
79+
.untilAsserted(
80+
() -> {
81+
InfrastructureClientTestCustomResource r =
82+
operator.get(
83+
InfrastructureClientTestCustomResource.class,
84+
"infrastructure-client-resource");
85+
assertThat(r).isNotNull();
86+
});
87+
88+
assertThat(
89+
operator
90+
.getReconcilerOfType(InfrastructureClientTestReconciler.class)
91+
.getNumberOfExecutions())
92+
.isEqualTo(1);
93+
}
94+
95+
@Test
96+
void shouldNotAccessNotPermittedResources() {
97+
assertThatThrownBy(
98+
() ->
99+
operator
100+
.getKubernetesClient()
101+
.apiextensions()
102+
.v1()
103+
.customResourceDefinitions()
104+
.list())
105+
.isInstanceOf(KubernetesClientException.class)
106+
.hasMessageContaining(
107+
"User \"%s\" cannot list resource \"customresourcedefinitions\""
108+
.formatted(RBAC_TEST_USER));
109+
110+
// but we should be able to access all resources with the infrastructure client
111+
var deploymentList =
112+
operator
113+
.getInfrastructureKubernetesClient()
114+
.apiextensions()
115+
.v1()
116+
.customResourceDefinitions()
117+
.list();
118+
assertThat(deploymentList).isNotNull();
119+
}
120+
121+
private void applyClusterRoleBinding(String filename) {
122+
var clusterRoleBinding =
123+
ReconcilerUtils.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
124+
operator.getInfrastructureKubernetesClient().resource(clusterRoleBinding).serverSideApply();
125+
}
126+
127+
private void applyClusterRole(String filename) {
128+
var clusterRole = ReconcilerUtils.loadYaml(ClusterRole.class, this.getClass(), filename);
129+
operator.getInfrastructureKubernetesClient().resource(clusterRole).serverSideApply();
130+
}
131+
132+
private void removeClusterRoleBinding(String filename) {
133+
var clusterRoleBinding =
134+
ReconcilerUtils.loadYaml(ClusterRoleBinding.class, this.getClass(), filename);
135+
operator.getInfrastructureKubernetesClient().resource(clusterRoleBinding).delete();
136+
}
137+
138+
private void removeClusterRole(String filename) {
139+
var clusterRoleBinding = ReconcilerUtils.loadYaml(ClusterRole.class, this.getClass(), filename);
140+
operator.getInfrastructureKubernetesClient().resource(clusterRoleBinding).delete();
141+
}
142+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.javaoperatorsdk.operator.baseapi.infrastructureclient;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("ict")
12+
public class InfrastructureClientTestCustomResource extends CustomResource<Void, Void>
13+
implements Namespaced {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.javaoperatorsdk.operator.baseapi.infrastructureclient;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import io.javaoperatorsdk.operator.api.reconciler.Context;
9+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
10+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
11+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
12+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
13+
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
14+
15+
@ControllerConfiguration(name = InfrastructureClientTestReconciler.TEST_RECONCILER)
16+
public class InfrastructureClientTestReconciler
17+
implements Reconciler<InfrastructureClientTestCustomResource>, TestExecutionInfoProvider {
18+
19+
private static final Logger log =
20+
LoggerFactory.getLogger(InfrastructureClientTestReconciler.class);
21+
22+
public static final String TEST_RECONCILER = "InfrastructureClientTestReconciler";
23+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
24+
25+
@Override
26+
public UpdateControl<InfrastructureClientTestCustomResource> reconcile(
27+
InfrastructureClientTestCustomResource resource,
28+
Context<InfrastructureClientTestCustomResource> context) {
29+
numberOfExecutions.addAndGet(1);
30+
log.info("Reconciled for: {}", ResourceID.fromResource(resource));
31+
return UpdateControl.noUpdate();
32+
}
33+
34+
public int getNumberOfExecutions() {
35+
return numberOfExecutions.get();
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: ClusterRoleBinding
3+
metadata:
4+
name: rbac-test-role-binding
5+
subjects:
6+
- kind: User
7+
name: rbac-test-user
8+
apiGroup: rbac.authorization.k8s.io
9+
roleRef:
10+
kind: ClusterRole
11+
name: rbac-test-role
12+
apiGroup: rbac.authorization.k8s.io
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: ClusterRole
3+
metadata:
4+
name: rbac-test-role
5+
rules:
6+
- apiGroups: [ "apiextensions.k8s.io"]
7+
resources: [ "customresourcedefinitions" ]
8+
verbs: [ "create", "update", "patch", "delete", "deletecollection" ] # explicitly don't include "list" for the test
9+
- apiGroups: [ "sample.javaoperatorsdk" ]
10+
resources: [ "infrastructureclienttestcustomresources" ]
11+
verbs: [ "get", "list", "watch", "create", "update", "patch", "delete", "deletecollection" ]

0 commit comments

Comments
 (0)