Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/main/java/org/jfrog/bamboo/JfContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jfrog.bamboo.config.ServerConfig;
import org.jfrog.bamboo.config.ServerConfigManager;
import org.jfrog.bamboo.utils.ExecutableRunner;

import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -65,4 +73,46 @@ public void validate(@NotNull ActionParametersMap params, @NotNull ErrorCollecti
errorCollection.addErrorMessage("JFrog configuration should be selected");
}
}

/**
* Resolves the task working directory: returns the user-supplied custom path if non-blank
* (failing if it does not exist), otherwise the task's default working directory.
*
* <p>Lives on the shared configurator so deployment-project subclasses such as
* {@link JfDeploymentTask} can reuse it without re-implementing the same logic.
*/
public File getWorkingDirectory(String customWd, File defaultWd) throws IOException {
if (StringUtils.isBlank(customWd)) {
return defaultWd;
}
if (!Files.exists(Paths.get(customWd))) {
throw new IOException("Working directory: '" + customWd + "' does not exist.");
}
return new File(customWd);
}

/**
* Runs {@code jf config add} to register a single JFrog server in the CLI's temp home dir.
*
* <p>Lives on the shared configurator so deployment-project subclasses such as
* {@link JfDeploymentTask} can reuse it without re-implementing the same CLI invocation.
*/
public int runConfigAdd(ExecutableRunner commandRunner, ServerConfig serverConfig)
throws IOException, InterruptedException {
List<String> args = new ArrayList<>(List.of(
"config",
"add",
serverConfig.getServerId(),
"--url=" + serverConfig.getUrl(),
"--interactive=false",
"--overwrite=true"
));
if (StringUtils.isNotBlank(serverConfig.getAccessToken())) {
args.add("--access-token=" + serverConfig.getAccessToken());
} else if (StringUtils.isNotBlank(serverConfig.getUsername()) && StringUtils.isNotBlank(serverConfig.getPassword())) {
args.add("--user=" + serverConfig.getUsername());
args.add("--password=" + serverConfig.getPassword());
}
return commandRunner.run(args);
}
}
165 changes: 165 additions & 0 deletions src/main/java/org/jfrog/bamboo/JfDeploymentTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package org.jfrog.bamboo;

import com.atlassian.bamboo.deployments.execution.DeploymentTaskContext;
import com.atlassian.bamboo.deployments.execution.DeploymentTaskType;
import com.atlassian.bamboo.task.TaskResult;
import com.atlassian.bamboo.task.TaskResultBuilder;
import com.atlassian.bamboo.variable.CustomVariableContext;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import org.jfrog.bamboo.config.ServerConfig;
import org.jfrog.bamboo.config.ServerConfigManager;
import org.jfrog.bamboo.utils.BambooUtils;
import org.jfrog.bamboo.utils.BuildLog;
import org.jfrog.bamboo.utils.ExecutableRunner;
import org.jfrog.bamboo.utils.Utils;

