Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring Ingress rules #1085

Merged
merged 1 commit into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package io.dekorate.kubernetes.annotation;

import io.sundr.builder.annotations.Buildable;
import io.sundr.builder.annotations.Pojo;

@Buildable(builderPackage = "io.fabric8.kubernetes.api.builder")
@Pojo(relativePath = "../config", autobox = true, mutable = true, withStaticBuilderMethod = true, withStaticAdapterMethod = false)
public @interface Ingress {

Expand All @@ -42,4 +44,9 @@
*/
String[] tlsHosts() default {};

/**
* Controls the generated ingress rules to be exposed as part of the Ingress resource.
*/
IngressRule[] rules() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright 2018 The original authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.dekorate.kubernetes.annotation;

import io.sundr.builder.annotations.Pojo;

@Pojo(relativePath = "../config", autobox = true, mutable = true, withStaticBuilderMethod = true, withStaticAdapterMethod = false)
public @interface IngressRule {

/**
* The host under which the rule is going to be used.
*/
String host();

/**
* The path under which the rule is going to be used. Default is "/".
*/
String path() default "/";

/**
* The path type strategy to use by the Ingress rule. Default is "Prefix".
*/
String pathType() default "Prefix";

/**
* The service name to be used by this Ingress rule. Default is the generated service name of the application.
*/
String serviceName() default "";

/**
* The service port name to be used by this Ingress rule. Default is the port name of the generated service
* of the application.
*/
String servicePortName() default "";

/**
* The service port number to be used by this Ingress rule. This is only used when the servicePortName is not set.
*/
int servicePortNumber() default -1;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@
*/
package io.dekorate.kubernetes.decorator;

import java.util.function.Predicate;
import static io.dekorate.kubernetes.decorator.AddServiceResourceDecorator.distinct;

import java.util.Arrays;
import java.util.Optional;

import io.dekorate.kubernetes.config.BaseConfig;
import io.dekorate.kubernetes.config.IngressRule;
import io.dekorate.kubernetes.config.Port;
import io.dekorate.utils.Strings;
import io.fabric8.kubernetes.api.builder.TypedVisitor;
Expand All @@ -25,98 +30,151 @@
import io.fabric8.kubernetes.api.model.networking.v1.IngressRuleBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackendBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressSpecBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort;
import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPortBuilder;

public class AddIngressRuleDecorator extends NamedResourceDecorator<IngressSpecBuilder> {

private final String host;
private final Port port;
private static final String DEFAULT_PREFIX = "Prefix";
private static final String DEFAULT_PATH = "/";

private final BaseConfig config;
private final IngressRule rule;

public AddIngressRuleDecorator(String name, String host, Port port) {
super(name);
this.host = host;
this.port = port;
public AddIngressRuleDecorator(BaseConfig config, IngressRule rule) {
super(config.getName());
this.config = config;
this.rule = rule;
}

@Override
public void andThenVisit(IngressSpecBuilder spec, ObjectMeta meta) {
Predicate<IngressRuleBuilder> matchingHost = r -> host == null && r.getHost() == null
|| (r.getHost() != null && r.getHost().equals(host));

if (!spec.hasMatchingRule(matchingHost)) {
spec.addNewRule().withHost(host).withNewHttp().addNewPath().withPathType("Prefix").withPath(port.getPath())
Optional<Port> defaultHostPort = Arrays.asList(config.getPorts()).stream()
.filter(distinct(p -> p.getName()))
.findFirst();
if (!spec.hasMatchingRule(existingRule -> Strings.equals(rule.getHost(), existingRule.getHost()))) {
spec.addNewRule()
.withHost(rule.getHost())
.withNewHttp()
.addNewPath()
.withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX))
.withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH))
.withNewBackend()
.withNewService()
.withName(name)
.withNewPort().withName(port.getName())
.withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort()
.withName(serviceName())
.withPort(createPort(defaultHostPort))
.endService()
.endBackend()
.endPath()
.endHttp()
.endRule();
} else {
spec.accept(new HostVisitor(meta));
spec.accept(new HostVisitor(defaultHostPort));
}
}

private String serviceName() {
return Strings.defaultIfEmpty(rule.getServiceName(), name);
}

private ServiceBackendPort createPort(Optional<Port> defaultHostPort) {
ServiceBackendPortBuilder builder = new ServiceBackendPortBuilder();
if (Strings.isNotNullOrEmpty(rule.getServicePortName())) {
builder.withName(rule.getServicePortName());
} else if (rule.getServicePortNumber() != null && rule.getServicePortNumber() >= 0) {
builder.withNumber(rule.getServicePortNumber());
} else if (Strings.isNullOrEmpty(rule.getServiceName()) || Strings.equals(rule.getServiceName(), name)) {
// Trying to get the port from the service
Port servicePort = defaultHostPort
.orElseThrow(() -> new RuntimeException("Could not find any matching port to configure the Ingress Rule. Specify the "
+ "service port using `kubernetes.ingress.service-port-name`"));
builder.withName(servicePort.getName());
} else {
throw new RuntimeException("The service port for '" + rule.getServiceName() + "' was not set. Specify one "
+ "using `kubernetes.ingress.service-port-name`");
}

return builder.build();
}

private class HostVisitor extends TypedVisitor<IngressRuleBuilder> {

private final ObjectMeta meta;
private final Optional<Port> defaultHostPort;

public HostVisitor(ObjectMeta meta) {
this.meta = meta;
public HostVisitor(Optional<Port> defaultHostPort) {
this.defaultHostPort = defaultHostPort;
}

@Override
public void visit(IngressRuleBuilder rule) {
Predicate<HTTPIngressPathBuilder> matchingPath = r -> r.getPath() != null && r.getPath().equals(port.getPath());
if (rule.getHost() != null && rule.getHost().equals(host)) {
if (!rule.hasHttp()) {
rule.withNewHttp()
public void visit(IngressRuleBuilder existingRule) {
if (Strings.equals(existingRule.getHost(), rule.getHost())) {
if (!existingRule.hasHttp()) {
existingRule.withNewHttp()
.addNewPath()
.withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX))
.withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH))
.withNewBackend()
.withNewService()
.withName(serviceName())
.withPort(createPort(defaultHostPort))
.endService()
.endBackend()
.endPath().endHttp();
} else if (existingRule.getHttp().getPaths().stream().noneMatch(p -> Strings.equals(p.getPath(), rule.getPath()))) {
existingRule.editHttp()
.addNewPath()
.withPathType("Prefix")
.withPath(Strings.isNotNullOrEmpty(port.getPath()) ? port.getPath() : "/")
.withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX))
.withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH))
.withNewBackend()
.withNewService()
.withName(name)
.withNewPort().withName(port.getName())
.withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort()
.withName(serviceName())
.withPort(createPort(defaultHostPort))
.endService()
.endBackend()
.endPath().endHttp();
} else {
rule.accept(new PathVisitor());
existingRule.accept(new PathVisitor(defaultHostPort));
}
}
}
}

