Merge "Fix broken table in gerrit chart README"
diff --git a/operator/README.md b/operator/README.md
index 9ce7d15..a659108 100644
--- a/operator/README.md
+++ b/operator/README.md
@@ -5,10 +5,15 @@
To build all components of the operator run:
```sh
-# With E2E tests
-mvn clean install jib:dockerBuild
-# Without E2E tests
-mvn clean install -DskipTests jib:dockerBuild
+mvn clean install
+```
+
+## Publish
+
+To publish the container image of the Gerrit Operator run:
+
+```sh
+mvn clean install -P publish
```
## Tests
@@ -54,8 +59,17 @@
- `gerritPwd`: The password of `gerritUser`
The properties should be set in the `test.properties` file. Alternatively, a
-path of a properties file can be configured by using
-`mvn clean install -Dproperties=<path to properties file> $TARGET`
+path of a properties file can be configured by using the
+`-Dproperties=<path to properties file>`-option.
+
+To run all E2E tests, use:
+
+```sh
+mvn clean install -P integration-test -Dproperties=<path to properties file>
+```
+
+Note, that running the E2E tests will also involve pushing the container image
+to the repository configured in the properties file.
## Deploy
@@ -68,6 +82,24 @@
Note that these do not include the -v1beta1.yaml files, as those are for old
Kubernetes versions.
+The operator requires a Java Keystore with a keypair inside to allow TLS
+verification for Kubernetes Admission Webhooks. To create a keystore and
+encode it with base64, run:
+
+```sh
+keytool \
+ -genkeypair \
+ -alias operator \
+ -keystore keystore \
+ -keyalg RSA \
+ -keysize 2048 \
+ -validity 3650
+cat keystore | base64 -b 0
+```
+
+Add the result to the Secret in `k8s/operator.yaml` (see comments in the file)
+and also add the base64-encoded password for the keystore to the secret.
+
Then the operator and associated RBAC rules can be deployed:
```sh
diff --git a/operator/k8s/operator.yaml b/operator/k8s/operator.yaml
index 47b0bb4..0d43bdb 100644
--- a/operator/k8s/operator.yaml
+++ b/operator/k8s/operator.yaml
@@ -10,6 +10,19 @@
name: gerrit-operator
namespace: gerrit-operator
+## Required to use an external/persistent keystore, otherwise a keystore using
+## self-signed certificates will be generated
+# ---
+# apiVersion: v1
+# kind: Secret
+# metadata:
+# name: gerrit-operator-ssl
+# namespace: gerrit-operator
+# data:
+# keystore.jks: # base64-encoded Java keystore
+# keystore.password: # base64-encoded Java keystore password
+# type: Opaque
+
---
apiVersion: apps/v1
kind: Deployment
@@ -30,18 +43,34 @@
- name: operator
image: k8sgerrit/gerrit-operator
imagePullPolicy: Always
+ env:
+ - name: NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /health
port: 8080
+ scheme: HTTPS
initialDelaySeconds: 1
livenessProbe:
httpGet:
path: /health
port: 8080
+ scheme: HTTPS
initialDelaySeconds: 30
+ ## Only required, if an external/persistent keystore is being used.
+ # volumeMounts:
+ # - name: ssl
+ # readOnly: true
+ # mountPath: /operator
+ # volumes:
+ # - name: ssl
+ # secret:
+ # secretName: gerrit-operator-ssl
---
apiVersion: rbac.authorization.k8s.io/v1
diff --git a/operator/pom.xml b/operator/pom.xml
index 649e89b..6c713bd 100644
--- a/operator/pom.xml
+++ b/operator/pom.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit.operator</groupId>
@@ -14,10 +16,126 @@
<fabric8.version>6.2.0</fabric8.version>
<flogger.version>0.7.4</flogger.version>
<javaoperatorsdk.version>4.2.4</javaoperatorsdk.version>
+ <jetty.version>11.0.15</jetty.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
+ <docker.registry>docker.io</docker.registry>
+ <docker.org>k8sgerrit</docker.org>
+
+ <test.docker.registry>docker.io</test.docker.registry>
+ <test.docker.org>k8sgerritdev</test.docker.org>
</properties>
+ <profiles>
+ <profile>
+ <id>publish</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-maven-plugin</artifactId>
+ <version>3.3.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <container>
+ <mainClass>com.google.gerrit.k8s.operator.Main</mainClass>
+ </container>
+ <containerizingMode>packaged</containerizingMode>
+ <from>
+ <image>gcr.io/distroless/java:11</image>
+ </from>
+ <to>
+ <image>${docker.registry}/${docker.org}/gerrit-operator</image>
+ <tags>
+ <tag>${project.version}</tag>
+ </tags>
+ </to>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>integration-test</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>properties-maven-plugin</artifactId>
+ <version>1.1.0</version>
+ <executions>
+ <execution>
+ <phase>initialize</phase>
+ <goals>
+ <goal>read-project-properties</goal>
+ </goals>
+ <configuration>
+ <files>
+ <file>${basedir}/test.properties</file>
+ </files>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-maven-plugin</artifactId>
+ <version>3.3.1</version>
+ <executions>
+ <execution>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <container>
+ <mainClass>com.google.gerrit.k8s.operator.Main</mainClass>
+ </container>
+ <containerizingMode>packaged</containerizingMode>
+ <from>
+ <image>gcr.io/distroless/java:11</image>
+ </from>
+ <to>
+ <image>
+ ${test.docker.registry}/${test.docker.org}/gerrit-operator</image>
+ <tags>
+ <tag>${project.version}</tag>
+ </tags>
+ </to>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.22.2</version>
+ <executions>
+ <execution>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/*E2E.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
<dependencies>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
@@ -41,9 +159,9 @@
<version>${fabric8.version}</version>
</dependency>
<dependency>
- <groupId>io.fabric8</groupId>
- <artifactId>istio-client</artifactId>
- <version>${fabric8.version}</version>
+ <groupId>io.fabric8</groupId>
+ <artifactId>istio-client</artifactId>
+ <version>${fabric8.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
@@ -52,9 +170,14 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.takes</groupId>
- <artifactId>takes</artifactId>
- <version>1.19</version>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>com.google.flogger</groupId>
@@ -67,6 +190,11 @@
<version>${flogger.version}</version>
</dependency>
<dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <version>5.1.0</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.19.0</version>
@@ -74,7 +202,12 @@
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
- <version>6.3.0.202209071007-r</version>
+ <version>6.5.0.202303070854-r</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk18on</artifactId>
+ <version>1.73</version>
</dependency>
<dependency>
<groupId>com.urswolfer.gerrit.client.rest</groupId>
@@ -111,7 +244,7 @@
<configuration>
<archive>
<manifest>
- <mainClass>com.google.gerrit.k8s.operator.GerritOperator</mainClass>
+ <mainClass>com.google.gerrit.k8s.operator.Main</mainClass>
<addDefaultImplementationEntries>
true
</addDefaultImplementationEntries>
@@ -123,18 +256,26 @@
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.1</version>
- <configuration>
- <container>
- <mainClass>com.google.gerrit.k8s.operator.GerritOperator</mainClass>
- </container>
- <containerizingMode>packaged</containerizingMode>
- <from>
- <image>gcr.io/distroless/java:11</image>
- </from>
- <to>
- <image>gerrit-operator</image>
- </to>
- </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>dockerBuild</goal>
+ </goals>
+ <configuration>
+ <container>
+ <mainClass>com.google.gerrit.k8s.operator.Main</mainClass>
+ </container>
+ <containerizingMode>packaged</containerizingMode>
+ <from>
+ <image>gcr.io/distroless/java:11</image>
+ </from>
+ <to>
+ <image>gerrit-operator</image>
+ </to>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -147,7 +288,6 @@
<version>2.22.2</version>
<configuration>
<includes>
- <include>**/*E2E.java</include>
<include>**/*Test.java</include>
</includes>
<rerunFailingTestsCount>1</rerunFailingTestsCount>
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/EnvModule.java b/operator/src/main/java/com/google/gerrit/k8s/operator/EnvModule.java
new file mode 100644
index 0000000..c9d3739
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/EnvModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class EnvModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(String.class)
+ .annotatedWith(Names.named("Namespace"))
+ .toInstance(System.getenv("NAMESPACE"));
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java b/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
index c630846..6f9be7e 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/GerritOperator.java
@@ -14,40 +14,98 @@
package com.google.gerrit.k8s.operator;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.k8s.operator.cluster.GerritClusterReconciler;
-import com.google.gerrit.k8s.operator.gerrit.GerritReconciler;
-import com.google.gerrit.k8s.operator.gitgc.GitGarbageCollectionReconciler;
-import com.google.gerrit.k8s.operator.receiver.ReceiverReconciler;
-import io.fabric8.kubernetes.client.Config;
-import io.fabric8.kubernetes.client.ConfigBuilder;
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.KubernetesClientBuilder;
-import io.javaoperatorsdk.operator.Operator;
-import java.io.IOException;
-import org.takes.facets.fork.FkRegex;
-import org.takes.facets.fork.TkFork;
-import org.takes.http.Exit;
-import org.takes.http.FtBasic;
+import static com.google.gerrit.k8s.operator.server.HttpServer.PORT;
+import com.google.common.flogger.FluentLogger;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.fabric8.kubernetes.api.model.ServicePort;
+import io.fabric8.kubernetes.api.model.ServicePortBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.javaoperatorsdk.operator.Operator;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import java.util.Map;
+import java.util.Set;
+
+@Singleton
public class GerritOperator {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ public static final String SERVICE_NAME = "gerrit-operator";
+ public static final int SERVICE_PORT = 8080;
- public static void main(String[] args) throws IOException {
- Config config = new ConfigBuilder().withNamespace(null).build();
- KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();
- Operator operator = new Operator(client);
- logger.atFine().log("Registering GerritCluster Reconciler");
- operator.register(new GerritClusterReconciler(client));
- logger.atFine().log("Registering GitGc Reconciler");
- operator.register(new GitGarbageCollectionReconciler(client));
- logger.atFine().log("Registering Gerrit Reconciler");
- operator.register(new GerritReconciler(client));
- logger.atFine().log("Registering Receiver Reconciler");
- operator.register(new ReceiverReconciler(client));
- operator.installShutdownHook();
+ private final KubernetesClient client;
+ private final LifecycleManager lifecycleManager;
+
+ @SuppressWarnings("rawtypes")
+ private final Set<Reconciler> reconcilers;
+
+ private final String namespace;
+
+ private Operator operator;
+ private Service svc;
+
+ @Inject
+ @SuppressWarnings("rawtypes")
+ public GerritOperator(
+ LifecycleManager lifecycleManager,
+ KubernetesClient client,
+ Set<Reconciler> reconcilers,
+ @Named("Namespace") String namespace) {
+ this.lifecycleManager = lifecycleManager;
+ this.client = client;
+ this.reconcilers = reconcilers;
+ this.namespace = namespace;
+ }
+
+ public void start() throws Exception {
+ operator = new Operator(client);
+ for (Reconciler<?> reconciler : reconcilers) {
+ logger.atInfo().log(
+ String.format("Registering reconciler: %s", reconciler.getClass().getSimpleName()));
+ operator.register(reconciler);
+ }
operator.start();
+ lifecycleManager.addShutdownHook(
+ new Runnable() {
+ @Override
+ public void run() {
+ shutdown();
+ }
+ });
+ applyService();
+ }
- new FtBasic(new TkFork(new FkRegex("/health", "ALL GOOD.")), 8080).start(Exit.NEVER);
+ public void shutdown() {
+ client.resource(svc).delete();
+ operator.stop();
+ }
+
+ private void applyService() {
+ ServicePort port =
+ new ServicePortBuilder()
+ .withName("http")
+ .withPort(SERVICE_PORT)
+ .withNewTargetPort(PORT)
+ .withProtocol("TCP")
+ .build();
+ svc =
+ new ServiceBuilder()
+ .withApiVersion("v1")
+ .withNewMetadata()
+ .withName(SERVICE_NAME)
+ .withNamespace(namespace)
+ .endMetadata()
+ .withNewSpec()
+ .withType("ClusterIP")
+ .withPorts(port)
+ .withSelector(Map.of("app", "gerrit-operator"))
+ .endSpec()
+ .build();
+
+ logger.atInfo().log(String.format("Applying Service for Gerrit Operator: %s", svc.toString()));
+ client.resource(svc).createOrReplace();
}
}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/LifecycleManager.java b/operator/src/main/java/com/google/gerrit/k8s/operator/LifecycleManager.java
new file mode 100644
index 0000000..8e556a7
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/LifecycleManager.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
+
+@Singleton
+public class LifecycleManager {
+ private List<Runnable> shutdownHooks = new ArrayList<>();
+
+ public LifecycleManager() {
+ Runtime.getRuntime().addShutdownHook(new Thread(this::executeShutdownHooks));
+ }
+
+ public void addShutdownHook(Runnable hook) {
+ shutdownHooks.add(hook);
+ }
+
+ private void executeShutdownHooks() {
+ for (Runnable hook : Lists.reverse(shutdownHooks)) {
+ hook.run();
+ }
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/Main.java b/operator/src/main/java/com/google/gerrit/k8s/operator/Main.java
new file mode 100644
index 0000000..cfba467
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/Main.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator;
+
+import com.google.gerrit.k8s.operator.server.HttpServer;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+ Injector injector = Guice.createInjector(Stage.PRODUCTION, new OperatorModule());
+ injector.getInstance(GerritOperator.class).start();
+ injector.getInstance(HttpServer.class).start();
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/OperatorModule.java b/operator/src/main/java/com/google/gerrit/k8s/operator/OperatorModule.java
new file mode 100644
index 0000000..4e78219
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/OperatorModule.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator;
+
+import com.google.gerrit.k8s.operator.cluster.GerritClusterReconciler;
+import com.google.gerrit.k8s.operator.gerrit.GerritReconciler;
+import com.google.gerrit.k8s.operator.gitgc.GitGarbageCollectionReconciler;
+import com.google.gerrit.k8s.operator.receiver.ReceiverReconciler;
+import com.google.gerrit.k8s.operator.server.ServerModule;
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+
+public class OperatorModule extends AbstractModule {
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected void configure() {
+ install(new EnvModule());
+ install(new ServerModule());
+
+ bind(String.class)
+ .annotatedWith(Names.named("Namespace"))
+ .toInstance(System.getenv("NAMESPACE"));
+ bind(KubernetesClient.class).toInstance(getKubernetesClient());
+ bind(LifecycleManager.class);
+ bind(GerritOperator.class);
+ Multibinder<Reconciler> reconcilers = Multibinder.newSetBinder(binder(), Reconciler.class);
+ reconcilers.addBinding().to(GerritClusterReconciler.class);
+ reconcilers.addBinding().to(GerritReconciler.class);
+ reconcilers.addBinding().to(GitGarbageCollectionReconciler.class);
+ reconcilers.addBinding().to(ReceiverReconciler.class);
+ }
+
+ private KubernetesClient getKubernetesClient() {
+ Config config = new ConfigBuilder().withNamespace(null).build();
+ return new KubernetesClientBuilder().withConfig(config).build();
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
index 48a973c..a9267ce 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
@@ -18,6 +18,8 @@
import com.google.gerrit.k8s.operator.gerrit.Gerrit;
import com.google.gerrit.k8s.operator.receiver.Receiver;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
@@ -38,6 +40,7 @@
import java.util.Map;
import java.util.stream.Collectors;
+@Singleton
@ControllerConfiguration(
dependents = {
@Dependent(type = GitRepositoriesPVC.class, useEventSourceWithName = PVC_EVENT_SOURCE),
@@ -56,19 +59,20 @@
private static final String GERRIT_INGRESS_EVENT_SOURCE = "gerrit-ingress";
private static final String GERRIT_ISTIO_EVENT_SOURCE = "gerrit-istio";
- private final KubernetesClient kubernetesClient;
+ private final KubernetesClient client;
private GerritIngress gerritIngress;
private GerritIstioGateway gerritIstioGateway;
+ @Inject
public GerritClusterReconciler(KubernetesClient client) {
- this.kubernetesClient = client;
+ this.client = client;
this.gerritIngress = new GerritIngress();
- this.gerritIngress.setKubernetesClient(kubernetesClient);
+ this.gerritIngress.setKubernetesClient(client);
this.gerritIstioGateway = new GerritIstioGateway();
- this.gerritIstioGateway.setKubernetesClient(kubernetesClient);
+ this.gerritIstioGateway.setKubernetesClient(client);
}
@Override
@@ -158,7 +162,7 @@
private List<String> getManagedMemberInstances(
GerritCluster gerritCluster,
Class<? extends GerritClusterMember<? extends GerritClusterMemberSpec, ?>> clazz) {
- return kubernetesClient
+ return client
.resources(clazz)
.inNamespace(gerritCluster.getMetadata().getNamespace())
.list()
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/GerritReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/GerritReconciler.java
index 96238a2..00d80cd 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/GerritReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/GerritReconciler.java
@@ -19,6 +19,8 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.k8s.operator.cluster.GerritCluster;
import com.google.gerrit.k8s.operator.cluster.GerritIngressConfig.IngressType;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.KubernetesClient;
@@ -44,6 +46,7 @@
import java.util.Optional;
import java.util.stream.Collectors;
+@Singleton
@ControllerConfiguration(
dependents = {
@Dependent(
@@ -73,6 +76,7 @@
private final GerritIstioVirtualService virtualService;
private final GerritIstioDestinationRule destinationRule;
+ @Inject
public GerritReconciler(KubernetesClient client) {
this.client = client;
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionReconciler.java
index 45bd3c1..9fc7012 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionReconciler.java
@@ -17,6 +17,8 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.k8s.operator.cluster.GerritCluster;
import com.google.gerrit.k8s.operator.gitgc.GitGarbageCollectionStatus.GitGcState;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Context;
@@ -38,20 +40,22 @@
import java.util.Set;
import java.util.stream.Collectors;
+@Singleton
@ControllerConfiguration
public class GitGarbageCollectionReconciler
implements Reconciler<GitGarbageCollection>,
EventSourceInitializer<GitGarbageCollection>,
ErrorStatusHandler<GitGarbageCollection> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final KubernetesClient kubernetesClient;
+ private final KubernetesClient client;
private GitGarbageCollectionCronJob dependentCronJob;
- public GitGarbageCollectionReconciler(KubernetesClient kubernetesClient) {
- this.kubernetesClient = kubernetesClient;
+ @Inject
+ public GitGarbageCollectionReconciler(KubernetesClient client) {
+ this.client = client;
this.dependentCronJob = new GitGarbageCollectionCronJob();
- this.dependentCronJob.setKubernetesClient(kubernetesClient);
+ this.dependentCronJob.setKubernetesClient(client);
}
@Override
@@ -116,7 +120,7 @@
private GitGarbageCollection excludeProjectsHandledSeparately(GitGarbageCollection currentGitGc) {
List<GitGarbageCollection> gitGcs =
- kubernetesClient
+ client
.resources(GitGarbageCollection.class)
.inNamespace(currentGitGc.getMetadata().getNamespace())
.list()
@@ -134,7 +138,7 @@
private void validateGitGCProjectList(GitGarbageCollection gitGc) {
List<GitGarbageCollection> gitGcs =
- kubernetesClient
+ client
.resources(GitGarbageCollection.class)
.inNamespace(gitGc.getMetadata().getNamespace())
.list()
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/ReceiverReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/ReceiverReconciler.java
index ba5918a..a8825a4 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/ReceiverReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/receiver/ReceiverReconciler.java
@@ -17,6 +17,8 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.k8s.operator.cluster.GerritCluster;
import com.google.gerrit.k8s.operator.cluster.GerritIngressConfig.IngressType;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
@@ -34,6 +36,7 @@
import java.util.Map;
import java.util.stream.Collectors;
+@Singleton
@ControllerConfiguration(
dependents = {
@Dependent(name = "receiver-deployment", type = ReceiverDeploymentDependentResource.class),
@@ -49,6 +52,7 @@
private final ReceiverIstioVirtualService virtualService;
+ @Inject
public ReceiverReconciler(KubernetesClient client) {
this.client = client;
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/FileSystemKeyStoreProvider.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/FileSystemKeyStoreProvider.java
new file mode 100644
index 0000000..fe2ef88
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/FileSystemKeyStoreProvider.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@Singleton
+public class FileSystemKeyStoreProvider implements KeyStoreProvider {
+ static final String KEYSTORE_PATH = "/operator/keystore.jks";
+ static final String KEYSTORE_PWD_FILE = "/operator/keystore.password";
+
+ @Override
+ public Path getKeyStorePath() {
+ return Path.of(KEYSTORE_PATH);
+ }
+
+ @Override
+ public String getKeyStorePassword() throws IOException {
+ return Files.readString(Path.of(KEYSTORE_PWD_FILE));
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/GeneratedKeyStoreProvider.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/GeneratedKeyStoreProvider.java
new file mode 100644
index 0000000..ba4f7c4
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/GeneratedKeyStoreProvider.java
@@ -0,0 +1,133 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import static com.google.gerrit.k8s.operator.GerritOperator.SERVICE_NAME;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+@Singleton
+public class GeneratedKeyStoreProvider implements KeyStoreProvider {
+ private static final Path KEYSTORE_PATH = Path.of("/tmp/keystore.jks");
+ private static final String ALIAS = "operator";
+
+ private final String namespace;
+ private final String password;
+
+ @Inject
+ public GeneratedKeyStoreProvider(@Named("Namespace") String namespace) {
+ this.namespace = namespace;
+ this.password = generatePassword();
+ generateKeyStore();
+ }
+
+ @Override
+ public Path getKeyStorePath() {
+ return KEYSTORE_PATH;
+ }
+
+ @Override
+ public String getKeyStorePassword() {
+ return password;
+ }
+
+ private String getCN() {
+ return String.format("%s.%s.svc", SERVICE_NAME, namespace);
+ }
+
+ private String generatePassword() {
+ return RandomStringUtils.randomAlphabetic(10);
+ }
+
+ private Certificate generateCertificate(KeyPair keyPair)
+ throws OperatorCreationException, CertificateException, CertIOException {
+ BouncyCastleProvider bcProvider = new BouncyCastleProvider();
+ Security.addProvider(bcProvider);
+
+ Instant start = Instant.now();
+ X500Name dnName = new X500Name(String.format("cn=%s", getCN()));
+ DERSequence subjectAlternativeNames =
+ new DERSequence(new ASN1Encodable[] {new GeneralName(GeneralName.dNSName, getCN())});
+
+ X509v3CertificateBuilder certBuilder =
+ new JcaX509v3CertificateBuilder(
+ dnName,
+ BigInteger.valueOf(start.toEpochMilli()),
+ Date.from(start),
+ Date.from(start.plus(365, ChronoUnit.DAYS)),
+ dnName,
+ keyPair.getPublic())
+ .addExtension(Extension.subjectAlternativeName, true, subjectAlternativeNames);
+
+ ContentSigner contentSigner =
+ new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
+ return new JcaX509CertificateConverter()
+ .setProvider(bcProvider)
+ .getCertificate(certBuilder.build(contentSigner));
+ }
+
+ private void generateKeyStore() {
+ KEYSTORE_PATH.getParent().toFile().mkdirs();
+ try (FileOutputStream fos = new FileOutputStream(KEYSTORE_PATH.toFile())) {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(4096);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ Certificate[] chain = {generateCertificate(keyPair)};
+
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null, null);
+ keyStore.setKeyEntry(ALIAS, keyPair.getPrivate(), password.toCharArray(), chain);
+ keyStore.store(fos, password.toCharArray());
+ } catch (IOException
+ | NoSuchAlgorithmException
+ | CertificateException
+ | KeyStoreException
+ | OperatorCreationException e) {
+ throw new IllegalStateException("Failed to create keystore.", e);
+ }
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/HealthcheckServlet.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/HealthcheckServlet.java
new file mode 100644
index 0000000..6097bde
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/HealthcheckServlet.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class HealthcheckServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ response.setContentType("application/text");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().println("ALL GOOD.");
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/HttpServer.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/HttpServer.java
new file mode 100644
index 0000000..10956d8
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/HttpServer.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+@Singleton
+public class HttpServer {
+ public static final String KEYSTORE_PATH = "/operator/keystore.jks";
+ public static final String KEYSTORE_PWD_FILE = "/operator/keystore.password";
+ public static final int PORT = 8080;
+
+ private final Server server = new Server();
+ private final KeyStoreProvider keyStoreProvider;
+
+ @Inject
+ public HttpServer(KeyStoreProvider keyStoreProvider) {
+ this.keyStoreProvider = keyStoreProvider;
+ }
+
+ public void start() throws Exception {
+ SslContextFactory.Server ssl = new SslContextFactory.Server();
+ ssl.setKeyStorePath(keyStoreProvider.getKeyStorePath().toString());
+ ssl.setTrustStorePath(keyStoreProvider.getKeyStorePath().toString());
+ ssl.setKeyStorePassword(keyStoreProvider.getKeyStorePassword());
+ ssl.setTrustStorePassword(keyStoreProvider.getKeyStorePassword());
+ ssl.setSniRequired(false);
+
+ HttpConfiguration sslConfiguration = new HttpConfiguration();
+ sslConfiguration.addCustomizer(new SecureRequestCustomizer(false));
+ HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(sslConfiguration);
+
+ ServerConnector connector = new ServerConnector(server, ssl, httpConnectionFactory);
+ connector.setPort(PORT);
+ server.setConnectors(new Connector[] {connector});
+
+ ServletHandler servletHandler = new ServletHandler();
+ servletHandler.addServletWithMapping(HealthcheckServlet.class, "/health");
+ server.setHandler(servletHandler);
+
+ server.start();
+ }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/KeyStoreProvider.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/KeyStoreProvider.java
new file mode 100644
index 0000000..de2cf62
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/KeyStoreProvider.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface KeyStoreProvider {
+ Path getKeyStorePath();
+
+ String getKeyStorePassword() throws IOException;
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/server/ServerModule.java b/operator/src/main/java/com/google/gerrit/k8s/operator/server/ServerModule.java
new file mode 100644
index 0000000..cc1a3aa
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/server/ServerModule.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.k8s.operator.server;
+
+import static com.google.gerrit.k8s.operator.server.FileSystemKeyStoreProvider.KEYSTORE_PATH;
+
+import com.google.inject.AbstractModule;
+import java.io.File;
+
+public class ServerModule extends AbstractModule {
+ public void configure() {
+ if (new File(KEYSTORE_PATH).exists()) {
+ bind(KeyStoreProvider.class).to(FileSystemKeyStoreProvider.class);
+ } else {
+ bind(KeyStoreProvider.class).to(GeneratedKeyStoreProvider.class);
+ }
+ bind(HttpServer.class);
+ }
+}