blob: 6b34dbea971c81fda6005d2c3e2b5deebfda9b28 [file] [log] [blame]
import org.jfrog.hudson.pipeline.common.types.ArtifactoryServer
import java.util.concurrent.TimeoutException
import groovy.time.TimeCategory
class Lock {
String name, id, path
String retryInterval, timeout, expiration
Boolean force
Map lockExtraMetadata
ArtifactoryServer artifactoryServer
private String lockFileContent
private String lockFileContentCache
private Boolean fileNotFound
final private String fileUri
final private def common = new com.mirantis.mk.Common()
final private def artifactoryTools = new com.mirantis.mk.Artifactory()
// Constructor
public Lock(Map args) {
// Mandatory
this.name = args.name
this.artifactoryServer = args.artifactoryServer
// Defaults
this.id = args.getOrDefault('id', '')
this.path = args.getOrDefault('path', 'binary-dev-local/locks')
this.retryInterval = args.getOrDefault('retryInterval', '5m')
this.timeout = args.getOrDefault('timeout', '3h')
this.expiration = args.getOrDefault('expiration', '24h')
this.force = args.getOrDefault('force', false)
this.lockExtraMetadata = args.getOrDefault('lockExtraMetadata', [:])
// Internal
this.fileUri = "/${path}/${name}.yaml".toLowerCase()
}
final private Map artObj
// getPasswordCredentials() is CPS-transformed function and cannot be used in constructor
final private Map getArtObj() {
def artifactoryCreds = common.getPasswordCredentials(artifactoryServer.getCredentialsId())
return [
'url': "${artifactoryServer.getUrl()}/artifactory",
'creds': [
'username': artifactoryCreds['username'],
'password': artifactoryCreds['password'],
]
]
}
// getter for lockFileContent
final private String getLockFileContent() {
if (this.lockFileContentCache == null) {
try {
this.lockFileContentCache = artifactoryTools.restCall(this.artObj, this.fileUri, 'GET', null, [:], '')
this.fileNotFound = false // file found
} catch (java.io.FileNotFoundException e) {
this.lockFileContentCache = ''
this.fileNotFound = true // file not found
} catch (Exception e) {
common.errorMsg(e.message)
this.lockFileContentCache = ''
this.fileNotFound = null // we don't know about file existence
}
}
return this.lockFileContentCache
}
public void lock() {
if (this.force) {
common.infoMsg("Ignore lock checking due 'force' flag presence")
} else {
waitLockReleased()
}
createLockFile()
}
public void unlock() {
if (!isLockFileExist()) {
common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' does not exist. No need to remove it")
// No need to continue if file does not exist
return
}
Map lockMeta = common.readYaml2(text: this.lockFileContent ?: '{}')
if (this.force || (this.id && this.id == lockMeta.getOrDefault('lockID', ''))) {
artifactoryTools.restCall(this.artObj, this.fileUri, 'DELETE', null, [:], '')
common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' has been removed")
} else {
throw new RuntimeException("Given lock ID '${this.id}' is not equal to '${lockMeta.get('lockID')}' ID in lock file")
}
}
private void createLockFile() {
this.id = UUID.randomUUID().toString()
Date now = new Date()
Date expiredAt = TimeCategory.plus(now, common.getDuration(this.expiration))
Map lockMeta = [
'lockID': this.id,
'createdAt': now.toString(),
'expiredAt': expiredAt.toString(),
]
lockMeta.putAll(this.lockExtraMetadata)
def commonMCP = new com.mirantis.mcp.Common()
artifactoryTools.restCall(this.artObj, this.fileUri, 'PUT', commonMCP.dumpYAML(lockMeta), [:], '')
common.infoMsg("Lock file '${this.artObj['url']}${this.fileUri}' has been created")
}
private void waitLockReleased() {
Long startTime = System.currentTimeMillis()
Long timeoutMillis = common.getDuration(this.timeout).toMilliseconds()
Long retryIntervalMillis = common.getDuration(this.retryInterval).toMilliseconds()
while (isLocked()) {
if (System.currentTimeMillis() - startTime >= timeoutMillis) {
throw new TimeoutException("Execution of waitLock timed out after ${this.timeout}")
}
common.infoMsg("'${this.name}' is locked. Retry in ${this.retryInterval}")
// Reset the cache so it will re-retrieve the file and its content
// otherwise it cannot determine that file has been removed on artifactory
// in the middle of waiting
this.lockFileContentCache = null
sleep(retryIntervalMillis)
}
}
private Boolean isLocked() {
if (!isLockFileExist()) {
common.infoMsg("Lock file for '${this.name}' does not exist")
return false
} else if (isLockExpired()) {
common.infoMsg("Lock '${this.name}' has been expired")
return false
}
return true
}
private Boolean isLockFileExist() {
// If there is something in file's content that it definitly exists
// If we don't know about file existence (fileNotFound == null) we assume it exists
return !this.lockFileContent.isEmpty() || !this.fileNotFound
}
private Boolean isLockExpired() {
if (!isLockFileExist()) {
return true
}
Map lockMeta = common.readYaml2(text: this.lockFileContent ?: '{}')
Date expirationTime = new Date(lockMeta.getOrDefault('expiredAt', '01/01/1970'))
Date currentTime = new Date()
return currentTime.after(expirationTime)
}
}