private class PathVisitor extends TypedVisitor<HTTPIngressPathBuilder> {

private final Optional<Port> defaultHostPort;

public PathVisitor(Optional<Port> defaultHostPort) {
this.defaultHostPort = defaultHostPort;
}

@Override
public void visit(HTTPIngressPathBuilder path) {
if (path.equals(port.getPath())) {
if (!path.hasBackend()) {
path.withNewBackend()
public void visit(HTTPIngressPathBuilder existingPath) {
if (Strings.equals(existingPath.getPath(), rule.getPath())) {
if (!existingPath.hasBackend()) {
existingPath.withNewBackend()
.withNewService()
.withName(name)
.withNewPort().withName(port.getName())
.withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort()
.withName(serviceName())
.withPort(createPort(defaultHostPort))
.endService()
.endBackend();
} else {
path.accept(new ServiceVisitor());
existingPath.accept(new ServiceVisitor(defaultHostPort));
}
}
}
}

private class ServiceVisitor extends TypedVisitor<IngressServiceBackendBuilder> {

private final Optional<Port> defaultHostPort;

public ServiceVisitor(Optional<Port> defaultHostPort) {
this.defaultHostPort = defaultHostPort;
}

@Override
public void visit(IngressServiceBackendBuilder service) {
service.withName(port.getName()).withNewPort()
.withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort();
service.withName(Strings.defaultIfEmpty(rule.getServiceName(), name)).withPort(createPort(defaultHostPort));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import io.dekorate.kubernetes.config.ContainerBuilder;
import io.dekorate.kubernetes.config.EditableKubernetesConfig;
import io.dekorate.kubernetes.config.ImageConfiguration;
import io.dekorate.kubernetes.config.IngressRule;
import io.dekorate.kubernetes.config.IngressRuleBuilder;
import io.dekorate.kubernetes.config.KubernetesConfig;
import io.dekorate.kubernetes.config.KubernetesConfigBuilder;
import io.dekorate.kubernetes.configurator.ApplyDeployToApplicationConfiguration;
Expand Down Expand Up @@ -138,11 +140,24 @@ protected void addDecorators(String group, KubernetesConfig config) {

Ports.getHttpPort(config).ifPresent(p -> {
resourceRegistry.decorate(group, new AddIngressDecorator(config, Labels.createLabelsAsMap(config, "Ingress")));
resourceRegistry.decorate(group, new AddIngressRuleDecorator(config.getName(), config.getIngress().getHost(), p));
resourceRegistry.decorate(group,
new AddIngressRuleDecorator(config, new IngressRuleBuilder()
.withHost(config.getIngress().getHost())
.withPath(p.getPath())
.withServicePortName(p.getName())
.withServicePortNumber(p.getHostPort()).build()));
});

if (config.getIngress() != null && Strings.isNotNullOrEmpty(config.getIngress().getTlsSecretName())) {
resourceRegistry.decorate(group, new AddIngressTlsDecorator(config.getName(), config.getIngress()));
if (config.getIngress() != null) {
if (Strings.isNotNullOrEmpty(config.getIngress().getTlsSecretName())) {
resourceRegistry.decorate(group, new AddIngressTlsDecorator(config.getName(), config.getIngress()));
}

if (config.getIngress().getRules() != null) {
for (IngressRule ingressRule : config.getIngress().getRules()) {
resourceRegistry.decorate(group, new AddIngressRuleDecorator(config, ingressRule));
}
}
}

if (config.isHeadless()) {
Expand Down
Loading