From 79953687f950f9859d7b7c8ebbf23265d206d545 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 29 Jul 2019 16:52:29 -0300 Subject: [PATCH] feat(operator): Add Quarkus Operator Based on https://www.instana.com/blog/writing-a-kubernetes-operator-in-java-part-3/ Fixes #898 --- cico_build_deploy.sh | 7 +- .../crds/launcher_v1alpha1_launcher_crd.yaml | 15 +++ operator/deploy/operator.yaml | 51 ++++++++ operator/deploy/role.yaml | 71 +++++++++++ operator/deploy/role_binding.yaml | 11 ++ operator/deploy/service_account.yaml | 4 + operator/example/cr_minimum.yaml | 19 +++ operator/example/cr_next.yaml | 36 ++++++ operator/example/launcher_cr.yaml | 13 ++ operator/pom.xml | 120 ++++++++++++++++++ operator/src/main/docker/Dockerfile.jvm | 22 ++++ operator/src/main/docker/Dockerfile.native | 26 ++++ .../launcher/operator/ClientProvider.java | 45 +++++++ .../launcher/operator/LauncherOperator.java | 44 +++++++ .../operator/LauncherResourceCache.java | 85 +++++++++++++ .../operator/cr/LauncherResource.java | 23 ++++ .../operator/cr/LauncherResourceDoneable.java | 10 ++ .../operator/cr/LauncherResourceList.java | 6 + .../operator/cr/LauncherResourceSpec.java | 102 +++++++++++++++ .../src/main/resources/application.properties | 3 + .../operator/LauncherOperatorTest.java | 14 ++ .../operator/cr/LauncherResourceTest.java | 29 +++++ pom.xml | 6 + web/pom.xml | 1 - 24 files changed, 761 insertions(+), 2 deletions(-) create mode 100644 operator/deploy/crds/launcher_v1alpha1_launcher_crd.yaml create mode 100644 operator/deploy/operator.yaml create mode 100644 operator/deploy/role.yaml create mode 100644 operator/deploy/role_binding.yaml create mode 100644 operator/deploy/service_account.yaml create mode 100644 operator/example/cr_minimum.yaml create mode 100644 operator/example/cr_next.yaml create mode 100644 operator/example/launcher_cr.yaml create mode 100644 operator/pom.xml create mode 100644 operator/src/main/docker/Dockerfile.jvm create mode 100644 operator/src/main/docker/Dockerfile.native create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/ClientProvider.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/LauncherOperator.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/LauncherResourceCache.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResource.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceDoneable.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceList.java create mode 100644 operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceSpec.java create mode 100644 operator/src/main/resources/application.properties create mode 100644 operator/src/test/java/io/fabric8/launcher/operator/LauncherOperatorTest.java create mode 100644 operator/src/test/java/io/fabric8/launcher/operator/cr/LauncherResourceTest.java diff --git a/cico_build_deploy.sh b/cico_build_deploy.sh index e3032aa39..da455d86e 100755 --- a/cico_build_deploy.sh +++ b/cico_build_deploy.sh @@ -69,7 +69,7 @@ docker build -t ${BUILDER_IMAGE} -f Dockerfile.build . docker run --detach=true --name ${BUILDER_CONT} -t -v $(pwd)/${TARGET_DIR}:/${TARGET_DIR}:Z ${BUILDER_IMAGE} /bin/tail -f /dev/null #FIXME -docker exec ${BUILDER_CONT} ./mvnw -B clean install -Dmaven.test.skip=true -DfailIfNoTests=false -DskipTests -Ddownload.plugin.skip.cache +docker exec ${BUILDER_CONT} ./mvnw -B clean install -Dmaven.test.skip=true -DfailIfNoTests=false -DskipTests -Ddownload.plugin.skip.cache -DoperatorNative docker exec -u root ${BUILDER_CONT} cp web/target/launcher-runner.jar /${TARGET_DIR} docker exec -u root ${BUILDER_CONT} cp -r web/target/lib/ /${TARGET_DIR}/lib @@ -84,6 +84,11 @@ if [ -z $CICO_LOCAL ]; then tag_push "${REGISTRY_URL}:${TAG}" tag_push "${REGISTRY_URL}:latest" + # Push Operator + docker tag "quay.io/launcher/launcher-operator:latest" "quay.io/launcher/launcher-operator:${TAG}" + docker push "quay.io/launcher/launcher-operator:${TAG}" + docker push "quay.io/launcher/launcher-operator:latest" + if [[ "$TARGET" != "rhel" && -n "${GENERATOR_DOCKER_HUB_PASSWORD}" ]]; then docker_login "${GENERATOR_DOCKER_HUB_USERNAME}" "${GENERATOR_DOCKER_HUB_PASSWORD}" tag_push "${DOCKER_HUB_URL}:${TAG}" diff --git a/operator/deploy/crds/launcher_v1alpha1_launcher_crd.yaml b/operator/deploy/crds/launcher_v1alpha1_launcher_crd.yaml new file mode 100644 index 000000000..163ef1c66 --- /dev/null +++ b/operator/deploy/crds/launcher_v1alpha1_launcher_crd.yaml @@ -0,0 +1,15 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: launchers.launcher.fabric8.io +spec: + group: launcher.fabric8.io + names: + kind: Launcher + listKind: LauncherList + plural: launchers + singular: launcher + scope: Namespaced + version: v1alpha2 + subresources: + status: {} diff --git a/operator/deploy/operator.yaml b/operator/deploy/operator.yaml new file mode 100644 index 000000000..56056b2b0 --- /dev/null +++ b/operator/deploy/operator.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: launcher-operator +spec: + replicas: 1 + selector: + matchLabels: + name: launcher-operator + template: + metadata: + labels: + name: launcher-operator + spec: + serviceAccountName: launcher-operator + containers: + - name: launcher-operator + image: quay.io/gastaldi/launcher-operator:latest + ports: + - containerPort: 60000 + name: metrics + imagePullPolicy: Always + readinessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 4 + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 4 + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 1 + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "launcher-operator" \ No newline at end of file diff --git a/operator/deploy/role.yaml b/operator/deploy/role.yaml new file mode 100644 index 000000000..69b474dad --- /dev/null +++ b/operator/deploy/role.yaml @@ -0,0 +1,71 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: launcher-operator +rules: + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + - serviceaccounts + - processedtemplates.template.openshift.io + verbs: + - '*' + - apiGroups: + - template.openshift.io + resources: + - processedtemplates + verbs: [ get, list, create, update, delete, deletecollection, watch] + - apiGroups: + - "" + - apps.openshift.io + resources: + - deploymentconfigs + - deploymentconfigs/instantiate + verbs: [ get, list, create, update, delete, deletecollection, watch] + - apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: [ get, list, create, update, delete, deletecollection, watch] + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create + - apiGroups: + - launcher.fabric8.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - list \ No newline at end of file diff --git a/operator/deploy/role_binding.yaml b/operator/deploy/role_binding.yaml new file mode 100644 index 000000000..336c322d6 --- /dev/null +++ b/operator/deploy/role_binding.yaml @@ -0,0 +1,11 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: launcher-operator +subjects: + - kind: ServiceAccount + name: launcher-operator +roleRef: + kind: Role + name: launcher-operator + apiGroup: rbac.authorization.k8s.io diff --git a/operator/deploy/service_account.yaml b/operator/deploy/service_account.yaml new file mode 100644 index 000000000..16f3d463e --- /dev/null +++ b/operator/deploy/service_account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: launcher-operator diff --git a/operator/example/cr_minimum.yaml b/operator/example/cr_minimum.yaml new file mode 100644 index 000000000..d2d606976 --- /dev/null +++ b/operator/example/cr_minimum.yaml @@ -0,0 +1,19 @@ +apiVersion: launcher.fabric8.io/v1alpha2 +kind: Launcher +metadata: + name: example-launcher +spec: + openshift: + consoleUrl: consoleUrl + git: + providers: + - id: GitHub + name: "GitHub" + apiUrl: https://api.github.com + repositoryUrl: https://github.com + type: GITHUB + clientProperties: + clientId: CHANGE_TO_CLIENT_ID + serverProperties: + oauthUrl: https://github.com/login/oauth/access_token + clientSecret: CHANGE_TO_CLIENT_SECRET diff --git a/operator/example/cr_next.yaml b/operator/example/cr_next.yaml new file mode 100644 index 000000000..549f2efe1 --- /dev/null +++ b/operator/example/cr_next.yaml @@ -0,0 +1,36 @@ +apiVersion: launcher.fabric8.io/v1alpha2 +kind: Launcher +metadata: + name: example-launcher +spec: + environment: production + catalog: + repositoryUrl: http://github.com/fabric8-launcher/launcher-booster-catalog + repositoryRef: master + filter: filter expression + openshift: + apiUrl: https://api.cluster.openshift.com + consoleUrl: https://console.cluster.openshift.com + username: username + password: password + token: token + impersonate: true + clientId: launcher + git: + providers: + - id: GitHub + name: "GitHub" + apiUrl: https://api.github.com + repositoryUrl: https://github.com + type: GITHUB + clientProperties: + clientId: CHANGE_TO_CLIENT_ID + serverProperties: + oauthUrl: https://github.com/login/oauth/access_token + clientSecret: CHANGE_TO_CLIENT_SECRET + username: username + token: token + keycloak: + url: https://sso.openshift.io/auth + realm: rh-developers-launch + clientId: launcher diff --git a/operator/example/launcher_cr.yaml b/operator/example/launcher_cr.yaml new file mode 100644 index 000000000..55710864a --- /dev/null +++ b/operator/example/launcher_cr.yaml @@ -0,0 +1,13 @@ +apiVersion: launcher.fabric8.io/v1alpha2 +kind: Launcher +metadata: + name: launcher +spec: + +####### OpenShift Configuration + openshift: + consoleUrl: ## + +####### OAuth Configuration + oauth: + enabled: true diff --git a/operator/pom.xml b/operator/pom.xml new file mode 100644 index 000000000..d3ca2d61e --- /dev/null +++ b/operator/pom.xml @@ -0,0 +1,120 @@ + + + + launcher-parent + io.fabric8.launcher + 1-SNAPSHOT + + 4.0.0 + launcher-operator + + + io.quarkus + quarkus-kubernetes-client + + + io.quarkus + quarkus-smallrye-health + + + io.fabric8.launcher + launcher-service-git-api + + + io.fabric8.launcher + launcher-service-openshift-api + + + + io.fabric8.launcher + launcher-base-test + test + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-kubernetes-client + test + + + + + launcher-operator + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + + + + + native + + + operatorNative + + + + true + + + + + io.quarkus + quarkus-maven-plugin + + + + native-image + + + true + -H:IncludeResources=../templates/openshift/launcher-template.yaml + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + \ No newline at end of file diff --git a/operator/src/main/docker/Dockerfile.jvm b/operator/src/main/docker/Dockerfile.jvm new file mode 100644 index 000000000..33d88f2eb --- /dev/null +++ b/operator/src/main/docker/Dockerfile.jvm @@ -0,0 +1,22 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/operator-example-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/operator-example-jvm +# +### +FROM fabric8/java-alpine-openjdk8-jre +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV AB_ENABLED=jmx_exporter +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/app.jar +ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file diff --git a/operator/src/main/docker/Dockerfile.native b/operator/src/main/docker/Dockerfile.native new file mode 100644 index 000000000..39a4c0a80 --- /dev/null +++ b/operator/src/main/docker/Dockerfile.native @@ -0,0 +1,26 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the docker image run: +# +# mvn package -Pnative -Dnative-image.docker-build=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t fabric8/launcher-operator . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 fabric8/launcher-operator +# +### + +FROM quay.io/quarkus/ubi-quarkus-native-image:19.0.2 as builder + +FROM registry.access.redhat.com/ubi8/ubi-minimal +WORKDIR /work/ +COPY target/*-runner /work/application +COPY --from=builder /opt/graalvm/jre/lib/amd64/libsunec.so /work/library/ +RUN chmod 775 /work +EXPOSE 8080 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0", "-Djava.library.path=/work/library"] \ No newline at end of file diff --git a/operator/src/main/java/io/fabric8/launcher/operator/ClientProvider.java b/operator/src/main/java/io/fabric8/launcher/operator/ClientProvider.java new file mode 100644 index 000000000..87c29a15e --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/ClientProvider.java @@ -0,0 +1,45 @@ +package io.fabric8.launcher.operator; + +import io.fabric8.kubernetes.api.model.apiextensions.CustomResourceDefinition; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.internal.KubernetesDeserializer; +import io.fabric8.launcher.operator.cr.LauncherResource; +import io.fabric8.launcher.operator.cr.LauncherResourceDoneable; +import io.fabric8.launcher.operator.cr.LauncherResourceList; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +public class ClientProvider { + + @Produces + @Singleton + KubernetesClient newClient(@ConfigProperty(name = "WATCH_NAMESPACE", defaultValue = "default") String namespace) { + // TODO: Remove when new quarkus is released + return new DefaultKubernetesClient().inNamespace(namespace); + } + + @Produces + @Singleton + NonNamespaceOperation> + makeCustomResourceClient(KubernetesClient defaultClient) { + + KubernetesDeserializer.registerCustomKind("launcher.fabric8.io/v1alpha1", "Launcher", LauncherResource.class); + CustomResourceDefinition crd = defaultClient + .customResourceDefinitions() + .list() + .getItems() + .stream() + .filter(d -> "launchers.launcher.fabric8.io".equals(d.getMetadata().getName())) + .findAny() + .orElseThrow( + () -> new RuntimeException("Deployment error: Custom resource definition launcher.fabric8.io not found.")); + return defaultClient + .customResources(crd, LauncherResource.class, LauncherResourceList.class, LauncherResourceDoneable.class) + .inNamespace(defaultClient.getNamespace()); + } +} \ No newline at end of file diff --git a/operator/src/main/java/io/fabric8/launcher/operator/LauncherOperator.java b/operator/src/main/java/io/fabric8/launcher/operator/LauncherOperator.java new file mode 100644 index 000000000..7a0dfc5dd --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/LauncherOperator.java @@ -0,0 +1,44 @@ +package io.fabric8.launcher.operator; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.launcher.operator.cr.LauncherResource; +import io.quarkus.runtime.StartupEvent; + +@ApplicationScoped +public class LauncherOperator { + + @Inject + KubernetesClient client; + + @Inject + LauncherResourceCache cache; + + void onStartup(@Observes StartupEvent _ev) { + new Thread(this::runWatch).start(); + } + + private void runWatch() { + cache.listThenWatch(this::handleEvent); + } + + private void handleEvent(Watcher.Action action, String uid) { + try { + LauncherResource resource = cache.get(uid); + System.out.println(getClass().getResource("/launcher-template.yaml")); + if (resource == null) { + // Resource was deleted + return; + } + System.out.println("INSTALL CUSTOM RESOURCE: " + resource); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } +} diff --git a/operator/src/main/java/io/fabric8/launcher/operator/LauncherResourceCache.java b/operator/src/main/java/io/fabric8/launcher/operator/LauncherResourceCache.java new file mode 100644 index 000000000..2323ceda1 --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/LauncherResourceCache.java @@ -0,0 +1,85 @@ +package io.fabric8.launcher.operator; + +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.launcher.operator.cr.LauncherResource; +import io.fabric8.launcher.operator.cr.LauncherResourceDoneable; +import io.fabric8.launcher.operator.cr.LauncherResourceList; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.function.BiConsumer; + +@ApplicationScoped +class LauncherResourceCache { + private final Map cache = new ConcurrentHashMap<>(); + + @Inject + NonNamespaceOperation> crClient; + + private final Executor executor = Executors.newSingleThreadExecutor(); + + LauncherResource get(String uid) { + return cache.get(uid); + } + + void listThenWatch(BiConsumer callback) { + try { + // list + crClient + .list() + .getItems() + .forEach(resource -> { + cache.put(resource.getMetadata().getUid(), resource); + String uid = resource.getMetadata().getUid(); + executor.execute(() -> callback.accept(Watcher.Action.ADDED, uid)); + } + ); + + // watch + crClient.watch(new Watcher() { + @Override + public void eventReceived(Action action, LauncherResource resource) { + try { + String uid = resource.getMetadata().getUid(); + if (cache.containsKey(uid)) { + int knownResourceVersion = Integer.parseInt(cache.get(uid).getMetadata().getResourceVersion()); + int receivedResourceVersion = Integer.parseInt(resource.getMetadata().getResourceVersion()); + if (knownResourceVersion > receivedResourceVersion) { + return; + } + } + System.out.println("received " + action + " for resource " + resource); + if (action == Action.ADDED || action == Action.MODIFIED) { + cache.put(uid, resource); + } else if (action == Action.DELETED) { + cache.remove(uid); + } else { + System.err.println("Received unexpected " + action + " event for " + resource); + System.exit(-1); + } + executor.execute(() -> callback.accept(action, uid)); + } catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } + + @Override + public void onClose(KubernetesClientException cause) { + cause.printStackTrace(); + System.exit(-1); + } + }); + } catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } +} \ No newline at end of file diff --git a/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResource.java b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResource.java new file mode 100644 index 000000000..9a622c6e1 --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResource.java @@ -0,0 +1,23 @@ +package io.fabric8.launcher.operator.cr; + +import io.fabric8.kubernetes.client.CustomResource; + +public class LauncherResource extends CustomResource { + private LauncherResourceSpec spec; + + public LauncherResourceSpec getSpec() { + return spec; + } + + public void setSpec(LauncherResourceSpec spec) { + this.spec = spec; + } + + @Override + public String toString() { + String name = getMetadata() != null ? getMetadata().getName() : "unknown"; + String version = getMetadata() != null ? getMetadata().getResourceVersion() : "unknown"; + return "name=" + name + " version=" + version + " value=" + spec; + } + +} diff --git a/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceDoneable.java b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceDoneable.java new file mode 100644 index 000000000..1079604a8 --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceDoneable.java @@ -0,0 +1,10 @@ +package io.fabric8.launcher.operator.cr; + +import io.fabric8.kubernetes.api.builder.Function; +import io.fabric8.kubernetes.client.CustomResourceDoneable; + +public class LauncherResourceDoneable extends CustomResourceDoneable { + public LauncherResourceDoneable(LauncherResource resource, Function function) { + super(resource, function); + } +} diff --git a/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceList.java b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceList.java new file mode 100644 index 000000000..bff361279 --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceList.java @@ -0,0 +1,6 @@ +package io.fabric8.launcher.operator.cr; + +import io.fabric8.kubernetes.client.CustomResourceList; + +public class LauncherResourceList extends CustomResourceList { +} diff --git a/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceSpec.java b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceSpec.java new file mode 100644 index 000000000..7206cb2f6 --- /dev/null +++ b/operator/src/main/java/io/fabric8/launcher/operator/cr/LauncherResourceSpec.java @@ -0,0 +1,102 @@ +package io.fabric8.launcher.operator.cr; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.fabric8.launcher.service.git.api.GitServiceConfig; +import io.quarkus.runtime.annotations.RegisterForReflection; +import org.immutables.value.Value.Default; +import org.immutables.value.Value.Enclosing; +import org.immutables.value.Value.Immutable; +import org.immutables.value.Value.Style; + +import javax.annotation.Nullable; +import java.util.List; + +@JsonDeserialize(as = ImmutableLauncherResourceSpec.class) +@Immutable +@Enclosing +@Style(passAnnotations = RegisterForReflection.class) +public interface LauncherResourceSpec { + + @Default + default String getEnvironment() { + return "production"; + } + @JsonProperty + OpenShift getOpenshift(); + + @JsonProperty + Git getGit(); + + @JsonProperty + @Nullable + BoosterCatalog getCatalog(); + + @JsonProperty + @Nullable + Keycloak getKeycloak(); + + @Immutable + @JsonDeserialize(as = ImmutableLauncherResourceSpec.OpenShift.class) + interface OpenShift { + + @Nullable + String getConsoleUrl(); + + @Default + default String getClientId() { + return "launcher"; + } + + @Default + default String getApiUrl() { + return "https://openshift.default.svc.cluster.local"; + } + + @JsonProperty("impersonate") + @Default + default boolean isImpersonate() { + return false; + } + + @Nullable + String getUsername(); + + @Nullable + String getPassword(); + + @Nullable + String getToken(); + } + + @Immutable + @JsonDeserialize(as = ImmutableLauncherResourceSpec.Git.class) + interface Git { + List getProviders(); + } + + @Immutable + @JsonDeserialize(as = ImmutableLauncherResourceSpec.BoosterCatalog.class) + interface BoosterCatalog { + @Default + default String getRepositoryUrl() { + return "https://github.com/fabric8-launcher/launcher-booster-catalog"; + } + @Default + default String getRepositoryRef() { + return "latest"; + } + @Nullable + String getFilter(); + @Nullable + String getReindexToken(); + } + + @Immutable + @JsonDeserialize(as = ImmutableLauncherResourceSpec.Keycloak.class) + interface Keycloak { + String getUrl(); + String getRealm(); + String getClientId(); + } +} diff --git a/operator/src/main/resources/application.properties b/operator/src/main/resources/application.properties new file mode 100644 index 000000000..7be2a079a --- /dev/null +++ b/operator/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# Configuration file +# key = value +#quarkus.kubernetes-client.namespace=launcher \ No newline at end of file diff --git a/operator/src/test/java/io/fabric8/launcher/operator/LauncherOperatorTest.java b/operator/src/test/java/io/fabric8/launcher/operator/LauncherOperatorTest.java new file mode 100644 index 000000000..fd2846558 --- /dev/null +++ b/operator/src/test/java/io/fabric8/launcher/operator/LauncherOperatorTest.java @@ -0,0 +1,14 @@ +package io.fabric8.launcher.operator; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +@QuarkusTest +class LauncherOperatorTest { + + @Test + void testInteractionWithAPIServer() { + } + + +} \ No newline at end of file diff --git a/operator/src/test/java/io/fabric8/launcher/operator/cr/LauncherResourceTest.java b/operator/src/test/java/io/fabric8/launcher/operator/cr/LauncherResourceTest.java new file mode 100644 index 000000000..eacf41feb --- /dev/null +++ b/operator/src/test/java/io/fabric8/launcher/operator/cr/LauncherResourceTest.java @@ -0,0 +1,29 @@ +package io.fabric8.launcher.operator.cr; + +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +class LauncherResourceTest { + + @Test + void should_deserialize_cr() throws Exception { + File contents = new File("example/cr_next.yaml"); + LauncherResource resource = Serialization.yamlMapper().readValue(contents, LauncherResource.class); + assertThat(resource).isNotNull(); + assertThat(resource.getSpec().getCatalog().getRepositoryUrl()).isEqualTo("http://github.com/fabric8-launcher/launcher-booster-catalog"); + assertThat(resource.getSpec().getCatalog().getRepositoryRef()).isEqualTo("master"); + } + @Test + void should_deserialize_minimum_cr() throws Exception { + File contents = new File("example/cr_minimum.yaml"); + LauncherResource resource = Serialization.yamlMapper().readValue(contents, LauncherResource.class); + assertThat(resource).isNotNull(); + assertThat(resource.getSpec().getOpenshift().getConsoleUrl()).isNotNull(); + assertThat(resource.getSpec().getGit().getProviders()).isNotEmpty(); + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index e1d1a7013..a4afffc56 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ frontend creator web + operator @@ -468,6 +469,11 @@ + + io.quarkus + quarkus-maven-plugin + ${version.quarkus} + org.jacoco jacoco-maven-plugin diff --git a/web/pom.xml b/web/pom.xml index 1300631be..730b60716 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -139,7 +139,6 @@ io.quarkus quarkus-maven-plugin - ${version.quarkus}