com.uber.jenkins.phabricator.PhabricatorNotifier.java Source code

Java tutorial

Introduction

Here is the source code for com.uber.jenkins.phabricator.PhabricatorNotifier.java

Source

// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package com.uber.jenkins.phabricator;

import com.uber.jenkins.phabricator.conduit.ConduitAPIClient;
import com.uber.jenkins.phabricator.conduit.ConduitAPIException;
import com.uber.jenkins.phabricator.conduit.Differential;
import com.uber.jenkins.phabricator.conduit.DifferentialClient;
import com.uber.jenkins.phabricator.coverage.CodeCoverageMetrics;
import com.uber.jenkins.phabricator.coverage.CoverageProvider;
import com.uber.jenkins.phabricator.credentials.ConduitCredentials;
import com.uber.jenkins.phabricator.provider.InstanceProvider;
import com.uber.jenkins.phabricator.tasks.NonDifferentialBuildTask;
import com.uber.jenkins.phabricator.tasks.NonDifferentialHarbormasterTask;
import com.uber.jenkins.phabricator.tasks.Task;
import com.uber.jenkins.phabricator.uberalls.UberallsClient;
import com.uber.jenkins.phabricator.unit.UnitTestProvider;
import com.uber.jenkins.phabricator.utils.CommonUtils;
import com.uber.jenkins.phabricator.utils.Logger;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Job;
import hudson.model.Result;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import jenkins.model.CauseOfInterruption;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import org.apache.commons.io.FilenameUtils;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PhabricatorNotifier extends Notifier {
    public static final String COBERTURA_CLASS_NAME = "com.uber.jenkins.phabricator.coverage.CoberturaCoverageProvider";

    private static final String JUNIT_PLUGIN_NAME = "junit";
    private static final String JUNIT_CLASS_NAME = "com.uber.jenkins.phabricator.unit.JUnitTestProvider";
    private static final String COBERTURA_PLUGIN_NAME = "cobertura";
    private static final String ABORT_TAG = "abort";
    private static final String UBERALLS_TAG = "uberalls";
    private static final String CONDUIT_TAG = "conduit";
    // Post a comment on success. Useful for lengthy builds.
    private final boolean commentOnSuccess;
    private final boolean uberallsEnabled;
    private final boolean coverageCheck;
    private final boolean commentWithConsoleLinkOnFailure;
    private final boolean preserveFormatting;
    private final String commentFile;
    private final String commentSize;
    private final boolean customComment;
    private final boolean processLint;
    private final String lintFile;
    private final String lintFileSize;
    private final double coverageThreshold;
    private UberallsClient uberallsClient;

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public PhabricatorNotifier(boolean commentOnSuccess, boolean uberallsEnabled, boolean coverageCheck,
            double coverageThreshold, boolean preserveFormatting, String commentFile, String commentSize,
            boolean commentWithConsoleLinkOnFailure, boolean customComment, boolean processLint, String lintFile,
            String lintFileSize) {
        this.commentOnSuccess = commentOnSuccess;
        this.uberallsEnabled = uberallsEnabled;
        this.coverageCheck = coverageCheck;
        this.commentFile = commentFile;
        this.commentSize = commentSize;
        this.lintFile = lintFile;
        this.lintFileSize = lintFileSize;
        this.preserveFormatting = preserveFormatting;
        this.commentWithConsoleLinkOnFailure = commentWithConsoleLinkOnFailure;
        this.customComment = customComment;
        this.processLint = processLint;
        this.coverageThreshold = coverageThreshold;
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }

    @Override
    public final boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher,
            final BuildListener listener) throws InterruptedException, IOException {
        EnvVars environment = build.getEnvironment(listener);
        Logger logger = new Logger(listener.getLogger());

        final String branch = environment.get("GIT_BRANCH");
        final String gitUrl = environment.get("GIT_URL");

        final UberallsClient uberallsClient = getUberallsClient(logger, gitUrl, branch);

        final boolean needsDecoration = build.getActions(PhabricatorPostbuildAction.class).size() == 0;

        final String diffID = environment.get(PhabricatorPlugin.DIFFERENTIAL_ID_FIELD);
        final String phid = environment.get(PhabricatorPlugin.PHID_FIELD);
        final boolean isDifferential = !CommonUtils.isBlank(diffID);

        InterruptedBuildAction action = build.getAction(InterruptedBuildAction.class);
        if (action != null) {
            List<CauseOfInterruption> causes = action.getCauses();
            for (CauseOfInterruption cause : causes) {
                if (cause instanceof PhabricatorCauseOfInterruption) {
                    logger.warn(ABORT_TAG, "Skipping notification step since this build was interrupted"
                            + " by a newer build with the same differential revision");
                    return true;
                }
            }
        }

        CoverageProvider coverageProvider;

        // Handle non-differential build invocations. If PHID is present but DIFF_ID is not, it means somebody is doing
        // a Harbormaster build on a commit rather than a differential, but still wants build status.
        // If DIFF_ID is present but PHID is not, it means somebody is doing a Differential build without Harbormaster.
        // So only skip build result processing if both are blank (e.g. master runs to update coverage data)
        if (CommonUtils.isBlank(phid) && !isDifferential) {
            if (needsDecoration) {
                build.addAction(PhabricatorPostbuildAction.createShortText(branch, null));
            }

            coverageProvider = getCoverageProvider(build, listener, Collections.<String>emptySet());
            CodeCoverageMetrics coverageResult = null;
            if (coverageProvider != null) {
                coverageResult = coverageProvider.getMetrics();
            }

            NonDifferentialBuildTask nonDifferentialBuildTask = new NonDifferentialBuildTask(logger, uberallsClient,
                    coverageResult, uberallsEnabled, environment.get("GIT_COMMIT"));

            // Ignore the result.
            nonDifferentialBuildTask.run();
            return true;
        }

        ConduitAPIClient conduitClient;
        try {
            conduitClient = getConduitClient(build.getParent());
        } catch (ConduitAPIException e) {
            e.printStackTrace(logger.getStream());
            logger.warn(CONDUIT_TAG, e.getMessage());
            return false;
        }

        final String buildUrl = environment.get("BUILD_URL");

        if (!isDifferential) {
            // Process harbormaster for non-differential builds
            Task.Result result = new NonDifferentialHarbormasterTask(logger, phid, conduitClient, build.getResult(),
                    buildUrl).run();
            return result == Task.Result.SUCCESS;
        }

        DifferentialClient diffClient = new DifferentialClient(diffID, conduitClient);
        Differential diff;
        try {
            diff = new Differential(diffClient.fetchDiff());
        } catch (ConduitAPIException e) {
            e.printStackTrace(logger.getStream());
            logger.warn(CONDUIT_TAG, "Unable to fetch differential from Conduit API");
            logger.warn(CONDUIT_TAG, e.getMessage());
            return true;
        }

        if (needsDecoration) {
            diff.decorate(build, this.getPhabricatorURL(build.getParent()));
        }

        Set<String> includeFileNames = new HashSet<String>();
        for (String file : diff.getChangedFiles()) {
            includeFileNames.add(FilenameUtils.getName(file));
        }

        coverageProvider = getCoverageProvider(build, listener, includeFileNames);
        CodeCoverageMetrics coverageResult = null;
        if (coverageProvider != null) {
            coverageResult = coverageProvider.getMetrics();
        }

        BuildResultProcessor resultProcessor = new BuildResultProcessor(logger, build, diff, diffClient,
                environment.get(PhabricatorPlugin.PHID_FIELD), coverageResult, buildUrl, preserveFormatting,
                coverageThreshold);

        if (uberallsEnabled) {
            boolean passBuildOnUberalls = resultProcessor.processParentCoverage(uberallsClient);
            if (!passBuildOnUberalls && coverageCheck) {
                build.setResult(Result.FAILURE);
            }
        }

        // Add in comments about the build result
        resultProcessor.processBuildResult(commentOnSuccess, commentWithConsoleLinkOnFailure);

        // Process unit tests results to send to Harbormaster
        resultProcessor.processUnitResults(getUnitProvider(build, listener));

        // Read coverage data to send to Harbormaster
        resultProcessor.processCoverage(coverageProvider);

        if (processLint) {
            // Read lint results to send to Harbormaster
            resultProcessor.processLintResults(lintFile, lintFileSize);
        }

        // Fail the build if we can't report to Harbormaster
        if (!resultProcessor.processHarbormaster()) {
            return false;
        }

        resultProcessor.processRemoteComment(commentFile, commentSize);

        resultProcessor.sendComment(commentWithConsoleLinkOnFailure);

        return true;
    }

    protected UberallsClient getUberallsClient(Logger logger, String gitUrl, String branch) {
        if (uberallsClient != null) {
            return uberallsClient;
        }

        setUberallsClient(new UberallsClient(getDescriptor().getUberallsURL(), logger, gitUrl, branch));
        return uberallsClient;
    }

    // Just for testing
    protected void setUberallsClient(UberallsClient client) {
        uberallsClient = client;
    }

    private ConduitAPIClient getConduitClient(Job owner) throws ConduitAPIException {
        ConduitCredentials credentials = getConduitCredentials(owner);
        if (credentials == null) {
            throw new ConduitAPIException("No credentials configured for conduit");
        }
        return new ConduitAPIClient(credentials.getGateway(), credentials.getToken().getPlainText());
    }

    /**
     * Get the cobertura coverage for the build
     *
     * @param build    The current build
     * @param listener The build listener
     * @return The current cobertura coverage, if any
     */
    private CoverageProvider getCoverageProvider(AbstractBuild build, BuildListener listener,
            Set<String> includeFileNames) {
        if (!build.getResult().isBetterOrEqualTo(Result.UNSTABLE)) {
            return null;
        }

        Logger logger = new Logger(listener.getLogger());
        InstanceProvider<CoverageProvider> provider = new InstanceProvider<CoverageProvider>(Jenkins.getInstance(),
                COBERTURA_PLUGIN_NAME, COBERTURA_CLASS_NAME, logger);
        CoverageProvider coverage = provider.getInstance();

        if (coverage == null) {
            return null;
        }

        coverage.setBuild(build);
        coverage.setIncludeFileNames(includeFileNames);
        if (coverage.hasCoverage()) {
            return coverage;
        } else {
            logger.info(UBERALLS_TAG, "No cobertura results found");
            return null;
        }
    }

    private UnitTestProvider getUnitProvider(AbstractBuild build, BuildListener listener) {
        Logger logger = new Logger(listener.getLogger());

        InstanceProvider<UnitTestProvider> provider = new InstanceProvider<UnitTestProvider>(Jenkins.getInstance(),
                JUNIT_PLUGIN_NAME, JUNIT_CLASS_NAME, logger);

        UnitTestProvider unitProvider = provider.getInstance();
        if (unitProvider == null) {
            return null;
        }
        unitProvider.setBuild(build);
        return unitProvider;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isCommentOnSuccess() {
        return commentOnSuccess;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isCustomComment() {
        return customComment;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isUberallsEnabled() {
        return uberallsEnabled;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isCoverageCheck() {
        return coverageCheck;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isCommentWithConsoleLinkOnFailure() {
        return commentWithConsoleLinkOnFailure;
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getCommentFile() {
        return commentFile;
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getCommentSize() {
        return commentSize;
    }

    @SuppressWarnings("UnusedDeclaration")
    public double getCoverageThreshold() {
        return coverageThreshold;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isPreserveFormatting() {
        return preserveFormatting;
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean isProcessLint() {
        return processLint;
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getLintFile() {
        return lintFile;
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getLintFileSize() {
        return lintFileSize;
    }

    private ConduitCredentials getConduitCredentials(Job owner) {
        return getDescriptor().getCredentials(owner);
    }

    private String getPhabricatorURL(Job owner) {
        ConduitCredentials credentials = getDescriptor().getCredentials(owner);
        if (credentials != null) {
            return credentials.getUrl();
        }
        return null;
    }

    // Overridden for better type safety.
    @Override
    public PhabricatorNotifierDescriptor getDescriptor() {
        return (PhabricatorNotifierDescriptor) super.getDescriptor();
    }
}