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

io.fabric8.maven.CreateEnvMojo Maven / Gradle / Ivy

There is a newer version: 3.2.13
Show newest version
/*
 * Copyright 2005-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;

import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerPort;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.ObjectReference;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.api.model.ReplicationControllerSpec;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServiceSpec;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.maven.support.DefaultExcludedEnvVariablesEnum;
import io.fabric8.maven.support.DockerCommandPlainPrint;
import io.fabric8.maven.support.IDockerCommandPlainPrintCostants;
import io.fabric8.maven.support.OrderedProperties;
import io.fabric8.openshift.api.model.Route;
import io.fabric8.openshift.api.model.RouteList;
import io.fabric8.openshift.api.model.RouteListBuilder;
import io.fabric8.openshift.api.model.RouteSpec;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.utils.Files;
import io.fabric8.utils.Strings;
import io.fabric8.utils.TablePrinter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
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.project.MavenProject;

/**
 * Generates a properties file that contains that env variables that are expected to be passed by kubernetes to the container.
 * The env variable configuration is determined by the kubernetes.json file and the services that are currently running in the target namespace.
 */
@Mojo(name = "create-env", defaultPhase = LifecyclePhase.COMPILE)
public class CreateEnvMojo extends AbstractFabric8Mojo {

    private static final String HOST_SUFFIX = "_SERVICE_HOST";
    private static final String PORT_SUFFIX = "_SERVICE_PORT";
    private static final String PROTO_SUFFIX = "_TCP_PROTO";
    private static final String DOCKE_ENV_PREFIX = "docker.env.";
    private static final String DOCKER_NAME = "docker.name";
    private static final String EXEC_ENV_SCRIPT = "environmentScript";

    @Parameter(property = "fabric8.envFile", defaultValue = "env.properties")
    private String envPropertiesFileName;

    @Parameter(property = "fabric8.envScript", defaultValue = "env.sh")
    private String envScriptFileName;
    
    @Parameter(property = "fabric8.dockerRunScript", defaultValue = "docker-run.sh")
    private String dockerRunScriptFileName;

    // the docker image name we will use
    private volatile String name;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            String basedir = System.getProperty("basedir", ".");
            File propertiesFile = new File(basedir + "/target/" + envPropertiesFileName).getCanonicalFile();
            File scriptFile = new File(basedir + "/target/" + envScriptFileName).getCanonicalFile();
            File dockerRunFile = new File(basedir + "/target/" + dockerRunScriptFileName).getCanonicalFile();
            
            Object config = loadKubernetesJson();
            List list = KubernetesHelper.toItemList(config);
            name = getDockerImage();
            if (name == null) {
                name = findFirstImageName(list);
            }
            Map env = getEnvFromConfig(list);
            String namespace = getNamespace();
            env.putAll(getNamespaceServiceEnv(namespace));
            removeDefaultEnv(env);
            expandEnvironmentVariable(env);
            displayEnv(env);

            StringBuilder sb = new StringBuilder();
            List volumeMount = getVolumeMountsFromConfig(list);
            List containerPort = getContainerPortsFromConfig(list);
            DockerCommandPlainPrint dockerCommandPlainPrint = new DockerCommandPlainPrint(sb);
            dockerCommandPlainPrint.appendParameters(env, IDockerCommandPlainPrintCostants.EXPRESSION_FLAG);
            dockerCommandPlainPrint.appendContainerPorts(containerPort, IDockerCommandPlainPrintCostants.PORT_FLAG);
            dockerCommandPlainPrint.appendVolumeMounts(volumeMount, IDockerCommandPlainPrintCostants.VOLUME_FLAG);
            dockerCommandPlainPrint.appendImageName(name);
            
            displayVolumes(volumeMount);
            displayContainerPorts(containerPort);
            displayDockerRunCommand(dockerCommandPlainPrint);

