package org.jfrog.bamboo.release.action; import com.atlassian.bamboo.build.Job; import com.atlassian.bamboo.build.ViewBuildResults; import com.atlassian.bamboo.builder.BuildState; import com.atlassian.bamboo.plan.Plan; import com.atlassian.bamboo.plan.PlanKey; import com.atlassian.bamboo.plan.PlanKeys; import com.atlassian.bamboo.plugin.RemoteAgentSupported; import com.atlassian.bamboo.repository.RepositoryException; import com.atlassian.bamboo.resultsummary.ResultsSummary; import com.atlassian.bamboo.security.acegi.acls.BambooPermission; import com.atlassian.bamboo.task.TaskDefinition; import com.atlassian.bamboo.v2.build.agent.capability.CapabilityContext; import com.atlassian.bamboo.v2.build.trigger.ManualBuildTriggerReason; import com.atlassian.bamboo.v2.build.trigger.TriggerReason; import com.atlassian.bamboo.variable.VariableDefinitionManager; import com.atlassian.user.User; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.opensymphony.xwork.ActionContext; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.jfrog.bamboo.admin.ServerConfig; import org.jfrog.bamboo.admin.ServerConfigManager; import org.jfrog.bamboo.context.AbstractBuildContext; import org.jfrog.bamboo.context.Maven3BuildContext; import org.jfrog.bamboo.promotion.PromotionContext; import org.jfrog.bamboo.promotion.PromotionThread; import org.jfrog.bamboo.release.provider.ReleaseProvider; import org.jfrog.bamboo.release.vcs.VcsTypes; import org.jfrog.bamboo.task.ArtifactoryGradleTask; import org.jfrog.bamboo.task.ArtifactoryMaven3Task; import org.jfrog.bamboo.util.ConstantValues; import org.jfrog.bamboo.util.TaskDefinitionHelper; import org.jfrog.bamboo.util.TaskUtils; import org.jfrog.bamboo.util.version.VersionHelper; import org.jfrog.build.api.release.Promotion; import org.jfrog.build.extractor.clientConfiguration.client.ArtifactoryBuildInfoClient; import java.io.IOException; import java.util.*; /** * An action to display when entering the "Artifactory Release & Promotion" tab from within a job. * Will display the versions for modules of a Maven build, or read the {@code * gradle.properties} file of a Gradle build in accordance to the property keys that were configured in the build. * * @author Tomer Cohen */ @RemoteAgentSupported public class ReleaseAndPromotionAction extends ViewBuildResults { public static final String PROMOTION_PUSH_TO_NEXUS_MODE = "pushToNexusMode"; public static final String NEXUS_PUSH_PLUGIN_NAME = "bintrayOsoPush"; public static final String NEXT_INTEG_KEY = "version.nextIntegValue"; public static final String RELEASE_VALUE_KEY = "version.releaseValue"; public static final String CURRENT_VALUE_KEY = "version.currentValue"; public static final String RELEASE_PROP_KEY = "version.releaseProp"; public static final String MODULE_KEY = "version.key"; private static final Logger log = Logger.getLogger(ReleaseAndPromotionAction.class); private static final String PROMOTION_NORMAL_MODE = "normalMode"; private static final Map<String, String> MODULE_VERSION_TYPES = ImmutableMap.of(ReleaseProvider.CFG_ONE_VERSION, "One version for all modules.", ReleaseProvider.CFG_VERSION_PER_MODULE, "Version per module", ReleaseProvider.CFG_USE_EXISTING_VERSION, "Use existing module versions"); public static PromotionContext promotionContext = new PromotionContext(); ServerConfigManager serverConfigManager; private String promotionMode = PROMOTION_NORMAL_MODE; private boolean promoting = true; private String promotionRepo = ""; private VariableDefinitionManager variableDefinitionManager; private String comment = ""; private String target = ""; private boolean useCopy; private boolean includeDependencies; private String artifactoryReleaseManagementUrl = ""; private String moduleVersionConfiguration = ReleaseProvider.CFG_ONE_VERSION; private boolean createVcsTag = true; private String tagUrl; private String tagComment; private String nextDevelopmentComment = "[artifactory-release] Next development version"; private String releasePublishingRepo; private String stagingComment = ""; private boolean useReleaseBranch = true; private CapabilityContext capabilityContext; private String releaseBranch; private List<ModuleVersionHolder> versions; public ReleaseAndPromotionAction() { this.serverConfigManager = ServerConfigManager.getInstance(); } @Override public String execute() throws Exception { String superResult = super.execute(); if (ERROR.equals(superResult)) { return ERROR; } ResultsSummary summary = getBuildResultsSummary(); if (summary == null) { log.error("This build has no results summary"); return ERROR; } return INPUT; } /** * This method is called by reflection via freemarker, it gets a task definition from the job, and gets the module * versions. * * @return A list of Module version holders, that hold the module name / property key, the original value that is * there right now, and the new value that it is to be replaced with. */ public List<ModuleVersionHolder> getVersions() throws RepositoryException, IOException { if (versions == null) { TaskDefinition definition = getReleaseTaskDefinition(); if (definition != null) { AbstractBuildContext context = AbstractBuildContext.createContextFromMap(definition.getConfiguration()); if (context != null) { VersionHelper versionHelper = VersionHelper.getHelperAccordingToType(context, getCapabilityContext()); if (versionHelper != null) { int latestBuildNumberWithBuildInfo = findLatestBuildNumberWithBuildInfo(); setVersions(versionHelper.filterPropertiesForRelease(getMutablePlan(), latestBuildNumberWithBuildInfo)); } } } } return versions; } public void setVersions(List<ModuleVersionHolder> versions) { this.versions = versions; } private int findLatestBuildNumberWithBuildInfo() { List<ResultsSummary> summaries = resultsSummaryManager.getResultSummariesForPlan(getMutablePlan(), 0, 100); Collections.sort(summaries, new Comparator<ResultsSummary>() { @Override public int compare(ResultsSummary o1, ResultsSummary o2) { if (o1.getBuildNumber() > o2.getBuildNumber()) { return -1; } else if (o2.getBuildNumber() < o2.getBuildNumber()) { return 1; } return 0; } }); for (ResultsSummary summary : summaries) { if (summary.getBuildState().equals(BuildState.SUCCESS)) { boolean biActive = Boolean.parseBoolean( summary.getCustomBuildData().get(ConstantValues.BUILD_RESULT_COLLECTION_ACTIVATED_PARAM)); if (biActive) { return summary.getBuildNumber(); } } } return -1; } /** * This method is called by reflection via freemarker, it gets a {@link TaskDefinition} for the job, and determines * if it is a Gradle build. * * @return True if this build is a Gradle build. */ public boolean isGradle() { TaskDefinition taskDefinition = getReleaseTaskDefinition(); if (taskDefinition == null) { return false; } return StringUtils.endsWith(taskDefinition.getPluginKey(), ArtifactoryGradleTask.TASK_NAME); } /** * This method is called by reflection via freemarker, it gets a {@link TaskDefinition} and determines if it is a * Maven build. * * @return True if this build is a Maven build. */ public boolean isMaven() { TaskDefinition taskDefinition = getReleaseTaskDefinition(); if (taskDefinition == null) { return false; } return StringUtils.endsWith(taskDefinition.getPluginKey(), ArtifactoryMaven3Task.TASK_NAME); } /** * This method is called by reflection via freemarker, it gets a {@link TaskDefinition} and determines if it uses a * Git repository. * * @return True if this build is using GIT as its SCM. */ public boolean isGit() { TaskDefinition taskDefinition = getReleaseTaskDefinition(); if (taskDefinition == null) { return false; } return VcsTypes.GIT.name().equals( taskDefinition.getConfiguration().get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.VCS_TYPE)); } public boolean isReleaseConfigured() { TaskDefinition taskDefinition = getReleaseTaskDefinition(); if (taskDefinition == null) { return false; } return StringUtils.isNotBlank( taskDefinition.getConfiguration().get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.VCS_TYPE)); } public Map<String, String> getModuleVersionTypes() { return MODULE_VERSION_TYPES; } /** * Perform a release build, sets the {@link AbstractBuildContext#ACTIVATE_RELEASE_MANAGEMENT} flag to {@code true} * and executes a manual build. * * @return {@code success} if the manual execution finished successfully. */ public String doReleaseBuild() throws RepositoryException, IOException { List<TaskDefinition> taskDefinitions = getMutablePlan().getBuildDefinition().getTaskDefinitions(); if (taskDefinitions.isEmpty()) { log.warn("No task definitions defined, cannot execute release build"); return ERROR; } User user = getUser(); PlanKey planKey = getMutablePlan().getPlanKey(); if (user == null || planKey == null) { return ERROR; } setBuildKey(planKey.getKey()); Map<String, String> configuration = Maps.newHashMap(); Map parameters = ActionContext.getContext().getParameters(); configuration.put(AbstractBuildContext.ACTIVATE_RELEASE_MANAGEMENT, String.valueOf(true)); configuration.put(AbstractBuildContext.ReleaseManagementContext.TAG_URL, getTagUrl()); configuration.put(AbstractBuildContext.ReleaseManagementContext.NEXT_DEVELOPMENT_COMMENT, getNextDevelopmentComment()); configuration.put(AbstractBuildContext.ReleaseManagementContext.STAGING_COMMENT, getStagingComment()); configuration.put(AbstractBuildContext.ReleaseManagementContext.REPO_KEY, getReleasePublishingRepo()); configuration.put(AbstractBuildContext.ReleaseManagementContext.TAG_COMMENT, getTagComment()); configuration.put(AbstractBuildContext.ReleaseManagementContext.RELEASE_BRANCH, getReleaseBranch()); String[] useReleaseBranchParam = (String[]) parameters.get("useReleaseBranch"); String useReleaseBranch = useReleaseBranchParam != null ? useReleaseBranchParam[0] : "false"; configuration.put(AbstractBuildContext.ReleaseManagementContext.USE_RELEASE_BRANCH, useReleaseBranch); String[] createVcsTagParam = (String[]) parameters.get("createVcsTag"); String createVcsTag = createVcsTagParam != null ? createVcsTagParam[0] : "false"; configuration.put(AbstractBuildContext.ReleaseManagementContext.CREATE_VCS_TAG, createVcsTag); configuration.put(ReleaseProvider.MODULE_VERSION_CONFIGURATION, getModuleVersionConfiguration()); TaskDefinition definition = TaskDefinitionHelper.findMavenOrGradleDefinition(taskDefinitions); if (definition == null) { log.error("No Maven or Gradle task found in job"); return ERROR; } AbstractBuildContext context = AbstractBuildContext.createContextFromMap(definition.getConfiguration()); VersionHelper helper = VersionHelper.getHelperAccordingToType(context, getCapabilityContext()); helper.addVersionFieldsToConfiguration(parameters, configuration, getModuleVersionConfiguration(), definition.getConfiguration()); planExecutionManager .startManualExecution(getPlanJob().getParent(), user, configuration, Maps.<String, String>newHashMap()); return SUCCESS; } public String getSelectedServerId() { TaskDefinition definition = getReleaseTaskDefinition(); if (definition == null) { return ""; } Map<String, String> configuration = definition.getConfiguration(); Map<String, String> filtered = Maps.filterKeys(configuration, new Predicate<String>() { @Override public boolean apply(String input) { return StringUtils.endsWith(input, AbstractBuildContext.SERVER_ID_PARAM); } }); return filtered.values().iterator().next(); } /** * @return Gets the current job. */ private Job getPlanJob() { return (Job) getMutablePlan(); } public String getModuleVersionConfiguration() { return moduleVersionConfiguration; } public void setModuleVersionConfiguration(String moduleVersionConfiguration) { this.moduleVersionConfiguration = moduleVersionConfiguration; } public boolean isCreateVcsTag() { return createVcsTag; } public void setCreateVcsTag(boolean createVcsTag) { this.createVcsTag = createVcsTag; } public String getTagUrl() throws RepositoryException, IOException { if (tagUrl != null) { return tagUrl; } String url = getDefaultTagUrl(); List<ModuleVersionHolder> moduleVersionHolders = getVersions(); if (moduleVersionHolders.isEmpty()) { return url; } url += moduleVersionHolders.get(0).getReleaseValue(); return url; } public void setTagUrl(String tagUrl) { this.tagUrl = tagUrl; } public String getTagComment() throws RepositoryException, IOException { if (tagComment == null) { List<ModuleVersionHolder> versions1 = getVersions(); String releaseValue; if (!versions1.isEmpty()) { releaseValue = versions1.get(0).getReleaseValue(); } else { releaseValue = "1.0.0"; } return "[artifactory-release] Release version " + releaseValue; } return tagComment; } public void setTagComment(String tagComment) { this.tagComment = tagComment; } public String getNextDevelopmentComment() { return nextDevelopmentComment != null ? nextDevelopmentComment : ""; } public void setNextDevelopmentComment(String nextDevelopmentComment) { this.nextDevelopmentComment = nextDevelopmentComment; } public List<String> getPublishingRepos() { String serverId = getSelectedServerId(); if (StringUtils.isBlank(serverId)) { return Lists.newArrayList(); } ServerConfigManager component = ServerConfigManager.getInstance(); return component.getDeployableRepos(Long.parseLong(serverId)); } public String getReleasePublishingRepo() { if (StringUtils.isBlank(releasePublishingRepo)) { TaskDefinition definition = getReleaseTaskDefinition(); if (definition == null) { return ""; } Map<String, String> configuration = definition.getConfiguration(); Map<String, String> filtered = Maps.filterKeys(configuration, new Predicate<String>() { @Override public boolean apply(String input) { return StringUtils.endsWith(input, AbstractBuildContext.PUBLISHING_REPO_PARAM) || StringUtils.endsWith(input, Maven3BuildContext.DEPLOYABLE_REPO_KEY); } }); return filtered.values().iterator().next(); } return releasePublishingRepo; } public void setReleasePublishingRepo(String releasePublishingRepo) { this.releasePublishingRepo = releasePublishingRepo; } private String getDefaultTagUrl() { TaskDefinition definition = getReleaseTaskDefinition(); if (definition == null) { return ""; } AbstractBuildContext context = AbstractBuildContext.createContextFromMap(definition.getConfiguration()); if (context == null) { return ""; } return StringUtils.trimToEmpty(context.releaseManagementContext.getVcsTagBase()); } public boolean isUseReleaseBranch() { return useReleaseBranch; } public void setUseReleaseBranch(boolean useReleaseBranch) { this.useReleaseBranch = useReleaseBranch; } public String getReleaseBranch() throws RepositoryException, IOException { if (releaseBranch == null) { TaskDefinition definition = getReleaseTaskDefinition(); if (definition == null) { return ""; } Map<String, String> configuration = definition.getConfiguration(); AbstractBuildContext context = AbstractBuildContext.createContextFromMap(configuration); String url = context.releaseManagementContext.getGitReleaseBranch(); List<ModuleVersionHolder> moduleVersionHolders = getVersions(); if (moduleVersionHolders.isEmpty()) { return url; } return url + moduleVersionHolders.get(0).getReleaseValue(); } return releaseBranch; } public void setReleaseBranch(String releaseBranch) { this.releaseBranch = releaseBranch; } public String getStagingComment() { return stagingComment; } public void setStagingComment(String stagingComment) { this.stagingComment = stagingComment; } public CapabilityContext getCapabilityContext() { return capabilityContext; } public void setCapabilityContext(CapabilityContext capabilityContext) { this.capabilityContext = capabilityContext; } public String getReleaseValue() throws RepositoryException, IOException { List<ModuleVersionHolder> versions = getVersions(); if (versions == null || versions.isEmpty()) { return ""; } return versions.get(0).getReleaseValue(); } public String getNextIntegValue() throws RepositoryException, IOException { List<ModuleVersionHolder> versions = getVersions(); if (versions == null || versions.isEmpty()) { return ""; } return versions.get(0).getNextIntegValue(); } /** * ****************************************************************************** */ public boolean isReleaseBuild() { Plan plan = getMutablePlan(); TaskDefinition mavenOrGradleDefinition = TaskDefinitionHelper.findMavenOrGradleDefinition(plan.getBuildDefinition().getTaskDefinitions()); if (mavenOrGradleDefinition == null) { return false; } ResultsSummary summary = getResultsSummary(); return summary != null && shouldShow(summary.getCustomBuildData()); } private boolean shouldShow(Map<String, String> customData) { return customData.containsKey(ConstantValues.BUILD_RESULT_COLLECTION_ACTIVATED_PARAM) && Boolean.valueOf(customData.get(ConstantValues.BUILD_RESULT_COLLECTION_ACTIVATED_PARAM)) && customData.containsKey(ConstantValues.BUILD_RESULT_RELEASE_ACTIVATED_PARAM) && Boolean.valueOf(customData.get(ConstantValues.BUILD_RESULT_RELEASE_ACTIVATED_PARAM)); } public boolean isPermittedToPromote() { return bambooPermissionManager.hasPlanPermission(BambooPermission.BUILD, PlanKeys.getPlanKey(getPlanKey())); } private TaskDefinition getReleaseTaskDefinition() { Job job = getPlanJob(); if (job == null) { return null; } List<TaskDefinition> taskDefinitions = job.getBuildDefinition().getTaskDefinitions(); return TaskDefinitionHelper.findReleaseTaskDefinition(taskDefinitions); } public String doPromote() throws IOException { String key = promotionContext.getBuildKey(); if (StringUtils.isNotBlank(key) && StringUtils.isBlank(getBuildKey())) { setBuildKey(key); } Integer number = promotionContext.getBuildNumber(); if (number != null && getBuildNumber() == null) { setBuildNumber(number); } if (getMutablePlan() == null) { return INPUT; } if (!isPermittedToPromote()) { log.error("You are not permitted to execute build promotion."); return ERROR; } ServerConfigManager component = ServerConfigManager.getInstance(); TaskDefinition definition = TaskUtils.getMavenOrGradleTaskDefinition(getMutablePlan()); if (definition == null) { return ERROR; } String serverId = getSelectedServerId(definition); if (StringUtils.isBlank(serverId)) { log.error("No selected Artifactory server Id"); return ERROR; } ServerConfig serverConfig = component.getServerConfigById(Long.parseLong(serverId)); if (serverConfig == null) { log.error("Error while retrieving target repository list: Could not find Artifactory server " + "configuration by the ID " + serverId); return ERROR; } Map<String, String> taskConfiguration = definition.getConfiguration(); AbstractBuildContext context = AbstractBuildContext.createContextFromMap(taskConfiguration); ArtifactoryBuildInfoClient client = TaskUtils.createClient(serverConfigManager, serverConfig, context, log); ResultsSummary summary = getResultsSummary(); TriggerReason reason = summary.getTriggerReason(); String username = ""; if (reason instanceof ManualBuildTriggerReason) { username = ((ManualBuildTriggerReason) reason).getUserName(); } new PromotionThread(this, client, username).start(); promoting = false; return SUCCESS; } public List<String> getResult() { return promotionContext.getLog(); } public boolean isDone() { return promotionContext.isDone(); } public String getPromotionMode() { return promotionMode; } public void setPromotionMode(String promotionMode) { this.promotionMode = promotionMode; } public Map<String, String> getSupportedPromotionModes() { Map<String, String> promotionModes = Maps.newHashMap(); promotionModes.put(PROMOTION_NORMAL_MODE, "Normal"); TaskDefinition definition = TaskUtils.getMavenOrGradleTaskDefinition(getMutablePlan()); if (MavenSyncUtils.isPushToNexusEnabled(serverConfigManager, definition, getSelectedServerId(definition))) { promotionModes.put(PROMOTION_PUSH_TO_NEXUS_MODE, "Promote to Bintray and Central"); } return promotionModes; } public List<String> getPromotionTargets() { return Lists.newArrayList(Promotion.RELEASED, Promotion.ROLLED_BACK); } public List<String> getPromotionRepos() { TaskDefinition definition = TaskUtils.getMavenOrGradleTaskDefinition(getMutablePlan()); if (definition == null) { return Lists.newArrayList(); } String selectedServerId = getSelectedServerId(definition); if (StringUtils.isBlank(selectedServerId)) { log.warn("No Artifactory server Id found"); return Lists.newArrayList(); } ServerConfigManager component = ServerConfigManager.getInstance(); return component.getDeployableRepos(Long.parseLong(selectedServerId)); } public String getSelectedServerId(TaskDefinition definition) { if (definition == null) { return ""; } Map<String, String> configuration = definition.getConfiguration(); Map<String, String> filtered = Maps.filterKeys(configuration, new Predicate<String>() { public boolean apply(String input) { return StringUtils.endsWith(input, AbstractBuildContext.SERVER_ID_PARAM); } }); return filtered.values().iterator().next(); } public String getPromotionRepo() { return promotionRepo; } public void setPromotionRepo(String promotionRepo) { this.promotionRepo = promotionRepo; } public boolean isPromoting() { return promoting; } public void setPromoting(boolean promoting) { this.promoting = promoting; } public VariableDefinitionManager getVariableDefinitionManager() { return variableDefinitionManager; } public void setVariableDefinitionManager(VariableDefinitionManager variableDefinitionManager) { this.variableDefinitionManager = variableDefinitionManager; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public boolean isUseCopy() { return useCopy; } public void setUseCopy(boolean useCopy) { this.useCopy = useCopy; } public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } public boolean isIncludeDependencies() { return includeDependencies; } public void setIncludeDependencies(boolean includeDependencies) { this.includeDependencies = includeDependencies; } public String getArtifactoryReleaseManagementUrl() { return artifactoryReleaseManagementUrl; } public void setArtifactoryReleaseManagementUrl(String artifactoryReleaseManagementUrl) { this.artifactoryReleaseManagementUrl = artifactoryReleaseManagementUrl; } public String doGetLog() { return SUCCESS; } }