io.fabric8.jenkins.openshiftsync.JenkinsUtils.java Source code

Java tutorial

Introduction

Here is the source code for io.fabric8.jenkins.openshiftsync.JenkinsUtils.java

Source

/**
 * Copyright (C) 2016 Red Hat, 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 io.fabric8.jenkins.openshiftsync;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.Action;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Job;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Queue;
import hudson.model.StringParameterDefinition;
import hudson.model.StringParameterValue;
import hudson.model.TopLevelItem;
import hudson.plugins.git.RevisionParameterAction;
import hudson.security.ACL;
import hudson.triggers.SafeTimerTask;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.openshift.api.model.Build;
import io.fabric8.openshift.api.model.BuildBuilder;
import io.fabric8.openshift.api.model.BuildConfig;
import io.fabric8.openshift.api.model.GitBuildSource;
import io.fabric8.openshift.api.model.GitSourceRevision;
import io.fabric8.openshift.api.model.JenkinsPipelineBuildStrategy;
import io.fabric8.openshift.api.model.SourceRevision;
import jenkins.model.Jenkins;
import jenkins.security.NotReallyRoleSensitiveCallable;
import jenkins.util.Timer;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import static io.fabric8.jenkins.openshiftsync.BuildConfigToJobMap.getJobFromBuildConfig;
import static io.fabric8.jenkins.openshiftsync.BuildPhases.CANCELLED;
import static io.fabric8.jenkins.openshiftsync.BuildPhases.PENDING;
import static io.fabric8.jenkins.openshiftsync.BuildRunPolicy.SERIAL;
import static io.fabric8.jenkins.openshiftsync.BuildRunPolicy.SERIAL_LATEST_ONLY;
import static io.fabric8.jenkins.openshiftsync.BuildWatcher.buildAdded;
import static io.fabric8.jenkins.openshiftsync.Constants.OPENSHIFT_ANNOTATIONS_BUILD_NUMBER;
import static io.fabric8.jenkins.openshiftsync.Constants.OPENSHIFT_BUILD_STATUS_FIELD;
import static io.fabric8.jenkins.openshiftsync.Constants.OPENSHIFT_LABELS_BUILD_CONFIG_NAME;
import static io.fabric8.jenkins.openshiftsync.CredentialsUtils.updateSourceCredentials;
import static io.fabric8.jenkins.openshiftsync.OpenShiftUtils.getOpenShiftClient;
import static io.fabric8.jenkins.openshiftsync.OpenShiftUtils.isCancelled;
import static io.fabric8.jenkins.openshiftsync.OpenShiftUtils.updateOpenShiftBuildPhase;
import static java.util.Collections.sort;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static org.apache.commons.lang.StringUtils.isBlank;

/**
 */
public class JenkinsUtils {

    private static final Logger LOGGER = Logger.getLogger(JenkinsUtils.class.getName());

    public static Job getJob(String job) {
        TopLevelItem item = Jenkins.getActiveInstance().getItem(job);
        if (item instanceof Job) {
            return (Job) item;
        }
        return null;
    }

    public static String getRootUrl() {
        // TODO is there a better place to find this?
        String root = Jenkins.getActiveInstance().getRootUrl();
        if (root == null || root.length() == 0) {
            root = "http://localhost:8080/";
        }
        return root;
    }

    public static void addJobParamForBuildEnvs(WorkflowJob job, JenkinsPipelineBuildStrategy strat,
            boolean replaceExisting) throws IOException {
        List<EnvVar> envs = strat.getEnv();
        if (envs.size() > 0) {
            // get existing property defs, including any manually added from the jenkins console independent of BC
            ParametersDefinitionProperty params = job.removeProperty(ParametersDefinitionProperty.class);
            Map<String, ParameterDefinition> paramMap = new HashMap<String, ParameterDefinition>();
            // store any existing parameters in map for easy key lookup
            if (params != null) {
                List<ParameterDefinition> existingParamList = params.getParameterDefinitions();
                for (ParameterDefinition param : existingParamList) {
                    paramMap.put(param.getName(), param);
                }
            }
            for (EnvVar env : envs) {
                if (replaceExisting) {
                    StringParameterDefinition envVar = new StringParameterDefinition(env.getName(), env.getValue());
                    paramMap.put(env.getName(), envVar);
                } else if (!paramMap.containsKey(env.getName())) {
                    // if list from BC did not have this parameter, it was added via `oc start-build -e` ... in this 
                    // case, we have chosen to make the default value an empty string
                    StringParameterDefinition envVar = new StringParameterDefinition(env.getName(), "");
                    paramMap.put(env.getName(), envVar);
                }
            }
            List<ParameterDefinition> newParamList = new ArrayList<ParameterDefinition>(paramMap.values());
            job.addProperty(new ParametersDefinitionProperty(newParamList));
        }
    }