            Properties properties = getProjectAndFabric8Properties(getProject());
            for (Map.Entry entry : env.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key == null) {
                    getLog().warn("Ignoring null key!");
                } else if (value == null) {
                    getLog().warn("Ignoring null value for key: " + key);
                } else {
                    properties.setProperty(DOCKE_ENV_PREFIX + key, value);
                }
            }
            if (name != null) {
                properties.setProperty(DOCKER_NAME, name);
            }
            String scriptFileAbsolutePath = scriptFile.getAbsolutePath();
            if (scriptFileAbsolutePath != null) {
                properties.setProperty(EXEC_ENV_SCRIPT, scriptFileAbsolutePath);
            }
            Properties envProperties = new OrderedProperties();
            Set> entries = env.entrySet();
            for (Map.Entry entry : entries) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key == null) {
                    getLog().warn("Ignoring null key!");
                } else if (value == null) {
                    getLog().warn("Ignoring null value for key: " + key);
                } else {
                    envProperties.put(key ,value);
                }
            }
            saveEnvScript(env, scriptFile);
            saveDockerRunScript(dockerCommandPlainPrint, dockerRunFile);
            saveProperties(envProperties, propertiesFile);

        } catch (IOException e) {
            throw new MojoExecutionException("Failed to load environment schemas: " + e, e);
        }
    }

    /**
     * Returns the first docker image name found in a ReplicationController
     */
    protected String findFirstImageName(List list) {
        for (HasMetadata hasMetadata : list) {
            if (hasMetadata instanceof ReplicationController) {
                ReplicationController rc = (ReplicationController) hasMetadata;
                ReplicationControllerSpec spec = rc.getSpec();
                if (spec != null) {
                    PodTemplateSpec podTemplateSpec = spec.getTemplate();
                    if (podTemplateSpec != null) {
                        PodSpec podSpec = podTemplateSpec.getSpec();
                        if (podSpec != null) {
                            List containers = podSpec.getContainers();
                            if (containers != null) {
                                for (Container container : containers) {
                                    String image = container.getImage();
                                    if (Strings.isNotBlank(image)) {
                                        return image;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * Return the Env Variables that correspond to each services that runs under the specified namespace.
     *
     * @param namespace The target namespace.
     * @return the env variables
     */
    Map getNamespaceServiceEnv(String namespace) {
        Map result = new HashMap<>();
        KubernetesClient kubernetes = getKubernetes();
        ServiceList serviceList = kubernetes.services().inNamespace(namespace).list();
        RouteList routeList = listRoutes(kubernetes, namespace);
        for (Service service : serviceList.getItems()) {
            String serviceName = KubernetesHelper.getName(service);
            String id = serviceName.toUpperCase().replace("-", "_");
            Route route = findRoute(serviceName, routeList);
            RouteSpec spec = null;
            if (route != null) {
                spec = route.getSpec();
            }
            ServiceSpec serviceSpec = service.getSpec();
            if (spec != null) {
                result.put(id + HOST_SUFFIX, spec.getHost());
            } else if (serviceSpec != null) {
                result.put(id + HOST_SUFFIX, serviceSpec.getClusterIP());
            }
            if (serviceSpec != null) {
                List ports = serviceSpec.getPorts();
                for (ServicePort port : ports) {
                    result.put(id + PORT_SUFFIX, String.valueOf(port.getPort()));
                    result.put(id + PROTO_SUFFIX, port.getProtocol());
                    result.put(id + PORT_SUFFIX + "_" + port.getPort() + PROTO_SUFFIX, port.getProtocol());
                }
            }
        }
        return result;
    }




    /**
     * Return Env variables found in the kubernetes config.
     *
     * @param entities The config instance.
     * @return A map with the env key value pairs.
     * @throws IOException
     */
    private Map getEnvFromConfig(List entities) throws IOException {
        Map result = new TreeMap<>();

        for (HasMetadata entity : entities) {
            if (entity instanceof Pod) {
                Pod pod = (Pod) entity;
                for (Container container : pod.getSpec().getContainers()) {
                    if (container.getImage().equals(name)) {
                        result.putAll(mapFromEnv(container.getEnv()));
                    }
                }
            } else if (entity instanceof ReplicationController) {
                ReplicationController replicationController = (ReplicationController) entity;
                for (Container container : replicationController.getSpec().getTemplate().getSpec().getContainers()) {
                    if (container.getImage().equals(name)) {
                        result.putAll(mapFromEnv(container.getEnv()));
                    }
                }
            }
        }
        return result;
    }
    
	/**
	 * Lets expand environment variables by overriding it via via the command
	 * line.
	 */
	protected void expandEnvironmentVariable(Map env) {
		String regex = "\\$\\{(.*?)\\}";
		MavenProject project = getProject();
		if (project != null) {
			Properties properties = getProjectAndFabric8Properties(project);
			for (Map.Entry entry : env.entrySet()) {
				String envValue = entry.getValue();
				if (envValue != null && !envValue.isEmpty() && envValue.contains("${") && envValue.contains("}")) {
					Pattern p = Pattern.compile(regex);
					Matcher m = p.matcher(entry.getValue());
					while (m.find()) {
						String parameterName = m.group(1);
						String name = "fabric8.create-env." + parameterName;
						String propertyValue = properties.getProperty(name);
						if (Strings.isNotBlank(propertyValue)) {
							getLog().info("Overriding environment variable " + parameterName + " with value: " + propertyValue);
							String replaced = checkAndReplaceValue(entry.getValue(), propertyValue, parameterName);
							entry.setValue(replaced);
						} else {
							getLog().info("No property defined for environment variable: " + parameterName);
						}
					}
				}
			}
		}
	}
    
	/**
	 * Return VolumeMounts found in the kubernetes config.
	 *
	 * @param entities The config instance.
	 * @return A list of VolumeMount objects.
	 * @throws IOException
	 */
	private List getVolumeMountsFromConfig(List entities) throws IOException {
		List volumeList = new ArrayList();

		for (HasMetadata entity : entities) {
			if (entity instanceof ReplicationController) {
				ReplicationController replicationController = (ReplicationController) entity;
				for (Container container : replicationController.getSpec().getTemplate().getSpec().getContainers()) {
					if (container.getImage().equals(name)) {
						if (!container.getVolumeMounts().isEmpty()) {
							return container.getVolumeMounts();
						}
					}
				}
			}
		}
		return volumeList;
	}
		
	/**
	 * Return container Ports found in the kubernetes config.
	 *
	 * @param entities The config instance.
	 * @return A list of ContainerPort objects.
	 * @throws IOException
	 */
	private List getContainerPortsFromConfig(List entities) throws IOException {
		List containerPortList = new ArrayList();

		for (HasMetadata entity : entities) {
            if (entity instanceof Pod) {
                Pod pod = (Pod) entity;
                for (Container container : pod.getSpec().getContainers()) {
                    if (container.getImage().equals(name)) {
                    	if (!container.getPorts().isEmpty()) {
                    	    containerPortList.addAll(container.getPorts());
                    	}
                    }
                }
            }
            else if (entity instanceof ReplicationController) {
					ReplicationController replicationController = (ReplicationController) entity;
					for (Container container : replicationController.getSpec().getTemplate().getSpec().getContainers()) {
						if (container.getImage().equals(name)) {
							if (!container.getPorts().isEmpty()) {
								containerPortList.addAll(container.getPorts());
							}
						}
					}
			    }
		    } 
		    return containerPortList;
	}

    private void displayEnv(Map map) {
        TablePrinter table = new TablePrinter();
        table.columns("Name", "Value");

        for (Map.Entry entry : map.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue();
            table.row(name, value);
        }

        getLog().info("");
        getLog().info("Generated Environment variables:");
        getLog().info("-------------------------------");

        List lines = table.asTextLines();
        for (String line : lines) {
            getLog().info(line);
        }
        getLog().info("");

    }
    
    private void displayVolumes(List volumeMount) {
        TablePrinter table = new TablePrinter();
        table.columns("Name", "Mount Path", "Read Only");

		Iterator it = volumeMount.iterator();
		while (it.hasNext()){
			VolumeMount vol = it.next();
            String name = vol.getName();
            String mounthPath = vol.getMountPath();
            String ro = String.valueOf(Boolean.FALSE);
            if (vol.getReadOnly() == true) {
            	ro = String.valueOf(Boolean.TRUE);
            }
			table.row(name, mounthPath, ro);
		}

        getLog().info("");
        getLog().info("Volumes Summary:");
        getLog().info("-------------------------------");

        List lines = table.asTextLines();
        for (String line : lines) {
            getLog().info(line);
        }
        getLog().info("");

    }
    
    private void displayContainerPorts(List containerPort) {
        TablePrinter table = new TablePrinter();
        table.columns("Host IP", "Host Port", "Container Port");

		Iterator it = containerPort.iterator();
		while (it.hasNext()){
			ContainerPort port = it.next();
            String hostIp = port.getHostIP();
            String hostPort = "";
            String contPort = "";
            if (port.getHostPort() != null) {
            	hostPort = String.valueOf(port.getHostPort());
            }
            if (port.getContainerPort() != null) {
            	contPort = String.valueOf(port.getContainerPort());
            }
			table.row(hostIp, hostPort, contPort);
		}

        getLog().info("");
        getLog().info("Container Ports Summary:");
        getLog().info("-------------------------------");

        List lines = table.asTextLines();
        for (String line : lines) {
            getLog().info(line);
        }
        getLog().info("");

    }
    
    private void removeDefaultEnv(Map map) {
        for(Iterator> it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = it.next();
            if(DefaultExcludedEnvVariablesEnum.contains(entry.getKey())) {
                it.remove();
            }
        }
    }
    
    private void displayDockerRunCommand(DockerCommandPlainPrint dockerCommandPlainPrint) {
        getLog().info("Docker Run Command:");
        getLog().info("-------------------------------");
        getLog().info(dockerCommandPlainPrint.getDockerPlainTextCommand().toString());
    }
    
    /**
     * Load the kubernetes configuration found in the project
     *
     * @return The Kubernetes config.
     */
    private Object loadKubernetesJson() throws MojoFailureException, IOException {
        File json = getKubernetesJson();
        if (!Files.isFile(json)) {
            if (Files.isFile(kubernetesSourceJson)) {
                json = kubernetesSourceJson;
            } else {
                throw new MojoFailureException("No such generated kubernetes json file: " + json + " or source json file " + kubernetesSourceJson);
            }
        }
        return KubernetesHelper.loadJson(json);
    }

    private static Map mapFromEnv(List envVars) {
        Map result = new HashMap<>();
        for (EnvVar envVar : envVars) {
            result.put(envVar.getName(), envVar.getValue());
        }
        return result;
    }

    private static void saveProperties(Properties properties, File propertiesFile) throws IOException {
        try (FileWriter writer = new FileWriter(propertiesFile)) {
            properties.store(writer, "Generated Environment Variables");
        }
    }

    private static void saveEnvScript(Map map, File scroptFile) throws IOException {
        try (FileWriter writer = new FileWriter(scroptFile)) {
            writer.append("#!/bin/bash").append("\n");
            for (Map.Entry entry: map.entrySet()) {
            	if (entry.getValue() != null && !entry.getValue().contains(" ")) {
                    writer.append("export ").append(entry.getKey()).append("=").append(entry.getValue()).append("\n");	
            	} else {
                    writer.append("export ").append(entry.getKey()).append("=").append("\"").append(entry.getValue()).append("\"").append("\n");
            	}
            }
            writer.flush();
        }
    }
    
    private static void saveDockerRunScript(DockerCommandPlainPrint printer, File scriptFile) throws IOException {
        try (FileWriter writer = new FileWriter(scriptFile)) {
            writer.append("#!/bin/bash").append("\n");
            writer.append(printer.getDockerPlainTextCommand().toString()).append("\n");
            writer.flush();
        }
    }

    private static RouteList listRoutes(KubernetesClient client, String namespace) {
        try {
            return client.adapt(OpenShiftClient.class).routes().inNamespace(namespace).list();
        } catch (Throwable t) {
            return new RouteListBuilder().build();
        }
    }

    private static Route findRoute(String serviceId, RouteList routeList) {
        for (Route route : routeList.getItems()) {
            RouteSpec spec = route.getSpec();
            if (spec != null) {
                ObjectReference to = spec.getTo();
                if (to != null) {
                    String name = to.getName();
                    if (serviceId.equals(name)) {
                        return route;
                    }
                }
            }
        }
        return null;
    }
    
    private static String checkAndReplaceValue(String envValue, String value, String parameterName) {
    	String escapedParameterName = "\\$\\{" + parameterName + "\\}";
    	String stringReplaced = envValue.replaceAll(escapedParameterName, value);
    	return stringReplaced;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy