From bd8ee6931d9cacb846256ff95eabf1ed7b690851 Mon Sep 17 00:00:00 2001 From: Andrej Petras Date: Fri, 24 Nov 2023 18:08:51 +0100 Subject: [PATCH] feat: migration --- .github/changelog.yaml | 13 ++ .github/dependabot.yml | 8 + .github/workflows/build-branch.yml | 13 ++ .github/workflows/build-pr.yml | 9 + .github/workflows/build-release.yml | 9 + .github/workflows/build.yml | 21 +++ .github/workflows/create-fix-branch.yml | 7 + .github/workflows/create-release.yml | 7 + .github/workflows/documentation.yml | 10 + .github/workflows/sonar-pr.yml | 12 ++ .gitignore | 43 +++++ pom.xml | 120 ++++++++++++ src/main/docker/Dockerfile.jvm | 13 ++ src/main/docker/Dockerfile.native | 11 ++ src/main/helm/Chart.yaml | 17 ++ src/main/helm/values.yaml | 4 + .../store/mfe/operator/Microfrontend.java | 11 ++ .../store/mfe/operator/MicrofrontendSpec.java | 178 ++++++++++++++++++ .../mfe/operator/MicrofrontendStatus.java | 63 +++++++ .../store/mfe/operator/OperatorConfig.java | 20 ++ .../ProductMicrofrontendReconciler.java | 112 +++++++++++ .../operator/client/ProductStoreService.java | 36 ++++ .../client/mappers/ProductStoreMapper.java | 12 ++ src/main/resources/application.properties | 25 +++ .../ProductMicrofrontendReconcilerIT.java | 8 + .../ProductMicrofrontendReconcilerTest.java | 93 +++++++++ src/test/resources/mockserver.properties | 16 ++ .../mockserver/onecx-product-store-mock.json | 52 +++++ 28 files changed, 943 insertions(+) create mode 100644 .github/changelog.yaml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build-branch.yml create mode 100644 .github/workflows/build-pr.yml create mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/create-fix-branch.yml create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/sonar-pr.yml create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/docker/Dockerfile.jvm create mode 100644 src/main/docker/Dockerfile.native create mode 100644 src/main/helm/Chart.yaml create mode 100644 src/main/helm/values.yaml create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/Microfrontend.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendSpec.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendStatus.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/OperatorConfig.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconciler.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/client/ProductStoreService.java create mode 100644 src/main/java/io/github/onecx/product/store/mfe/operator/client/mappers/ProductStoreMapper.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerIT.java create mode 100644 src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerTest.java create mode 100644 src/test/resources/mockserver.properties create mode 100644 src/test/resources/mockserver/onecx-product-store-mock.json diff --git a/.github/changelog.yaml b/.github/changelog.yaml new file mode 100644 index 0000000..0a5526e --- /dev/null +++ b/.github/changelog.yaml @@ -0,0 +1,13 @@ +sections: + - title: Major changes + labels: + - "release/super-feature" + - title: Complete changelog + labels: + - "bug" + - "enhancement" + - "dependencies" +template: | + {{ range $section := .Sections }}{{ if $section.Items }}### {{ $section.GetTitle }}{{ range $item := $section.Items }} + * [#{{ $item.GetID }}]({{ $item.GetURL }}) - {{ $item.GetTitle }}{{ end }}{{ end }} + {{ end }} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..64412da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: maven + directory: "/" + schedule: + interval: daily + labels: + - dependencies \ No newline at end of file diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml new file mode 100644 index 0000000..7270213 --- /dev/null +++ b/.github/workflows/build-branch.yml @@ -0,0 +1,13 @@ +name: Build Feature Branch + +on: + push: + branches: + - '**' + - '!main' + - '!fix/[0-9]+.[0-9]+.x' + +jobs: + branch: + uses: onecx/ci-quarkus/.github/workflows/build-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..acff155 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,9 @@ +name: Build Pull Request + +on: + pull_request: + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/build-pr.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..b04a59a --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,9 @@ +name: Build Release +on: + push: + tags: + - '**' +jobs: + release: + uses: onecx/ci-quarkus/.github/workflows/build-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..53b0ddd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,21 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - 'main' + - 'fix/[0-9]+.[0-9]+.x' + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +jobs: + build: + uses: onecx/ci-quarkus/.github/workflows/build.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/create-fix-branch.yml b/.github/workflows/create-fix-branch.yml new file mode 100644 index 0000000..92af624 --- /dev/null +++ b/.github/workflows/create-fix-branch.yml @@ -0,0 +1,7 @@ +name: Create Fix Branch +on: + workflow_dispatch: +jobs: + fix: + uses: onecx/ci-common/.github/workflows/create-fix-branch.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..c97eb42 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,7 @@ +name: Create Release Version +on: + workflow_dispatch: +jobs: + release: + uses: onecx/ci-common/.github/workflows/create-release.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..e43d7dc --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,10 @@ +name: Update documentation +on: + push: + branches: [ main ] + paths: + - 'docs/**' +jobs: + release: + uses: onecx/ci-common/.github/workflows/documentation.yml@v1 + secrets: inherit diff --git a/.github/workflows/sonar-pr.yml b/.github/workflows/sonar-pr.yml new file mode 100644 index 0000000..02c3e1e --- /dev/null +++ b/.github/workflows/sonar-pr.yml @@ -0,0 +1,12 @@ +name: Sonar Pull Request + +on: + workflow_run: + workflows: ["Build Pull Request"] + types: + - completed + +jobs: + pr: + uses: onecx/ci-quarkus/.github/workflows/quarkus-pr-sonar.yml@v1 + secrets: inherit \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c7863e --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0c75fcc --- /dev/null +++ b/pom.xml @@ -0,0 +1,120 @@ + + + + 4.0.0 + + + io.github.onecx + onecx-quarkus3-parent + 999-SNAPSHOT + + + onecx-product-store-mfe-operator + 999-SNAPSHOT + + + + io.quarkiverse.operatorsdk + quarkus-operator-sdk-bundle-generator + + + io.quarkiverse.operatorsdk + quarkus-operator-sdk + + + + + org.tkit.quarkus.lib + tkit-quarkus-log-cdi + + + org.tkit.quarkus.lib + tkit-quarkus-log-rs + + + org.tkit.quarkus.lib + tkit-quarkus-log-json + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-micrometer-registry-prometheus + + + io.quarkus + quarkus-opentelemetry + + + io.quarkiverse.openapi.generator + quarkus-openapi-generator + + + io.quarkus + quarkus-rest-client-reactive + + + io.quarkus + quarkus-rest-client-reactive-jackson + + + + + org.mapstruct + mapstruct + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkiverse.mockserver + quarkus-mockserver-test + + + io.swagger.parser.v3 + swagger-parser + + + test + + + io.swagger.parser.v3 + swagger-parser + test + + + + + + + + com.googlecode.maven-download-plugin + download-maven-plugin + + + generate-resources + + wget + + + + + https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-operator-mfe-v1.yaml + target/tmp/openapi + onecx-product-store-operator-mfe-v1.yaml + true + + + + + + diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..a554421 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,13 @@ +FROM registry.access.redhat.com/ubi9/openjdk-17:1.15 + +ENV LANGUAGE='en_US:en' + +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" \ No newline at end of file diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..c97f217 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,11 @@ +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.2 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/src/main/helm/Chart.yaml b/src/main/helm/Chart.yaml new file mode 100644 index 0000000..2e1c992 --- /dev/null +++ b/src/main/helm/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +name: onecx-product-store-mfe-operator +version: 0.0.0 +appVersion: 0.0.0 +description: Onecx product store mfe operator +keywords: + - tenant +sources: + - https://github.com/onecx/onecx-product-store-mfe-operator +maintainers: + - name: Tkit Developer + email: tkit_dev@1000kit.org +dependencies: + - name: helm-quarkus-app + alias: app + version: ^0 + repository: oci://ghcr.io/onecx/charts \ No newline at end of file diff --git a/src/main/helm/values.yaml b/src/main/helm/values.yaml new file mode 100644 index 0000000..9bdd554 --- /dev/null +++ b/src/main/helm/values.yaml @@ -0,0 +1,4 @@ +app: + image: + repository: "onecx/onecx-product-store-mfe-operator" + tag: 999-SNAPSHOT diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/Microfrontend.java b/src/main/java/io/github/onecx/product/store/mfe/operator/Microfrontend.java new file mode 100644 index 0000000..32b2f7b --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/Microfrontend.java @@ -0,0 +1,11 @@ +package io.github.onecx.product.store.mfe.operator; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Version("v1") +@Group("io.github.onecx.product.store.mfe") +public class Microfrontend extends CustomResource implements Namespaced { +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendSpec.java b/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendSpec.java new file mode 100644 index 0000000..8c49ced --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendSpec.java @@ -0,0 +1,178 @@ +package io.github.onecx.product.store.mfe.operator; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MicrofrontendSpec { + + @JsonProperty("product-store") + private String productStore; + + @JsonProperty("mfe-id") + private String mfeId; + + @JsonProperty("base-path") + private String basePath; + @JsonProperty("product-name") + private String productName; + + @JsonProperty("remote-entry") + private String remoteEntry; + + @JsonProperty("remote-name") + private String remoteName; + + @JsonProperty("exposed-module") + private String exposedModule; + + @JsonProperty("display-name") + private String displayName; + + @JsonProperty("module-type") + private ModuleType moduleType; + + @JsonProperty("wc-tag-name") + private String wcTagName; + + @JsonProperty("app-id") + private String appId; + + @JsonProperty("app-version") + private String appVersion; + + @JsonProperty("note") + private String note; + + @JsonProperty("contact") + private String contact; + + @JsonProperty("remote-base-url") + private String remoteBaseUrl; + + public enum ModuleType { + + ANGULAR, + WEBCOMPONENT; + } + + public String getMfeId() { + return mfeId; + } + + public void setMfeId(String mfeId) { + this.mfeId = mfeId; + } + + public String getProductStore() { + return productStore; + } + + public void setProductStore(String productStore) { + this.productStore = productStore; + } + + public String getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = basePath; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getRemoteEntry() { + return remoteEntry; + } + + public void setRemoteEntry(String remoteEntry) { + this.remoteEntry = remoteEntry; + } + + public String getRemoteName() { + return remoteName; + } + + public void setRemoteName(String remoteName) { + this.remoteName = remoteName; + } + + public String getExposedModule() { + return exposedModule; + } + + public void setExposedModule(String exposedModule) { + this.exposedModule = exposedModule; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public ModuleType getModuleType() { + return moduleType; + } + + public void setModuleType(ModuleType moduleType) { + this.moduleType = moduleType; + } + + public String getWcTagName() { + return wcTagName; + } + + public void setWcTagName(String wcTagName) { + this.wcTagName = wcTagName; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppVersion() { + return appVersion; + } + + public void setAppVersion(String appVersion) { + this.appVersion = appVersion; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public String getContact() { + return contact; + } + + public void setContact(String contact) { + this.contact = contact; + } + + public String getRemoteBaseUrl() { + return remoteBaseUrl; + } + + public void setRemoteBaseUrl(String remoteBaseUrl) { + this.remoteBaseUrl = remoteBaseUrl; + } +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendStatus.java b/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendStatus.java new file mode 100644 index 0000000..de2d919 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/MicrofrontendStatus.java @@ -0,0 +1,63 @@ +package io.github.onecx.product.store.mfe.operator; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus; + +public class MicrofrontendStatus extends ObservedGenerationAwareStatus { + + @JsonProperty("product-name") + private String productName; + + @JsonProperty("mfe-id") + private String mfeId; + + @JsonProperty("response-code") + private int responseCode; + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public String getMfeId() { + return mfeId; + } + + public void setMfeId(String mfeId) { + this.mfeId = mfeId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/OperatorConfig.java b/src/main/java/io/github/onecx/product/store/mfe/operator/OperatorConfig.java new file mode 100644 index 0000000..0d49a08 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/OperatorConfig.java @@ -0,0 +1,20 @@ +package io.github.onecx.product.store.mfe.operator; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; + +@StaticInitSafe +@ConfigMapping(prefix = "onecx.product.store.mfe.operator") +public interface OperatorConfig { + + @WithName("multi-enabled") + @WithDefault("false") + boolean enabled(); + + @WithName("name") + @WithDefault("onecx") + String name(); + +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconciler.java b/src/main/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconciler.java new file mode 100644 index 0000000..11540d0 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconciler.java @@ -0,0 +1,112 @@ +package io.github.onecx.product.store.mfe.operator; + +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.onecx.product.store.mfe.operator.client.ProductStoreService; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter; +import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter; + +@ControllerConfiguration(onAddFilter = ProductMicrofrontendReconciler.SecretAddFilter.class, onUpdateFilter = ProductMicrofrontendReconciler.SecretUpdateFilter.class) +public class ProductMicrofrontendReconciler implements Reconciler, ErrorStatusHandler { + + private static final Logger log = LoggerFactory.getLogger(ProductMicrofrontendReconciler.class); + + @Inject + ProductStoreService service; + + private static final boolean MULTI_ENABLED = ConfigProvider.getConfig() + .getValue("onecx.product.store.mfe.operator.multi-enabled", boolean.class); + + private static final String MULTI_NAME = ConfigProvider.getConfig() + .getValue("onecx.product.store.mfe.operator.name", String.class); + + @Override + public UpdateControl reconcile(Microfrontend microfrontend, Context context) + throws Exception { + + String mfeId = microfrontend.getSpec().getMfeId(); + String productName = microfrontend.getSpec().getProductName(); + + log.info("Reconcile microfrontend: {} for product: {}", mfeId, productName); + + int responseCode = service.updateMicrofrontend(microfrontend); + + updateStatusPojo(microfrontend, responseCode); + log.info("Microfrontend '{}' reconciled - updating status", microfrontend.getMetadata().getName()); + return UpdateControl.updateStatus(microfrontend); + + } + + @Override + public ErrorStatusUpdateControl updateErrorStatus(Microfrontend microfrontend, + Context context, Exception e) { + + int responseCode = -1; + if (e.getCause() instanceof WebApplicationException re) { + responseCode = re.getResponse().getStatus(); + } + + log.error("Error reconcile resource", e); + MicrofrontendStatus status = new MicrofrontendStatus(); + status.setProductName(null); + status.setMfeId(null); + status.setResponseCode(responseCode); + status.setStatus("ERROR"); + status.setMessage(e.getMessage()); + microfrontend.setStatus(status); + return ErrorStatusUpdateControl.updateStatus(microfrontend); + } + + private void updateStatusPojo(Microfrontend microfrontend, int responseCode) { + MicrofrontendStatus result = new MicrofrontendStatus(); + MicrofrontendSpec spec = microfrontend.getSpec(); + result.setProductName(spec.getProductName()); + result.setMfeId(spec.getMfeId()); + result.setResponseCode(responseCode); + var status = switch (responseCode) { + case 201: + yield "CREATED"; + case 200: + yield "UPDATED"; + default: + yield "UNDEFINED"; + }; + result.setStatus(status); + microfrontend.setStatus(result); + } + + public static class SecretAddFilter implements OnAddFilter { + + @Override + public boolean accept(Microfrontend resource) { + if (resource.getSpec() == null) { + return false; + } + if (!MULTI_ENABLED) { + return true; + } + return MULTI_NAME.equals(resource.getSpec().getProductStore()); + } + } + + public static class SecretUpdateFilter implements OnUpdateFilter { + + @Override + public boolean accept(Microfrontend newResource, Microfrontend oldResource) { + if (newResource.getSpec() == null) { + return false; + } + if (!MULTI_ENABLED) { + return true; + } + return MULTI_NAME.equals(newResource.getSpec().getProductStore()); + } + } + +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/client/ProductStoreService.java b/src/main/java/io/github/onecx/product/store/mfe/operator/client/ProductStoreService.java new file mode 100644 index 0000000..c58eefd --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/client/ProductStoreService.java @@ -0,0 +1,36 @@ +package io.github.onecx.product.store.mfe.operator.client; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import gen.io.github.onecx.product.store.mfe.v1.api.OperatorMfeApi; +import gen.io.github.onecx.product.store.mfe.v1.model.UpdateMfeRequest; +import io.github.onecx.product.store.mfe.operator.Microfrontend; +import io.github.onecx.product.store.mfe.operator.MicrofrontendSpec; +import io.github.onecx.product.store.mfe.operator.client.mappers.ProductStoreMapper; + +@ApplicationScoped +public class ProductStoreService { + + private static final Logger log = LoggerFactory.getLogger(ProductStoreService.class); + + @Inject + @RestClient + OperatorMfeApi client; + + @Inject + ProductStoreMapper mapper; + + public int updateMicrofrontend(Microfrontend microfrontend) { + MicrofrontendSpec spec = microfrontend.getSpec(); + UpdateMfeRequest dto = mapper.map(spec); + try (var response = client.createOrUpdateMfe(spec.getMfeId(), dto)) { + log.info("Update micro-fronted response {}", response.getStatus()); + return response.getStatus(); + } + } +} diff --git a/src/main/java/io/github/onecx/product/store/mfe/operator/client/mappers/ProductStoreMapper.java b/src/main/java/io/github/onecx/product/store/mfe/operator/client/mappers/ProductStoreMapper.java new file mode 100644 index 0000000..a07c292 --- /dev/null +++ b/src/main/java/io/github/onecx/product/store/mfe/operator/client/mappers/ProductStoreMapper.java @@ -0,0 +1,12 @@ +package io.github.onecx.product.store.mfe.operator.client.mappers; + +import org.mapstruct.Mapper; + +import gen.io.github.onecx.product.store.mfe.v1.model.UpdateMfeRequest; +import io.github.onecx.product.store.mfe.operator.MicrofrontendSpec; + +@Mapper +public interface ProductStoreMapper { + + UpdateMfeRequest map(MicrofrontendSpec spec); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..43432e2 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,25 @@ + +# BUILD +quarkus.openapi-generator.codegen.input-base-dir=target/tmp/openapi +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_mfe_v1_yaml.config-key=product_store_client +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_mfe_v1_yaml.base-package=gen.io.github.onecx.product.store.mfe.v1 +quarkus.openapi-generator.codegen.spec.onecx_product_store_operator_mfe_v1_yaml.return-response=true + + +# CONFIG + +quarkus.kubernetes-client.devservices.override-kubeconfig=true + +# validate product store name, relevant only for multi-store +onecx.product.store.mfe.operator.multi-enabled=false +onecx.product.store.mfe.operator.name=onecx + + +# TEST +%test.quarkus.mockserver.devservices.config-class-path=true +%test.quarkus.mockserver.devservices.config-file=/mockserver.properties +%test.quarkus.mockserver.devservices.config-dir=/mockserver +%test.quarkus.mockserver.devservices.log=false +%test.quarkus.mockserver.devservices.reuse=true +%test.quarkus.rest-client.product_store_client.url=${quarkus.mockserver.endpoint} + diff --git a/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerIT.java b/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerIT.java new file mode 100644 index 0000000..04f28a7 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerIT.java @@ -0,0 +1,8 @@ +package io.github.onecx.product.store.mfe.operator; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class ProductMicrofrontendReconcilerIT extends ProductMicrofrontendReconcilerTest { + +} diff --git a/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerTest.java b/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerTest.java new file mode 100644 index 0000000..65a6663 --- /dev/null +++ b/src/test/java/io/github/onecx/product/store/mfe/operator/ProductMicrofrontendReconcilerTest.java @@ -0,0 +1,93 @@ +package io.github.onecx.product.store.mfe.operator; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.stream.Stream; + +import jakarta.inject.Inject; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.Operator; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class ProductMicrofrontendReconcilerTest { + + final static Logger log = LoggerFactory.getLogger(ProductMicrofrontendReconcilerTest.class); + + @Inject + Operator operator; + + @Inject + KubernetesClient client; + + @BeforeAll + public static void init() { + Awaitility.setDefaultPollDelay(2, SECONDS); + Awaitility.setDefaultPollInterval(2, SECONDS); + Awaitility.setDefaultTimeout(10, SECONDS); + } + + @ParameterizedTest + @MethodSource("provideTestData") + void microfrontendTest(String name, MicrofrontendSpec spec, String status) { + + operator.start(); + + Microfrontend microfrontend = new Microfrontend(); + microfrontend.setMetadata(new ObjectMetaBuilder().withName(name).withNamespace(client.getNamespace()).build()); + microfrontend.setSpec(spec); + + log.info("Creating test microfrontend object: {}", microfrontend); + client.resource(microfrontend).serverSideApply(); + + log.info("Waiting 4 seconds and status muss be still null"); + + await().pollDelay(2, SECONDS).untilAsserted(() -> { + MicrofrontendStatus mfeStatus = client.resource(microfrontend).get().getStatus(); + assertThat(mfeStatus).isNotNull(); + assertThat(mfeStatus.getStatus()).isNotNull().isEqualTo(status); + }); + } + + private static Stream provideTestData() { + return Stream.of( + Arguments.of("test-1", createSpec("test-1", "product-test", "/test1"), "CREATED"), + Arguments.of("test-2", createSpec("test-2", "product-test-2", "/test2"), "CREATED"), + Arguments.of("test-3", createSpec("test-3", "product-test-2", "/test3"), "UPDATED"), + Arguments.of("test-error-1", createSpec("test-error-1", "product-test-2", "/test2"), "ERROR"), + Arguments.of("test-error-2", createSpec("test-error-2", "product-test-2", "/test2"), "ERROR")); + } + + private static MicrofrontendSpec createSpec(String mfeId, String productName, String basePath) { + MicrofrontendSpec spec = new MicrofrontendSpec(); + spec.setMfeId(mfeId); + spec.setProductName(productName); + spec.setBasePath(basePath); + spec.setProductStore("test"); + spec.setRemoteBaseUrl("test"); + spec.setRemoteName("test"); + spec.setExposedModule("test"); + spec.setDisplayName("dn"); + spec.setModuleType(MicrofrontendSpec.ModuleType.ANGULAR); + spec.setWcTagName("wc"); + spec.setAppId("appId"); + spec.setAppVersion("app-version"); + spec.setNote("note"); + spec.setContact("contact"); + spec.setRemoteBaseUrl("rbu"); + spec.setRemoteEntry("re"); + return spec; + } +} diff --git a/src/test/resources/mockserver.properties b/src/test/resources/mockserver.properties new file mode 100644 index 0000000..8371764 --- /dev/null +++ b/src/test/resources/mockserver.properties @@ -0,0 +1,16 @@ +mockserver.initializationJsonPath=/mockserver/*.json +# watch changes in the file +mockserver.watchInitializationJson=true + + +# Certificate Generation +# dynamically generated CA key pair (if they don't already exist in specified directory) +mockserver.dynamicallyCreateCertificateAuthorityCertificate=true +# save dynamically generated CA key pair in working directory +mockserver.directoryToSaveDynamicSSLCertificate=. +# certificate domain name (default "localhost") +mockserver.sslCertificateDomainName=localhost +# comma separated list of ip addresses for Subject Alternative Name domain names (default empty list) +mockserver.sslSubjectAlternativeNameDomains=www.example.com,www.another.com +# comma separated list of ip addresses for Subject Alternative Name ips (default empty list) +mockserver.sslSubjectAlternativeNameIps=127.0.0.1 \ No newline at end of file diff --git a/src/test/resources/mockserver/onecx-product-store-mock.json b/src/test/resources/mockserver/onecx-product-store-mock.json new file mode 100644 index 0000000..a61147f --- /dev/null +++ b/src/test/resources/mockserver/onecx-product-store-mock.json @@ -0,0 +1,52 @@ +[ + { + "id": "1", + "httpRequest": { + "method" : "PUT", + "path": "/operator/mfe/v1/test-1" + }, + "httpResponse": { + "statusCode": 201 + } + }, + { + "id": "2", + "httpRequest": { + "method" : "PUT", + "path": "/operator/mfe/v1/test-2" + }, + "httpResponse": { + "statusCode": 201 + } + }, + { + "id": "3", + "httpRequest": { + "method" : "PUT", + "path": "/operator/mfe/v1/test-3" + }, + "httpResponse": { + "statusCode": 200 + } + }, + { + "id": "4", + "httpRequest": { + "method" : "PUT", + "path": "/operator/mfe/v1/test-error-1" + }, + "httpResponse": { + "statusCode": 500 + } + }, + { + "id": "5", + "httpRequest": { + "method" : "PUT", + "path": "/operator/mfe/v1/test-error-2" + }, + "httpResponse": { + "statusCode": 400 + } + } +] \ No newline at end of file