    public static List<Action> setJobRunParamsFromEnv(JenkinsPipelineBuildStrategy strat,
            List<Action> buildActions) {
        List<EnvVar> envs = strat.getEnv();
        if (envs.size() > 0) {
            List<ParameterValue> envVarList = new ArrayList<ParameterValue>();
            for (EnvVar env : envs) {
                StringParameterValue envVar = new StringParameterValue(env.getName(), env.getValue());
                envVarList.add(envVar);
            }
            buildActions.add(new ParametersAction(envVarList));
        }

        return buildActions;
    }

    public static boolean triggerJob(WorkflowJob job, Build build) throws IOException {
        if (isAlreadyTriggered(job, build)) {
            return false;
        }

        String buildConfigName = build.getStatus().getConfig().getName();
        if (isBlank(buildConfigName)) {
            return false;
        }

        BuildConfigProjectProperty bcProp = job.getProperty(BuildConfigProjectProperty.class);
        if (bcProp == null) {
            return false;
        }

        switch (bcProp.getBuildRunPolicy()) {
        case SERIAL_LATEST_ONLY:
            cancelQueuedBuilds(job, bcProp.getUid());
            if (job.isBuilding()) {
                return false;
            }
            break;
        case SERIAL:
            if (job.isInQueue() || job.isBuilding()) {
                return false;
            }
            break;
        default:
        }

        ObjectMeta meta = build.getMetadata();
        String namespace = meta.getNamespace();
        BuildConfig buildConfig = getOpenShiftClient().buildConfigs().inNamespace(namespace)
                .withName(buildConfigName).get();
        if (buildConfig == null) {
            return false;
        }

        updateSourceCredentials(buildConfig);

        List<Action> buildActions = new ArrayList<Action>();
        buildActions.add(new CauseAction(new BuildCause(build, bcProp.getUid())));

        GitBuildSource gitBuildSource = build.getSpec().getSource().getGit();
        SourceRevision sourceRevision = build.getSpec().getRevision();

        if (gitBuildSource != null && sourceRevision != null) {
            GitSourceRevision gitSourceRevision = sourceRevision.getGit();
            if (gitSourceRevision != null) {
                try {
                    URIish repoURL = new URIish(gitBuildSource.getUri());
                    buildActions.add(new RevisionParameterAction(gitSourceRevision.getCommit(), repoURL));
                } catch (URISyntaxException e) {
                    LOGGER.log(SEVERE, "Failed to parse git repo URL" + gitBuildSource.getUri(), e);
                }
            }
        }

        // grab envs from actual build in case user overrode default values via `oc start-build -e`
        JenkinsPipelineBuildStrategy strat = build.getSpec().getStrategy().getJenkinsPipelineStrategy();
        // only add new param defs for build envs which are not in build config envs
        addJobParamForBuildEnvs(job, strat, false);
        // now add the actual param values stemming from openshift build env vars for this specific job
        buildActions = setJobRunParamsFromEnv(strat, buildActions);

        if (job.scheduleBuild2(0, buildActions.toArray(new Action[buildActions.size()])) != null) {
            updateOpenShiftBuildPhase(build, PENDING);
            // If builds are queued too quickly, Jenkins can add the cause to the previous queued build so let's add a tiny
            // sleep.
            try {
                Thread.sleep(50l);
            } catch (InterruptedException e) {
                // Ignore
            }
            return true;
        }

        return false;
    }

