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

io.fabric8.maven.plugin.DeployMojo Maven / Gradle / Ivy

/*
 * 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.plugin;


import com.fasterxml.jackson.core.JsonProcessingException;
import io.fabric8.kubernetes.api.Controller;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.api.model.ObjectReference;
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.*;
import io.fabric8.kubernetes.internal.HasMetadataComparator;
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.Template;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.utils.Files;
import io.fabric8.utils.Strings;
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;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import static io.fabric8.kubernetes.api.KubernetesHelper.DEFAULT_NAMESPACE;
import static io.fabric8.kubernetes.api.KubernetesHelper.createIntOrString;

/**
 * Applies the Kubernetes UYAML to a namespace in a kubernetes environment
 */
@Mojo(name = "deploy", requiresDependencyResolution = ResolutionScope.RUNTIME, defaultPhase = LifecyclePhase.INSTALL)
public class DeployMojo extends AbstractFabric8Mojo {

    /**
     * The domain added to the service ID when creating OpenShift routes
     */
    @Parameter(property = "fabric8.domain")
    protected String routeDomain;

    /**
     * Should we fail the build if an apply fails?
     */
    @Parameter(property = "fabric8.deploy.failOnError", defaultValue = "true")
    protected boolean failOnError;
    /**
     * Should we update resources by deleting them first and then creating them again?
     */
    @Parameter(property = "fabric8.recreate", defaultValue = "false")
    protected boolean recreate;

    /**
     * The generated kubernetes YAML file
     */
    @Parameter(property = "fabric8.kubernetesManifest", defaultValue = "${basedir}/target/classes/META-INF/fabric8/kubernetes.yml")
    private File kubernetesManifest;

    /**
     * The generated openshift YAML file
     */
    @Parameter(property = "fabric8.openshiftManifest", defaultValue = "${basedir}/target/classes/META-INF/fabric8/openshift.yml")
    private File openshiftManifest;

    /**
     * Should we create new kubernetes resources?
     */
    @Parameter(property = "fabric8.deploy.create", defaultValue = "true")
    private boolean createNewResources;

    /**
     * Should we use rolling upgrades to apply changes?
     */
    @Parameter(property = "fabric8.rolling", defaultValue = "false")
    private boolean rollingUpgrades;

    /**
     * Should we fail if there is no kubernetes json
     */
    @Parameter(property = "fabric8.deploy.failOnNoKubernetesJson", defaultValue = "false")
    private boolean failOnNoKubernetesJson;

    /**
     * In services only mode we only process services so that those can be recursively created/updated first
     * before creating/updating any pods and replication controllers
     */
    @Parameter(property = "fabric8.deploy.servicesOnly", defaultValue = "false")
    private boolean servicesOnly;

    /**
     * Do we want to ignore services. This is particularly useful when in recreate mode
     * to let you easily recreate all the ReplicationControllers and Pods but leave any service
     * definitions alone to avoid changing the portalIP addresses and breaking existing pods using
     * the service.
     */
    @Parameter(property = "fabric8.deploy.ignoreServices", defaultValue = "false")
    private boolean ignoreServices;

    /**
     * Process templates locally in Java so that we can apply OpenShift templates on any Kubernetes environment
     */
    @Parameter(property = "fabric8.deploy.processTemplatesLocally", defaultValue = "false")
    private boolean processTemplatesLocally;

    /**
     * Should we delete all the pods if we update a Replication Controller
     */
    @Parameter(property = "fabric8.deploy.deletePods", defaultValue = "true")
    private boolean deletePodsOnReplicationControllerUpdate;

    /**
     * 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
     */
    @Parameter(property = "fabric8.deploy.ignoreRunningOAuthClients", defaultValue = "true")
    private boolean ignoreRunningOAuthClients;

    /**
     * Should we create external Ingress/Routes for any LoadBalancer services which don't already have them.
     */
    @Parameter(property = "fabric8.deploy.createExternalUrls", defaultValue = "true")
    private boolean createExternalUrls;

    /**
     * The folder we should store any temporary json files or results
     */
    @Parameter(property = "fabric8.deploy.jsonLogDir", defaultValue = "${basedir}/target/fabric8/applyJson")
    private File jsonLogDir;

    /**
     * Namespace under which to operate
     */
    @Parameter(property = "fabric8.namespace")
    private String namespace;

    // Kubernetes client
    private KubernetesClient kubernetes;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        KubernetesClient kubernetes = createKubernetesClient();
        File manifest;
        String clusterKind = "Kubernetes";
        if (KubernetesHelper.isOpenShift(kubernetes)) {
            manifest = openshiftManifest;
            clusterKind = "OpenShift";

        } else {
            manifest = kubernetesManifest;
        }
        if (!Files.isFile(manifest)) {
            if (failOnNoKubernetesJson) {
                throw new MojoFailureException("No such generated manifest file: " + manifest);
            } else {
                getLog().warn("No such generated manifest file: " + manifest + " for this project so ignoring");
                return;
            }
        }

