org.jfrog.hudson.release.ReleaseAction.java Source code

Java tutorial

Introduction

Here is the source code for org.jfrog.hudson.release.ReleaseAction.java

Source

/*
 * Copyright (C) 2011 JFrog Ltd.
 *
 * 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 org.jfrog.hudson.release;

import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.collect.Maps;
import hudson.Util;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
import hudson.model.*;
import hudson.tasks.BuildWrapper;
import org.apache.commons.lang.StringUtils;
import org.jfrog.hudson.ArtifactoryPlugin;
import org.jfrog.hudson.ArtifactoryServer;
import org.jfrog.hudson.PluginSettings;
import org.jfrog.hudson.UserPluginInfo;
import org.jfrog.hudson.action.ActionableHelper;
import org.jfrog.hudson.release.scm.AbstractScmCoordinator;
import org.jfrog.hudson.release.scm.svn.SubversionManager;
import org.jfrog.hudson.util.ErrorResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This action leads to execution of the release wrapper. It will collect information from the user about the release
 * and will trigger the release build. This action is not saved in the job xml file.
 *
 * @author Yossi Shaul
 */
public abstract class ReleaseAction<P extends AbstractProject & BuildableItem, W extends BuildWrapper>
        implements Action {

    private static final Logger log = Logger.getLogger(ReleaseAction.class.getName());
    public static final String RT_RELEASE_STAGING = "RT_RELEASE_STAGING_";

    protected final transient P project;
    protected VERSIONING versioning;
    /**
     * The next release version to change the model to if using one global version.
     */
    protected String releaseVersion;
    /**
     * Next (development) version to change the model to if using one global version.
     */
    protected String nextVersion;
    protected transient boolean strategyRequestFailed = false;
    protected transient String strategyRequestErrorMessage = null;
    protected transient boolean strategyPluginExists;
    protected transient Map stagingStrategy;
    protected transient String defaultVersioning;
    protected transient VersionedModule defaultGlobalModule;
    protected transient Map<String, VersionedModule> defaultModules;
    protected transient VcsConfig defaultVcsConfig;
    protected transient PromotionConfig defaultPromotionConfig;
    Boolean pro;
    boolean createVcsTag;
    String tagUrl;
    String tagComment;
    String nextDevelCommitComment;
    String stagingRepositoryKey;
    String stagingComment;
    boolean createReleaseBranch;
    String releaseBranch;
    private Class<W> wrapperClass;
    boolean overrideCredentials;
    String username;
    String password;

    public ReleaseAction(P project, Class<W> wrapperClass) {
        this.project = project;
        this.wrapperClass = wrapperClass;
    }

    /**
     * invoked from the jelly file
     *
     * @throws Exception Any exception
     */
    @SuppressWarnings("UnusedDeclaration")
    public void init() throws Exception {
        initBuilderSpecific();
        resetFields();
        if (!UserPluginInfo.NO_PLUGIN_KEY.equals(getSelectedStagingPluginName())) {
            PluginSettings selectedStagingPluginSettings = getSelectedStagingPlugin();
            try {
                stagingStrategy = getArtifactoryServer().getStagingStrategy(selectedStagingPluginSettings,
                        Util.rawEncode(project.getName()), project);
            } catch (Exception e) {
                log.log(Level.WARNING, "Failed to obtain staging strategy: " + e.getMessage(), e);
                strategyRequestFailed = true;
                strategyRequestErrorMessage = "Failed to obtain staging strategy '"
                        + selectedStagingPluginSettings.getPluginName() + "': " + e.getMessage()
                        + ".\nPlease review the log for further information.";
                stagingStrategy = null;
            }
            strategyPluginExists = (stagingStrategy != null) && !stagingStrategy.isEmpty();
        }

        prepareDefaultVersioning();
        prepareDefaultGlobalModule();
        prepareDefaultModules();
        prepareDefaultVcsSettings();
        prepareDefaultPromotionConfig();
    }

    private void resetFields() {
        strategyRequestFailed = false;
        strategyRequestErrorMessage = null;
        strategyPluginExists = false;
        stagingStrategy = null;
        defaultVersioning = null;
        defaultGlobalModule = null;
        defaultModules = null;
        defaultVcsConfig = null;
        defaultPromotionConfig = null;
    }

    public boolean isStrategyRequestFailed() {
        return strategyRequestFailed;
    }

    public String getStrategyRequestErrorMessage() {
        return strategyRequestErrorMessage;
    }

    public abstract String getTargetRemoteName();

    public String getTitle() {
        StringBuilder titleBuilder = new StringBuilder("Artifactory Pro Release Staging");
        String pluginName = getSelectedStagingPluginName();
        if (strategyPluginExists && StringUtils.isNotBlank(pluginName)) {
            titleBuilder.append(" - Using the '").append(pluginName).append("' staging plugin.");
        }
        return titleBuilder.toString();
    }

    /**
     * @return The message to display on the left panel for the perform release action.
     */
    public String getDisplayName() {
        return "Artifactory Release Staging";
    }

    public String getUrlName() {
        return "release";
    }

    public VERSIONING getVersioning() {
        return versioning;
    }

    public String getReleaseVersion() {
        return releaseVersion;
    }

    public String getNextVersion() {
        return nextVersion;
    }

    public boolean isCreateVcsTag() {
        return createVcsTag;
    }

    public String getTagUrl() {
        return tagUrl;
    }

    public String getTagComment() {
        return StringUtils.isNotBlank(tagComment) ? tagComment : getDefaultVcsConfig().getTagComment();
    }

    public boolean isOverrideCredentials() {
        return overrideCredentials;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getNextDevelCommitComment() {
        return nextDevelCommitComment;
    }

    public boolean isCreateReleaseBranch() {
        return createReleaseBranch;
    }

    public String getReleaseBranch() {
        return releaseBranch;
    }

    public String getStagingComment() {
        return stagingComment;
    }

    public String getStagingRepositoryKey() {
        return stagingRepositoryKey;
    }

    public String latestVersioningSelection() {
        return VERSIONING.GLOBAL.name();
    }

    public String getDefaultGlobalReleaseVersion() {
        return (defaultGlobalModule != null) ? defaultGlobalModule.getReleaseVersion() : null;
    }

    public String getDefaultGlobalNextDevelopmentVersion() {
        return (defaultGlobalModule != null) ? defaultGlobalModule.getNextDevelopmentVersion() : null;
    }

    public Collection<VersionedModule> getDefaultModules() {
        return defaultModules.values();
    }

    public boolean isGit() {
        return AbstractScmCoordinator.isGitScm(project);
    }

    public VcsConfig getDefaultVcsConfig() {
        return defaultVcsConfig;
    }

    public PromotionConfig getDefaultPromotionConfig() {
        return defaultPromotionConfig;
    }

    /**
     * @return List of target repositories for deployment (release repositories first). Called from the UI.
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    public abstract List<String> getRepositoryKeys();

    @SuppressWarnings("UnusedDeclaration")
    public abstract boolean isArtifactoryPro();

    public abstract ArtifactoryServer getArtifactoryServer();

    /**
     * This method is used to initiate a release staging process using the Artifactory Release Staging API.
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    protected void doApi(StaplerRequest req, StaplerResponse resp) throws IOException, ServletException {
        try {
            log.log(Level.INFO, "Initiating Artifactory Release Staging using API");
            // Enforce release permissions
            project.checkPermission(ArtifactoryPlugin.RELEASE);
            // In case a staging user plugin is configured, the init() method invoke it:
            init();
            // Read the values provided by the staging user plugin and assign them to data members in this class.
            // Those values can be overriden by URL arguments sent with the API:
            readStagingPluginValues();
            // Read values from the request and override the staging plugin values:
            overrideStagingPluginParams(req);
            // Schedule the release build:
            if (!project.scheduleBuild(0, new Cause.UserIdCause(), this)) {
                log.log(Level.SEVERE, "Failed to schedule a release build following a Release API invocation");
                resp.setStatus(StaplerResponse.SC_INTERNAL_SERVER_ERROR);
            }
        } catch (Exception e) {
            log.log(Level.SEVERE, "Artifactory Release Staging API invocation failed: " + e.getMessage(), e);
            resp.setStatus(StaplerResponse.SC_INTERNAL_SERVER_ERROR);
            ErrorResponse errorResponse = new ErrorResponse(StaplerResponse.SC_INTERNAL_SERVER_ERROR,
                    e.getMessage());
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            resp.getWriter().write(mapper.writeValueAsString(errorResponse));
        }
    }

    /**
     * Form submission is calling this method
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    public void doSubmit(StaplerRequest req, StaplerResponse resp) throws IOException, ServletException {
        // Enforce release permissions
        project.checkPermission(ArtifactoryPlugin.RELEASE);
        readRequestParams(req, false);
        // Schedule release build
        if (project.scheduleBuild(0, new Cause.UserIdCause(), this)) {
            // Redirect to the project page
            resp.sendRedirect(project.getAbsoluteUrl());
        }
    }

    private void overrideStagingPluginParams(StaplerRequest req) throws Exception {
        req.bindParameters(this);
        String versioningStr = req.getParameter("versioning");
        if (versioningStr != null) {
            versioning = VERSIONING.valueOf(versioningStr);
            switch (versioning) {
            case GLOBAL:
                doGlobalVersioning(req);
                break;
            case PER_MODULE:
                doPerModuleVersioning(req);
            }
        }

        if (req.getParameter("createVcsTag") != null) {
            createVcsTag = Boolean.valueOf(req.getParameter("createVcsTag"));
        }
        if (req.getParameter("tagUrl") != null) {
            tagUrl = req.getParameter("tagUrl");
        }
        if (req.getParameter("tagComment") != null) {
            tagComment = req.getParameter("tagComment");
        }
        if (req.getParameter("createReleaseBranch") != null) {
            createReleaseBranch = Boolean.valueOf(req.getParameter("createReleaseBranch"));
        }
        if (req.getParameter("releaseBranch") != null) {
            releaseBranch = req.getParameter("releaseBranch");
        }
        if (req.getParameter("nextDevelCommitComment") != null) {
            nextDevelCommitComment = req.getParameter("nextDevelCommitComment");
        }
        if (req.getParameter("repositoryKey") != null) {
            stagingRepositoryKey = req.getParameter("repositoryKey");
        }
        if (req.getParameter("stagingComment") != null) {
            stagingComment = req.getParameter("stagingComment");
        }
        if (req.getParameter("overrideCredentials") != null) {
            overrideCredentials = Boolean.valueOf(req.getParameter("overrideCredentials"));
        }
        if (req.getParameter("username") != null) {
            username = req.getParameter("username");
        }
        if (req.getParameter("password") != null) {
            password = req.getParameter("password");
        }

    }

    private void readRequestParams(StaplerRequest req, boolean api) {
        req.bindParameters(this);

        String versioningStr = req.getParameter("versioning");
        versioning = VERSIONING.valueOf(versioningStr);
        switch (versioning) {
        case GLOBAL:
            doGlobalVersioning(req);
            break;
        case PER_MODULE:
            doPerModuleVersioning(req);
        }

        createVcsTag = req.getParameter("createVcsTag") != null;
        if (createVcsTag) {
            tagUrl = req.getParameter("tagUrl");
            tagComment = req.getParameter("tagComment");
        }

        overrideCredentials = req.getParameter("overrideCredentials") != null;
        if (overrideCredentials) {
            username = req.getParameter("username");
            password = req.getParameter("password");
        }

        nextDevelCommitComment = req.getParameter("nextDevelCommitComment");
        createReleaseBranch = req.getParameter("createReleaseBranch") != null;
        if (createReleaseBranch) {
            releaseBranch = req.getParameter("releaseBranch");
        }

        stagingRepositoryKey = req.getParameter("repositoryKey");
        stagingComment = req.getParameter("stagingComment");
    }

    public abstract String getReleaseVersionFor(Object moduleName);

    public abstract String getNextVersionFor(Object moduleName);

    protected void initBuilderSpecific() throws Exception {
    }

    protected String getDefaultNextDevelCommitMessage() {
        return SubversionManager.COMMENT_PREFIX + "Next development version";
    }

    protected String getBaseTagUrlAccordingToScm(String baseTagUrl) {
        if (AbstractScmCoordinator.isSvn(project) && !baseTagUrl.endsWith("/")) {
            return baseTagUrl + "/";
        }
        return baseTagUrl;
    }

    /**
     * Execute the {@link VERSIONING#GLOBAL} strategy of the versioning mechanism, which assigns all modules the same
     * version for the release and for the next development version.
     *
     * @param req The request that is coming from the form when staging.
     */
    protected void doGlobalVersioning(StaplerRequest req) {
        if (req.getParameter("releaseVersion") != null) {
            releaseVersion = req.getParameter("releaseVersion");
        }
        if (req.getParameter("nextVersion") != null) {
            nextVersion = req.getParameter("nextVersion");
        }
    }

    protected void doGlobalVersioning() {
        releaseVersion = defaultGlobalModule.getReleaseVersion();
        nextVersion = defaultGlobalModule.getNextDevelopmentVersion();
    }

    protected W getWrapper() {
        return ActionableHelper.getBuildWrapper(project, wrapperClass);
    }

    protected String calculateReleaseVersion(String fromVersion) {
        return fromVersion.replace("-SNAPSHOT", "");
    }

    /**
     * Calculates the next snapshot version based on the current release version
     *
     * @param fromVersion The version to bump to next development version
     * @return The next calculated development (snapshot) version
     */
    protected String calculateNextVersion(String fromVersion) {
        // first turn it to release version
        fromVersion = calculateReleaseVersion(fromVersion);
        String nextVersion;
        int lastDotIndex = fromVersion.lastIndexOf('.');
        try {
            if (lastDotIndex != -1) {
                // probably a major minor version e.g., 2.1.1
                String minorVersionToken = fromVersion.substring(lastDotIndex + 1);
                String nextMinorVersion;
                int lastDashIndex = minorVersionToken.lastIndexOf('-');
                if (lastDashIndex != -1) {
                    // probably a minor-buildNum e.g., 2.1.1-4 (should change to 2.1.1-5)
                    String buildNumber = minorVersionToken.substring(lastDashIndex + 1);
                    int nextBuildNumber = Integer.parseInt(buildNumber) + 1;
                    nextMinorVersion = minorVersionToken.substring(0, lastDashIndex + 1) + nextBuildNumber;
                } else {
                    nextMinorVersion = Integer.parseInt(minorVersionToken) + 1 + "";
                }
                nextVersion = fromVersion.substring(0, lastDotIndex + 1) + nextMinorVersion;
            } else {
                // maybe it's just a major version; try to parse as an int
                int nextMajorVersion = Integer.parseInt(fromVersion) + 1;
                nextVersion = nextMajorVersion + "";
            }
        } catch (NumberFormatException e) {
            return fromVersion;
        }
        return nextVersion + "-SNAPSHOT";
    }

    /**
     * Execute the {@link VERSIONING#PER_MODULE} strategy of the versioning mechanism, which assigns each module its own
     * version for release and for the next development version
     *
     * @param req The request that is coming from the form when staging.
     */
    protected abstract void doPerModuleVersioning(StaplerRequest req);

    protected abstract void doPerModuleVersioning(Map<String, VersionedModule> defaultModules);

    protected abstract PluginSettings getSelectedStagingPlugin() throws Exception;

    protected abstract String getSelectedStagingPluginName();

    protected abstract void prepareBuilderSpecificDefaultGlobalModule();

    protected abstract void prepareBuilderSpecificDefaultModules();

    protected abstract void prepareBuilderSpecificDefaultVcsConfig();

    protected abstract void prepareBuilderSpecificDefaultPromotionConfig();

    protected void prepareBuilderSpecificDefaultVersioning() {
    }

    /**
     * Read the values provided by the staging user plugin and assign them to data members in this class.
     * Those values can be overriden by URL arguments sent with the API:
     */
    private void readStagingPluginValues() {
        versioning = VERSIONING.valueOf(defaultVersioning);

        createVcsTag = defaultVcsConfig.isCreateTag();
        overrideCredentials = defaultVcsConfig.isOverrideCredentials();
        username = defaultVcsConfig.getUsername();
        password = defaultVcsConfig.getPassword();
        tagUrl = defaultVcsConfig.getTagUrlOrName();
        tagComment = defaultVcsConfig.getTagComment();
        nextDevelCommitComment = defaultVcsConfig.getNextDevelopmentVersionComment();
        createReleaseBranch = defaultVcsConfig.isUseReleaseBranch();
        releaseBranch = defaultVcsConfig.getReleaseBranchName();
        stagingRepositoryKey = defaultPromotionConfig.getTargetRepository();
        stagingComment = defaultPromotionConfig.getComment();

        switch (versioning) {
        case GLOBAL:
            doGlobalVersioning();
            break;
        case PER_MODULE:
            doPerModuleVersioning(defaultModules);
        }
    }

    private void prepareDefaultVersioning() {
        if (strategyPluginExists) {
            if (stagingStrategy.containsKey("defaultModuleVersion")) {
                defaultVersioning = VERSIONING.GLOBAL.name();
            } else if (stagingStrategy.containsKey("moduleVersionsMap")) {
                defaultVersioning = VERSIONING.PER_MODULE.name();
            }
        }

        if (StringUtils.isBlank(defaultVersioning)) {
            prepareBuilderSpecificDefaultVersioning();
        }
    }

    private void prepareDefaultGlobalModule() {
        if (strategyPluginExists) {
            if (stagingStrategy.containsKey("defaultModuleVersion")) {
                Map<String, String> defaultModuleVersion = (Map<String, String>) stagingStrategy
                        .get("defaultModuleVersion");
                defaultGlobalModule = new VersionedModule(defaultModuleVersion.get("moduleId"),
                        defaultModuleVersion.get("nextRelease"), defaultModuleVersion.get("nextDevelopment"));
            }
        }

        if (defaultGlobalModule == null) {
            prepareBuilderSpecificDefaultGlobalModule();
        }
    }

    private void prepareDefaultModules() {
        if (strategyPluginExists) {
            if (stagingStrategy.containsKey("moduleVersionsMap")) {
                Map<String, ? extends Map<String, String>> moduleVersionsMap = (Map<String, ? extends Map<String, String>>) stagingStrategy
                        .get("moduleVersionsMap");
                defaultModules = Maps.newHashMap();
                if (!moduleVersionsMap.isEmpty()) {
                    for (Map<String, String> moduleVersion : moduleVersionsMap.values()) {
                        String moduleId = moduleVersion.get("moduleId");
                        defaultModules.put(moduleId, new VersionedModule(moduleId, moduleVersion.get("nextRelease"),
                                moduleVersion.get("nextDevelopment")));
                    }
                }
            }
        }

        if (defaultModules == null) {
            prepareBuilderSpecificDefaultModules();
        }
    }

    private void prepareDefaultVcsSettings() {
        if (strategyPluginExists) {
            if (stagingStrategy.containsKey("vcsConfig")) {
                Map<String, Object> vcsConfig = (Map<String, Object>) stagingStrategy.get("vcsConfig");
                defaultVcsConfig = new VcsConfig(((Boolean) vcsConfig.get("useReleaseBranch")),
                        getStagingConfigAsString(vcsConfig, "releaseBranchName"),
                        ((Boolean) vcsConfig.get("createTag")), getStagingConfigAsString(vcsConfig, "tagUrlOrName"),
                        getStagingConfigAsString(vcsConfig, "tagComment"),
                        getStagingConfigAsString(vcsConfig, "nextDevelopmentVersionComment"));
            }
        }

        if (defaultVcsConfig == null) {
            prepareBuilderSpecificDefaultVcsConfig();
        }
    }

    private void prepareDefaultPromotionConfig() {
        if (strategyPluginExists) {
            if (stagingStrategy.containsKey("promotionConfig")) {
                Map<String, String> promotionConfig = (Map<String, String>) stagingStrategy.get("promotionConfig");
                defaultPromotionConfig = new PromotionConfig(promotionConfig.get("targetRepository"),
                        promotionConfig.get("comment"));
            }
        }

        if (defaultPromotionConfig == null) {
            prepareBuilderSpecificDefaultPromotionConfig();
        }
    }

    private String getStagingConfigAsString(Map<String, Object> configMap, String key) {
        if (configMap.containsKey(key)) {
            return configMap.get(key).toString();
        }
        return null;
    }

    protected List<hudson.tasks.Builder> getBuilders() {
        if (project instanceof MatrixProject)
            return ((MatrixProject) project).getBuilders();
        if (project instanceof MatrixConfiguration)
            return ((MatrixConfiguration) project).getBuilders();

        return ((FreeStyleProject) project).getBuilders();
    }

    public StandardCredentials getGitCredentials() {
        if (overrideCredentials) {
            return new UsernamePasswordCredentialsImpl(null, null, "release staging Git credentials", username,
                    password);
        }
        return null;
    }

    public enum VERSIONING {
        GLOBAL("One version for all modules"), PER_MODULE("Version per module"), NONE(
                "Use existing module versions");
        // The description to display in the UI
        private final String displayMessage;

        VERSIONING(String displayMessage) {
            this.displayMessage = displayMessage;
        }

        public String getDisplayMessage() {
            return displayMessage;
        }
    }

    /**
     * Add some of the release build properties to a map.
     */
    public void addVars(Map<String, String> env) {
        if (tagUrl != null) {
            env.put("RELEASE_SCM_TAG", tagUrl);
            env.put(RT_RELEASE_STAGING + "SCM_TAG", tagUrl);
        }
        if (releaseBranch != null) {
            env.put("RELEASE_SCM_BRANCH", releaseBranch);
            env.put(RT_RELEASE_STAGING + "SCM_BRANCH", releaseBranch);
        }
        if (releaseVersion != null) {
            env.put(RT_RELEASE_STAGING + "VERSION", releaseVersion);
        }
        if (nextVersion != null) {
            env.put(RT_RELEASE_STAGING + "NEXT_VERSION", nextVersion);
        }
    }
}