/** * 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.devops.ProjectConfig; import io.fabric8.devops.ProjectConfigs; import io.fabric8.devops.ProjectRepositories; import io.fabric8.kubernetes.api.Annotations; import io.fabric8.kubernetes.api.KubernetesHelper; import io.fabric8.kubernetes.api.ServiceNames; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; import io.fabric8.kubernetes.api.model.ReplicationController; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.maven.support.JsonSchema; import io.fabric8.maven.support.JsonSchemas; import io.fabric8.openshift.api.model.DeploymentConfig; import io.fabric8.openshift.api.model.Template; import io.fabric8.utils.Files; import io.fabric8.utils.GitHelpers; import io.fabric8.utils.Objects; import io.fabric8.utils.Strings; import io.fabric8.utils.Systems; import io.fabric8.utils.URLUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.DistributionManagement; import org.apache.maven.model.Site; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import static io.fabric8.utils.PropertiesHelper.findPropertiesWithPrefix; import static io.fabric8.utils.PropertiesHelper.toMap; /** * Abstract base class for Fabric8 based Mojos */ public abstract class AbstractFabric8Mojo extends AbstractNamespacedMojo { private static final String DEFAULT_CONFIG_FILE_NAME = "kubernetes.json"; public static String[] ICON_EXTENSIONS = new String[]{".svg", ".png", ".gif", ".jpg", ".jpeg"}; /** * Name of the created app zip file */ @Parameter(property = "fabric8.zip.file", defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}-app.zip") protected File zipFile; /** * The folder used for defining project specific files */ @Parameter(property = "fabric8.source.dir", defaultValue = "${basedir}/src/main/fabric8") protected File appConfigDir; /** * Provides the resource name of the icon to use; found using the current classpath (including the ones shipped inside the maven plugin). * <p/> * You can refer to a common set of icons by setting this option to a value of: * <ul> * <li>activemq</li> * <li>camel</li> * <li>java</li> * <li>jetty</li> * <li>karaf</li> * <li>mule</li> * <li>spring-boot</li> * <li>tomcat</li> * <li>tomee</li> * <li>weld</li> * <li>wildfly</li> * </ul> */ @Parameter(property = "fabric8.iconRef") protected String iconRef; /** * The generated kubernetes JSON file */ @Parameter(property = "fabric8.json.target", defaultValue = "${basedir}/target/classes/kubernetes.json") private File kubernetesJson; /** * The source kubernetes JSON file */ @Parameter(property = "fabric8.json.source", defaultValue = "${basedir}/src/main/fabric8/kubernetes.json") protected File kubernetesSourceJson; /** * Whether we should combine kubernetes JSON dependencies on the classpath into the generated JSON */ @Parameter(property = "fabric8.combineDependencies", defaultValue = "false") protected boolean combineDependencies; /** * The generated kubernetes JSON file dependencies on the classpath */ @Parameter(property = "fabric8.combineJson.target") private File kubernetesCombineJson; /** * Should we exclude OpenShift templates and any extensions like OAuthConfigs in the generated or combined JSON? */ @Parameter(property = "fabric8.pureKubernetes", defaultValue = "false") protected boolean pureKubernetes; /** * The number of replicas of this container if we are auto generating the kubernetes JSON file (creating * a <a href="http://fabric8.io/v2/replicationControllers.html">Replication Controller</a> if this value * is greater than 0 or a <a href="http://fabric8.io/v2/pods.html">pod</a> if not). */ @Parameter(property = "fabric8.replicas", defaultValue = "1") private Integer replicas; /** * Whether or not we should ignoreProject this maven project from goals like fabric8:deploy */ @Parameter(property = "fabric8.ignoreProject", defaultValue = "false") private boolean ignoreProject; /** * The properties file used to specify environment variables which allows ${FOO_BAR} expressions to be used * without any Maven property expansion */ @Parameter(property = "fabric8.envProperties", defaultValue = "${basedir}/src/main/fabric8/env.properties") protected File envPropertiesFile; /** * Specifies a file which maps environment variables or system properties to annotations which are then recorded on the * ReplicationController of the generated or applied JSON */ @Parameter(property = "fabric8.environmentVariableToAnnotationsFile", defaultValue = "${basedir}/src/main/fabric8/environemntToAnnotations.properties") protected File environmentVariableToAnnotationsFile; @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; /** * Files to be excluded */ @Parameter(property = "fabric8.excludedFiles", defaultValue = "io.fabric8.agent.properties") private String[] filesToBeExcluded; /** * Is this build a CD Pipeline build (and so raise warning levels if we cannot detect CD related * metadata for the build, * such as the git commit id, git URL, Jenkins job URL etc */ @Parameter(property = "fabric8.cd.build", defaultValue = "false") private boolean cdBuild; /** * The environment variable used to detect if the current build is inside a CD Pipeline build * to enable verbose logging if we cannot auto default the CD related metadata for the build, * such as the git commit id, git URL, Jenkins job URL etc */ @Parameter(property = "fabric8.cd.envVar", defaultValue = "JENKINS_HOME") private String cdEnvVarName; /** * The docker image to use. */ @Parameter(property = "fabric8.image") private String dockerImage; /** * Whether to try to fetch extended environment metadata during the <tt>json</tt>, or <tt>apply</tt> goals. * <p/> * The following ENV variables is supported: <tt>BUILD_URI</tt>, <tt>GIT_URL</tt>, <tt>GIT_COMMIT</tt>, <tt>GIT_BRANCH</tt> * If any of these ENV variable is empty then if this option is enabled, then the value is attempted to * be fetched from an online connection to the Kubernetes master. If the connection fails then the * goal will report this as a failure gently and continue. * <p/> * This option can be turned off, to avoid any live connection to the Kubernetes master. */ @Parameter(property = "fabric8.extended.environment.metadata", defaultValue = "true") private Boolean extendedMetadata; protected static File copyReadMe(File src, File appBuildDir) throws IOException { return copyReadMe(src, appBuildDir, null); } protected static File copyReadMe(File src, File appBuildDir, String outputFileName) throws IOException { File[] files = src.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase(Locale.ENGLISH).startsWith("readme."); } }); if (files != null && files.length == 1) { File readme = files[0]; if (Strings.isNullOrBlank(outputFileName)) { outputFileName = readme.getName(); } File outFile = new File(appBuildDir, outputFileName); Files.copy(readme, outFile); return outFile; } return null; } protected static Object loadJsonFile(File file) throws MojoExecutionException { try { return KubernetesHelper.loadJson(file); } catch (IOException e) { throw new MojoExecutionException("Failed to parse JSON " + file + ". " + e, e); } } @Override public MavenProject getProject() { return project; } protected static URLClassLoader createURLClassLoader(Collection<URL> jars) { return new URLClassLoader(jars.toArray(new URL[jars.size()])); } public File getKubernetesJson() { return kubernetesJson; } public File getKubernetesCombineJson() { return kubernetesCombineJson; } public Integer getReplicas() { return replicas; } public boolean isIgnoreProject() { return ignoreProject; } public File getZipFile() { return zipFile; } /** * Returns true if this project is a pom packaging project */ protected boolean isPom(MavenProject reactorProject) { return "pom".equals(reactorProject.getPackaging()); } protected InputStream loadPluginResource(String iconRef) throws MojoExecutionException { InputStream answer = Thread.currentThread().getContextClassLoader().getResourceAsStream(iconRef); if (answer == null) { answer = getTestClassLoader().getResourceAsStream(iconRef); } if (answer == null) { answer = this.getClass().getResourceAsStream(iconRef); } return answer; } protected URLClassLoader getCompileClassLoader() throws MojoExecutionException { try { List<String> classpathElements = getProject().getCompileClasspathElements(); return createClassLoader(classpathElements, getProject().getBuild().getOutputDirectory()); } catch (Exception e) { throw new MojoExecutionException("Failed to resolve classpath: " + e, e); } } protected URLClassLoader getTestClassLoader() throws MojoExecutionException { try { List<String> classpathElements = getProject().getTestClasspathElements(); return createClassLoader(classpathElements, getProject().getBuild().getTestOutputDirectory()); } catch (Exception e) { throw new MojoExecutionException("Failed to resolve classpath: " + e, e); } } protected URLClassLoader createClassLoader(List<String> classpathElements, String... paths) throws MalformedURLException { List<URL> urls = new ArrayList<>(); for (String path : paths) { URL url = pathToUrl(path); urls.add(url); } for (Object object : classpathElements) { if (object != null) { String path = object.toString(); URL url = pathToUrl(path); urls.add(url); } } getLog().debug("Creating class loader from: " + urls); return createURLClassLoader(urls); } private URL pathToUrl(String path) throws MalformedURLException { File file = new File(path); return file.toURI().toURL(); } protected boolean hasConfigDir() { return appConfigDir.isDirectory(); } protected boolean isPomProject() { return isPom(getProject()); } protected void addEnvironmentAnnotations(File json) throws MojoExecutionException { try { Object dto = loadJsonFile(json); if (dto instanceof KubernetesList) { KubernetesList container = (KubernetesList) dto; List<HasMetadata> items = container.getItems(); addEnvironmentAnnotations(items); getLog().info("Added environment annotations:"); printSummary(items); container.setItems(items); KubernetesHelper.saveJson(json, container); } else if (dto instanceof Template) { Template container = (Template) dto; List<HasMetadata> items = container.getObjects(); addEnvironmentAnnotations(items); getLog().info("Added environment annotations:"); printSummary(items); container.setObjects(items); getLog().info("Template is now:"); printSummary(container.getObjects()); KubernetesHelper.saveJson(json, container); } } catch (IOException e) { throw new MojoExecutionException("Failed to updated JSON file " + json + ". " + e, e); } } protected void addEnvironmentAnnotations(Iterable<HasMetadata> items) throws MojoExecutionException { if (items != null) { for (HasMetadata item : items) { if (item instanceof KubernetesList) { KubernetesList list = (KubernetesList) item; addEnvironmentAnnotations(list.getItems()); } else if (item instanceof Template) { Template template = (Template) item; addEnvironmentAnnotations(template.getObjects()); } else if (item instanceof ReplicationController) { addEnvironmentAnnotations(item); } else if (item instanceof DeploymentConfig) { addEnvironmentAnnotations(item); } } } } protected void addEnvironmentAnnotations(HasMetadata resource) throws MojoExecutionException { Map<String, String> mapEnvVarToAnnotation = new HashMap<>(); String resourceName = "environmentAnnotations.properties"; URL url = getClass().getResource(resourceName); if (url == null) { throw new MojoExecutionException("Cannot find resource `" + resourceName + "` on the classpath!"); } addPropertiesFileToMap(url, mapEnvVarToAnnotation); addPropertiesFileToMap(this.environmentVariableToAnnotationsFile, mapEnvVarToAnnotation); Map<String, String> annotations = KubernetesHelper.getOrCreateAnnotations(resource); Set<Map.Entry<String, String>> entries = mapEnvVarToAnnotation.entrySet(); for (Map.Entry<String, String> entry : entries) { String envVar = entry.getKey(); String annotation = entry.getValue(); if (Strings.isNotBlank(envVar) && Strings.isNotBlank(annotation)) { String value = Systems.getEnvVarOrSystemProperty(envVar); if (Strings.isNullOrBlank(value)) { value = tryDefaultAnnotationEnvVar(envVar); } if (Strings.isNotBlank(value)) { String oldValue = annotations.get(annotation); if (Strings.isNotBlank(oldValue)) { getLog().debug("Not adding annotation `" + annotation + "` to " + KubernetesHelper.getKind(resource) + " " + KubernetesHelper.getName(resource) + " with value `" + value + "` as there is already an annotation value of `" + oldValue + "`"); } else { annotations.put(annotation, value); } } } } // lets try and figure out the documentation URL String docUrl = findDocumentationUrl(); if (Strings.isNotBlank(docUrl)) { annotations.put(Annotations.Builds.DOCS_URL, docUrl); getLog().info("Found documentation URL: " + docUrl); } } protected String findDocumentationUrl() { DistributionManagement distributionManagement = findProjectDistributionManagement(); if (distributionManagement != null) { Site site = distributionManagement.getSite(); if (site != null) { String url = site.getUrl(); if (Strings.isNotBlank(url)) { // lets replace any properties... MavenProject project = getProject(); if (project != null) { url = replaceProperties(url, project.getProperties()); } // lets convert the internal dns name to a public name try { String urlToParse = url; int idx = url.indexOf("://"); if (idx > 0) { // lets strip any previous schemes such as "dav:" int idx2 = url.substring(0, idx).lastIndexOf(':'); if (idx2 >= 0 && idx2 < idx) { urlToParse = url.substring(idx2 + 1); } } URL u = new URL(urlToParse); String host = u.getHost(); // lets see if the host name is a service name in which case we'll resolve to the public URL String ns = null; try { KubernetesClient kubernetes = getKubernetes(); ns = kubernetes.getNamespace(); Service service = kubernetes.services().inNamespace(ns).withName(host).get(); if (service != null) { String publicUrl = KubernetesHelper.getServiceURL(kubernetes, host, ns, u.getProtocol(), true); return URLUtils.pathJoin(publicUrl, u.getPath()); } else { if (host.indexOf('.') < 0) { // the kubernetes service name is not running so lets not generate a Docs link! getLog().info("Not generating a documentation link annotation as the service " + host + " is not running!"); return null; } } } catch (Exception e) { getLog().warn("Could not look up service " + host + " in namespace " + ns + " due to : " + e); } } catch (MalformedURLException e) { getLog().error("Failed to parse URL: " + url, e); } return url; } } } return null; } /** * Replaces all text of the form <code>{foo}</code>$ with the value in the properties object */ protected static String replaceProperties(String text, Properties properties) { Set<Map.Entry<Object, Object>> entries = properties.entrySet(); for (Map.Entry<Object, Object> entry : entries) { Object key = entry.getKey(); Object value = entry.getValue(); if (key != null && value != null) { String pattern = "${" + key + "}"; text = Strings.replaceAllWithoutRegex(text, pattern, value.toString()); } } return text; } protected DistributionManagement findProjectDistributionManagement() { MavenProject project = getProject(); while (project != null) { DistributionManagement distributionManagement = project.getDistributionManagement(); if (distributionManagement != null) { return distributionManagement; } project = project.getParent(); } return null; } /** * Tries to default some environment variables if they are not already defined. * * This can happen if using Jenkins Workflow which doens't seem to define BUILD_URL or GIT_URL for example * * @return the value of the environment variable name if it can be found or calculated */ protected String tryDefaultAnnotationEnvVar(String envVarName) { // only do this if enabled if (extendedMetadata != null && !extendedMetadata) { return null; } MavenProject rootProject = getRootProject(); File basedir = rootProject.getBasedir(); if (basedir == null) { basedir = getProject().getBasedir(); } if (basedir == null) { basedir = new File(System.getProperty("basedir", ".")); } ProjectConfig projectConfig = ProjectConfigs.loadFromFolder(basedir); String repoName = rootProject.getArtifactId(); String userEnvVar = "JENKINS_GOGS_USER"; String username = Systems.getEnvVarOrSystemProperty(userEnvVar); if (Objects.equal("BUILD_URL", envVarName)) { String jobUrl = projectConfig.getLink("Job"); if (Strings.isNullOrBlank(jobUrl)) { String name = projectConfig.getBuildName(); if (Strings.isNullOrBlank(name)) { // lets try deduce the jenkins build name we'll generate if (Strings.isNotBlank(repoName)) { name = repoName; if (Strings.isNotBlank(username)) { name = ProjectRepositories.createBuildName(username, repoName); } else { warnIfInCDBuild("Cannot auto-default BUILD_URL as there is no environment variable `" + userEnvVar + "` defined so we can't guess the Jenkins build URL"); } } } if (Strings.isNotBlank(name)) { try { // this requires online access to kubernetes so we should silently fail if no connection String jenkinsUrl = KubernetesHelper.getServiceURLInCurrentNamespace(getKubernetes(), ServiceNames.JENKINS, "http", null, true); jobUrl = URLUtils.pathJoin(jenkinsUrl, "/job", name); } catch (Throwable e) { Throwable cause = e; boolean notFound = false; boolean connectError = false; Iterable<Throwable> it = createExceptionIterable(e); for (Throwable t : it) { connectError = t instanceof ConnectException || "No route to host".equals(t.getMessage()); notFound = t instanceof IllegalArgumentException || t.getMessage() != null && t.getMessage().startsWith("No kubernetes service could be found for name"); if (connectError || notFound) { cause = t; break; } } if (connectError) { warnIfInCDBuild("Cannot connect to Kubernetes to find jenkins service URL: " + cause.getMessage()); } else if (notFound) { // the message from the exception is good as-is warnIfInCDBuild(cause.getMessage()); } else { warnIfInCDBuild("Cannot find jenkins service URL: " + cause, cause); } } } } if (Strings.isNotBlank(jobUrl)) { String buildId = Systems.getEnvVarOrSystemProperty("BUILD_ID"); if (Strings.isNotBlank(buildId)) { jobUrl = URLUtils.pathJoin(jobUrl, buildId); } else { warnIfInCDBuild("Cannot find BUILD_ID to create a specific jenkins build URL. So using: " + jobUrl); } } return jobUrl; } else if (Objects.equal("GIT_URL", envVarName)) { if (Strings.isNotBlank(repoName) && Strings.isNotBlank(username)) { try { // this requires online access to kubernetes so we should silently fail if no connection String gogsUrl = KubernetesHelper.getServiceURLInCurrentNamespace(getKubernetes(), ServiceNames.GOGS, "http", null, true); String rootGitUrl = URLUtils.pathJoin(gogsUrl, username, repoName); String gitCommitId = getGitCommitId(envVarName, basedir); if (Strings.isNotBlank(gitCommitId)) { rootGitUrl = URLUtils.pathJoin(rootGitUrl, "commit", gitCommitId); } return rootGitUrl; } catch (Throwable e) { Throwable cause = e; boolean notFound = false; boolean connectError = false; Iterable<Throwable> it = createExceptionIterable(e); for (Throwable t : it) { notFound = t instanceof IllegalArgumentException || t.getMessage() != null && t.getMessage().startsWith("No kubernetes service could be found for name"); connectError = t instanceof ConnectException || "No route to host".equals(t.getMessage()); if (connectError) { cause = t; break; } } if (connectError) { warnIfInCDBuild("Cannot connect to Kubernetes to find gogs service URL: " + cause.getMessage()); } else if (notFound) { // the message from the exception is good as-is warnIfInCDBuild(cause.getMessage()); } else { warnIfInCDBuild("Cannot find gogs service URL: " + cause, cause); } } } else { warnIfInCDBuild("Cannot auto-default GIT_URL as there is no environment variable `" + userEnvVar + "` defined so we can't guess the Gogs build URL"); } /* TODO this is the git clone url; while we could try convert from it to a browse URL its probably too flaky? try { url = GitHelpers.extractGitUrl(basedir); } catch (IOException e) { warnIfInCDBuild("Failed to find git url in directory " + basedir + ". " + e, e); } if (Strings.isNotBlank(url)) { // for gogs / github style repos we trim the .git suffix for browsing return Strings.stripSuffix(url, ".git"); } */ } else if (Objects.equal("GIT_COMMIT", envVarName)) { return getGitCommitId(envVarName, basedir); } else if (Objects.equal("GIT_BRANCH", envVarName)) { Repository repository = getGitRepository(basedir, envVarName); try { if (repository != null) { return repository.getBranch(); } } catch (IOException e) { warnIfInCDBuild("Failed to find git commit id. " + e, e); } finally { if (repository != null) { repository.close(); } } } return null; } protected String getGitCommitId(String envVarName, File basedir) { Repository repository = getGitRepository(basedir, envVarName); try { if (repository != null) { getLog().info("Looking at repo with directory " + repository.getDirectory()); Iterable<RevCommit> logs = new Git(repository).log().call(); for (RevCommit rev : logs) { return rev.getName(); } warnIfInCDBuild("Cannot default " + envVarName + " no commits could be found"); } else { warnIfInCDBuild("Cannot default " + envVarName + " as no git repository could be found"); } } catch (Exception e) { warnIfInCDBuild("Failed to find git commit id. " + e, e); } finally { if (repository != null) { try { repository.close(); } catch (Exception e) { // ignore } } } return null; } protected void warnIfInCDBuild(String message) { if (isInCDBuild()) { getLog().warn(message); } else { getLog().debug(message); } } /** * Returns true if the current build is being run inside a CI / CD build in which case * lets warn if we cannot detect things like the GIT commit or Jenkins build server URL */ protected boolean isInCDBuild() { if (cdBuild) { return true; } String envVar = System.getenv(cdEnvVarName); return Strings.isNotBlank(envVar); } protected void warnIfInCDBuild(String message, Throwable exception) { if (isInCDBuild()) { getLog().warn(message, exception); } else { getLog().debug(message, exception); } } /** * Creates an Iterable to walk the exception from the bottom up * (the last caused by going upwards to the root exception). * * @see java.lang.Iterable * @param exception the exception * @return the Iterable */ protected static Iterable<Throwable> createExceptionIterable(Throwable exception) { List<Throwable> throwables = new ArrayList<Throwable>(); Throwable current = exception; // spool to the bottom of the caused by tree while (current != null) { throwables.add(current); current = current.getCause(); } Collections.reverse(throwables); return throwables; } protected Repository getGitRepository(File basedir, String envVarName) { try { File gitFolder = GitHelpers.findGitFolder(basedir); if (gitFolder == null) { warnIfInCDBuild("Could not find .git folder based on the current basedir of " + basedir); return null; } FileRepositoryBuilder builder = new FileRepositoryBuilder(); Repository repository = builder .readEnvironment() .setGitDir(gitFolder) .build(); if (repository == null) { warnIfInCDBuild("Cannot create default value for $" + envVarName + " as no .git/config file could be found"); } return repository; } catch (Exception e) { warnIfInCDBuild("Failed to initialise Git Repository: " + e, e); return null; } } protected boolean shouldGenerateForThisProject() { return !isPomProject() || hasConfigDir(); } /** * Returns all the environment variable properties defined in the pom.xml which are prefixed with "fabric8.env." */ public Map<String, String> getEnvironmentVariableProperties() throws MojoExecutionException { Map<String, String> rawProperties = findPropertiesWithPrefix(getProject().getProperties(), "fabric8.env.", Strings.toEnvironmentVariableFunction()); Set<Map.Entry<String, String>> entries = rawProperties.entrySet(); Map<String, String> answer = new HashMap<>(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); value = unquoteTemplateExpression(value); answer.put(key, value); } addPropertiesFileToMap(this.envPropertiesFile, answer); return answer; } protected static void addPropertiesFileToMap(File file, Map<String, String> answer) throws MojoExecutionException { if (file != null && file.isFile() && file.exists()) { try (FileInputStream in = new FileInputStream(file)){ Properties properties = new Properties(); properties.load(in); Map<String, String> map = toMap(properties); answer.putAll(map); } catch (IOException e) { throw new MojoExecutionException("Failed to load properties file: " + file + ". " + e, e); } } } protected static void addPropertiesFileToMap(URL url, Map<String, String> answer) throws MojoExecutionException { if (url != null) { try (InputStream in = url.openStream()) { Properties properties = new Properties(); properties.load(in); Map<String, String> map = toMap(properties); answer.putAll(map); } catch (IOException e) { throw new MojoExecutionException("Failed to load properties URL: " + url + ". " + e, e); } } } /** * If supported we should escape <code>${FOO}</code> for template expressions */ public static String unquoteTemplateExpression(String value) { // turns out escaping of ${foo} expressions isn't supported yet in maven inside <properties> elements /* int idx = 0; while (true) { idx = value.indexOf("\\${", idx); if (idx >= 0) { value = value.substring(0, idx) + value.substring(++idx); } else { break; } } */ return value; } public JsonSchema getEnvironmentVariableJsonSchema() throws IOException, MojoExecutionException { JsonSchema schema = JsonSchemas.loadEnvironmentSchemas(getCompileClassLoader(), getProject().getBuild().getOutputDirectory()); if (schema == null) { getLog().debug("No environment schemas found for file: " + JsonSchemas.ENVIRONMENT_SCHEMA_FILE); schema = new JsonSchema(); } Map<String, String> envs = getEnvironmentVariableProperties(); JsonSchemas.addEnvironmentVariables(schema, envs); return schema; } protected File copyIconToFolder(File appBuildDir) throws MojoExecutionException, IOException { if (Strings.isNotBlank(iconRef)) { File[] icons = appBuildDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name == null) { return false; } String lower = name.toLowerCase(); if (lower.startsWith("icon.")) { for (String ext : ICON_EXTENSIONS) { if (lower.endsWith(ext)) { return true; } } } return false; } }); if (icons == null || icons.length == 0) { // lets copy the iconRef InputStream in = loadPluginResource(iconRef); if (in == null) { // maybe it dont have extension so try to find it for (String ext : ICON_EXTENSIONS) { String name = iconRef + ext; in = loadPluginResource(name); if (in != null) { iconRef = name; break; } } } if (in != null) { String fileName = "icon." + Files.getFileExtension(iconRef); File outFile = new File(appBuildDir, fileName); Files.copy(in, new FileOutputStream(outFile)); getLog().info("Generated icon file " + outFile + " from icon reference: " + iconRef); return outFile; } } } return null; } /** * Copies any local configuration files into the app directory */ protected void copyAppConfigFiles(File appBuildDir, File appConfigDir) throws IOException { File[] files = appConfigDir.listFiles(); if (files != null) { appBuildDir.mkdirs(); for (File file : files) { if (!toBeExclude(file.getName())) { File outFile = new File(appBuildDir, file.getName()); if (file.isDirectory()) { copyAppConfigFiles(outFile, file); } else { Files.copy(file, outFile); } } } } } protected boolean toBeExclude(String fileName) { List excludedFilesList = Arrays.asList(filesToBeExcluded); Boolean result = excludedFilesList.contains(fileName); return result; } protected void copyReadMe(File appBuildDir) throws IOException { MavenProject project = getProject(); copyReadMe(project.getFile().getParentFile(), appBuildDir); } protected void copySummaryText(File appBuildDir) throws IOException { MavenProject project = getProject(); String description = project.getDescription(); if (Strings.isNotBlank(description)) { File summaryMd = new File(appBuildDir, "Summary.md"); summaryMd.getParentFile().mkdirs(); if (!summaryMd.exists()) { byte[] bytes = description.getBytes(); Files.copy(new ByteArrayInputStream(bytes), new FileOutputStream(summaryMd)); } } } protected void printSummary(Object kubeResource) throws IOException { if (kubeResource instanceof Template) { Template template = (Template) kubeResource; String id = KubernetesHelper.getName(template); getLog().info(" Template " + id + " " + KubernetesHelper.summaryText(template)); printSummary(template.getObjects()); return; } List<HasMetadata> list = KubernetesHelper.toItemList(kubeResource); for (Object object : list) { if (object != null) { if (object instanceof List) { printSummary(object); } else { String kind = object.getClass().getSimpleName(); String id = KubernetesHelper.getObjectId(object); getLog().info(" " + kind + " " + id + " " + KubernetesHelper.summaryText(object)); } } } } public String getDockerImage() { if (Strings.isNullOrBlank(dockerImage)) { // lets see if the old maven property was specified MavenProject project = getProject(); if (project != null) { dockerImage = project.getProperties().getProperty("docker.image"); } } return dockerImage; } Set<File> getDependencies() throws IOException { Set<File> dependnencies = new LinkedHashSet<>(); MavenProject project = getProject(); Path dir = Paths.get(project.getBuild().getOutputDirectory(), "deps"); if (!dir.toFile().exists() && !dir.toFile().mkdirs()) { throw new IOException("Cannot create temp directory at:" + dir.toAbsolutePath()); } for (Artifact candidate : project.getDependencyArtifacts()) { File f = candidate.getFile(); if (f == null) { continue; } else if (f.getName().endsWith("jar") && hasKubernetesJson(f)) { getLog().info("Found file:" + f.getAbsolutePath()); try (FileInputStream fis = new FileInputStream(f); JarInputStream jis = new JarInputStream(fis)) { Zips.unzip(new FileInputStream(f), dir.toFile()); File jsonPath = dir.resolve(DEFAULT_CONFIG_FILE_NAME).toFile(); if (jsonPath.exists()) { dependnencies.add(jsonPath); } } } else if (isKubernetesJsonArtifact(candidate.getClassifier(), candidate.getType())) { dependnencies.add(f); } } return dependnencies; } static boolean isKubernetesJsonArtifact(String classifier, String type) { return Objects.equal("json", type) && Objects.equal("kubernetes", classifier); } static boolean hasKubernetesJson(File f) throws IOException { try (FileInputStream fis = new FileInputStream(f); JarInputStream jis = new JarInputStream(fis)) { for (JarEntry entry = jis.getNextJarEntry(); entry != null; entry = jis.getNextJarEntry()) { if (entry.getName().equals(DEFAULT_CONFIG_FILE_NAME)) { return true; } } } return false; } /** * 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<io.fabric8.openshift.api.model.Parameter> 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; } }