From 435b88c6a92cae6091895ed2d035d13c9bf3429d Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 9 Jan 2023 19:02:25 +0530 Subject: [PATCH] feat (jkube-kit/config/service) : Detect and warn user if Ingress controller not available (#1478) + Add IngressControllerDetectorService which will try to detect whether any Ingress Controller is present in the cluster before applying Ingress. At the moment, it supports detecting these Ingress Controllers: - Contour - Ingress Kong - Ingress Nginx - OpenShift Ingress Controller This is based on Kind Ingress guide, we can also expand it if we want to. Signed-off-by: Rohan Kumar --- CHANGELOG.md | 1 + .../kit/common/IngressControllerDetector.java | 51 +++++++ .../kit/common/util/KubernetesHelper.java | 29 ++++ .../kit/common/util/KubernetesHelperTest.java | 79 ++++++++++ .../kit/config/service/ApplyService.java | 9 +- .../kit/config/service/JKubeServiceHub.java | 14 +- .../ContourIngressControllerDetector.java | 64 ++++++++ .../IngressControllerDetectorService.java | 53 +++++++ .../KongIngressControllerDetector.java | 73 +++++++++ .../NginxIngressControllerDetector.java | 59 ++++++++ .../OpenShiftIngressControllerDetector.java | 66 +++++++++ .../META-INF/jkube/ingress-detectors | 4 + .../kit/config/service/ApplyServiceTest.java | 8 +- .../kit/config/service/DebugServiceTest.java | 4 +- .../ContourIngressControllerDetectorTest.java | 119 +++++++++++++++ .../IngressControllerDetectorServiceTest.java | 86 +++++++++++ .../KongIngressControllerDetectorTest.java | 138 ++++++++++++++++++ .../NginxIngressControllerDetectorTest.java | 109 ++++++++++++++ ...penShiftIngressControllerDetectorTest.java | 110 ++++++++++++++ .../plugin/mojo/build/ApplyMojoTest.java | 5 +- .../plugin/mojo/develop/WatchMojoTest.java | 4 +- 21 files changed, 1077 insertions(+), 8 deletions(-) create mode 100644 jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/IngressControllerDetector.java create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetector.java create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorService.java create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetector.java create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetector.java create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetector.java create mode 100644 jkube-kit/config/service/src/main/resources/META-INF/jkube/ingress-detectors create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetectorTest.java create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorServiceTest.java create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetectorTest.java create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetectorTest.java create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetectorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 47bf27edb2..062fc7cd6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Usage: * Fix #1316: Add support for adding InitContainers via plugin configuration * Fix #1439: Add hint to use jkube.domain if createExternalUrls is used without domain * Fix #1459: Route Generation should support `8443` as default web port +* Fix #1478: Should detect and warn the user if creating ingress and ingress controller not available * Fix #1546: Migrate to JUnit5 testing framework * Fix #1829: Add trailing newline for labels/annotations for multiline values to avoid setting block chomping indicator * Fix #1858: Properties in image name not replaced diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/IngressControllerDetector.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/IngressControllerDetector.java new file mode 100644 index 0000000000..22f81079d2 --- /dev/null +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/IngressControllerDetector.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.common; + +import io.fabric8.kubernetes.client.KubernetesClientException; + +public interface IngressControllerDetector { + /** + * Returns boolean value whether Ingress Controller is running in Kubernetes Cluster + * @return true if Ingress Controller is present, false otherwise + */ + default boolean isDetected() { + try { + return hasIngressClass() && isIngressControllerReady(); + } catch (KubernetesClientException exception) { + return false; + } + } + + /** + * Returns whether IngressClass for corresponding controller is installed in cluster + * + * @return true if IngressClass is installed, false otherwise + */ + boolean hasIngressClass(); + + /** + * Is Ingress Controller pod running in the cluster + * + * @return boolean value if Ingress Controller is running, false otherwise. + */ + boolean isIngressControllerReady(); + + /** + * Returns boolean value whether currently logged user has got enough permissions to + * check for current IngressController implementation + * @return true if it has permissions, false otherwise + */ + boolean hasPermissions(); +} diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/KubernetesHelper.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/KubernetesHelper.java index e1e6c5ca95..34b5772b09 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/KubernetesHelper.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/KubernetesHelper.java @@ -40,6 +40,9 @@ import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.HTTPHeader; +import io.fabric8.kubernetes.api.model.authorization.v1.ResourceAttributesBuilder; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; import io.fabric8.kubernetes.client.utils.ApiVersionUtil; import org.eclipse.jkube.kit.common.KitLogger; @@ -797,5 +800,31 @@ public static List convertMapToHTTPHeaderList(Map he } return httpHeaders; } + + public static SelfSubjectAccessReview createNewSelfSubjectAccessReview(String group, String resource, String namespace, String verb) { + SelfSubjectAccessReviewBuilder ssarBuilder = new SelfSubjectAccessReviewBuilder(); + ResourceAttributesBuilder resourceAttributesBuilder = new ResourceAttributesBuilder(); + if (StringUtils.isNotBlank(group)) { + resourceAttributesBuilder.withGroup(group); + } + if (StringUtils.isNotBlank(resource)) { + resourceAttributesBuilder.withResource(resource); + } + if (StringUtils.isNotBlank(namespace)) { + resourceAttributesBuilder.withNamespace(namespace); + } + if (StringUtils.isNotBlank(verb)) { + resourceAttributesBuilder.withVerb(verb); + } + return ssarBuilder.withNewSpec() + .withResourceAttributes(resourceAttributesBuilder.build()) + .endSpec() + .build(); + } + + public static boolean hasAccessForAction(KubernetesClient client, SelfSubjectAccessReview accessReview) { + SelfSubjectAccessReview accessReviewFromServer = client.authorization().v1().selfSubjectAccessReview().create(accessReview); + return accessReviewFromServer.getStatus().getAllowed(); + } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/KubernetesHelperTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/KubernetesHelperTest.java index d87b29384c..22f045a72d 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/KubernetesHelperTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/KubernetesHelperTest.java @@ -22,6 +22,14 @@ import java.util.List; import java.util.Map; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; +import io.fabric8.kubernetes.api.model.authorization.v1.SubjectAccessReviewStatus; +import io.fabric8.kubernetes.api.model.authorization.v1.SubjectAccessReviewStatusBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.V1AuthorizationAPIGroupDSL; +import io.fabric8.kubernetes.client.dsl.AuthorizationAPIGroupDSL; +import io.fabric8.kubernetes.client.dsl.InOutCreateable; import org.eclipse.jkube.kit.common.KitLogger; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -54,6 +62,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.hasAccessForAction; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class KubernetesHelperTest { @@ -409,6 +421,53 @@ void extractPodLabelSelector_withJobWithNoSelector_shouldReturnTemplateLabels() .containsEntry("template", "label"); } + @Test + void createNewSelfSubjectAccessReview_whenGroupResourceNamespaceVerbProvided_thenShouldCreateSelfSubjectAccessReview() { + // When + SelfSubjectAccessReview selfSubjectAccessReview = KubernetesHelper.createNewSelfSubjectAccessReview("example.com", "foos", "test-ns", "list"); + + // Then + assertThat(selfSubjectAccessReview) + .hasFieldOrPropertyWithValue("spec.resourceAttributes.group", "example.com") + .hasFieldOrPropertyWithValue("spec.resourceAttributes.resource", "foos") + .hasFieldOrPropertyWithValue("spec.resourceAttributes.namespace", "test-ns") + .hasFieldOrPropertyWithValue("spec.resourceAttributes.verb", "list"); + } + + @Test + void hasAccessForAction_whenApiServerReturnsAllowedTrue_thenReturnTrue() { + try (KubernetesClient kubernetesClient = mock(KubernetesClient.class)) { + // Given + mockKubernetesClientAccessReviewCallReturns(kubernetesClient, new SubjectAccessReviewStatusBuilder() + .withAllowed(true) + .build()); + SelfSubjectAccessReview selfSubjectAccessReview = createNewTestSelfSubjectAccessReview(); + + // When + boolean result = hasAccessForAction(kubernetesClient, selfSubjectAccessReview); + + // Then + assertThat(result).isTrue(); + } + } + + @Test + void hasAccessForAction_whenApiServerReturnsAllowedFalse_thenReturnFalse() { + try (KubernetesClient kubernetesClient = mock(KubernetesClient.class)) { + // Given + mockKubernetesClientAccessReviewCallReturns(kubernetesClient, new SubjectAccessReviewStatusBuilder() + .withAllowed(false) + .build()); + SelfSubjectAccessReview selfSubjectAccessReview = createNewTestSelfSubjectAccessReview(); + + // When + boolean result = hasAccessForAction(kubernetesClient, selfSubjectAccessReview); + + // Then + assertThat(result).isFalse(); + } + } + private void assertLocalFragments(File[] fragments, int expectedSize) { assertThat(fragments).hasSize(expectedSize); assertThat(Arrays.stream(fragments).anyMatch( f -> f.getName().equals("deployment.yml"))).isTrue(); @@ -431,4 +490,24 @@ private List getRemoteFragments(int port) { remoteStrList.add(String.format("http://localhost:%d/sa.yml", port)); return remoteStrList; } + + private void mockKubernetesClientAccessReviewCallReturns(KubernetesClient kubernetesClient, SubjectAccessReviewStatus status) { + AuthorizationAPIGroupDSL authorizationAPIGroupDSL = mock(AuthorizationAPIGroupDSL.class); + V1AuthorizationAPIGroupDSL v1AuthorizationAPIGroupDSL = mock(V1AuthorizationAPIGroupDSL.class); + InOutCreateable inOutCreateable = mock(InOutCreateable.class); + when(kubernetesClient.authorization()).thenReturn(authorizationAPIGroupDSL); + when(authorizationAPIGroupDSL.v1()).thenReturn(v1AuthorizationAPIGroupDSL); + when(v1AuthorizationAPIGroupDSL.selfSubjectAccessReview()).thenReturn(inOutCreateable); + when(inOutCreateable.create(any())).thenReturn(new SelfSubjectAccessReviewBuilder().withStatus(status).build()); + } + + private SelfSubjectAccessReview createNewTestSelfSubjectAccessReview() { + return new SelfSubjectAccessReviewBuilder() + .withNewSpec() + .withNewResourceAttributes() + .withGroup("foo") + .endResourceAttributes() + .endSpec() + .build(); + } } diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ApplyService.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ApplyService.java index abefcc7084..e3d038aa23 100644 --- a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ApplyService.java +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ApplyService.java @@ -36,6 +36,7 @@ import org.eclipse.jkube.kit.common.util.OpenshiftHelper; import org.eclipse.jkube.kit.common.util.ResourceUtil; import org.eclipse.jkube.kit.common.util.UserConfigurationCompare; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.eclipse.jkube.kit.config.service.kubernetes.KubernetesClientUtil; import com.fasterxml.jackson.core.JsonProcessingException; @@ -112,12 +113,14 @@ public class ApplyService { private boolean rollingUpgradePreserveScale = true; private boolean recreateMode; private final PatchService patchService; + private final IngressControllerDetectorService ingressControllerDetectorService; // This map is to track projects created. private static final Set projectsCreated = new HashSet<>(); - public ApplyService(KubernetesClient kubernetesClient, KitLogger log) { + public ApplyService(KubernetesClient kubernetesClient, IngressControllerDetectorService ingressControllerDetectorService, KitLogger log) { this.kubernetesClient = kubernetesClient; this.patchService = new PatchService(kubernetesClient, log); + this.ingressControllerDetectorService = ingressControllerDetectorService; this.log = log; } @@ -192,7 +195,11 @@ private void applyEntity(Object dto, String sourceName) { } else if (dto instanceof StatefulSet) { applyResource((StatefulSet) dto, sourceName, kubernetesClient.apps().statefulSets()); } else if (dto instanceof Ingress) { + ingressControllerDetectorService.detect(); applyResource((Ingress) dto, sourceName, kubernetesClient.extensions().ingresses()); + }else if (dto instanceof io.fabric8.kubernetes.api.model.networking.v1.Ingress) { + ingressControllerDetectorService.detect(); + applyResource((io.fabric8.kubernetes.api.model.networking.v1.Ingress) dto, sourceName, kubernetesClient.network().v1().ingresses()); } else if (dto instanceof PersistentVolumeClaim) { applyPersistentVolumeClaim((PersistentVolumeClaim) dto, sourceName); } else if (dto instanceof CustomResourceDefinition) { diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/JKubeServiceHub.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/JKubeServiceHub.java index 6a2e8ed705..ea39635825 100644 --- a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/JKubeServiceHub.java +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/JKubeServiceHub.java @@ -14,6 +14,7 @@ package org.eclipse.jkube.kit.config.service; import java.io.Closeable; +import java.util.List; import java.util.Objects; import java.util.Optional; @@ -22,15 +23,18 @@ import lombok.Getter; import lombok.Setter; import org.eclipse.jkube.kit.build.service.docker.access.DockerAccess; +import org.eclipse.jkube.kit.common.IngressControllerDetector; import org.eclipse.jkube.kit.common.service.MigrateService; import org.eclipse.jkube.kit.build.service.docker.DockerServiceHub; import org.eclipse.jkube.kit.common.KitLogger; import org.eclipse.jkube.kit.common.util.LazyBuilder; +import org.eclipse.jkube.kit.common.util.PluginServiceFactory; import org.eclipse.jkube.kit.config.access.ClusterAccess; import org.eclipse.jkube.kit.config.access.ClusterConfiguration; import org.eclipse.jkube.kit.common.JKubeConfiguration; import org.eclipse.jkube.kit.config.resource.ResourceService; import org.eclipse.jkube.kit.config.resource.RuntimeMode; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.eclipse.jkube.kit.config.service.kubernetes.KubernetesUndeployService; import org.eclipse.jkube.kit.config.service.openshift.OpenshiftUndeployService; import org.eclipse.jkube.kit.config.service.plugins.PluginManager; @@ -105,7 +109,7 @@ private void initLazyBuilders() { kubernetesClientLazyBuilder = new LazyBuilder<>(() -> getClusterAccess().createDefaultClient()); buildServiceManager = new LazyBuilder<>(() -> new BuildServiceManager(this)); pluginManager = new LazyBuilder<>(() -> new PluginManager(this)); - applyService = new LazyBuilder<>(() -> new ApplyService(getClient(), log)); + applyService = new LazyBuilder<>(() -> new ApplyService(getClient(), createIngressControllerDetectorService(), log)); portForwardService = new LazyBuilder<>(() -> { getClient(); return new PortForwardService(log); @@ -181,4 +185,12 @@ public KubernetesClient getClient() { public ClusterAccess getClusterAccess() { return clusterAccessLazyBuilder.get(); } + + private IngressControllerDetectorService createIngressControllerDetectorService() { + final PluginServiceFactory pluginServiceFactory = new PluginServiceFactory<>(getClient()); + List ingressControllerDetectors = pluginServiceFactory.createServiceObjects("META-INF/jkube/ingress-detectors"); + IngressControllerDetectorService ingressControllerDetectorService = new IngressControllerDetectorService(log); + ingressControllerDetectorService.setIngressControllerDetectors(ingressControllerDetectors); + return ingressControllerDetectorService; + } } diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetector.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetector.java new file mode 100644 index 0000000000..84ca9812e1 --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetector.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.util.KubernetesHelper; + +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.createNewSelfSubjectAccessReview; +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.hasAccessForAction; + +public class ContourIngressControllerDetector implements IngressControllerDetector { + private final KubernetesClient client; + private static final String INGRESS_CONTOUR_NAMESPACE = "projectcontour"; + + public ContourIngressControllerDetector(KubernetesClient client) { + this.client = client; + } + + @Override + public boolean hasIngressClass() { + return !client.network().v1().ingressClasses() + .withLabel("app.kubernetes.io/name", "contour") + .list() + .getItems() + .isEmpty(); + } + + @Override + public boolean isIngressControllerReady() { + return isComponentReady("envoy") && + isComponentReady("contour"); + } + + @Override + public boolean hasPermissions() { + SelfSubjectAccessReview listIngressClassAccess = createNewSelfSubjectAccessReview("networking.k8s.io", "ingressclasses", null, "list"); + SelfSubjectAccessReview listPodsAnyNamespaceAccess = createNewSelfSubjectAccessReview(null, "pods", INGRESS_CONTOUR_NAMESPACE, "list"); + + return hasAccessForAction(client, listIngressClassAccess) && hasAccessForAction(client, listPodsAnyNamespaceAccess); + } + + private boolean isComponentReady(String componentName) { + return client.pods() + .inNamespace(INGRESS_CONTOUR_NAMESPACE) + .withLabel("app.kubernetes.io/component", componentName) + .list() + .getItems() + .stream() + .anyMatch(KubernetesHelper::isPodReady); + } +} diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorService.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorService.java new file mode 100644 index 0000000000..37d5bf63e9 --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorService.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.KitLogger; + +import java.util.ArrayList; +import java.util.List; + +public class IngressControllerDetectorService { + private final KitLogger log; + private final List ingressControllerDetectors; + + public IngressControllerDetectorService(KitLogger log) { + this.log = log; + ingressControllerDetectors = new ArrayList<>(); + } + + public void setIngressControllerDetectors(List detectors) { + ingressControllerDetectors.addAll(detectors); + } + + public boolean detect() { + boolean anyDetectorHadPermission = false; + for (IngressControllerDetector detector : ingressControllerDetectors) { + boolean permitted = detector.hasPermissions(); + anyDetectorHadPermission |= permitted; + if (permitted && detector.isDetected()) { + return true; + } + } + if (anyDetectorHadPermission) { + logNoIngressControllerWarning(); + } + return false; + } + + private void logNoIngressControllerWarning() { + log.warn("Applying Ingress resources, but no Ingress Controller seems to be running"); + } +} diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetector.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetector.java new file mode 100644 index 0000000000..895916e020 --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetector.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.util.KubernetesHelper; + +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.createNewSelfSubjectAccessReview; +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.hasAccessForAction; + +public class KongIngressControllerDetector implements IngressControllerDetector { + private final KubernetesClient client; + private static final String INGRESS_KONG_NAMESPACE = "kong"; + + public KongIngressControllerDetector(KubernetesClient client) { + this.client = client; + } + + @Override + public boolean hasIngressClass() { + return client.network().v1().ingressClasses() + .list() + .getItems() + .stream() + .anyMatch(i -> i.getSpec().getController().equals("ingress-controllers.konghq.com/kong")); + } + + @Override + public boolean isIngressControllerReady() { + return checkHelmInstallationAvailable() || checkManualInstallationAvailable(); + } + + @Override + public boolean hasPermissions() { + SelfSubjectAccessReview listIngressClassAccess = createNewSelfSubjectAccessReview("networking.k8s.io", "ingressclasses", null, "list"); + SelfSubjectAccessReview listPodsAnyNamespaceAccess = createNewSelfSubjectAccessReview(null, "pods", null, "list"); + + return hasAccessForAction(client, listIngressClassAccess) && hasAccessForAction(client, listPodsAnyNamespaceAccess); + } + + private boolean checkManualInstallationAvailable() { + return client.pods() + .inNamespace(INGRESS_KONG_NAMESPACE) + .withLabel("app", "ingress-kong") + .list() + .getItems() + .stream() + .anyMatch(KubernetesHelper::isPodReady); + } + + private boolean checkHelmInstallationAvailable() { + return client.pods() + .inAnyNamespace() + .withLabel("app.kubernetes.io/name", "kong") + .list() + .getItems() + .stream() + .anyMatch(KubernetesHelper::isPodReady); + } +} diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetector.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetector.java new file mode 100644 index 0000000000..f884be8f04 --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetector.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.util.KubernetesHelper; + +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.createNewSelfSubjectAccessReview; +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.hasAccessForAction; + +public class NginxIngressControllerDetector implements IngressControllerDetector { + private final KubernetesClient client; + private static final String INGRESS_NGINX_NAMESPACE = "ingress-nginx"; + + public NginxIngressControllerDetector(KubernetesClient client) { + this.client = client; + } + + @Override + public boolean hasIngressClass() { + return !client.network().v1().ingressClasses() + .withLabel("app.kubernetes.io/name", INGRESS_NGINX_NAMESPACE) + .list() + .getItems() + .isEmpty(); + } + + @Override + public boolean isIngressControllerReady() { + return client.pods() + .inNamespace(INGRESS_NGINX_NAMESPACE) + .withLabel("app.kubernetes.io/name", INGRESS_NGINX_NAMESPACE) + .list() + .getItems() + .stream() + .anyMatch(KubernetesHelper::isPodReady); + } + + @Override + public boolean hasPermissions() { + SelfSubjectAccessReview listIngressClassAccess = createNewSelfSubjectAccessReview("networking.k8s.io", "ingressclasses", null, "list"); + SelfSubjectAccessReview listPodsAnyNamespaceAccess = createNewSelfSubjectAccessReview(null, "pods", INGRESS_NGINX_NAMESPACE, "list"); + + return hasAccessForAction(client, listIngressClassAccess) && hasAccessForAction(client, listPodsAnyNamespaceAccess); + } +} diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetector.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetector.java new file mode 100644 index 0000000000..b0342f1444 --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetector.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.openshift.client.OpenShiftClient; +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.util.OpenshiftHelper; + +import java.util.Objects; + +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.createNewSelfSubjectAccessReview; +import static org.eclipse.jkube.kit.common.util.KubernetesHelper.hasAccessForAction; + +public class OpenShiftIngressControllerDetector implements IngressControllerDetector { + private final KubernetesClient client; + private static final String INGRESS_OPENSHIFT_NAMESPACE = "openshift-ingress-operator"; + + public OpenShiftIngressControllerDetector(KubernetesClient client) { + this.client = client; + } + + @Override + public boolean hasIngressClass() { + return client.network().v1().ingressClasses() + .list() + .getItems() + .stream() + .anyMatch(i -> i.getSpec().getController().equals("openshift.io/ingress-to-route")); + } + + @Override + public boolean isIngressControllerReady() { + if (OpenshiftHelper.isOpenShift(client)) { + OpenShiftClient openShiftClient = client.adapt(OpenShiftClient.class); + return openShiftClient.operator().ingressControllers() + .inNamespace(INGRESS_OPENSHIFT_NAMESPACE) + .list() + .getItems() + .stream() + .anyMatch(o -> Objects.equals(o.getSpec().getReplicas(), o.getStatus().getAvailableReplicas())); + } + return false; + } + + @Override + public boolean hasPermissions() { + SelfSubjectAccessReview listIngressClassAccess = createNewSelfSubjectAccessReview("networking.k8s.io", "ingressclasses", null, "list"); + SelfSubjectAccessReview listPodsAnyNamespaceAccess = createNewSelfSubjectAccessReview("operator.openshift.io", "ingresscontrollers", INGRESS_OPENSHIFT_NAMESPACE, "list"); + + return hasAccessForAction(client, listIngressClassAccess) && hasAccessForAction(client, listPodsAnyNamespaceAccess); + } +} + diff --git a/jkube-kit/config/service/src/main/resources/META-INF/jkube/ingress-detectors b/jkube-kit/config/service/src/main/resources/META-INF/jkube/ingress-detectors new file mode 100644 index 0000000000..11bc0b2423 --- /dev/null +++ b/jkube-kit/config/service/src/main/resources/META-INF/jkube/ingress-detectors @@ -0,0 +1,4 @@ +org.eclipse.jkube.kit.config.service.ingresscontroller.NginxIngressControllerDetector,100 +org.eclipse.jkube.kit.config.service.ingresscontroller.ContourIngressControllerDetector,100 +org.eclipse.jkube.kit.config.service.ingresscontroller.KongIngressControllerDetector,100 +org.eclipse.jkube.kit.config.service.ingresscontroller.OpenShiftIngressControllerDetector,100 \ No newline at end of file diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ApplyServiceTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ApplyServiceTest.java index 925238582d..ccf7a103e3 100644 --- a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ApplyServiceTest.java +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ApplyServiceTest.java @@ -35,6 +35,7 @@ import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.openshift.client.OpenShiftClient; import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.eclipse.jkube.kit.config.service.openshift.WebServerEventCollector; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -73,7 +74,8 @@ class ApplyServiceTest { @BeforeEach void setUp() { log = new KitLogger.SilentLogger(); - applyService = new ApplyService(client, log); + IngressControllerDetectorService ingressControllerDetectorService = new IngressControllerDetectorService(log); + applyService = new ApplyService(client, ingressControllerDetectorService, log); applyService.setNamespace("default"); } @@ -419,7 +421,7 @@ void applyToMultipleNamespaceNoNamespaceConfigured() { // Then collector.assertEventsRecordedInOrder("serviceaccount-default-create", "configmap-ns1-create", "ingress-ns2-create"); - assertThat(mockServer.getRequestCount()).isEqualTo(5); + assertThat(mockServer.getRequestCount()).isEqualTo(6); applyService.setFallbackNamespace(null); applyService.setNamespace(configuredNamespace); } @@ -451,7 +453,7 @@ void applyToMultipleNamespacesOverriddenWhenNamespaceConfigured() { // Then collector.assertEventsRecordedInOrder("serviceaccount-default-ns-create", "configmap-default-ns-create", "ingress-default-ns-create"); - assertThat(mockServer.getRequestCount()).isEqualTo(5); + assertThat(mockServer.getRequestCount()).isEqualTo(6); applyService.setFallbackNamespace(null); } diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/DebugServiceTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/DebugServiceTest.java index b548a4d44d..e7a94a0f3b 100644 --- a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/DebugServiceTest.java +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/DebugServiceTest.java @@ -42,6 +42,7 @@ import okhttp3.mockwebserver.RecordedRequest; import org.assertj.core.groups.Tuple; import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -79,7 +80,8 @@ class DebugServiceTest { void setUp() { logger = spy(new KitLogger.SilentLogger()); singleThreadExecutor = Executors.newSingleThreadExecutor(); - final ApplyService applyService = new ApplyService(kubernetesClient, logger); + IngressControllerDetectorService ingressControllerDetectorService = new IngressControllerDetectorService(logger); + final ApplyService applyService = new ApplyService(kubernetesClient, ingressControllerDetectorService, logger); applyService.setNamespace(kubernetesClient.getNamespace()); debugService = new DebugService(logger, kubernetesClient, new PortForwardService(logger), applyService); } diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetectorTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetectorTest.java new file mode 100644 index 0000000000..33234dbe36 --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/ContourIngressControllerDetectorTest.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.api.model.PodListBuilder; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassListBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.HttpURLConnection; + +import static org.assertj.core.api.Assertions.assertThat; + +@EnableKubernetesMockClient +class ContourIngressControllerDetectorTest { + private KubernetesMockServer mockServer; + private KubernetesClient client; + private ContourIngressControllerDetector detector; + + @BeforeEach + void setUp() { + detector = new ContourIngressControllerDetector(client); + } + + @Test + void isDetected_whenNothingProvided_thenReturnFalse() { + assertThat(detector.isDetected()).isFalse(); + } + + @Test + void isDetected_whenIngressClassPresentAndControllerRunning_thenReturnTrue() { + // Given + mockServer.expect().get() + .withPath("/apis/networking.k8s.io/v1/ingressclasses?labelSelector=app.kubernetes.io%2Fname%3Dcontour") + .andReturn(HttpURLConnection.HTTP_OK, new IngressClassListBuilder() + .addToItems(new IngressClassBuilder().build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/namespaces/projectcontour/pods?labelSelector=app.kubernetes.io%2Fcomponent%3Denvoy") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder() + .addToItems(new PodBuilder() + .withNewMetadata().withName("envoy").endMetadata() + .withNewStatus().withPhase("Running").endStatus() + .build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/namespaces/projectcontour/pods?labelSelector=app.kubernetes.io%2Fcomponent%3Dcontour") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder() + .addToItems(new PodBuilder() + .withNewMetadata().withName("contour").endMetadata() + .withNewStatus().withPhase("Running").endStatus() + .build()) + .build()) + .once(); + + // When + boolean result = detector.isDetected(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsAllowed_thenReturnTrue() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(true) + .endStatus() + .build()) + .times(2); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsNotAllowed_thenReturnFalse() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(false) + .endStatus() + .build()) + .once(); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isFalse(); + } +} diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorServiceTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorServiceTest.java new file mode 100644 index 0000000000..2f76e3374f --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/IngressControllerDetectorServiceTest.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import org.eclipse.jkube.kit.common.IngressControllerDetector; +import org.eclipse.jkube.kit.common.KitLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; + +class IngressControllerDetectorServiceTest { + private KitLogger logger; + private IngressControllerDetectorService ingressControllerService; + + @BeforeEach + void setUp() { + logger = spy(new KitLogger.SilentLogger()); + ingressControllerService = new IngressControllerDetectorService(logger); + } + + @Test + void detect_whenIngressControllerDetected_thenReturnTrue() { + // Given + IngressControllerDetector ingressControllerDetector = mock(IngressControllerDetector.class); + when(ingressControllerDetector.hasPermissions()).thenReturn(true); + when(ingressControllerDetector.isDetected()).thenReturn(true); + ingressControllerService.setIngressControllerDetectors(Collections.singletonList(ingressControllerDetector)); + + // When + boolean result = ingressControllerService.detect(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void detect_whenIngressControllerNotDetected_thenReturnFalseAndLogWarning() { + // Given + IngressControllerDetector ingressControllerDetector = mock(IngressControllerDetector.class); + when(ingressControllerDetector.hasPermissions()).thenReturn(true); + when(ingressControllerDetector.isDetected()).thenReturn(false); + ingressControllerService.setIngressControllerDetectors(Collections.singletonList(ingressControllerDetector)); + + // When + boolean result = ingressControllerService.detect(); + + // Then + assertThat(result).isFalse(); + verify(logger).warn("Applying Ingress resources, but no Ingress Controller seems to be running"); + } + + @Test + void detect_whenNoPermissionToCheck_thenReturnFalse() { + // Given + IngressControllerDetector ingressControllerDetector = mock(IngressControllerDetector.class); + when(ingressControllerDetector.hasPermissions()).thenReturn(false); + ingressControllerService.setIngressControllerDetectors(Collections.singletonList(ingressControllerDetector)); + + // When + boolean result = ingressControllerService.detect(); + + // Then + assertThat(result).isFalse(); + verify(logger, times(0)).warn(anyString()); + } +} diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetectorTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetectorTest.java new file mode 100644 index 0000000000..534c277edb --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/KongIngressControllerDetectorTest.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.api.model.PodListBuilder; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassListBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.HttpURLConnection; + +import static org.assertj.core.api.Assertions.assertThat; + +@EnableKubernetesMockClient +class KongIngressControllerDetectorTest { + private KubernetesMockServer mockServer; + private KubernetesClient client; + private KongIngressControllerDetector detector; + + @BeforeEach + void setUp() { + detector = new KongIngressControllerDetector(client); + } + + @Test + void isDetected_whenNothingProvided_thenReturnFalse() { + assertThat(detector.isDetected()).isFalse(); + } + + @Test + void isDetected_whenIngressClassPresentAndManualInstallation_thenReturnTrue() { + // Given + mockServer.expect().get() + .withPath("/apis/networking.k8s.io/v1/ingressclasses") + .andReturn(HttpURLConnection.HTTP_OK, new IngressClassListBuilder() + .addToItems(new IngressClassBuilder().withNewSpec().withController("ingress-controllers.konghq.com/kong").endSpec().build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/pods?labelSelector=app.kubernetes.io%2Fname%3Dkong") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder().build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/namespaces/kong/pods?labelSelector=app%3Dingress-kong") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder() + .addToItems(new PodBuilder().withNewStatus().withPhase("Running").endStatus().build()) + .build()) + .once(); + + // When + boolean result = detector.isDetected(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void isDetected_whenIngressClassPresentAndHelmInstallation_thenReturnTrue() { + // Given + mockServer.expect().get() + .withPath("/apis/networking.k8s.io/v1/ingressclasses") + .andReturn(HttpURLConnection.HTTP_OK, new IngressClassListBuilder() + .addToItems(new IngressClassBuilder().withNewSpec().withController("ingress-controllers.konghq.com/kong").endSpec().build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/namespaces/kong/pods?labelSelector=app%3Dingress-kong") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder().build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/pods?labelSelector=app.kubernetes.io%2Fname%3Dkong") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder() + .addToItems(new PodBuilder().withNewStatus().withPhase("Running").endStatus().build()) + .build()) + .once(); + + // When + boolean result = detector.isDetected(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsAllowed_thenReturnTrue() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(true) + .endStatus() + .build()) + .times(2); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsNotAllowed_thenReturnFalse() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(false) + .endStatus() + .build()) + .once(); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isFalse(); + } +} diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetectorTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetectorTest.java new file mode 100644 index 0000000000..fa763730c0 --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/NginxIngressControllerDetectorTest.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.api.model.PodListBuilder; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassListBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.HttpURLConnection; + +import static org.assertj.core.api.Assertions.assertThat; + + +@EnableKubernetesMockClient +class NginxIngressControllerDetectorTest { + private KubernetesMockServer mockServer; + private KubernetesClient client; + private NginxIngressControllerDetector detector; + + @BeforeEach + void setUp() { + detector = new NginxIngressControllerDetector(client); + } + + @Test + void isDetected_whenNothingProvided_thenReturnFalse() { + assertThat(detector.isDetected()).isFalse(); + } + + @Test + void isDetected_whenIngressClassPresentAndControllerRunning_thenReturnTrue() { + // Given + mockServer.expect().get() + .withPath("/apis/networking.k8s.io/v1/ingressclasses?labelSelector=app.kubernetes.io%2Fname%3Dingress-nginx") + .andReturn(HttpURLConnection.HTTP_OK, new IngressClassListBuilder() + .addToItems(new IngressClassBuilder().build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/api/v1/namespaces/ingress-nginx/pods?labelSelector=app.kubernetes.io%2Fname%3Dingress-nginx") + .andReturn(HttpURLConnection.HTTP_OK, new PodListBuilder() + .addToItems(new PodBuilder().withNewStatus().withPhase("Running").endStatus().build()) + .build()) + .once(); + + // When + boolean result = detector.isDetected(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsAllowed_thenReturnTrue() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(true) + .endStatus() + .build()) + .times(2); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsNotAllowed_thenReturnFalse() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(false) + .endStatus() + .build()) + .once(); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isFalse(); + } +} diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetectorTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetectorTest.java new file mode 100644 index 0000000000..f31742bc35 --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/ingresscontroller/OpenShiftIngressControllerDetectorTest.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.config.service.ingresscontroller; + +import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReviewBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.IngressClassListBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import io.fabric8.openshift.api.model.operator.v1.IngressControllerBuilder; +import io.fabric8.openshift.api.model.operator.v1.IngressControllerListBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.HttpURLConnection; + +import static org.assertj.core.api.Assertions.assertThat; + +@EnableKubernetesMockClient +class OpenShiftIngressControllerDetectorTest { + private KubernetesMockServer mockServer; + private KubernetesClient client; + private OpenShiftIngressControllerDetector detector; + + @BeforeEach + void setUp() { + detector = new OpenShiftIngressControllerDetector(client); + } + + @Test + void isDetected_whenNothingProvided_thenReturnFalse() { + assertThat(detector.isDetected()).isFalse(); + } + + @Test + void isDetected_whenIngressClassPresentAndControllerRunning_thenReturnTrue() { + // Given + mockServer.expect().get() + .withPath("/apis/networking.k8s.io/v1/ingressclasses") + .andReturn(HttpURLConnection.HTTP_OK, new IngressClassListBuilder() + .addToItems(new IngressClassBuilder().withNewSpec().withController("openshift.io/ingress-to-route").endSpec().build()) + .build()) + .once(); + mockServer.expect().get() + .withPath("/apis/operator.openshift.io/v1/namespaces/openshift-ingress-operator/ingresscontrollers") + .andReturn(HttpURLConnection.HTTP_OK, new IngressControllerListBuilder() + .addToItems(new IngressControllerBuilder() + .withNewSpec().withReplicas(1).endSpec() + .withNewStatus().withAvailableReplicas(1).endStatus() + .build()) + .build()) + .once(); + + // When + boolean result = detector.isDetected(); + + // Then + assertThat(result).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsAllowed_thenReturnTrue() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(true) + .endStatus() + .build()) + .times(2); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isTrue(); + } + + @Test + void hasPermissions_whenAccessReviewReturnsNotAllowed_thenReturnFalse() { + // Given + mockServer.expect().post() + .withPath("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews") + .andReturn(HttpURLConnection.HTTP_CREATED, new SelfSubjectAccessReviewBuilder() + .withNewStatus() + .withAllowed(false) + .endStatus() + .build()) + .once(); + + // When + boolean permitted = detector.hasPermissions(); + + // Then + assertThat(permitted).isFalse(); + } +} \ No newline at end of file diff --git a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojoTest.java b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojoTest.java index f95d922e7d..8dc175b7a3 100644 --- a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojoTest.java +++ b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojoTest.java @@ -31,6 +31,7 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -53,16 +54,18 @@ class ApplyMojoTest { private MavenProject mavenProject; private NamespacedOpenShiftClient defaultKubernetesClient; private String kubeConfigNamespace; + private IngressControllerDetectorService ingressControllerDetectorService; private ApplyMojo applyMojo; @BeforeEach void setUp(@TempDir Path temporaryFolder) throws IOException { + ingressControllerDetectorService = mock(IngressControllerDetectorService.class); jKubeServiceHubMockedConstruction = mockConstruction(JKubeServiceHub.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS), (mock, context) -> { when(mock.getClient()).thenReturn(defaultKubernetesClient); when(mock.getClusterAccess().createDefaultClient()).thenReturn(defaultKubernetesClient); - when(mock.getApplyService()).thenReturn(new ApplyService(defaultKubernetesClient, new KitLogger.SilentLogger())); + when(mock.getApplyService()).thenReturn(new ApplyService(defaultKubernetesClient, ingressControllerDetectorService, new KitLogger.SilentLogger())); }); clusterAccessMockedConstruction = mockConstruction(ClusterAccess.class, (mock, context) -> when(mock.getNamespace()).thenAnswer(invocation -> kubeConfigNamespace)); diff --git a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/develop/WatchMojoTest.java b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/develop/WatchMojoTest.java index 90d64b9ac1..f888ab3232 100644 --- a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/develop/WatchMojoTest.java +++ b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/develop/WatchMojoTest.java @@ -26,6 +26,7 @@ import org.eclipse.jkube.kit.config.resource.ResourceConfig; import org.eclipse.jkube.kit.config.service.ApplyService; import org.eclipse.jkube.kit.config.service.JKubeServiceHub; +import org.eclipse.jkube.kit.config.service.ingresscontroller.IngressControllerDetectorService; import org.eclipse.jkube.watcher.api.WatcherManager; import io.fabric8.openshift.client.OpenShiftClient; @@ -71,9 +72,10 @@ void setUp(@TempDir File temporaryFolder) throws IOException { mockedDockerAccessFactory = mock(DockerAccessFactory.class); mockedJavaProject = mock(JavaProject.class); mockedClusterAccess = mock(ClusterAccess.class); + IngressControllerDetectorService mockedIngressControllerDetectorService = mock(IngressControllerDetectorService.class); watcherManagerMockedStatic = mockStatic(WatcherManager.class); - when(mockedJKubeServiceHub.getApplyService()).thenReturn(new ApplyService(mockKubernetesClient, new KitLogger.SilentLogger())); + when(mockedJKubeServiceHub.getApplyService()).thenReturn(new ApplyService(mockKubernetesClient, mockedIngressControllerDetectorService, new KitLogger.SilentLogger())); when(mockedJavaProject.getProperties()).thenReturn(new Properties()); when(mavenProject.getArtifactId()).thenReturn("artifact-id"); when(mavenProject.getVersion()).thenReturn("1337");