blob: 60d0991cf0a9f4d4bfdba0d0a65a2e13753baf54 [file] [log] [blame]
// Copyright (C) 2017 The Bazel Authors.
//
// 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 build.bazel.ci
// Automatic import in Jenkins but we need them to compile outside of Jenkins
import com.cloudbees.groovy.cps.NonCPS
import hudson.model.*
import jenkins.model.*
import hudson.FilePath
import hudson.remoting.Channel
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
/**
* This class provide utility methods of the Jenkins API
*/
class JenkinsUtils {
/** A list of regex aliases to know that ubuntu is linux */
private static def NODE_ALIASES = [
"ubuntu.*": "linux-x86_64",
"darwin.*": "darwin-x86_64",
]
/** Normalize the node label into a compatible platform */
// TODO(dmarting): does that really belongs here?
@NonCPS
static def normalizeNodeLabel(node_label) {
for (e in NODE_ALIASES) {
if (node_label.matches(e.key)) {
return e.value
}
}
return node_label
}
/** Returns the list of all slave' names (optionally filtering by label) */
@NonCPS
static def nodeNames(label = null) {
def nodes = jenkins.model.Jenkins.instance.nodes
if (label != null) {
nodes = nodes.findAll { node -> label in node.labelString.split() }
}
return nodes.collect { node -> node.name }
}
/** Returns the list of job in the folder `folderName` (empty or null for top folder). */
@NonCPS
private static def folderJobs(folderName) {
if (folderName == null || folderName.isEmpty()) {
return jenkins.model.Jenkins.instance.items
} else {
return jenkins.model.Jenkins.instance.getItemByFullName(folderName).getAllJobs()
}
}
/**
* Returns the list of names of jobs in the folder `folderName` (empty or null for the
* top folder).
*/
@NonCPS
static def jobs(folderName) {
return folderJobs(folderName).collect { job -> job.name }
}
/**
* Returns the list of names of jobs in the folder `folder` (empty or null for the top
* folder) whose description contains the string `descr`
*/
@NonCPS
static def jobsWithDescription(folder, descr) {
def items = jenkins.model.Jenkins.instance.items
def jobs = folderJobs(folder)
return jobs.findAll {
job -> job.description != null && job.description.contains(descr) }.collect {
job -> job.name }
}
/** Returns the last build of a job in a specific folder */
@NonCPS
static def getLastRun(folder, job) {
def j = folderJobs(folder).find { it -> it.name.equals(job) }
if (j != null) {
def run = j.getLastCompletedBuild()
if (run != null) {
return new org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper(run, false)
}
}
return null
}
/** Returns the URL of the console for a job run */
@NonCPS
static def getConsoleUrl(RunWrapper run) {
return "${run.absoluteUrl}console"
}
/** Returns the URL of the blue ocean view for a job run */
@NonCPS
static def getBlueOceanUrl(RunWrapper run) {
def name = java.net.URLEncoder.encode(run.fullProjectName, "UTF-8")
def url = new URL(run.absoluteUrl)
def path = "/blue/organizations/jenkins/${name}/detail/${run.projectName}/${run.number}/pipeline/"
return new URL(url.protocol, url.host, url.port, path).toString()
}
/** Returns the URL to the small icon for a run */
@NonCPS
static def getSmallIconUrl(RunWrapper run) {
return "${Jenkins.RESOURCE_PATH}/images/16x16/${run.rawBuild.getIconColor()}.png"
}
/**
* A utility method that look for an artifact matching a pattern in upstream
* builds.
*/
@NonCPS
static def findAndCopyUpstreamArtifacts(run, pattern) {
def build = findLastBuildWithUpstream(run.rawBuild)
if (build == null) {
return null
}
return _findAndCopyUpstreamArtifacts(build.getCauses(), pattern)
}
@NonCPS
private static def _findAndCopyUpstreamArtifacts(causes, pattern) {
for (cause in causes) {
if (cause instanceof Cause.UpstreamCause) {
def upstreamRun = cause.getUpstreamRun()
def artifacts = upstreamRun.getArtifacts()
for (artifact in artifacts) {
if (artifact.toString().matches(pattern)) {
return [artifactPath: artifact.toString(),
artifactName: artifact.getFileName(),
upstreamBuild: cause.getUpstreamBuild(),
upstreamProject: cause.getUpstreamProject()]
}
}
def res = _findAndCopyUpstreamArtifacts(cause.upstreamCauses, pattern)
if (res != null && !res.isEmpty()) {
return res;
}
}
}
return null
}
/**
* Find the latest build that was run by upstream.
* In case of re-run, the current build will not be triggered by upstream project.
* In that case we want to look at build history to fetch the latest one that was build
* with upstream.
*/
@NonCPS
private static def findLastBuildWithUpstream(run) {
while (run != null) {
if (run.getCauses().any { it.class.toString().contains("UpstreamCause") }) {
return run
}
run = run.getPreviousBuild()
}
return run
}
@NonCPS
private static def _pruneIfOlderThan(file, timestamp) throws IOException {
if (file.isDirectory()) {
boolean empty = true
for (child in file.list()) {
if (!_pruneIfOlderThan(child, timestamp)) {
empty = false
}
}
if (empty) {
if (file.delete()) {
return true
}
}
} else if (file.lastModified() < timestamp) {
if (file.delete()) {
return true
}
}
return false
}
@NonCPS
private static def createFilePath(env, path) {
if (env['NODE_NAME'].equals("master")) {
return new FilePath(path);
} else {
return new FilePath(Jenkins.getInstance().getComputer(env['NODE_NAME']).getChannel(), path);
}
}
/** Prune file that are older than timestamp on the current node. */
@NonCPS
public static def pruneIfOlderThan(env, path, timestamp) {
return _pruneIfOlderThan(createFilePath(env, path), timestamp)
}
/** Touch a file anywhere on the FS on the current node. */
@NonCPS
public static def touchFileIfExists(env, path) {
FilePath f = createFilePath(env, path)
def r = f.exists()
if (r) {
try {
f.touch(System.currentTimeMillis())
} catch(IOException ex) {
// The file might be busy, especially on windows, swallowing exception
}
}
return r;
}
/** Read a file from the node, but without reporting anything in the Jenkins UI. */
@NonCPS
public static def readFile(env, path) {
return createFilePath(env, path).readToString()
}
/** Save the current log to a file on the current node. */
@NonCPS
public static void saveLog(env, RunWrapper run, path) {
createFilePath(env, path).copyFrom(run.getRawBuild().getLogInputStream())
}
/** Returns the recursive list of files of a folder, ignoring some files. */
@NonCPS
public static def list(env, dir, excludes) {
def directory = createFilePath(env, dir)
def results = directory.list("**", excludes.join(","))
def directoryUri = directory.toURI()
return results.collect { it -> directoryUri.relativize(it.toURI()) }
}
}