        if (kubernetes.getMasterUrl() == null || Strings.isNullOrBlank(kubernetes.getMasterUrl().toString())) {
            throw new MojoFailureException("Cannot find Kubernetes master URL");
        }
        log.info("Using " + clusterKind +" at: " + kubernetes.getMasterUrl() + " in namespace " + getNamespace() + " with manifest: " + manifest);

        try {
            Controller controller = createController();
            controller.setAllowCreate(createNewResources);
            controller.setServicesOnlyMode(servicesOnly);
            controller.setIgnoreServiceMode(ignoreServices);
            controller.setLogJsonDir(jsonLogDir);
            controller.setBasedir(getRootProjectFolder());
            controller.setIgnoreRunningOAuthClients(ignoreRunningOAuthClients);
            controller.setProcessTemplatesLocally(processTemplatesLocally);
            controller.setDeletePodsOnReplicationControllerUpdate(deletePodsOnReplicationControllerUpdate);
            controller.setRollingUpgrade(rollingUpgrades);
            controller.setRollingUpgradePreserveScale(isRollingUpgradePreserveScale());

            boolean openShift = KubernetesHelper.isOpenShift(kubernetes);
            if (openShift) {
                getLog().info("OpenShift platform detected");
            } else {
                disableOpenShiftFeatures(controller);
            }


            String fileName = manifest.getName();
            Object dto = KubernetesHelper.loadYaml(manifest, KubernetesResource.class);
            if (dto == null) {
                throw new MojoFailureException("Cannot load kubernetes YAML: " + manifest);
            }

            // lets check we have created the namespace
            String namespace = getNamespace();
            controller.applyNamespace(namespace);
            controller.setNamespace(namespace);

            if (dto instanceof Template) {
                Template template = (Template) dto;
                dto = applyTemplates(template, kubernetes, controller, fileName);
            }

            Set resources = new LinkedHashSet<>();

            Set entities = new TreeSet<>(new HasMetadataComparator());
            for (KubernetesResource resource : resources) {
                entities.addAll(KubernetesHelper.toItemList(resource));
            }

            entities.addAll(KubernetesHelper.toItemList(dto));

            if (createExternalUrls) {
                if (controller.getOpenShiftClientOrNull() != null) {
                    createRoutes(controller, entities);
                } else {
                    createIngress(controller, kubernetes, entities);
                }
            }

            // Apply all items
            for (HasMetadata entity : entities) {
                if (entity instanceof Pod) {
                    Pod pod = (Pod) entity;
                    controller.applyPod(pod, fileName);
                } else if (entity instanceof Service) {
                    Service service = (Service) entity;
                    controller.applyService(service, fileName);
                } else if (entity instanceof ReplicationController) {
                    ReplicationController replicationController = (ReplicationController) entity;
                    controller.applyReplicationController(replicationController, fileName);
                } else if (entity != null) {
                    controller.apply(entity, fileName);
                }
            }
        } catch (Exception e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    public static Route createRouteForService(String routeDomainPostfix, String namespace, Service service, Log log) {
        Route route = null;
        String id = KubernetesHelper.getName(service);
        if (Strings.isNotBlank(id) && shouldCreateExternalURLForService(log, service, id)) {
            route = new Route();
            String routeId = id;
            KubernetesHelper.setName(route, namespace, routeId);
            RouteSpec routeSpec = new RouteSpec();
            ObjectReference objectRef = new ObjectReference();
            objectRef.setName(id);
            objectRef.setNamespace(namespace);
            routeSpec.setTo(objectRef);
            if (!Strings.isNullOrBlank(routeDomainPostfix)) {
                String host = Strings.stripSuffix(Strings.stripSuffix(id, "-service"), ".");
                routeSpec.setHost(host + "." + Strings.stripPrefix(routeDomainPostfix, "."));
            } else {
                routeSpec.setHost("");
            }
            route.setSpec(routeSpec);
            String json;
            try {
                json = KubernetesHelper.toJson(route);
            } catch (JsonProcessingException e) {
                json = e.getMessage() + ". object: " + route;
            }
            log.debug("Created route: " + json);
        }
        return route;
    }

    public static Ingress createIngressForService(String routeDomainPostfix, String namespace, Service service, Log log) {
        Ingress ingress = null;
        String serviceName = KubernetesHelper.getName(service);
        ServiceSpec serviceSpec = service.getSpec();
        if (serviceSpec != null && Strings.isNotBlank(serviceName) &&
                shouldCreateExternalURLForService(log, service, serviceName)) {
            String ingressId = serviceName;
            String host = "";
            if (Strings.isNotBlank(routeDomainPostfix)) {
                host = serviceName  + "-" + namespace + "." + Strings.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(createIntOrString(portNumber.intValue())).
                                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 = KubernetesHelper.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. */ protected static boolean shouldCreateExternalURLForService(Log log, Service service, String id) { if ("kubernetes".equals(id) || "kubernetes-ro".equals(id)) { return false; } Set ports = KubernetesHelper.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); return false; } else { log.info("Not generating route for service " + id + " as only single port services are supported. Has ports: " + ports); return false; } } public boolean isRollingUpgrades() { return rollingUpgrades; } public boolean isRollingUpgradePreserveScale() { return false; } public MavenProject getProject() { return project; } /** * Lets disable OpenShift-only features if we are not running on OpenShift */ protected void disableOpenShiftFeatures(Controller controller) { // TODO we could check if the Templates service is running and if so we could still support templates? this.processTemplatesLocally = true; controller.setSupportOAuthClients(false); controller.setProcessTemplatesLocally(true); } protected Object applyTemplates(Template template, KubernetesClient kubernetes, Controller controller, String fileName) throws Exception { KubernetesHelper.setNamespace(template, getNamespace()); overrideTemplateParameters(template); return controller.applyTemplate(template, fileName); } /** * Before applying the given template lets allow template parameters to be overridden via the maven * properties - or optionally - via the command line if in interactive mode. */ protected void overrideTemplateParameters(Template template) { List parameters = template.getParameters(); MavenProject project = getProject(); if (parameters != null && project != null) { Properties properties = getProjectAndFabric8Properties(project); boolean missingProperty = false; for (io.fabric8.openshift.api.model.Parameter parameter : parameters) { String parameterName = parameter.getName(); String name = "fabric8.apply." + parameterName; String propertyValue = properties.getProperty(name); if (propertyValue != null) { getLog().info("Overriding template parameter " + name + " with value: " + propertyValue); parameter.setValue(propertyValue); } else { missingProperty = true; getLog().info("No property defined for template parameter: " + name); } } if (missingProperty) { getLog().debug("Current properties " + new TreeSet<>(properties.keySet())); } } } protected Properties getProjectAndFabric8Properties(MavenProject project) { Properties properties = project.getProperties(); properties.putAll(project.getProperties()); // let system properties override so we can read from the command line properties.putAll(System.getProperties()); return properties; } protected void createRoutes(Controller controller, Collection collection) { String routeDomainPostfix = this.routeDomain; Log log = getLog(); String namespace = getNamespace(); // lets get the routes first to see if we should bother try { OpenShiftClient openshiftClient = controller.getOpenShiftClientOrNull(); 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, log); if (route != null) { routes.add(route); } } } collection.addAll(routes); } protected void createIngress(Controller controller, KubernetesClient kubernetesClient, Collection collection) { String routeDomainPostfix = this.routeDomain; Log log = getLog(); String namespace = 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 (Object object : collection) { if (object instanceof Service) { Service service = (Service) object; if (!serviceHasIngressRule(ingressList, service)) { Ingress ingress = createIngressForService(routeDomainPostfix, namespace, service, log); 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); } /** * 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) { List rules = spec.getRules(); if (rules != null) { for (IngressRule rule : rules) { HTTPIngressRuleValue http = rule.getHttp(); if (http != null) { List paths = http.getPaths(); if (paths != null) { for (HTTPIngressPath path : paths) { IngressBackend backend = path.getBackend(); if (backend != null) { if (Objects.equals(serviceName, backend.getServiceName())) { return true; } } } } } } } } } return false; } private KubernetesClient createKubernetesClient() { Config config = new ConfigBuilder().withNamespace(getNamespace()).build(); return new DefaultKubernetesClient(config); } protected Controller createController() { Controller controller = new Controller(createKubernetesClient()); controller.setThrowExceptionOnError(failOnError); controller.setRecreateMode(recreate); getLog().debug("Using recreate mode: " + recreate); return controller; } protected synchronized String getNamespace() { if (Strings.isNullOrBlank(namespace)) { namespace = KubernetesHelper.defaultNamespace(); } if (Strings.isNullOrBlank(namespace)) { namespace = DEFAULT_NAMESPACE; } return namespace; } public String getRouteDomain() { return routeDomain; } public boolean isFailOnError() { return failOnError; } public boolean isRecreate() { return recreate; } /** * Returns the root project folder */ protected File getRootProjectFolder() { File answer = null; MavenProject project = getProject(); while (project != null) { File basedir = project.getBasedir(); if (basedir != null) { answer = basedir; } project = project.getParent(); } return answer; } /** * Returns the root project folder */ protected MavenProject getRootProject() { MavenProject project = getProject(); while (project != null) { MavenProject parent = project.getParent(); if (parent == null) { break; } project = parent; } return project; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy