/**
* © Copyright 2015 Hewlett Packard Enterprise Development LP
* Copyright (c) ActiveState 2014 - ALL RIGHTS RESERVED.
*/
package com.hpe.cloudfoundryjenkins;
import com.hpe.cloudfoundryjenkins.CloudFoundryPushPublisher.DescriptorImpl;
import com.hpe.cloudfoundryjenkins.CloudFoundryPushPublisher.EnvironmentVariable;
import com.hpe.cloudfoundryjenkins.CloudFoundryPushPublisher.ManifestChoice;
import com.hpe.cloudfoundryjenkins.CloudFoundryPushPublisher.ServiceName;
import hudson.model.AbstractBuild;
import hudson.model.TaskListener;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Contains all deployment info of a single application.
* The class is in charge of default values, of expanding token macros,
* and of reading from the Jenkins config if needed.
*/
public class DeploymentInfo {
private String appName;
private int memory;
private String hostname;
private int instances;
private int timeout;
private boolean noRoute;
private String appPath;
private String buildpack;
private String command;
private String domain;
private String stack;
private Map<String, String> envVars = new HashMap<String, String>();
private List<String> servicesNames = new ArrayList<String>();
/**
* Constructor for reading the manifest.yml file.
* Takes an appInfo Map that is created from a ManifestReader.
*/
public DeploymentInfo(AbstractBuild build, TaskListener listener, PrintStream logger, Map<String, Object> appInfo,
String jenkinsBuildName, String defaultDomain, String manifestPath)
throws IOException, ManifestParsingException, InterruptedException, MacroEvaluationException {
readManifestFile(logger, appInfo, jenkinsBuildName, defaultDomain, manifestPath);
expandTokenMacros(build, listener);
}
/**
* Constructor for reading the optional Jenkins config.
*/
public DeploymentInfo(AbstractBuild build, TaskListener listener, PrintStream logger,
ManifestChoice optionalJenkinsConfig, String jenkinsBuildName, String defaultDomain)
throws IOException, ManifestParsingException, InterruptedException, MacroEvaluationException {
readOptionalJenkinsConfig(logger, optionalJenkinsConfig, jenkinsBuildName, defaultDomain);
expandTokenMacros(build, listener);
}
/**
* Constructor for reading the manifest.yml file. (Without token expansion)
*/
public DeploymentInfo(PrintStream logger, Map<String, Object> appInfo,
String jenkinsBuildName, String defaultDomain, String manifestPath)
throws IOException, ManifestParsingException, InterruptedException, MacroEvaluationException {
readManifestFile(logger, appInfo, jenkinsBuildName, defaultDomain, manifestPath);
}
/**
* Constructor for reading the optional Jenkins config. (Without token expansion)
*/
public DeploymentInfo(PrintStream logger, ManifestChoice optionalJenkinsConfig,
String jenkinsBuildName, String defaultDomain)
throws IOException, ManifestParsingException, InterruptedException, MacroEvaluationException {
readOptionalJenkinsConfig(logger, optionalJenkinsConfig, jenkinsBuildName, defaultDomain);
}
private void readManifestFile(PrintStream logger, Map<String, Object> manifestJson,
String jenkinsBuildName, String defaultDomain, String manifestPath) {
// Important optional attributes, we should warn in case they are missing
if (manifestJson == null) {
manifestJson = new HashMap<String, Object>();
}
appName = (String) manifestJson.get("name");
if (appName == null) {
logger.println("WARNING: No application name. Using Jenkins build name: " + jenkinsBuildName);
appName = jenkinsBuildName;
}
int memory;
Object mem = manifestJson.get("memory");
if (mem == null) {
logger.println("WARNING: No manifest value for memory. Using default value: " +
DescriptorImpl.DEFAULT_MEMORY);
memory = DescriptorImpl.DEFAULT_MEMORY;
} else {
// The YAML parser from ManifestReader might make the memory value an Integer or a String
// depending on whether or not there is a unit at the end of the value
if (mem instanceof Integer) {
memory = (Integer) mem;
} else {
String memString = (String) mem;
memString = memString.toLowerCase();
if (memString.endsWith("m")) {
memory = Integer.parseInt(memString.substring(0, memString.length() - 1));
} else if (memString.endsWith("mb")) {
memory = Integer.parseInt(memString.substring(0, memString.length() - 2));
} else if (memString.endsWith("g")) {
memory = Integer.parseInt(memString.substring(0, memString.length() - 1));
memory = memory * 1024;
} else if (memString.endsWith("gb")) {
memory = Integer.parseInt(memString.substring(0, memString.length() - 2));
memory = memory * 1024;
} else {
logger.println("WARNING: Unknown unit for memory in manifest. Using default value: " +
DescriptorImpl.DEFAULT_MEMORY);
memory = DescriptorImpl.DEFAULT_MEMORY;
}
}
}
this.memory = memory;
hostname = (String) manifestJson.get("host");
if (hostname == null) {
logger.println("WARNING: No manifest value for hostname. Using app name: " + appName);
hostname = appName;
}
// Non-important optional attributes, no need to warn
Integer instances = (Integer) manifestJson.get("instances");
if (instances == null) {
instances = DescriptorImpl.DEFAULT_INSTANCES;
}
this.instances = instances;
Integer timeout = (Integer) manifestJson.get("timeout");
if (timeout == null) {
timeout = DescriptorImpl.DEFAULT_TIMEOUT;
}
this.timeout = timeout;
String stack = (String) manifestJson.get("stack");
if (stack == null) {
stack = DescriptorImpl.DEFAULT_STACK;
}
this.stack = stack;
Boolean noRoute = (Boolean) manifestJson.get("no-route");
if (noRoute == null) {
noRoute = false;
}
this.noRoute = noRoute;
String domain = (String) manifestJson.get("domain");
if (domain == null) {
domain = defaultDomain;
}
this.domain = domain;
String appPath = (String) manifestJson.get("path");
if (appPath == null) {
appPath = "";
}
// The path in manifest.yml is from the relative position of the manifest.yml file
// We need to transform it into a path relative to the workspace
Path sourcePath = Paths.get(manifestPath);
sourcePath = sourcePath.getParent();
if (sourcePath == null) {
sourcePath = Paths.get(".");
}
Path targetPath = Paths.get(appPath);
this.appPath = sourcePath.resolve(targetPath).normalize().toString();
// Optional attributes with no defaults, it's ok if those are null
this.buildpack = (String) manifestJson.get("buildpack");
this.command = (String) manifestJson.get("command");
// Env vars and services
try {
@SuppressWarnings("unchecked")
Map<String, String> envVarsSuppressed = (Map<String, String>) manifestJson.get("env");
if (envVarsSuppressed != null) {
this.envVars = envVarsSuppressed;
}
} catch (ClassCastException e) {
logger.println("WARNING: Could not parse env vars into a map. Ignoring env vars.");
}
try {
@SuppressWarnings("unchecked")
List<String> servicesSuppressed = (List<String>) manifestJson.get("services");
if (servicesSuppressed != null) {
this.servicesNames = servicesSuppressed;
}
} catch (ClassCastException e) {
logger.println("WARNING: Could not parse services into a list. Ignoring services.");
}
}
private void readOptionalJenkinsConfig(PrintStream logger, ManifestChoice jenkinsConfig,
String jenkinsBuildName, String defaultDomain) {
this.appName = jenkinsConfig.appName;
if (appName.equals("")) {
logger.println("WARNING: No application name. Using Jenkins build name: " + jenkinsBuildName);
appName = jenkinsBuildName;
}
this.memory = jenkinsConfig.memory;
if (memory == 0) {
logger.println("WARNING: Missing value for memory. Using default value: " + DescriptorImpl.DEFAULT_MEMORY);
memory = DescriptorImpl.DEFAULT_MEMORY;
}
this.hostname = jenkinsConfig.hostname;
if (hostname.equals("")) {
logger.println("WARNING: Missing value for hostname. Using app name: " + appName);
hostname = appName;
}
this.instances = jenkinsConfig.instances;
if (instances == 0) {
instances = DescriptorImpl.DEFAULT_INSTANCES;
}
this.timeout = jenkinsConfig.timeout;
if (timeout == 0) {
timeout = DescriptorImpl.DEFAULT_TIMEOUT;
}
this.stack = jenkinsConfig.stack;
if (stack.equals("")) {
stack = DescriptorImpl.DEFAULT_STACK;
}
// noRoute's default value is already false, which is acceptable
this.noRoute = jenkinsConfig.noRoute;
this.domain = jenkinsConfig.domain;
if (domain.equals("")) {
domain = defaultDomain;
}
// These must be null, not just empty string
this.buildpack = jenkinsConfig.buildpack;
if (buildpack.equals("")) {
buildpack = null;
}
this.command = jenkinsConfig.command;
if (command.equals("")) {
command = null;
}
// Paths.get("").normalize() causes a bug in some versions of Java
// Replace "" with "." to avoid the bug
String tempAppPath = jenkinsConfig.appPath;
if (tempAppPath.isEmpty()) {
tempAppPath = ".";
}
this.appPath = Paths.get(tempAppPath).normalize().toString();
List<EnvironmentVariable> manifestEnvVars = jenkinsConfig.envVars;
if (manifestEnvVars != null) {
for (EnvironmentVariable var : manifestEnvVars) {
this.envVars.put(var.key, var.value);
}
}
List<ServiceName> manifestServicesNames = jenkinsConfig.servicesNames;
if (manifestServicesNames != null) {
for (ServiceName service : manifestServicesNames) {
this.servicesNames.add(service.name);
}
}
}
private void expandTokenMacros(AbstractBuild build, TaskListener listener)
throws InterruptedException, MacroEvaluationException, IOException {
this.appName = TokenMacro.expandAll(build, listener, this.appName);
this.hostname = TokenMacro.expandAll(build, listener, this.hostname);
this.appPath = TokenMacro.expandAll(build, listener, this.appPath);
this.buildpack = TokenMacro.expandAll(build, listener, this.buildpack);
this.command = TokenMacro.expandAll(build, listener, this.command);
this.domain = TokenMacro.expandAll(build, listener, this.domain);
Map<String, String> expandedEnvVars = new HashMap<String, String>();
for (String envVarName : this.envVars.keySet()) {
try {
String expandedEnvVarName = TokenMacro.expandAll(build, listener, envVarName);
String expandedEnvVarValue = TokenMacro.expandAll(build, listener, this.envVars.get(envVarName));
expandedEnvVars.put(expandedEnvVarName, expandedEnvVarValue);
} catch (MacroEvaluationException e) {
// If a token exists but isn't recognized, then it's probably an environment variable
// meant for the CF target, so leave it alone.
expandedEnvVars.put(envVarName, this.envVars.get(envVarName));
}
}
this.envVars = expandedEnvVars;
List<String> expandedServicesNames = new ArrayList<String>();
for (String serviceName : this.servicesNames) {
String expandedServiceName = TokenMacro.expandAll(build, listener, serviceName);
expandedServicesNames.add(expandedServiceName);
}
this.servicesNames = expandedServicesNames;
}
public String getAppName() {
return appName;
}
public int getMemory() {
return memory;
}
public String getHostname() {
return hostname;
}
public int getInstances() {
return instances;
}
public String getStack() {
return stack;
}
public int getTimeout() {
return timeout;
}
public boolean isNoRoute() {
return noRoute;
}
public String getAppPath() {
return appPath;
}
public String getBuildpack() {
return buildpack;
}
public String getCommand() {
return command;
}
public String getDomain() {
return domain;
}
public Map<String, String> getEnvVars() {
return envVars;
}
public List<String> getServicesNames() {
return servicesNames;
}
}