All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fabric8.maven.enricher.standard.DefaultServiceEnricher Maven / Gradle / Ivy

There is a newer version: 4.1.0
Show newest version
/*
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you 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.fabric8.maven.enricher.standard;

import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.builder.TypedVisitor;
import io.fabric8.kubernetes.api.model.HasMetadata;
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.ServiceFluent;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServiceSpec;
import io.fabric8.maven.core.config.ServiceConfig;
import io.fabric8.maven.core.config.ServiceProtocol;
import io.fabric8.maven.core.handler.HandlerHub;
import io.fabric8.maven.core.handler.ServiceHandler;
import io.fabric8.maven.core.util.Configs;
import io.fabric8.maven.core.util.MavenUtil;
import io.fabric8.maven.docker.config.BuildImageConfiguration;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.enricher.api.BaseEnricher;
import io.fabric8.maven.enricher.api.EnricherContext;
import io.fabric8.utils.Strings;
import org.apache.maven.shared.utils.StringUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * An enricher for creating default services when not present.
 *
 * @author roland
 * @since 25/05/16
 */
public class DefaultServiceEnricher extends BaseEnricher {

    ServiceHandler serviceHandler;

    // Available configuration keys
    private enum Config implements Configs.Key {
        // Default name to use instead of a calculated one
        name,

        // Whether allow headless services.
        headless {{ d = "false"; }},

        // Whether expose the service as ingress. Needs an 'exposeController'
        // running
        expose {{ d = "false"; }},

        // Type of the service (LoadBalancer, NodePort, ...)
        type {{ d = "LoadBalancer"; }};

        public String def() { return d; } protected String d;
    }

    public DefaultServiceEnricher(EnricherContext buildContext) {
        super(buildContext, "fmp-service");
        HandlerHub handlers = new HandlerHub(buildContext.getProject());
        serviceHandler = handlers.getServiceHandler();
    }

    @Override
    public void addMissingResources(KubernetesListBuilder builder) {
        final ServiceConfig defaultServiceConfig = extractDefaultServiceConfig();
        final Service defaultService = serviceHandler.getService(defaultServiceConfig);

        if (hasServices(builder)) {
            builder.accept(new TypedVisitor() {
                @Override
                public void visit(ServiceBuilder service) {
                    mergeServices(service, defaultService);
                }
            });
        } else {
            if (defaultService != null) {
                ServiceSpec spec = defaultService.getSpec();
                if (spec != null) {
                    List ports = spec.getPorts();
                    if (ports != null) {
                        log.info("Adding a default Service with ports [%s]", formatPortsAsList(ports));
                        builder.addToServiceItems(defaultService);
                    }
                }
            }
        }
    }

    // Check for all build configs, extract the exposed ports and create a single service for all of them
    private ServiceConfig extractDefaultServiceConfig() {
        List ports = extractPortsFromImageConfiguration(getImages());

        ServiceConfig.Builder ret = new ServiceConfig.Builder();
        ret.name(getConfig(Config.name, MavenUtil.createDefaultResourceName(getProject())));

        if (ports.size() > 0) {
            ret.ports(ports);

        } else {
            if (Configs.asBoolean(getConfig(Config.headless))) {
                ret.headless(true);
            }
        }

        ret.expose(Configs.asBoolean(getConfig(Config.expose)));

        // Add a default type if configured
        ret.type(getConfig(Config.type));

        return ret.build();
    }


    // Examine images for build configuration and extract all ports
    private List extractPortsFromImageConfiguration(List images) {
        List ret = new ArrayList<>();
        for (ImageConfiguration image : images) {
            BuildImageConfiguration buildConfig = image.getBuildConfiguration();
            if (buildConfig != null) {
                List ports = buildConfig.getPorts();
                if (ports != null) {
                    Set portNumbers = new HashSet<>();
                    Set portNames = new HashSet<>();

                    for (String port : ports) {
                        /// Todo: Check IANA names (also in case port is not numeric)
                        // TODO: Check Image labels for port specification
                        int portI = Integer.parseInt(port);
                        portNumbers.add(portI);
                    }

                    for (Integer portNumber : portNumbers) {
                        int portI = portNumber.intValue();
                        int servicePort = portI;

                        // lets default to a nicer port number if its not already used
                        if (!portNumbers.contains(80)) {
                            if (portI == 8080 || portI == 9090) {
                                servicePort = 80;
                            }
                        }
                        String name = getDefaultPortName(servicePort);
                        if (!portNames.add(name)) {
                            name = null;
                        }
                        ret.add(
                                new ServiceConfig.Port.Builder()
                                    .protocol(ServiceProtocol.TCP) // TODO: default for the moment
                                    .port(servicePort)
                                    .targetPort(portI)
                                    .name(name)
                                    .build()
                        );
                    }
                }
            }
        }
        return ret;
    }