import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* JFrog CLI Task for Bamboo Deployment Projects.
*
* <p>Implements {@link DeploymentTaskType} so it appears in the Deployment Project task picker,
* where the plain {@link JfTask} (which only implements {@link com.atlassian.bamboo.task.TaskType})
* is invisible.
*
* <p>Shares its configurator ({@link JfContext}) and FTL template with {@link JfTask}, so the
* user-facing configuration UI is identical in both build plans and deployment projects.
*/
public class JfDeploymentTask extends JfContext implements DeploymentTaskType {

private BuildLog buildLog;

@Inject
private ServerConfigManager serverConfigManager;

@Inject
@ComponentImport
private CustomVariableContext customVariableContext;

@Inject
@ComponentImport
private PluginAccessor pluginAccessor;

/**
* Executes the JFrog CLI command in a Deployment Project environment.
*/
@Override
public @NotNull TaskResult execute(@NotNull DeploymentTaskContext taskContext) {
buildLog = new BuildLog(taskContext.getBuildLogger());
TaskResultBuilder resultBuilder = TaskResultBuilder.newBuilder(taskContext);

String selectedServerId = taskContext.getConfigurationMap().get(JF_TASK_SERVER_ID);
ServerConfig selectedServerConfig = serverConfigManager.getServerConfigById(selectedServerId);
if (selectedServerConfig == null) {
buildLog.error("The selected Server ID doesn't exist: " + selectedServerId);
return resultBuilder.failedWithError().build();
}

try {
String jfrogTmpDir = BambooUtils.getJfrogTmpDir(customVariableContext);

// Download CLI (if needed) and retrieve path
String jfExecutablePath = JfInstaller.getJfExecutable(selectedServerConfig, jfrogTmpDir, buildLog);

Map<String, String> envs = createDeploymentEnvironmentVariables(taskContext, selectedServerConfig);
String customWd = taskContext.getConfigurationMap().get(JF_TASK_WORKING_DIRECTORY);
File workingDir = getWorkingDirectory(customWd, taskContext.getWorkingDirectory());
buildLog.info("Working directory: " + workingDir);

List<String> secrets = List.of(selectedServerConfig.getPassword(), selectedServerConfig.getAccessToken());
ExecutableRunner commandRunner = new ExecutableRunner(jfExecutablePath, workingDir, envs, secrets, buildLog);

// Run 'jf config add' for every configured JFrog server
for (ServerConfig serverConfig : serverConfigManager.getAllServerConfigs()) {
int exitCode = runConfigAdd(commandRunner, serverConfig);
if (exitCode != 0) {
return resultBuilder.failedWithError().build();
}
}

// Make the selected server the default
int exitCode = commandRunner.run(List.of("config", "use", selectedServerConfig.getServerId()));
if (exitCode != 0) {
return resultBuilder.failedWithError().build();
}

// Run the user-supplied CLI command
String cliCommand = taskContext.getConfigurationMap().get(JF_TASK_COMMAND);
cliCommand = StringUtils.removeStart(cliCommand, "jf ");

List<String> unwrappedArgs = Utils.splitStringPreservingQuotes(cliCommand)
.stream()
.map(Utils::unQuote)
.collect(Collectors.toList());

exitCode = commandRunner.run(unwrappedArgs);
if (exitCode != 0) {
return resultBuilder.failedWithError().build();
}
} catch (Exception e) {
buildLog.error(ExceptionUtils.getRootCauseMessage(e), e);
return resultBuilder.failedWithError().build();
}

return resultBuilder.success().build();
}

/**
* Builds the JFrog CLI environment variables for a deployment-project context.
*
* <p>Unlike the build-plan variant, there is no {@link com.atlassian.bamboo.v2.build.BuildContext}
* available here. Build-specific variables ({@code JFROG_CLI_BUILD_NAME},
* {@code JFROG_CLI_BUILD_NUMBER}, {@code JFROG_CLI_BUILD_URL}) are therefore omitted; users who
* need them can pass {@code --build-name} / {@code --build-number} flags in their command.
*/
private Map<String, String> createDeploymentEnvironmentVariables(
DeploymentTaskContext taskContext, ServerConfig serverConfig) throws IOException {

Map<String, String> envs = new HashMap<>();
envs.put("JFROG_CLI_SERVER_ID", serverConfig.getServerId());

// Isolate the CLI home dir per deployment run using the unique deployment result ID
String deploymentKey = String.valueOf(taskContext.getDeploymentContext().getDeploymentResultId());
envs.put("JFROG_CLI_HOME_DIR", BambooUtils.getJfrogTmpSubdir(customVariableContext, deploymentKey));

// Shared cache for CLI binary and build-info extractors across all deployment runs on this agent
envs.put("JFROG_CLI_DEPENDENCIES_DIR", BambooUtils.getJfrogTmpSubdir(customVariableContext, "dependencies"));

if (StringUtils.isNotBlank(serverConfig.getCliRepository())) {
envs.put("JFROG_CLI_RELEASES_REPO", serverConfig.getServerId() + "/" + serverConfig.getCliRepository());
}

envs.put("JFROG_CLI_USER_AGENT", BambooUtils.getJFrogPluginIdentifier(pluginAccessor));
envs.put("JFROG_CLI_LOG_TIMESTAMP", "OFF");

buildLog.info("The following JFrog CLI environment variables will be used: " + envs);
return envs;
}

// Setters for Spring injection and unit-test overrides

@SuppressWarnings("unused")
public void setServerConfigManager(ServerConfigManager serverConfigManager) {
this.serverConfigManager = serverConfigManager;
}

@SuppressWarnings("unused")
public void setCustomVariableContext(CustomVariableContext customVariableContext) {
this.customVariableContext = customVariableContext;
}

@SuppressWarnings("unused")
public void setPluginAccessor(PluginAccessor pluginAccessor) {
this.pluginAccessor = pluginAccessor;
}
}
13 changes: 13 additions & 0 deletions src/main/resources/atlassian-plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@
<resource type="download" name="icon" location="/images/jfrogLogo.png"/>
</taskType>

<!-- Deployment-project variant of JfTask.
JfTask implements TaskType only and is therefore invisible in the Deployment Project task
picker. This entry implements DeploymentTaskType so the same JFrog CLI Task experience is
available when setting up a Deployment Project environment. The configuration UI and FTL
template are shared with JfTask via JfContext. -->
<taskType key="JfDeploymentTask" name="JFrog CLI Task" class="org.jfrog.bamboo.JfDeploymentTask">
<category name="deployment"/>
<configuration class="org.jfrog.bamboo.JfContext"/>
<resource type="freemarker" name="edit" location="/templates/editJfTask.ftl"/>
<description>Run JFrog CLI commands from a Bamboo Deployment Project task.</description>
<resource type="download" name="icon" location="/images/jfrogLogo.png"/>
</taskType>

<!-- buildProcessor Occurs on the Agent (both local and remote) after the builder has run. -->
<buildProcessor
key="AgentPostBuildAction"
Expand Down
Loading