    private static boolean isAlreadyTriggered(WorkflowJob job, Build build) {
        return getRun(job, build) != null;
    }

    public synchronized static void cancelBuild(WorkflowJob job, Build build) {
        if (!cancelQueuedBuild(job, build)) {
            cancelRunningBuild(job, build);
        }
        try {
            updateOpenShiftBuildPhase(build, CANCELLED);
        } catch (Exception e) {
            throw e;
        }
    }

    private static WorkflowRun getRun(WorkflowJob job, Build build) {
        if (build != null && build.getMetadata() != null) {
            return getRun(job, build.getMetadata().getUid());
        }
        return null;
    }

    private static WorkflowRun getRun(WorkflowJob job, String buildUid) {
        for (WorkflowRun run : job.getBuilds()) {
            BuildCause cause = run.getCause(BuildCause.class);
            if (cause != null && cause.getUid().equals(buildUid)) {
                return run;
            }
        }
        return null;
    }

    private static boolean cancelRunningBuild(WorkflowJob job, Build build) {
        String buildUid = build.getMetadata().getUid();
        WorkflowRun run = getRun(job, buildUid);
        if (run != null && run.isBuilding()) {
            terminateRun(run);
            return true;
        }
        return false;
    }

    private static boolean cancelNotYetStartedBuild(WorkflowJob job, Build build) {
        String buildUid = build.getMetadata().getUid();
        WorkflowRun run = getRun(job, buildUid);
        if (run != null && run.hasntStartedYet()) {
            terminateRun(run);
            return true;
        }
        return false;
    }

    private static void cancelNotYetStartedBuilds(WorkflowJob job, String bcUid) {
        cancelQueuedBuilds(job, bcUid);
        for (WorkflowRun run : job.getBuilds()) {
            if (run != null && run.hasntStartedYet()) {
                BuildCause cause = run.getCause(BuildCause.class);
                if (cause != null && cause.getBuildConfigUid().equals(bcUid)) {
                    terminateRun(run);
                }
            }
        }
    }

    private static void terminateRun(final WorkflowRun run) {
        ACL.impersonate(ACL.SYSTEM, new NotReallyRoleSensitiveCallable<Void, RuntimeException>() {
            @Override
            public Void call() throws RuntimeException {
                run.doTerm();
                Timer.get().schedule(new SafeTimerTask() {
                    @Override
                    public void doRun() {
                        ACL.impersonate(ACL.SYSTEM, new NotReallyRoleSensitiveCallable<Void, RuntimeException>() {
                            @Override
                            public Void call() throws RuntimeException {
                                run.doKill();
                                return null;
                            }
                        });
                    }
                }, 5, TimeUnit.SECONDS);
                return null;
            }
        });
    }

    @SuppressFBWarnings("SE_BAD_FIELD")
    public static boolean cancelQueuedBuild(WorkflowJob job, Build build) {
        String buildUid = build.getMetadata().getUid();
        final Queue buildQueue = Jenkins.getActiveInstance().getQueue();
        for (final Queue.Item item : buildQueue.getItems()) {
            for (Cause cause : item.getCauses()) {
                if (cause instanceof BuildCause && ((BuildCause) cause).getUid().equals(buildUid)) {
                    return ACL.impersonate(ACL.SYSTEM,
                            new NotReallyRoleSensitiveCallable<Boolean, RuntimeException>() {
                                @Override
                                public Boolean call() throws RuntimeException {
                                    buildQueue.cancel(item);
                                    return true;
                                }
                            });
                }
            }
        }
        return cancelNotYetStartedBuild(job, build);
    }

    public static void cancelQueuedBuilds(WorkflowJob job, String bcUid) {
        Queue buildQueue = Jenkins.getActiveInstance().getQueue();
        for (Queue.Item item : buildQueue.getItems()) {
            for (Cause cause : item.getCauses()) {
                if (cause instanceof BuildCause) {
                    BuildCause buildCause = (BuildCause) cause;
                    if (buildCause.getBuildConfigUid().equals(bcUid)) {
                        Build build = new BuildBuilder().withNewMetadata().withNamespace(buildCause.getNamespace())
                                .withName(buildCause.getName()).and().build();
                        cancelQueuedBuild(job, build);
                    }
                }
            }
        }
    }