    private String formatPortsAsList(List ports)  {
        List p = new ArrayList<>();
        for (ServicePort port : ports) {
            IntOrString targetPort = port.getTargetPort();
            String val = targetPort.getStrVal();
            if (val == null) {
                val = Integer.toString(targetPort.getIntVal());
            }
            p.add(val);
        }
        return StringUtils.join(p.iterator(), ",");
    }

    private void mergeServices(ServiceBuilder loadedService, Service defaultService) {
        String defaultServiceName = KubernetesHelper.getName(defaultService);
        if (Strings.isNullOrBlank(defaultServiceName)) {
            defaultServiceName = getProject().getArtifactId();
        }
        ObjectMeta loadedMetadata = loadedService.getMetadata();
        if (loadedMetadata == null) {
            loadedMetadata = defaultService.getMetadata();
            loadedService.withNewMetadataLike(loadedMetadata).endMetadata();
        }
        String name = KubernetesHelper.getName(loadedMetadata);
        if (Strings.isNullOrBlank(name)) {
            loadedService.withNewMetadataLike(loadedMetadata).withName(defaultServiceName).endMetadata();
            name = KubernetesHelper.getName(loadedService.getMetadata());
        }
        if (defaultService == null) {
            return;
        }

        // lets find a suitable service to match against
        if (Objects.equals(name, defaultServiceName)) {
            ServiceSpec matchedSpec = defaultService.getSpec();
            if (matchedSpec != null) {
                if (loadedService.getSpec() == null) {
                    loadedService.withNewSpecLike(matchedSpec).endSpec();
                } else {
                    ServiceFluent.SpecNested loadedSpec = loadedService.editSpec();
                    if (loadedSpec == null) {
                        loadedService.withNewSpecLike(matchedSpec).endSpec();
                    } else {
                        List ports = loadedSpec.getPorts();
                        if (ports == null || ports.isEmpty()) {
                            loadedSpec.withPorts(matchedSpec.getPorts()).endSpec();
                        } else {
                            // lets default any missing values
                            for (ServicePort port : ports) {
                                if (Strings.isNullOrBlank(port.getProtocol())) {
                                    port.setProtocol("TCP");
                                }
                                if (Strings.isNullOrBlank(port.getName())) {
                                    port.setName(getDefaultPortName(port.getPort()));
                                }
                            }
                            // lets add first missing port
                            List matchedPorts = matchedSpec.getPorts();
                            if (matchedPorts != null && ports.isEmpty()) {
                                for (ServicePort matchedPort : matchedPorts) {
                                    if (!hasPort(ports, matchedPort)) {
                                        ports.add(matchedPort);
                                        // lets only add 1 port
                                        break;
                                    }
                                }
                            }
                            loadedSpec.withPorts(ports).endSpec();
                        }
                    }
                }
            }
        }
    }

    public static String getDefaultPortName(Integer port) {
        if (port != null) {
            switch (port) {
                case 80:
                case 8080:
                case 9090:
                    return "http";
                case 443:
                    return "https";
                case 8778:
                    return "jolokia";
                case 9779:
                    return "prometheus";
            }
        }
        return "default";
    }

    /**
     * Returns true if the given port can be found in the collection
     */
    private boolean hasPort(List ports, ServicePort port) {
        for (ServicePort aPort : ports) {
            if (Objects.equals(port.getPort(), aPort.getPort())) {
                return true;
            }
        }
        return false;
    }

    private boolean hasServices(KubernetesListBuilder builder) {
        for (HasMetadata item : builder.getItems()) {
            if ("Service".equals(item.getKind())) {
                return true;
            }
        }
        return false;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy