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
+}