From ca6c7a34cd920094935829d4e050e04b1b4b3288 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Thu, 28 May 2020 15:26:44 +0530 Subject: [PATCH] Fix #184: IngressEnricher seems to be broken Ported PR https://github.com/fabric8io/fabric8-maven-plugin/pull/1730 --- CHANGELOG.md | 1 + .../kit/config/resource/ResourceConfig.java | 1 + .../jkube/kit/enricher/api/BaseEnricher.java | 2 + .../kit/enricher/api/util/Constants.java | 1 + .../api/util/KubernetesResourceUtil.java | 31 ++ .../generic/DefaultNamespaceEnricher.java | 10 +- .../enricher/generic/IngressEnricher.java | 128 +++++--- .../generic/openshift/ExposeEnricher.java | 4 +- .../generic/openshift/RouteEnricher.java | 132 +++++--- .../enricher/generic/IngressEnricherTest.java | 98 ++++++ .../generic/openshift/RouteEnricherTest.java | 71 ++++ .../config/profiles-lookup-dir/profiles.yaml | 1 + .../doc/src/main/asciidoc/inc/_faq.adoc | 24 ++ .../inc/goals/build/_jkube-apply.adoc | 4 - .../inc/goals/build/_jkube-resource.adoc | 8 + .../maven/plugin/mojo/build/ApplyMojo.java | 310 ------------------ .../maven/plugin/mojo/build/ResourceMojo.java | 14 + .../META-INF/jkube/profiles-default.yml | 5 +- .../expected/openshift.yml | 1 - pom.xml | 2 +- quickstarts/maven/spring-boot/README.md | 113 +++++++ quickstarts/maven/spring-boot/pom.xml | 26 ++ 22 files changed, 583 insertions(+), 404 deletions(-) create mode 100644 jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/IngressEnricherTest.java create mode 100644 jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricherTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8370ce9172..c99a9d500e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Usage: ### 1.0.0-SNAPSHOT * Fix #173: Use OpenShift compliant git/vcs annotations * Fix #182: Assembly is never null +* Fix #184: IngressEnricher seems to be broken, Port of fabric8io/fabric8-maven-plugin#1730 * Fix #198: Wildfly works in OpenShift with S2I binary build (Docker) * Fix #199: BaseGenerator retrieves runtime mode from context (not from missing properties) * Fix #201: Webapp-Wildfly supports S2I source builds too (3 modes Docker, OpenShift-Docker, OpenShift-S2I) diff --git a/jkube-kit/config/resource/src/main/java/org/eclipse/jkube/kit/config/resource/ResourceConfig.java b/jkube-kit/config/resource/src/main/java/org/eclipse/jkube/kit/config/resource/ResourceConfig.java index f8f482520d..35be27a7a0 100644 --- a/jkube-kit/config/resource/src/main/java/org/eclipse/jkube/kit/config/resource/ResourceConfig.java +++ b/jkube-kit/config/resource/src/main/java/org/eclipse/jkube/kit/config/resource/ResourceConfig.java @@ -81,6 +81,7 @@ public class ResourceConfig { @Singular private List ingressRules; private OpenshiftBuildConfig openshiftBuildConfig; + private String routeDomain; public static ResourceConfigBuilder toBuilder(ResourceConfig original) { return Optional.ofNullable(original).orElse(new ResourceConfig()).toBuilder(); diff --git a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/BaseEnricher.java b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/BaseEnricher.java index d1484ccbd8..69be5bef0e 100644 --- a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/BaseEnricher.java +++ b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/BaseEnricher.java @@ -53,6 +53,8 @@ public class BaseEnricher implements Enricher { public static final String ENRICH_ALL_WITH_IMAGE_TRIGGERS = "jkube.openshift.enrichAllWithImageChangeTrigger"; public static final String OPENSHIFT_DEPLOY_TIMEOUT_SECONDS = "jkube.openshift.deployTimeoutSeconds"; private static final String SWITCH_TO_DEPLOYMENT = "jkube.build.switchToDeployment"; + public static final String CREATE_EXTERNAL_URLS = "jkube.createExternalUrls"; + public static final String JKUBE_DOMAIN = "jkube.domain"; protected static final String GENERATE_ROUTE = "jkube.openshift.generateRoute"; protected KitLogger log; diff --git a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/Constants.java b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/Constants.java index 7f9a2839a8..01b86ae009 100644 --- a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/Constants.java +++ b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/Constants.java @@ -20,5 +20,6 @@ public class Constants { public static final String RESOURCE_SOURCE_URL_ANNOTATION = "maven.jkube.io/source-url"; public static final String RESOURCE_APP_CATALOG_ANNOTATION = "maven.jkube.io/app-catalog"; + public static final String EXPOSE_LABEL = "expose"; } diff --git a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/KubernetesResourceUtil.java b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/KubernetesResourceUtil.java index 781ae96e0f..1a62a99136 100644 --- a/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/KubernetesResourceUtil.java +++ b/jkube-kit/enricher/api/src/main/java/org/eclipse/jkube/kit/enricher/api/util/KubernetesResourceUtil.java @@ -81,6 +81,7 @@ import org.eclipse.jkube.kit.common.util.ResourceUtil; import org.eclipse.jkube.kit.config.image.ImageName; import org.eclipse.jkube.kit.config.resource.GroupArtifactVersion; +import org.eclipse.jkube.kit.config.resource.JKubeAnnotations; import org.eclipse.jkube.kit.config.resource.PlatformMode; import org.eclipse.jkube.kit.config.resource.ResourceVersioning; import org.apache.commons.lang3.ArrayUtils; @@ -88,6 +89,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.eclipse.jkube.kit.enricher.api.util.Constants.EXPOSE_LABEL; + /** * Utility class for handling Kubernetes resource descriptors * @@ -881,6 +884,34 @@ public static HasMetadata mergeResources(HasMetadata item1, HasMetadata item2, K return item1; } + public static boolean isExposedService(ObjectMeta metadata, KitLogger log) { + if (metadata != null && metadata.getLabels() != null) { + Map labels = metadata.getLabels(); + return "true".equals(labels.get(EXPOSE_LABEL)) || "true".equals(labels.get(JKubeAnnotations.SERVICE_EXPOSE_URL.value())); + } else { + log.info("No Metadata for service! " + (metadata != null ? metadata.getName() : null)); + } + return false; + } + + public static ObjectMeta removeExposeLabel(ObjectMeta metadata) { + Map labels; + if (metadata != null) { + labels = metadata.getLabels(); + if (labels != null) { + if ("true".equals(labels.get(EXPOSE_LABEL))) { + labels.remove(EXPOSE_LABEL); + } + if("true".equals(labels.get(JKubeAnnotations.SERVICE_EXPOSE_URL.value()))) { + labels.remove(JKubeAnnotations.SERVICE_EXPOSE_URL.value()); + } + } + } + return metadata; + } + + + protected static HasMetadata mergeConfigMaps(ConfigMap cm1, ConfigMap cm2, KitLogger log, boolean switchOnLocalCustomisation) { ConfigMap cm1OrCopy = cm1; if (!switchOnLocalCustomisation) { diff --git a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/DefaultNamespaceEnricher.java b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/DefaultNamespaceEnricher.java index 6ae21452a8..a5609cd0ce 100644 --- a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/DefaultNamespaceEnricher.java +++ b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/DefaultNamespaceEnricher.java @@ -127,18 +127,22 @@ public void visit(ObjectMetaBuilder metaBuilder) { }); // Removing namespace annotation from the namespace and project objects being generated. - // to avoid unncessary trouble while applying these resources. + // to avoid unnecessary trouble while applying these resources. builder.accept(new TypedVisitor() { @Override public void visit(NamespaceBuilder builder) { - builder.withNewStatus().withPhase("Active").endStatus().editMetadata().withNamespace(null).endMetadata().build(); + if (builder.buildStatus().getPhase().equals("active")) { + builder.editOrNewStatus().endStatus().build(); + } } }); builder.accept(new TypedVisitor() { @Override public void visit(ProjectBuilder builder) { - builder.withNewStatus().withPhase("Active").endStatus().editMetadata().withNamespace(null).endMetadata().build(); + if (builder.buildStatus().getPhase().equals("active")) { + builder.editOrNewStatus().endStatus().build(); + } } }); } diff --git a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/IngressEnricher.java b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/IngressEnricher.java index 8b59f97378..5c8833ffb7 100644 --- a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/IngressEnricher.java +++ b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/IngressEnricher.java @@ -16,23 +16,30 @@ import io.fabric8.kubernetes.api.builder.TypedVisitor; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPathBuilder; +import io.fabric8.kubernetes.api.model.extensions.Ingress; import io.fabric8.kubernetes.api.model.extensions.IngressBackendBuilder; import io.fabric8.kubernetes.api.model.extensions.IngressBuilder; import io.fabric8.kubernetes.api.model.extensions.IngressSpecBuilder; -import org.eclipse.jkube.kit.config.resource.JKubeAnnotations; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.common.util.FileUtil; +import org.eclipse.jkube.kit.common.util.KubernetesHelper; import org.eclipse.jkube.kit.config.resource.PlatformMode; import org.eclipse.jkube.kit.config.resource.ResourceConfig; import org.eclipse.jkube.kit.enricher.api.BaseEnricher; import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext; import java.util.List; -import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.isExposedService; +import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.removeExposeLabel; + /** * Enricher which generates an Ingress for each exposed Service */ @@ -44,49 +51,73 @@ public IngressEnricher(JKubeEnricherContext buildContext) { @Override public void create(PlatformMode platformMode, final KubernetesListBuilder listBuilder) { + ResourceConfig resourceConfig = getConfiguration().getResource(); + Boolean shouldCreateIngress = getValueFromConfig(CREATE_EXTERNAL_URLS, false); + if (shouldCreateIngress.equals(Boolean.FALSE)) { + return; + } + if (platformMode == PlatformMode.kubernetes) { listBuilder.accept(new TypedVisitor() { @Override public void visit(ServiceBuilder serviceBuilder) { - addIngress(listBuilder, serviceBuilder); + Ingress ingress = addIngress(listBuilder, serviceBuilder, getRouteDomain(resourceConfig), log); + if (ingress != null) { + listBuilder.addToItems(ingress); + } } }); - } } - private void addIngress(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder) { - ObjectMeta metadata = serviceBuilder.getMetadata(); - if (metadata != null && isExposedService(serviceBuilder)) { - String name = metadata.getName(); - if (!hasIngress(listBuilder, name)) { + protected static Ingress addIngress(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder, String routeDomainPostfix, KitLogger log) { + ObjectMeta serviceMetadata = serviceBuilder.buildMetadata(); + if (serviceMetadata != null && isExposedService(serviceMetadata, log) && shouldCreateExternalURLForService(serviceBuilder, log)) { + String serviceName = serviceMetadata.getName(); + if (!hasIngress(listBuilder, serviceName)) { Integer servicePort = getServicePort(serviceBuilder); if (servicePort != null) { - ResourceConfig resourceConfig = getConfiguration().getResource(); IngressBuilder ingressBuilder = new IngressBuilder(). - withMetadata(serviceBuilder.getMetadata()). + withMetadata(serviceMetadata). withNewSpec(). - endSpec(); - IngressSpecBuilder specBuilder = new IngressSpecBuilder().withBackend(new IngressBackendBuilder(). - withNewServiceName(name). - withNewServicePort(getServicePort(serviceBuilder)). - build()); - if (resourceConfig != null) { - specBuilder.addAllToRules(resourceConfig.getIngressRules()); + // removing `expose : true` label from metadata. + ingressBuilder.withNewMetadataLike(removeExposeLabel(ingressBuilder.buildMetadata())); + + if (StringUtils.isNotBlank(routeDomainPostfix)) { + routeDomainPostfix = serviceName + "." + FileUtil.stripPrefix(routeDomainPostfix, "."); + ingressBuilder = ingressBuilder.withSpec(new IngressSpecBuilder().addNewRule(). + withHost(routeDomainPostfix). + withNewHttp(). + withPaths(new HTTPIngressPathBuilder() + .withNewBackend() + .withServiceName(serviceName) + .withServicePort(KubernetesHelper.createIntOrString(getServicePort(serviceBuilder))) + .endBackend() + .build()) + .endHttp(). + endRule().build()); + } else { + ingressBuilder.withSpec(new IngressSpecBuilder().withBackend(new IngressBackendBuilder(). + withNewServiceName(serviceName) + .withNewServicePort(getServicePort(serviceBuilder)) + .build()).build()); } + + return ingressBuilder.build(); } } } + return null; } - private Integer getServicePort(ServiceBuilder serviceBuilder) { - ServiceSpec spec = serviceBuilder.getSpec(); + private static Integer getServicePort(ServiceBuilder serviceBuilder) { + ServiceSpec spec = serviceBuilder.buildSpec(); if (spec != null) { List ports = spec.getPorts(); - if (ports != null && ports.size() > 0) { + if (ports != null && !ports.isEmpty()) { for (ServicePort port : ports) { if (port.getName().equals("http") || port.getProtocol().equals("http")) { return port.getPort(); @@ -98,19 +129,19 @@ private Integer getServicePort(ServiceBuilder serviceBuilder) { } } } - return null; + return 0; } /** * Returns true if we already have a route created for the given name */ - private boolean hasIngress(final KubernetesListBuilder listBuilder, final String name) { + private static boolean hasIngress(final KubernetesListBuilder listBuilder, final String name) { final AtomicBoolean answer = new AtomicBoolean(false); listBuilder.accept(new TypedVisitor() { @Override public void visit(IngressBuilder builder) { - ObjectMeta metadata = builder.getMetadata(); + ObjectMeta metadata = builder.buildMetadata(); if (metadata != null && name.equals(metadata.getName())) { answer.set(true); } @@ -119,23 +150,44 @@ public void visit(IngressBuilder builder) { return answer.get(); } - private boolean isExposedService(ServiceBuilder serviceBuilder) { - Service service = serviceBuilder.build(); - return isExposedService(service); - } - - private boolean isExposedService(Service service) { - ObjectMeta metadata = service.getMetadata(); - if (metadata != null) { - Map labels = metadata.getLabels(); - if (labels != null) { - if ("true".equals(labels.get("expose")) || "true".equals(labels.get(JKubeAnnotations.SERVICE_EXPOSE_URL.value()))) { + /** + * Should we try to create an external URL for the given service? + *

