Created
January 28, 2019 19:23
-
-
Save guykisel/da2750e77e9f4c0e9651a163a1b49ac4 to your computer and use it in GitHub Desktop.
simplified jenkins p4-plugin wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.transform.Field | |
// we want this to be global so that we can consistently | |
// sync the same changelist over the course of our pipeline | |
@Field currentChangelist = '' | |
def riotP4Sync(Map config = [:]) { | |
def humanReadableName = safePath("${JOB_NAME}-${STAGE_NAME}") | |
def jenkinsWorkspaceName = safePath("${JOB_NAME}-") + workspaceShortname(env.STAGE_NAME) | |
// we create a custom workspace to work around two issues: | |
// 1. this lets us make sure our workspace is safe for concurrent runs of the same pipeline on the same machine | |
// 2. this lets us have separate workspaces for each stage in a pipeline that does a sync | |
ws(jenkinsWorkspaceName) { | |
echo "[INFO] [riotP4Sync] Running in ${pwd()}" | |
// if we zero index then we never get a "1" workspace :( | |
def workspaceNumber = 1 | |
// jenkins puts @<number> at the end of a workspace when running pipelines concurrently | |
if (pwd().contains('@')) { | |
workspaceNumber = pwd().split('@').last() | |
if (!workspaceNumber.isInteger()) { | |
error("${workspaceNumber} is not an integer! Something went wrong in riotP4Sync. PWD is ${pwd()}") | |
} | |
} | |
def p4WorkspaceName = safePath("${humanReadableName}-${workspaceNumber}") | |
echo "[INFO] [riotP4Sync] Using P4 Workspace ${p4WorkspaceName}" | |
def defaultConfig = [ | |
workspacePattern: p4WorkspaceName, | |
syncType: 'AutoCleanImpl', | |
stream: '', | |
view: '', | |
quiet: true, | |
have: true, | |
charset: 'utf8', | |
parallel: true, | |
clobber: true, | |
] | |
def filesInWorkspace = true | |
try { | |
// see if we're in an empty dir | |
// an empty dir either means this is a brand new pipeline or | |
// we're running after someone manually deleted the dir as cleanup | |
filesInWorkspace = sh(returnStdout: true, script: 'ls').trim() | |
} catch (err) { | |
echo '[WARN] [riotP4Sync] checking for files in workspace failed' | |
} | |
if (!filesInWorkspace) { | |
// if we're in an empty dir, we should do a fresh sync. | |
defaultConfig.syncType = 'ForceCleanImpl' | |
} | |
if (riotCommon.workspaceIsEphemeral()) { | |
// if we're on a docker build node we don't care about existing files | |
// because our build node is ephemeral | |
defaultConfig.have = false | |
} | |
def requiredParams = ["credentialsId"] | |
def p4Config = riotCommon.overwriteDefaultConfig(defaultConfig, config, false) | |
if (!riotCommon.verifyConfig(p4Config, requiredParams, true)) { | |
error("Failed to provide a required parameter.") | |
} | |
if (!p4Config.view && !p4Config.stream) { | |
error("You must provide either a view spec or a stream.") | |
} | |
if (p4Config.view) { | |
p4Config.view = viewSpecWithWorkspace(p4Config.view, p4Config.workspacePattern) | |
} | |
echo '[INFO] [riotP4Sync] P4 Sync Config:' | |
def configArray = riotCommon.mapToArray(p4Config) | |
// do the actual sync | |
syncConfig(p4Config) | |
pwd() | |
} | |
} | |
def safePath(path) { | |
// remove characters that aren't alphanumeric | |
path.trim().replaceAll("[^a-zA-Z0-9]+","-") | |
} | |
def syncConfig(p4Config) { | |
// do the actual p4 sync using the p4 plugin | |
p4sync( | |
credential: p4Config.credentialsId, | |
populate: [$class: p4Config.syncType, | |
have: p4Config.have, | |
modtime: false, | |
parallel: [ | |
enable: p4Config.parallel, | |
minbytes: '1024', | |
minfiles: '1', | |
path: '/usr/local/bin/p4', | |
threads: '4' | |
], | |
// specify the changelist to sync (syncs latest if nothing provided) | |
pin: currentP4Changelist(p4Config.changelist), | |
quiet: p4Config.quiet, | |
revert: true, | |
], | |
workspace: [$class: 'ManualWorkspaceImpl', | |
charset: p4Config.charset, | |
name: p4Config.workspacePattern, | |
pinHost: false, | |
spec: [allwrite: false, | |
clobber: p4Config.clobber, | |
compress: false, | |
line: 'LOCAL', | |
locked: false, | |
modtime: false, | |
rmdir: false, | |
streamName: p4Config.stream, | |
view: p4Config.view | |
] | |
] | |
) | |
// write the changelist we just synced back to the global changelist var | |
currentChangelist = env.P4_CHANGELIST | |
currentChangelist | |
} | |
// return the globally tracked p4 changelist for the current build | |
// returns empty string if we haven't synced yet | |
def currentP4Changelist(changelist) { | |
// intentionally using currentChangelist from global scope | |
if (changelist) { | |
// allow manually overriding the globally set changelist | |
currentChangelist = changelist | |
echo "[INFO] [currentP4Changelist] Changelist was specified: ${changelist}" | |
return currentChangelist | |
} | |
if (currentChangelist) { | |
echo "[INFO] [currentP4Changelist] Changelist was previously set: ${currentChangelist}" | |
return currentChangelist | |
} | |
echo "[INFO] [currentP4Changelist] Current changelist not found, will build most recent changelist." | |
'' | |
} | |
String viewSpecWithWorkspace(viewspec, workspacePattern) { | |
def p4Viewspec = '' | |
if (viewspec) { | |
// Allow viewspec to be specified as an array. If so, join and insert workspace name | |
if ([Collection, Object[]].any { it.isAssignableFrom(viewspec.getClass()) }) { | |
p4Viewspec = viewspec.join("\n") | |
} else { | |
p4Viewspec = viewspec | |
} | |
// __WORKSPACE__ is a magic string we find and replace so that pipelines don't | |
// have to hardcode their workspace path | |
p4Viewspec = p4Viewspec.replace("__WORKSPACE__", workspacePattern) | |
} | |
p4Viewspec | |
} | |
def riotP4SyncContext(Map config = [:], Closure body) { | |
def jenkinsWorkspacePath = '' | |
def defaultConfig = [ | |
stageName: 'P4 Sync' | |
] | |
def p4Config = riotCommon.overwriteDefaultConfig(defaultConfig, config, false) | |
stage(p4Config.stageName) { | |
try { | |
jenkinsWorkspacePath = riotP4Sync(p4Config) | |
} catch (err) { | |
// if we didn't force sync, and the sync failed, let's try again with a force sync if we're on a persistent node | |
if (p4Config.syncType != 'ForceCleanImpl' && !riotCommon.isOnDockerJenkins()) { | |
p4Config.syncType = 'ForceCleanImpl' | |
try { | |
jenkinsWorkspacePath = riotP4Sync(p4Config) | |
} catch (err2) { | |
throw err | |
} | |
} else { | |
throw err | |
} | |
} | |
} | |
echo "[INFO] [riotP4SyncContext] P4 Sync complete, running in ${jenkinsWorkspacePath}" | |
ws(jenkinsWorkspacePath) { | |
// run the closure in the custom workspace | |
body() | |
} | |
jenkinsWorkspacePath | |
} | |
// return a four character string representing the workspace | |
def workspaceShortname(string) { | |
def safeString = safePath(string) | |
try { | |
return md5sum(safeString)[0..3] | |
} catch (err) { | |
try { | |
// as a fallback just grab first two and last two chars | |
return safeString[0..1] + safeString[-2..-1] | |
} catch (err2) { | |
return safeString | |
} | |
} | |
} | |
def md5sum(string) { | |
// md5sum works on windows and linux, but md5 is for os x | |
return sh(returnStdout: true, script: "echo -n ${string} | md5sum || echo -n ${string} | md5").trim() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment