/*
* Copyright 2016 ThoughtWorks, Inc.
*
* Licensed 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 com.thoughtworks.go.config;
import com.thoughtworks.go.domain.TaskProperty;
import com.thoughtworks.go.domain.config.Arguments;
import com.thoughtworks.go.util.ArrayUtil;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.StringUtil;
import com.thoughtworks.go.utils.CommandUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* This was copied from the ExecBuilder class in ccmain. Look for references there.
*/
@ConfigTag("exec")
public class ExecTask extends AbstractTask implements CommandTask {
public static final String TYPE = "exec";
@ConfigAttribute("command")
private String command = "";
@ConfigAttribute(value = "args", allowNull = true)
private String args = "";
@ConfigAttribute(value = "workingdir", allowNull = true)
private String workingDirectory;
@ConfigAttribute("timeout")
private Long timeout = NO_TIMEOUT_FOR_COMMANDLINE;
@ConfigSubtag(label = "arguments")
private Arguments argList = new Arguments();
public static final String EXEC_CONFIG_ERROR = "Can not use both 'args' attribute and 'arg' sub element in 'exec' element!";
private static final long NO_TIMEOUT_FOR_COMMANDLINE = -1;
public static final String COMMAND = "command";
public static final String ARGS = "args";
public static final String ARG_LIST_STRING = "argListString";
public static final String WORKING_DIR = "workingDirectory";
public static final String TIMEOUT = "timeout";
private final String CUSTOM_COMMAND = "Custom Command";
public ExecTask() {
}
public ExecTask(String command, String args, String workingDir) {
this(command, workingDir);
this.args = args;
}
//used for test
public ExecTask(String args, Arguments argList) {
this("echo", args, argList);//TODO: delete me, there is a validation that enforces not both attributes are populated - shilpa / jj
}
@Override
public String describe() {
if (null != argList && !argList.isEmpty()) {
return CommandUtils.shellJoin(ArrayUtil.pushToArray(command, argList.toStringArray()));
}
if (null != args && !"".equals(args)) {
return command + " " + args;
}
return command;
}
public ExecTask(String command, String args, Arguments argList) {
this.command = command;
this.args = args;
this.argList = argList;
}
public ExecTask(String command, Arguments argList, String workingDir) {
this(command, workingDir);
this.argList = argList;
}
private ExecTask(String command, String workingDir) {
this.command = command;
this.workingDirectory = workingDir;
}
protected void setTaskConfigAttributes(Map attributeMap) {
if (attributeMap.containsKey(COMMAND)) {
command = (String) attributeMap.get(COMMAND);
}
if (attributeMap.containsKey(ARG_LIST_STRING)) {
clearCurrentArgsAndArgList();
String value = (String) attributeMap.get(ARG_LIST_STRING);
if (!StringUtil.isBlank(value)) {
String[] arguments = value.split("\n");
for (String arg : arguments) {
argList.add(new Argument(arg));
}
}
}
if (attributeMap.containsKey(ARGS)) {
String val = (String) attributeMap.get(ARGS);
setArgs(val);
}
if (attributeMap.containsKey(WORKING_DIR)) {
final String newWorkingDir = (String) attributeMap.get(WORKING_DIR);
setWorkingDirectory(newWorkingDir);
}
}
public void setArgsList(String[] arguments) {
clearCurrentArgsAndArgList();
for (String arg : arguments) {
argList.add(new Argument(arg));
}
}
public void setArgs(String val) {
clearCurrentArgsAndArgList();
if (!StringUtil.isBlank(val)) {
this.args = val;
}
}
public void setWorkingDirectory(String newWorkingDir) {
workingDirectory = StringUtil.isBlank(newWorkingDir) ? null : newWorkingDir;
}
private void clearCurrentArgsAndArgList() {
args = "";
argList.clear();
}
public void validateTask(ValidationContext ctx) {
validateCommand();
if (!usingBothArgsAndArgList()) {
errors.add(ARGS, EXEC_CONFIG_ERROR);
errors.add(ARG_LIST_STRING, EXEC_CONFIG_ERROR);
}
if (workingDirectory != null && !FileUtil.isFolderInsideSandbox(workingDirectory)) {
if (ctx.isWithinPipelines()) {
errors.add(WORKING_DIR,
String.format("The path of the working directory for the custom command in job '%s' in stage '%s' of pipeline '%s' is outside the agent sandbox.", ctx.getJob().name(),
ctx.getStage().name(), ctx.getPipeline().name()));
} else {
errors.add(WORKING_DIR,
String.format("The path of the working directory for the custom command in job '%s' in stage '%s' of template '%s' is outside the agent sandbox.", ctx.getJob().name(),
ctx.getStage().name(), ctx.getTemplate().name()));
}
}
for (Argument argument : getArgList()) {
if (!argument.errors().isEmpty()) {
errors.add(ARG_LIST_STRING, argument.errors().asString());
}
}
}
private void validateCommand() {
if (StringUtil.isBlank(command)) {
errors.add(COMMAND, "Command cannot be empty");
}
}
@Override
public String getTaskType() {
return TYPE;
}
public String getTypeForDisplay() {
return CUSTOM_COMMAND;
}
public List<TaskProperty> getPropertiesForDisplay() {
ArrayList<TaskProperty> taskProperties = new ArrayList<>();
taskProperties.add(new TaskProperty("COMMAND", command));
String arguments = arguments();
if (!arguments.isEmpty()) {
taskProperties.add(new TaskProperty("ARGUMENTS", arguments));
}
if (workingDirectory != null) {
taskProperties.add(new TaskProperty("WORKING_DIR", workingDirectory));
}
if (!(timeout == null || timeout.equals(NO_TIMEOUT_FOR_COMMANDLINE))) {
taskProperties.add(new TaskProperty("TIMEOUT", timeout.toString()));
}
return taskProperties;
}
private String argsAsString(final String delimiter) {
StringBuilder args = new StringBuilder();
for (int i = 0; i < argList.size(); ) {
args.append(argList.get(i).getValue());
if (++i < argList.size()) {
args.append(delimiter);
}
}
return args.toString();
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExecTask execTask = (ExecTask) o;
if (timeout != execTask.timeout) {
return false;
}
if (args != null ? !args.equals(execTask.args) : execTask.args != null) {
return false;
}
if (argList != null ? !argList.equals(execTask.argList) : execTask.argList != null) {
return false;
}
if (command != null ? !command.equals(execTask.command) : execTask.command != null) {
return false;
}
if (workingDirectory != null ? !workingDirectory.equals(execTask.workingDirectory) : execTask.workingDirectory != null) {
return false;
}
return super.equals(execTask);
}
public int hashCode() {
int result;
result = command.hashCode();
result = 31 * result + (args != null ? args.hashCode() : 0);
result = 31 * result + (workingDirectory != null ? workingDirectory.hashCode() : 0);
result = 31 * result + (int) (timeout ^ (timeout >>> 32));
return result;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public long getTimeout() {
return timeout;
}
public Arguments getArgList() {
return argList;
}
public String getArgListString() {
return argsAsString("\n");
}
private boolean usingBothArgsAndArgList() {
return args.isEmpty() || argList.isEmpty();
}
public String command() {
return command;
}
public String getCommand() {
return command();
}
public void setCommand(String command) {
this.command = command;
}
public String getArgs() {
return args;
}
public String workingDirectory() {
return workingDirectory;
}
@Override
public String toString() {
return "ExecTask{" +
"command='" + command + '\'' +
", args='" + args + '\'' +
", workingDir='" + workingDirectory + '\'' +
", timeout=" + timeout +
", argList=" + argList +
'}';
}
@Override
public String arguments() {
if (!args.isEmpty()) {
return args;
}
return argsAsString(" ");
}
}