+ * By default lets ignore the kubernetes services and any service which does not expose ports 80 and 443 + * + * @return true if we should create an Ingress for this service. + */ + private static boolean shouldCreateExternalURLForService(ServiceBuilder service, KitLogger log) { + String serviceName = service.buildMetadata().getName(); + ServiceSpec spec = service.buildSpec(); + if (spec != null && !isKuberentesSystemService(serviceName)) { + List ports = spec.getPorts(); + log.debug("Service " + serviceName + " has ports: " + ports); + if (ports.size() == 1) { + String type = spec.getType(); + if (Objects.equals(type, "LoadBalancer")) { return true; } + log.info("Not generating Ingress for service " + serviceName + " type is not LoadBalancer: " + type); + } else { + log.info("Not generating Ingress for service " + serviceName + " as only single port services are supported. Has ports: " + ports); } - } else { - log.info("No Metadata for service! " + service); } return false; } + + private static boolean isKuberentesSystemService(String serviceName) { + return "kubernetes".equals(serviceName) || "kubernetes-ro".equals(serviceName); + } + + private String getRouteDomain(ResourceConfig resourceConfig) { + if (resourceConfig != null && resourceConfig.getRouteDomain() != null) { + return resourceConfig.getRouteDomain(); + } + String routeDomainFromProperties = getValueFromConfig(JKUBE_DOMAIN, ""); + if (StringUtils.isNotEmpty(routeDomainFromProperties)) { + return routeDomainFromProperties; + } + return null; + } } diff --git a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/ExposeEnricher.java b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/ExposeEnricher.java index c57e0961c7..ae80e3921e 100644 --- a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/ExposeEnricher.java +++ b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/ExposeEnricher.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Set; +import static org.eclipse.jkube.kit.enricher.api.util.Constants.EXPOSE_LABEL; + /** * Enricher for enabling exposing of HTTP / HTTPS based services */ @@ -41,8 +43,6 @@ public ExposeEnricher(JKubeEnricherContext buildContext) { private Set webPorts = new HashSet<>(Arrays.asList(80, 443, 8080, 9080, 9090, 9443)); - public static final String EXPOSE_LABEL = "expose"; - @Override public void create(PlatformMode platformMode, KubernetesListBuilder builder) { List items = builder.getItems(); diff --git a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricher.java b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricher.java index 9e04dcd3cc..5a26956ff3 100644 --- a/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricher.java +++ b/jkube-kit/enricher/generic/src/main/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricher.java @@ -17,23 +17,28 @@ import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.KubernetesListBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServiceSpec; import io.fabric8.openshift.api.model.Route; import io.fabric8.openshift.api.model.RouteBuilder; import io.fabric8.openshift.api.model.RoutePort; -import org.eclipse.jkube.kit.config.resource.JKubeAnnotations; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jkube.kit.common.util.FileUtil; import org.eclipse.jkube.kit.config.resource.PlatformMode; +import org.eclipse.jkube.kit.config.resource.ResourceConfig; import org.eclipse.jkube.kit.enricher.api.BaseEnricher; import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.isExposedService; +import static org.eclipse.jkube.kit.enricher.api.util.KubernetesResourceUtil.removeExposeLabel; + /** * Enricher which generates a Route for each exposed Service */ @@ -44,9 +49,16 @@ public RouteEnricher(JKubeEnricherContext buildContext) { super(buildContext, "jkube-openshift-route"); this.generateRoute = getValueFromConfig(GENERATE_ROUTE, true); } + private String routeDomainPostfix; @Override public void create(PlatformMode platformMode, final KubernetesListBuilder listBuilder) { + ResourceConfig resourceConfig = getConfiguration().getResource(); + + if (resourceConfig != null && resourceConfig.getRouteDomain() != null) { + routeDomainPostfix = resourceConfig.getRouteDomain(); + } + if(platformMode == PlatformMode.openshift && generateRoute.equals(Boolean.TRUE)) { final List routes = new ArrayList<>(); listBuilder.accept(new TypedVisitor() { @@ -65,32 +77,13 @@ public void visit(ServiceBuilder serviceBuilder) { } } - private void addRoute(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder, List routes) { - ObjectMeta metadata = serviceBuilder.getMetadata(); - if (metadata != null && isExposedService(serviceBuilder)) { - String name = metadata.getName(); - if (!hasRoute(listBuilder, name)) { - RoutePort routePort = createRoutePort(serviceBuilder); - if (routePort != null) { - // TODO one day lets support multiple ports on a Route when the model supports it - routes.add(new RouteBuilder(). - withMetadata(serviceBuilder.getMetadata()). - withNewSpec(). - withPort(routePort). - withNewTo().withKind("Service").withName(name).endTo(). - endSpec(). - build()); - } - } - } - } private RoutePort createRoutePort(ServiceBuilder serviceBuilder) { RoutePort routePort = null; - ServiceSpec spec = serviceBuilder.getSpec(); + ServiceSpec spec = serviceBuilder.buildSpec(); if (spec != null) { List ports = spec.getPorts(); - if (ports != null && ports.size() > 0) { + if (ports != null && !ports.isEmpty()) { ServicePort servicePort = ports.get(0); if (servicePort != null) { IntOrString targetPort = servicePort.getTargetPort(); @@ -104,6 +97,76 @@ private RoutePort createRoutePort(ServiceBuilder serviceBuilder) { return routePort; } + private String prepareHostForRoute(String routeDomainPostfix, String name) { + String ret = FileUtil.stripPostfix(name,"-service"); + ret = FileUtil.stripPostfix(ret,"."); + ret += "."; + ret += FileUtil.stripPrefix(routeDomainPostfix, "."); + return ret; + } + + private Set getPorts(ServiceBuilder service) { + Set answer = new HashSet<>(); + if (service != null) { + ServiceSpec spec = getOrCreateSpec(service); + for (ServicePort port : spec.getPorts()) { + answer.add(port.getPort()); + } + } + return answer; + } + + public static ServiceSpec getOrCreateSpec(ServiceBuilder entity) { + ServiceSpec spec = entity.buildSpec(); + if (spec == null) { + spec = new ServiceSpec(); + entity.editOrNewSpec().endSpec(); + } + return spec; + } + + private boolean hasExactlyOneServicePort(ServiceBuilder service, String id) { + Set ports = getPorts(service); + if (ports.size() != 1) { + log.info("Not generating route for service " + id + " as only single port services are supported. Has ports: " + + ports); + return false; + } else { + return true; + } + } + + private void addRoute(KubernetesListBuilder listBuilder, ServiceBuilder serviceBuilder, List routes) { + ObjectMeta serviceMetadata = serviceBuilder.buildMetadata(); + + if (serviceMetadata != null && StringUtils.isNotBlank(serviceMetadata.getName()) + && hasExactlyOneServicePort(serviceBuilder, serviceMetadata.getName()) && isExposedService(serviceMetadata, log)) { + String name = serviceMetadata.getName(); + if (!hasRoute(listBuilder, name)) { + if (StringUtils.isNotBlank(routeDomainPostfix)) { + routeDomainPostfix = prepareHostForRoute(routeDomainPostfix, name); + } else { + routeDomainPostfix = ""; + } + + RoutePort routePort = createRoutePort(serviceBuilder); + if (routePort != null) { + RouteBuilder routeBuilder = new RouteBuilder(). + withMetadata(serviceMetadata). + withNewSpec(). + withPort(routePort). + withNewTo().withKind("Service").withName(name).endTo(). + withHost(routeDomainPostfix.isEmpty() ? null : routeDomainPostfix). + endSpec(); + + // removing `expose : true` label from metadata. + routeBuilder.withNewMetadataLike(removeExposeLabel(routeBuilder.buildMetadata())); + routes.add(routeBuilder.build()); + } + } + } + } + /** * Returns true if we already have a route created for the given name */ @@ -113,7 +176,7 @@ private boolean hasRoute(final KubernetesListBuilder listBuilder, final String n @Override public void visit(RouteBuilder builder) { - ObjectMeta metadata = builder.getMetadata(); + ObjectMeta metadata = builder.buildMetadata(); if (metadata != null && name.equals(metadata.getName())) { answer.set(true); } @@ -122,23 +185,4 @@ public void visit(RouteBuilder builder) { return answer.get(); } - protected boolean isExposedService(ServiceBuilder serviceBuilder) { - Service service = serviceBuilder.build(); - return isExposedService(service); - } - - protected boolean isExposedService(Service service) { - ObjectMeta metadata = service.getMetadata(); - if (metadata != null) { - Map labels = metadata.getLabels(); - if (labels != null) { - if ("true".equals(labels.get("expose")) || "true".equals(labels.get(JKubeAnnotations.SERVICE_EXPOSE_URL.value()))) { - return true; - } - } - } else { - log.info("No Metadata for service! " + service); - } - return false; - } } diff --git a/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/IngressEnricherTest.java b/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/IngressEnricherTest.java new file mode 100644 index 0000000000..a6cc3ba199 --- /dev/null +++ b/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/IngressEnricherTest.java @@ -0,0 +1,98 @@ +/** + * 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.enricher.generic; + +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import mockit.Expectations; +import mockit.Mocked; +import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.config.resource.PlatformMode; +import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext; +import org.junit.Test; + +import static org.eclipse.jkube.kit.enricher.api.BaseEnricher.CREATE_EXTERNAL_URLS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class IngressEnricherTest { + @Mocked + private JKubeEnricherContext context; + + @Mocked + private KitLogger logger; + + @Test + public void testCreate() { + // Given + new Expectations() {{ + // Enable creation of Ingress for Service of type LoadBalancer + context.getProperty(CREATE_EXTERNAL_URLS); + result = "true"; + }}; + + Service providedService = getTestService().build(); + KubernetesListBuilder kubernetesListBuilder = new KubernetesListBuilder().addToItems(providedService); + IngressEnricher ingressEnricher = new IngressEnricher(context); + + // When + ingressEnricher.create(PlatformMode.kubernetes, kubernetesListBuilder); + + // Then + Ingress ingress = (Ingress) kubernetesListBuilder.buildLastItem(); + assertEquals(2, kubernetesListBuilder.buildItems().size()); + assertNotNull(ingress); + assertEquals(providedService.getMetadata().getName(), ingress.getMetadata().getName()); + assertNotNull(ingress.getSpec()); + assertEquals(providedService.getMetadata().getName(), ingress.getSpec().getBackend().getServiceName()); + assertEquals(providedService.getSpec().getPorts().get(0).getTargetPort(), ingress.getSpec().getBackend().getServicePort()); + } + + @Test + public void testAddIngress() { + // Given + ServiceBuilder testSvcBuilder = getTestService(); + KubernetesListBuilder kubernetesListBuilder = new KubernetesListBuilder().addToItems(testSvcBuilder); + + // When + Ingress ingress = IngressEnricher.addIngress(kubernetesListBuilder, testSvcBuilder, "org.eclipse.jkube", logger); + + // Then + assertNotNull(ingress); + assertEquals(testSvcBuilder.buildMetadata().getName(), ingress.getMetadata().getName()); + assertEquals(1, ingress.getSpec().getRules().size()); + assertEquals(testSvcBuilder.getMetadata().getName() + "." + "org.eclipse.jkube", ingress.getSpec().getRules().get(0).getHost()); + } + + private ServiceBuilder getTestService() { + return new ServiceBuilder() + .withNewMetadata() + .withName("test-svc") + .addToLabels("expose", "true") + .endMetadata() + .withNewSpec() + .addNewPort() + .withName("http") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8080)) + .endPort() + .addToSelector("group", "test") + .withType("LoadBalancer") + .endSpec(); + } +} diff --git a/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricherTest.java b/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricherTest.java new file mode 100644 index 0000000000..0f7b021832 --- /dev/null +++ b/jkube-kit/enricher/generic/src/test/java/org/eclipse/jkube/enricher/generic/openshift/RouteEnricherTest.java @@ -0,0 +1,71 @@ +/** + * 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.enricher.generic.openshift; + +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.openshift.api.model.Route; +import mockit.Mocked; +import org.eclipse.jkube.kit.config.resource.PlatformMode; +import org.eclipse.jkube.kit.enricher.api.JKubeEnricherContext; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class RouteEnricherTest { + @Mocked + private JKubeEnricherContext context; + + @Test + public void testCreate() { + // Given + Service providedService = getTestService().build(); + KubernetesListBuilder kubernetesListBuilder = new KubernetesListBuilder().addToItems(providedService); + RouteEnricher routeEnricher = new RouteEnricher(context); + + // When + routeEnricher.create(PlatformMode.openshift, kubernetesListBuilder); + + // Then + Route route = (Route) kubernetesListBuilder.buildLastItem(); + assertEquals(2, kubernetesListBuilder.buildItems().size()); + assertNotNull(route); + assertEquals(providedService.getMetadata().getName(), route.getMetadata().getName()); + assertNotNull(route.getSpec()); + assertEquals("Service", route.getSpec().getTo().getKind()); + assertEquals("test-svc", route.getSpec().getTo().getName()); + assertEquals(8080, route.getSpec().getPort().getTargetPort().getIntVal().intValue()); + } + + private ServiceBuilder getTestService() { + return new ServiceBuilder() + .withNewMetadata() + .withName("test-svc") + .addToLabels("expose", "true") + .endMetadata() + .withNewSpec() + .addNewPort() + .withName("http") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8080)) + .endPort() + .addToSelector("group", "test") + .withType("LoadBalancer") + .endSpec(); + } +} diff --git a/jkube-kit/profile/src/test/resources/jkube/config/profiles-lookup-dir/profiles.yaml b/jkube-kit/profile/src/test/resources/jkube/config/profiles-lookup-dir/profiles.yaml index 8d04578e53..83b23ccadd 100644 --- a/jkube-kit/profile/src/test/resources/jkube/config/profiles-lookup-dir/profiles.yaml +++ b/jkube-kit/profile/src/test/resources/jkube/config/profiles-lookup-dir/profiles.yaml @@ -39,3 +39,4 @@ - jkube-image - jkube-project - jkube-debug + - jkube-ingress diff --git a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/_faq.adoc b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/_faq.adoc index e6015e82a5..659344a4c7 100644 --- a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/_faq.adoc +++ b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/_faq.adoc @@ -121,3 +121,27 @@ spec: Where the above defines the `PersistentVolumeClaim` called `foo` which is then mounted into the container at `/whatnot` +=== How do I generate Ingress for my generated Service? +`Ingress` generation is supported by Eclipse JKube for `Service` objects of type `LoadBalancer`. In order to generate `Ingress` you need to enable `jkube.createExternalUrls` property to `true` and `jkube.domain` property to desired host suffix, it would be appended to your service name for host value. You can also also provide a host for it in XML config like this: +``` + + org.eclipse.jkube + kubernetes-maven-plugin + ${project.version} + + + + org.eclipse.jkube + + + + + + LoadBalancer + + + + + +``` +You can find an example in our link:https://github.com/eclipse/jkube/tree/master/quickstarts/maven/spring-boot[spring-boot] quickstart in `kubernetes-with-ingress` profile. \ No newline at end of file diff --git a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-apply.adoc b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-apply.adoc index f75f0137c4..cd2413e401 100644 --- a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-apply.adoc +++ b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-apply.adoc @@ -69,10 +69,6 @@ endif::[] | Do we want to ignore OAuthClients which are already running?. OAuthClients are shared across namespaces so we should not try to update or create/delete global oauth clients, defaults to `true` | `jkube.deploy.ignoreRunningOAuthClients` -| *createExternalUrls* -| Should we create external Ingress for any LoadBalancer Services which don't already have them. Defaults to `false` -| `jkube.deploy.createExternalUrls` - | *jsonLogDir* | The folder we should store any temporary json files or results. Defaults to `${basedir}/target/jkube/applyJson` | `jkube.deploy.jsonLogDir` diff --git a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-resource.adoc b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-resource.adoc index a756559d8e..5e46d00ddd 100644 --- a/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-resource.adoc +++ b/kubernetes-maven-plugin/doc/src/main/asciidoc/inc/goals/build/_jkube-resource.adoc @@ -327,4 +327,12 @@ endif::[] | Skip resource generation. Defaults to `false` | `jkube.skip.resource` +| *createExternalUrls* +| Should we create external Ingress for any LoadBalancer Services which don't already have them. Defaults to `false` +| `jkube.createExternalUrls` + +| *domain* +| Domain added to the Service ID when creating Kubernetes Ingresses or OpenShift routes +| `jkube.domain` + |=== diff --git a/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojo.java b/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojo.java index 1d507f4427..1d1d9d976e 100644 --- a/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojo.java +++ b/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ApplyMojo.java @@ -13,39 +13,18 @@ */ package org.eclipse.jkube.maven.plugin.mojo.build; - -import com.fasterxml.jackson.core.JsonProcessingException; import io.fabric8.kubernetes.api.model.DoneableService; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Namespace; -import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.ServicePort; -import io.fabric8.kubernetes.api.model.ServiceSpec; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPath; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPathBuilder; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressRuleValue; -import io.fabric8.kubernetes.api.model.extensions.Ingress; -import io.fabric8.kubernetes.api.model.extensions.IngressBackend; -import io.fabric8.kubernetes.api.model.extensions.IngressBuilder; -import io.fabric8.kubernetes.api.model.extensions.IngressList; -import io.fabric8.kubernetes.api.model.extensions.IngressRule; -import io.fabric8.kubernetes.api.model.extensions.IngressSpec; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; import io.fabric8.openshift.api.model.Project; -import io.fabric8.openshift.api.model.Route; -import io.fabric8.openshift.api.model.RouteList; -import io.fabric8.openshift.api.model.RouteSpec; -import io.fabric8.openshift.api.model.RouteTargetReference; -import io.fabric8.openshift.api.model.RouteTargetReferenceBuilder; -import io.fabric8.openshift.client.OpenShiftClient; import org.eclipse.jkube.kit.common.KitLogger; -import org.eclipse.jkube.kit.common.util.FileUtil; import org.eclipse.jkube.kit.common.util.KubernetesHelper; import org.eclipse.jkube.kit.common.util.OpenshiftHelper; import org.eclipse.jkube.kit.common.util.ResourceUtil; @@ -57,7 +36,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -68,13 +46,9 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; /** @@ -86,12 +60,6 @@ public class ApplyMojo extends AbstractJKubeMojo implements ManifestProvider { public static final String DEFAULT_KUBERNETES_MANIFEST = "${basedir}/target/classes/META-INF/jkube/kubernetes.yml"; public static final String DEFAULT_OPENSHIFT_MANIFEST = "${basedir}/target/classes/META-INF/jkube/openshift.yml"; - /** - * The domain added to the service ID when creating OpenShift routes - */ - @Parameter(property = "jkube.domain") - protected String routeDomain; - /** * Should we fail the build if an apply fails? */ @@ -163,16 +131,6 @@ public class ApplyMojo extends AbstractJKubeMojo implements ManifestProvider { @Parameter(property = "jkube.deploy.ignoreRunningOAuthClients", defaultValue = "true") private boolean ignoreRunningOAuthClients; - /** - * Should we create external Ingress/Routes for any LoadBalancer Services which don't already have them. - *

- * We now do not do this by default and defer this to the - * exposecontroller to decide - * if Ingress or Router is being used or whether we should use LoadBalancer or NodePorts for single node clusters - */ - @Parameter(property = "jkube.deploy.createExternalUrls", defaultValue = "false") - private boolean createExternalUrls; - /** * The folder we should store any temporary json files or results */ @@ -289,13 +247,6 @@ public void executeInternal() throws MojoExecutionException { applyService.setNamespace(namespace); - if (createExternalUrls) { - if (applyService.getOpenShiftClient() != null) { - createRoutes(entities); - } else { - createIngress(kubernetes, entities); - } - } applyEntities(kubernetes, namespace, manifest.getName(), entities); log.info("[[B]]HINT:[[B]] Use the command `%s get pods -w` to watch your pods start up", clusterAccess.isOpenShiftImageStream(log) ? "oc" : "kubectl"); @@ -312,158 +263,6 @@ protected void initServices(KubernetesClient kubernetes) { log.debug("No services required in ApplyMojo"); } - private Route createRouteForService(String routeDomainPostfix, String namespace, Service service) { - Route route = null; - String id = KubernetesHelper.getName(service); - if (StringUtils.isNotBlank(id) && hasExactlyOneService(service, id)) { - route = new Route(); - ObjectMeta routeMeta = KubernetesHelper.getOrCreateMetadata(route); - routeMeta.setName(id); - routeMeta.setNamespace(namespace); - - RouteSpec routeSpec = new RouteSpec(); - RouteTargetReference objectRef = new RouteTargetReferenceBuilder().withName(id).build(); - //objectRef.setNamespace(namespace); - routeSpec.setTo(objectRef); - if (StringUtils.isNotBlank(routeDomainPostfix)) { - routeSpec.setHost(prepareHostForRoute(routeDomainPostfix, id)); - } else { - routeSpec.setHost(""); - } - route.setSpec(routeSpec); - String json; - try { - json = ResourceUtil.toJson(route); - } catch (JsonProcessingException e) { - json = e.getMessage() + ". object: " + route; - } - log.debug("Created route: " + json); - } - return route; - } - - private String prepareHostForRoute(String routeDomainPostfix, String name) { - String ret = FileUtil.stripPostfix(name,"-service"); - ret = FileUtil.stripPostfix(ret,"."); - ret += "."; - ret += FileUtil.stripPrefix(routeDomainPostfix, "."); - return ret; - } - - - private Ingress createIngressForService(String routeDomainPostfix, String namespace, Service service) { - Ingress ingress = null; - String serviceName = KubernetesHelper.getName(service); - ServiceSpec serviceSpec = service.getSpec(); - if (serviceSpec != null && StringUtils.isNotBlank(serviceName) && shouldCreateExternalURLForService(service, serviceName)) { - String ingressId = serviceName; - String host = ""; - if (StringUtils.isNotBlank(routeDomainPostfix)) { - host = serviceName + "." + namespace + "." + FileUtil.stripPrefix(routeDomainPostfix, "."); - } - List paths = new ArrayList<>(); - List ports = serviceSpec.getPorts(); - if (ports != null) { - for (ServicePort port : ports) { - Integer portNumber = port.getPort(); - if (portNumber != null) { - HTTPIngressPath path = - new HTTPIngressPathBuilder() - .withNewBackend() - .withServiceName(serviceName) - .withServicePort(KubernetesHelper.createIntOrString(portNumber)) - .endBackend() - .build(); - paths.add(path); - } - } - } - if (paths.isEmpty()) { - return ingress; - } - ingress = new IngressBuilder(). - withNewMetadata().withName(ingressId).withNamespace(namespace).endMetadata(). - withNewSpec(). - addNewRule(). - withHost(host). - withNewHttp(). - withPaths(paths). - endHttp(). - endRule(). - endSpec().build(); - - String json; - try { - json = ResourceUtil.toJson(ingress); - } catch (JsonProcessingException e) { - json = e.getMessage() + ". object: " + ingress; - } - log.debug("Created ingress: " + json); - } - return ingress; - } - - - /** - * Should we try to create an external URL for the given service? - *

- * By default lets ignore the kubernetes services and any service which does not expose ports 80 and 443 - * - * @return true if we should create an OpenShift Route for this service. - */ - private boolean shouldCreateExternalURLForService(Service service, String id) { - if ("kubernetes".equals(id) || "kubernetes-ro".equals(id)) { - return false; - } - Set ports = getPorts(service); - log.debug("Service " + id + " has ports: " + ports); - if (ports.size() == 1) { - String type = null; - ServiceSpec spec = service.getSpec(); - if (spec != null) { - type = spec.getType(); - if (Objects.equals(type, "LoadBalancer")) { - return true; - } - } - log.info("Not generating route for service " + id + " type is not LoadBalancer: " + type); - } else { - log.info("Not generating route for service " + id + " as only single port services are supported. Has ports: " + ports); - } - return false; - } - - private boolean hasExactlyOneService(Service service, String id) { - Set ports = getPorts(service); - if (ports.size() != 1) { - log.info("Not generating route for service " + id + " as only single port services are supported. Has ports: " + - ports); - return false; - } else { - return true; - } - } - - private Set getPorts(Service service) { - Set answer = new HashSet<>(); - if (service != null) { - ServiceSpec spec = getOrCreateSpec(service); - for (ServicePort port : spec.getPorts()) { - answer.add(port.getPort()); - } - } - return answer; - } - - public static ServiceSpec getOrCreateSpec(Service entity) { - ServiceSpec spec = entity.getSpec(); - if (spec == null) { - spec = new ServiceSpec(); - entity.setSpec(spec); - } - return spec; - } - protected void applyEntities(KubernetesClient kubernetes, String namespace, String fileName, Set entities) throws Exception { // Apply all items for (HasMetadata entity : entities) { @@ -525,10 +324,6 @@ protected boolean isExposeService(Service service) { return expose != null && expose.toLowerCase().equals("true"); } - public boolean isRollingUpgrades() { - return rollingUpgrades; - } - public boolean isRollingUpgradePreserveScale() { return false; } @@ -547,74 +342,6 @@ protected void disableOpenShiftFeatures(ApplyService applyService) { applyService.setProcessTemplatesLocally(true); } - - protected void createRoutes(Collection collection) { - String routeDomainPostfix = this.routeDomain; - Log log = getLog(); - String namespace = clusterAccess.getNamespace(); - // lets get the routes first to see if we should bother - try { - OpenShiftClient openshiftClient = applyService.getOpenShiftClient(); - if (openshiftClient == null) { - return; - } - RouteList routes = openshiftClient.routes().inNamespace(namespace).list(); - if (routes != null) { - routes.getItems(); - } - } catch (Exception e) { - log.warn("Cannot load OpenShift Routes; maybe not connected to an OpenShift platform? " + e, e); - return; - } - List routes = new ArrayList<>(); - for (Object object : collection) { - if (object instanceof Service) { - Service service = (Service) object; - Route route = createRouteForService(routeDomainPostfix, namespace, service); - if (route != null) { - routes.add(route); - } - } - } - collection.addAll(routes); - } - - protected void createIngress(KubernetesClient kubernetesClient, Collection collection) { - String routeDomainPostfix = this.routeDomain; - Log log = getLog(); - String namespace = clusterAccess.getNamespace(); - List ingressList = null; - // lets get the routes first to see if we should bother - try { - IngressList ingresses = kubernetesClient.extensions().ingresses().inNamespace(namespace).list(); - if (ingresses != null) { - ingressList = ingresses.getItems(); - } - } catch (Exception e) { - log.warn("Cannot load Ingress instances. Must be an older version of Kubernetes? Error: " + e, e); - return; - } - List ingresses = new ArrayList<>(); - for (HasMetadata object : collection) { - if (object instanceof Service) { - Service service = (Service) object; - if (!serviceHasIngressRule(ingressList, service)) { - Ingress ingress = createIngressForService(routeDomainPostfix, namespace, service); - if (ingress != null) { - ingresses.add(ingress); - log.info("Created ingress for " + namespace + ":" + KubernetesHelper.getName(service)); - } else { - log.debug("No ingress required for " + namespace + ":" + KubernetesHelper.getName(service)); - } - } else { - log.info("Already has ingress for service " + namespace + ":" + KubernetesHelper.getName(service)); - } - } - } - collection.addAll(ingresses); - - } - protected void processCustomEntities(KubernetesClient client, String namespace, List customResourceDefinitions, boolean isDelete) throws Exception { if(customResourceDefinitions == null) return; @@ -652,43 +379,6 @@ protected Map getCustomResourcesFileToNamemap() throws IOException return fileToCrdGroupMap; } - /** - * Returns true if there is an existing ingress rule for the given service - */ - private boolean serviceHasIngressRule(List ingresses, Service service) { - String serviceName = KubernetesHelper.getName(service); - for (Ingress ingress : ingresses) { - IngressSpec spec = ingress.getSpec(); - if (spec == null) { - break; - } - List rules = spec.getRules(); - if (rules == null) { - break; - } - for (IngressRule rule : rules) { - HTTPIngressRuleValue http = rule.getHttp(); - if (http == null) { - break; - } - List paths = http.getPaths(); - if (paths == null) { - break; - } - for (HTTPIngressPath path : paths) { - IngressBackend backend = path.getBackend(); - if (backend == null) { - break; - } - if (Objects.equals(serviceName, backend.getServiceName())) { - return true; - } - } - } - } - return false; - } - /** * Returns the root project folder */ diff --git a/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ResourceMojo.java b/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ResourceMojo.java index 58e5c258fb..c5c7778e8f 100644 --- a/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ResourceMojo.java +++ b/kubernetes-maven-plugin/plugin/src/main/java/org/eclipse/jkube/maven/plugin/mojo/build/ResourceMojo.java @@ -197,6 +197,20 @@ public class ResourceMojo extends AbstractJKubeMojo { @Parameter(property = "jkube.namespace") private String namespace; + /** + * Should we create external Ingress/Routes for any LoadBalancer Services which don't already have them. + *

+ * if Ingress or Router is being used or whether we should use LoadBalancer or NodePorts for single node clusters + */ + @Parameter(property = "jkube.createExternalUrls", defaultValue = "false") + private Boolean createExternalUrls; + + /* + * The domain added to the service ID when creating Kubernetes Ingress + */ + @Parameter(property = "jkube.domain") + protected String routeDomain; + @Parameter(property = "jkube.sidecar", defaultValue = "false") private Boolean sidecar; diff --git a/kubernetes-maven-plugin/plugin/src/main/resources/META-INF/jkube/profiles-default.yml b/kubernetes-maven-plugin/plugin/src/main/resources/META-INF/jkube/profiles-default.yml index 340629b17b..5c9dbaf5d1 100644 --- a/kubernetes-maven-plugin/plugin/src/main/resources/META-INF/jkube/profiles-default.yml +++ b/kubernetes-maven-plugin/plugin/src/main/resources/META-INF/jkube/profiles-default.yml @@ -48,6 +48,9 @@ - jkube-openshift-deploymentconfig - jkube-openshift-project + # Ingress + - jkube-ingress + # ----------------------------------------- # TODO: Document and verify enrichers below # Health checks @@ -60,7 +63,7 @@ - jkube-healthcheck-docker - jkube-healthcheck-webapp - jkube-prometheus - # Dependencies shouldn't be enriched anymore, therefor it's last in the list + # Dependencies shouldn't be enriched anymore, therefore it's last in the list - jkube-dependency - jkube-revision-history - jkube-docker-registry-secret diff --git a/openshift-maven-plugin/it/src/it/simple-with-route-flag-true/expected/openshift.yml b/openshift-maven-plugin/it/src/it/simple-with-route-flag-true/expected/openshift.yml index 5f8a2aa3b9..5cb0289f30 100644 --- a/openshift-maven-plugin/it/src/it/simple-with-route-flag-true/expected/openshift.yml +++ b/openshift-maven-plugin/it/src/it/simple-with-route-flag-true/expected/openshift.yml @@ -116,7 +116,6 @@ items: kind: Route metadata: labels: - expose: "true" app: jkube-maven-sample-zero-config provider: jkube version: 0.1-SNAPSHOT diff --git a/pom.xml b/pom.xml index 74563ddae7..4bb3fa072c 100644 --- a/pom.xml +++ b/pom.xml @@ -451,7 +451,7 @@ sonar jkubeio_jkube - ${artifactId} + ${project.artifactId} jkubeio https://sonarcloud.io ${env.SONARQUBE_TOKEN} diff --git a/quickstarts/maven/spring-boot/README.md b/quickstarts/maven/spring-boot/README.md index 2f7af31008..fd04154a00 100644 --- a/quickstarts/maven/spring-boot/README.md +++ b/quickstarts/maven/spring-boot/README.md @@ -52,3 +52,116 @@ Below command will deploy your application on OpenShift cluster. ``` mvn k8s:deploy -Pkubernetes ``` + +Once deployed, you can see the pods running inside your Kubernetes cluster: +``` +~/work/repos/jkube/quickstarts/maven/spring-boot : $ kubectl get pods +NAME READY STATUS RESTARTS AGE +spring-boot-59b4bb66-j42fv 1/1 Running 0 94s +``` +You can try to access your application using `minikube service` command like this: +``` +~/work/repos/jkube/quickstarts/maven/spring-boot : $ minikube service spring-boot +|-----------|-------------|-------------|----------------------------| +| NAMESPACE | NAME | TARGET PORT | URL | +|-----------|-------------|-------------|----------------------------| +| default | spring-boot | http | http://192.168.39.76:32429 | +|-----------|-------------|-------------|----------------------------| + Opening service default/spring-boot in default browser... +``` + +#### Generating Ingress for your generated Service +Eclipse JKube is also able to generate default `Ingress` object based upon your `Service`. At the moment `Ingress` generation is only allowed for `Service` objects of type `LoadBalancer` and is disabled by default. In order to do it, you need to follow the following steps: + +- Make sure you have an `Ingress` controller running inside your Kubernetes cluster, for the sake of this example; we're using [Ngnix Controller](https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/) bundled as an addon in Minikube: +``` +# Enable Ingress Controller addon +minikube addons enable ingress + +# Make sure it's running +~/work/repos/jkube/quickstarts/maven/spring-boot : $ kubectl get pods -nkube-system | grep nginx +nginx-ingress-controller-6fc5bcc8c9-gt4mg 1/1 Running 3 4h5m +``` + +- In order to generate `Ingress` object you need to set `jkube.createExternalUrls` property to `true` and `jkube.domain` property to your desired hostname suffix. You can also provide `routeDomain` in XML config like listed below. You can find full example in this project with profile `kubernetes-with-ingress`. +``` + + + org.eclipse.jkube + + +``` + +- Once all setup, you can run the resource goal like this: +``` +~/work/repos/jkube/quickstarts/maven/spring-boot : $ mvn k8s:resource -Pkubernetes-with-ingress +[INFO] Scanning for projects... +[INFO] +[INFO] ----------< org.eclipse.jkube.quickstarts.maven:spring-boot >----------- +[INFO] Building Eclipse JKube :: Quickstarts :: Maven :: Spring Boot Web 1.0.0-SNAPSHOT +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- kubernetes-maven-plugin:1.0.0-SNAPSHOT:resource (default-cli) @ spring-boot --- +[INFO] k8s: Running generator spring-boot +[INFO] k8s: spring-boot: Using Docker image quay.io/jkube/jkube-java-binary-s2i:0.0.5 as base / builder +[INFO] k8s: jkube-controller: Adding a default Deployment +[INFO] k8s: jkube-service: Adding a default service 'spring-boot' with ports [8080] +[INFO] k8s: jkube-healthcheck-spring-boot: Adding readiness probe on port 8080, path='/actuator/health', scheme='HTTP', with initial delay 10 seconds +[INFO] k8s: jkube-healthcheck-spring-boot: Adding liveness probe on port 8080, path='/actuator/health', scheme='HTTP', with initial delay 180 seconds +[INFO] k8s: jkube-revision-history: Adding revision history limit to 2 +[INFO] k8s: validating /home/rohaan/work/repos/jkube/quickstarts/maven/spring-boot/target/classes/META-INF/jkube/kubernetes/spring-boot-deployment.yml resource +[INFO] k8s: validating /home/rohaan/work/repos/jkube/quickstarts/maven/spring-boot/target/classes/META-INF/jkube/kubernetes/spring-boot-ingress.yml resource +[INFO] k8s: validating /home/rohaan/work/repos/jkube/quickstarts/maven/spring-boot/target/classes/META-INF/jkube/kubernetes/spring-boot-service.yml resource +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 6.320 s +[INFO] Finished at: 2020-06-04T15:57:19+05:30 +[INFO] ------------------------------------------------------------------------ +~/work/repos/jkube/quickstarts/maven/spring-boot : $ ls target/classes/META-INF/jkube/kubernetes/ +spring-boot-deployment.yml spring-boot-ingress.yml spring-boot-service.yml +~/work/repos/jkube/quickstarts/maven/spring-boot : $ +``` +- Now try to apply them onto Kubernetes cluster: +``` +~/work/repos/jkube/quickstarts/maven/spring-boot : $ mvn k8s:apply -Pkubernetes-with-ingress +[INFO] Scanning for projects... +[INFO] +[INFO] ----------< org.eclipse.jkube.quickstarts.maven:spring-boot >----------- +[INFO] Building Eclipse JKube :: Quickstarts :: Maven :: Spring Boot Web 1.0.0-SNAPSHOT +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- kubernetes-maven-plugin:1.0.0-SNAPSHOT:apply (default-cli) @ spring-boot --- +[INFO] k8s: Using Kubernetes at https://192.168.39.76:8443/ in namespace default with manifest /home/rohaan/work/repos/jkube/quickstarts/maven/spring-boot/target/classes/META-INF/jkube/kubernetes.yml +[INFO] k8s: Using namespace: default +[INFO] k8s: Creating a Service from kubernetes.yml namespace default name spring-boot +[INFO] k8s: Created Service: target/jkube/applyJson/default/service-spring-boot-2.json +[INFO] k8s: Creating a Deployment from kubernetes.yml namespace default name spring-boot +[INFO] k8s: Created Deployment: target/jkube/applyJson/default/deployment-spring-boot-2.json +[WARNING] The client is using resource type 'ingresses' with unstable version 'v1beta1' +[INFO] k8s: Creating a Ingress from kubernetes.yml namespace default name spring-boot +[INFO] k8s: Created Ingress: target/jkube/applyJson/default/ingress-spring-boot-2.json +[INFO] k8s: HINT: Use the command `kubectl get pods -w` to watch your pods start up +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 9.480 s +[INFO] Finished at: 2020-06-04T16:01:23+05:30 +[INFO] ------------------------------------------------------------------------ +~/work/repos/jkube/quickstarts/maven/spring-boot : $ kubectl get ing +NAME HOSTS ADDRESS PORTS AGE +spring-boot spring-boot.org.eclipse.jkube 80 12s +~/work/repos/jkube/quickstarts/maven/spring-boot : $ kubectl get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 33m +spring-boot LoadBalancer 10.96.99.20 8080:32648/TCP 14s +``` +Make sure you have this entry in your `/etc/hosts` file like this: +``` +192.168.39.76(cluster ip) spring-boot.org.eclipse.jkube +``` +Once done, you can try curling with your host URL: +``` +~/work/repos/jkube/quickstarts/maven/spring-boot : $ curl spring-boot.org.eclipse.jkube/ +Greetings from Spring Boot!! +``` diff --git a/quickstarts/maven/spring-boot/pom.xml b/quickstarts/maven/spring-boot/pom.xml index 95edb999f4..0d10744766 100644 --- a/quickstarts/maven/spring-boot/pom.xml +++ b/quickstarts/maven/spring-boot/pom.xml @@ -185,6 +185,32 @@ + + kubernetes-with-ingress + + true + org.eclipse.jkube.quickstart + + + + + org.eclipse.jkube + kubernetes-maven-plugin + ${project.version} + + + + + + LoadBalancer + + + + + + + +