    public static WorkflowJob getJobFromBuild(Build build) {
        String buildConfigName = build.getStatus().getConfig().getName();
        if (StringUtils.isEmpty(buildConfigName)) {
            return null;
        }
        BuildConfig buildConfig = getOpenShiftClient().buildConfigs()
                .inNamespace(build.getMetadata().getNamespace()).withName(buildConfigName).get();
        if (buildConfig == null) {
            return null;
        }
        return getJobFromBuildConfig(buildConfig);
    }

    public static void maybeScheduleNext(WorkflowJob job) {
        BuildConfigProjectProperty bcp = job.getProperty(BuildConfigProjectProperty.class);
        if (bcp == null) {
            return;
        }
        List<Build> builds = getOpenShiftClient().builds().inNamespace(bcp.getNamespace())
                .withField(OPENSHIFT_BUILD_STATUS_FIELD, BuildPhases.NEW)
                .withLabel(OPENSHIFT_LABELS_BUILD_CONFIG_NAME, bcp.getName()).list().getItems();
        handleBuildList(job, builds, bcp);
    }

    public static void handleBuildList(WorkflowJob job, List<Build> builds,
            BuildConfigProjectProperty buildConfigProjectProperty) {
        if (builds.isEmpty()) {
            return;
        }
        boolean isSerialLatestOnly = SERIAL_LATEST_ONLY.equals(buildConfigProjectProperty.getBuildRunPolicy());
        if (isSerialLatestOnly) {
            // Try to cancel any builds that haven't actually started, waiting for executor perhaps.
            cancelNotYetStartedBuilds(job, buildConfigProjectProperty.getUid());
        }
        sort(builds, new Comparator<Build>() {
            @Override
            public int compare(Build b1, Build b2) {
                // Order so cancellations are first in list so we can stop processing build list when build run policy is
                // SerialLatestOnly and job is currently building.
                Boolean b1Cancelled = b1.getStatus() != null && b1.getStatus().getCancelled() != null
                        ? b1.getStatus().getCancelled()
                        : false;
                Boolean b2Cancelled = b2.getStatus() != null && b2.getStatus().getCancelled() != null
                        ? b2.getStatus().getCancelled()
                        : false;
                // Inverse comparison as boolean comparison would put false before true. Could have inverted both cancellation
                // states but this removes that step.
                int cancellationCompare = b2Cancelled.compareTo(b1Cancelled);
                if (cancellationCompare != 0) {
                    return cancellationCompare;
                }

                return Long.compare(
                        Long.parseLong(b1.getMetadata().getAnnotations().get(OPENSHIFT_ANNOTATIONS_BUILD_NUMBER)),
                        Long.parseLong(b2.getMetadata().getAnnotations().get(OPENSHIFT_ANNOTATIONS_BUILD_NUMBER)));
            }
        });
        boolean isSerial = SERIAL.equals(buildConfigProjectProperty.getBuildRunPolicy());
        boolean jobIsBuilding = job.isBuilding();
        for (int i = 0; i < builds.size(); i++) {
            Build b = builds.get(i);
            // For SerialLatestOnly we should try to cancel all builds before the latest one requested.
            if (isSerialLatestOnly) {
                // If the job is currently building, then let's return on the first non-cancellation request so we do not try to
                // queue a new build.
                if (jobIsBuilding && !isCancelled(b.getStatus())) {
                    return;
                }

                if (i < builds.size() - 1) {
                    cancelQueuedBuild(job, b);
                    updateOpenShiftBuildPhase(b, CANCELLED);
                    continue;
                }
            }
            boolean buildAdded = false;
            try {
                buildAdded = buildAdded(b);
            } catch (IOException e) {
                ObjectMeta meta = b.getMetadata();
                LOGGER.log(WARNING, "Failed to add new build " + meta.getNamespace() + "/" + meta.getName(), e);
            }
            // If it's a serial build then we only need to schedule the first build request.
            if (isSerial && buildAdded) {
                return;
            }
        }
    }
}