Implement artifactory upload function
Related: RE-3241
Related: PRODX-54274
Change-Id: I02e5c0283df63a834340b6d78060dd327c1ba39b
diff --git a/vars/artifactory.groovy b/vars/artifactory.groovy
new file mode 100644
index 0000000..4c5447c
--- /dev/null
+++ b/vars/artifactory.groovy
@@ -0,0 +1,207 @@
+/**
+ * This method creates config Map object and calls upload(Map) method
+ *
+ * @param server a server ID - file name at resources/../servers
+ * @param spec a JSON representation of upload spec
+ * @return a List object that contains upload results
+ */
+List upload(String server, String spec) {
+ return upload(server: server, spec: spec)
+}
+
+
+/**
+ * This method converts JSON representation of spec into Map object and calls
+ * upload(String, Map)
+ *
+ * @param config a Map object with params `[ server: String, spec: String or Map ]`
+ * @return a List object that contains upload results
+ */
+List upload(Map config) {
+ if (config.spec?.getClass() in [java.util.LinkedHashMap, net.sf.json.JSONObject]) {
+ return upload(config.server, config.spec)
+ }
+ return upload(config.server, parseJSON(config.spec))
+}
+
+
+/**
+ * This method uploads files into artifactory instance
+ *
+ * Input spec example:
+ * [ files: [[
+ * pattern: "**",
+ * target: "my-repository/path/to/upload",
+ * props: "myPropKey1=myPropValue1;myPropKey2=myPropValue2", // optional
+ * ],]
+ *
+ * Result example:
+ * [[
+ * localPath: "local/path/to/file.name",
+ * remotePath: "/path/to/upload/local/path/to/file.name",
+ * repo: "my-repository",
+ * size: 12345,
+ * uri: ... ,
+ * checksums: [
+ * md5: ... ,
+ * sha1: ... ,
+ * sha256: ... "
+ * ],
+ * ],]
+ *
+ * @param server a server ID - server name at resources/../servers
+ * @param spec a Map object with upload spec
+ * @return a List object that contains upload results
+ */
+List upload(String server, Map spec) {
+ List result = []
+
+ Map artConfig = parseJSON(loadResource("artifactory/servers/${server}.json"))
+ String uploadScript = loadResource("artifactory/scripts/upload.sh")
+
+ List artCredentials = [usernamePassword(
+ credentialsId: artConfig.credentialsId,
+ usernameVariable: 'ARTIFACTORY_USERNAME',
+ passwordVariable: 'ARTIFACTORY_PASSWORD')]
+
+ List files = createFileListBySpec(spec)
+
+ retry(artConfig.get('connectionRetry', 1)) {
+ withCredentials(artCredentials) {
+ files.each{ file ->
+ String scriptRawResult
+ List envList = [
+ "ARTIFACTORY_URL=${artConfig.artifactoryUrl}",
+ "ARTIFACTORY_TARGET=${file.target}",
+ "ARTIFACTORY_PROPS=${file.props}",
+ "FILE_TO_UPLOAD=${file.name}"
+ ]
+ withEnv(envList) {
+ scriptRawResult = sh \
+ script: uploadScript,
+ returnStdout: true
+ }
+
+ Map scriptResult = parseScriptResult(scriptRawResult)
+ Map uploadResult = parseJSON(scriptResult.stdout)
+ ['created', 'createdBy', 'downloadUri', 'mimeType', 'originalChecksums'].each {
+ uploadResult.remove(it)
+ }
+ uploadResult.localPath = file.name
+ uploadResult.put('remotePath', uploadResult.remove('path'));
+ result << uploadResult
+ }
+ }
+ }
+ return result
+}
+
+
+/**
+ * This method looks up for local files to upload according to the spec
+ *
+ * @param spec a Map object with upload spec
+ * @return a List object that contains found local files to upload
+ */
+List createFileListBySpec(Map spec) {
+ List result = []
+
+ Map jenkinsProps = [
+ "build.name": env.JOB_NAME,
+ "build.number": env.BUILD_NUMBER,
+ "build.timestamp": currentBuild.startTimeInMillis
+ ]
+
+ spec.files?.each{ specItem ->
+ Map targetProps = specItem.props?.split(';')?.findAll{ it.contains('=') }?.collectEntries{
+ List parts = it.split('=', 2)
+ return [(parts[0]): parts[1]]
+ } ?: [:]
+
+ String props = (jenkinsProps + targetProps)
+ .collect{ "${it.key}=${it.value}" }
+ .join(';')
+
+ if (!(specItem.pattern && specItem.target)) {
+ error "ArtifactoryUploader: Malformed upload spec:\n${spec}"
+ }
+
+ List targetFiles = []
+ try {
+ targetFiles = findFiles(glob: specItem.pattern)
+ .findAll{ !it.directory }
+ .collect{ it.path }
+ } catch (Exception e) {
+ error "ArtifactoryUploader: Unable to find files by pattern: ${specItem.pattern}"
+ }
+
+ targetFiles.each{ file ->
+ result << [ name: file, target: specItem.target, props: props ]
+ }
+ }
+ return result
+}
+
+
+/**
+ * This method parses uploading results
+ *
+ * @param scriptRawResult a JSON representation of uploading results
+ * @return a Map object that contains uploading results
+ */
+Map parseScriptResult(String scriptRawResult) {
+ Map result = parseJSON scriptRawResult
+
+ if (result.exit_code == 0 &&
+ result.response_code &&
+ result.response_code in 200..299 &&
+ result.stdout) { return result }
+
+ String errorMessage = "ArtifactoryUploader: Upload failed"
+ if (result.stdout) {
+ errorMessage += "\nStdout: ${result.stdout}"
+ }
+ if (result.stderr) {
+ errorMessage += "\nStderr: ${result.stderr}"
+ }
+ if (result.response_code) {
+ errorMessage += "\nResponse code: ${result.response_code}"
+ }
+ error errorMessage
+}
+
+
+/**
+ * This method loads resourcse file from the library
+ *
+ * @param path path to the resource file
+ * @return content of the resource file
+ */
+String loadResource(String path) {
+ try {
+ return libraryResource(path)
+ } catch (Exception e) {
+ error "ArtifactoryUploader: Unable to load resource: ${path}"
+ }
+}
+
+
+/**
+ * This method converts a JSON representation into an object
+ *
+ * @param text a JSON content
+ * @return a Map or List object
+ */
+Map parseJSON(String text) {
+ def json = new groovy.json.JsonSlurper()
+ Map result
+ try {
+ // result = readJSON text: text
+ result = json.parseText(text)
+ } catch (Exception e) {
+ json = null
+ error "ArtifactoryUploader: Unable to parse JSON:\n${text}"
+ }
+ json = null
+ return result
+}