Add new function for moveItem instead of exists

Existing function uses apy/copy that available only for Artifactory Pro
New function uses ability to deploy artifacts by checksums available on free versions

Related-PROD: RE-3325

Change-Id: Icf00b98265500158222432b5082cb88eb3a32e8e
diff --git a/src/com/mirantis/mcp/MCPArtifactory.groovy b/src/com/mirantis/mcp/MCPArtifactory.groovy
index 14bd19f..881e44a 100644
--- a/src/com/mirantis/mcp/MCPArtifactory.groovy
+++ b/src/com/mirantis/mcp/MCPArtifactory.groovy
@@ -130,6 +130,7 @@
 
 /**
  * Move/copy an artifact or a folder to the specified destination
+ * Uses curl to download/upload/delete files since /api/copy and /api/move are not supported
  *
  * @param artifactoryURL String, an URL to Artifactory
  * @param sourcePath String, a source path to the artifact including repository name
@@ -137,10 +138,207 @@
  * @param copy boolean, whether to copy or move the item, default is move
  * @param dryRun boolean, whether to perform dry run on not, default is false
  */
-def moveItem (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false) {
-    def url = "${artifactoryURL}/api/${copy ? 'copy' : 'move'}/${sourcePath}?to=/${dstPath}&dry=${dryRun ? '1' : '0'}"
-    def http = new com.mirantis.mk.Http()
-    return http.doPost(url, 'artifactory')
+def moveItem (String artifactoryURL, String sourcePath, String dstPath, boolean copy = false, boolean dryRun = false, String credentialsId = 'artifactory') {
+    def respCode = 200
+    def respText = ''
+
+    withCredentials([
+            [$class          : 'UsernamePasswordMultiBinding',
+             credentialsId   : credentialsId,
+             passwordVariable: 'ARTIFACTORY_PASSWORD',
+             usernameVariable: 'ARTIFACTORY_LOGIN']
+    ]) {
+        try {
+            // Check if source is a file or directory
+            def storageUrl = "${artifactoryURL}/api/storage/${sourcePath}"
+            def storageResult = sh(script: """
+                set +e
+                response=\$(curl -s -w "\\n%{http_code}" -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}' 2>&1)
+                echo "\$response"
+            """, returnStdout: true).trim()
+
+            def storageLines = storageResult.split('\n')
+            def storageCode = storageLines.length > 0 && storageLines[-1] ==~ /^\d{3}$/ ? storageLines[-1].toInteger() : 404
+            def storageBody = storageLines.length > 1 ? storageLines[0..-2].join('\n') : ''
+
+            if (storageCode != 200) {
+                return [storageCode, storageBody ?: "Source path not found: ${sourcePath}"]
+            }
+
+            def storageInfo = new groovy.json.JsonSlurperClassic().parseText(storageBody)
+            def isDirectory = storageInfo.get('children') != null
+
+            if (dryRun) {
+                respText = "DRY RUN: Would ${copy ? 'copy' : 'move'} ${isDirectory ? 'directory' : 'file'} from ${sourcePath} to ${dstPath}"
+                return [200, respText]
+            }
+
+            if (isDirectory) {
+                // Handle directory: recursively copy all files using checksum deploy
+                def filesToCopy = getDirectoryFiles(artifactoryURL, sourcePath, credentialsId)
+                def errors = []
+
+                filesToCopy.each { filePath ->
+                    def relativePath = filePath.replaceFirst("^${sourcePath}/", '')
+                    def copyResult = copyFileByChecksum(artifactoryURL, filePath, "${dstPath}/${relativePath}", credentialsId)
+
+                    if (copyResult[0] != 200) {
+                        errors.add("Failed to copy ${filePath}: HTTP ${copyResult[0]} - ${copyResult[1]}")
+                        respCode = copyResult[0]
+                    }
+                }
+
+                // Delete source directory if move (not copy)
+                if (!copy && respCode == 200) {
+                    def deleteResult = deleteItem(artifactoryURL, sourcePath)
+                    if (deleteResult[0] != 200) {
+                        errors.add("Failed to delete source directory: HTTP ${deleteResult[0]}")
+                        respCode = deleteResult[0]
+                    }
+                }
+
+                respText = errors ? errors.join('; ') : "Successfully ${copy ? 'copied' : 'moved'} directory from ${sourcePath} to ${dstPath}"
+            } else {
+                // Handle single file using checksum deploy
+                def copyResult = copyFileByChecksum(artifactoryURL, sourcePath, dstPath, credentialsId)
+                respCode = copyResult[0]
+                respText = copyResult[1]
+                // Delete source file if move (not copy)
+                if (!copy && respCode == 200) {
+                    def deleteResult = deleteItem(artifactoryURL, sourcePath)
+                    if (deleteResult[0] != 200) {
+                        respCode = deleteResult[0]
+                        respText = "Copied but failed to delete source: ${deleteResult[1]}"
+                    } else {
+                        respText = respText ?: "Successfully ${copy ? 'copied' : 'moved'} file from ${sourcePath} to ${dstPath}"
+                    }
+                } else if (respCode == 200) {
+                    respText = respText ?: "Successfully ${copy ? 'copied' : 'moved'} file from ${sourcePath} to ${dstPath}"
+                }
+            }
+            //If successful, rewrite the return code to the expected one
+            if ( respCode ==~ /^2\d{2}$/ ) {
+                respCode = 200
+            }
+        } catch (Exception e) {
+            respCode = 500
+            respText = "Error during ${copy ? 'copy' : 'move'} operation: ${e.getMessage()}"
+        }
+    }
+
+    return [respCode, respText]
+}
+
+/**
+ * Copy a file using checksum-based deploy API (no file download required)
+ * Uses JFrog REST API: PUT /artifactory/api/checksum/deploy/{repoKey}/{filePath}
+ *
+ * @param artifactoryURL String, an URL to Artifactory
+ * @param sourcePath String, a source path to the artifact including repository name
+ * @param dstPath String, a destination path to the artifact including repository name
+ * @return Array with [responseCode, responseText]
+ */
+def copyFileByChecksum(String artifactoryURL, String sourcePath, String dstPath, String credentialsId = 'artifactory') {
+    def respCode = 200
+    def respText = ''
+
+    withCredentials([
+            [$class          : 'UsernamePasswordMultiBinding',
+             credentialsId   : credentialsId,
+             passwordVariable: 'ARTIFACTORY_PASSWORD',
+             usernameVariable: 'ARTIFACTORY_LOGIN']
+    ]) {
+        // Get checksums from source file
+        def storageUrl = "${artifactoryURL}/api/storage/${sourcePath}"
+        def storageResult = sh(script: """
+            set +e
+            response=\$(curl -s -w "\\n%{http_code}" -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}' 2>&1)
+            echo "\$response"
+        """, returnStdout: true).trim()
+
+        def storageLines = storageResult.split('\n')
+        def storageCode = storageLines.length > 0 && storageLines[-1] ==~ /^\d{3}$/ ? storageLines[-1].toInteger() : 404
+        def storageBody = storageLines.length > 1 ? storageLines[0..-2].join('\n') : ''
+
+        if (storageCode != 200) {
+            return [storageCode, storageBody ?: "Source file not found: ${sourcePath}"]
+        }
+
+        def storageInfo = new groovy.json.JsonSlurperClassic().parseText(storageBody)
+        def checksums = storageInfo.get('checksums', [:])
+        def md5 = checksums.get('md5', '')
+        def sha1 = checksums.get('sha1', '')
+        def sha256 = checksums.get('sha256', '')
+
+        if (!md5 && !sha1) {
+            return [500, "Source file has no checksums available: ${sourcePath}"]
+        }
+
+        // Use checksum deploy API to copy file without downloading
+        def deployUrl = "${artifactoryURL}/${dstPath}"
+        // Build curl command with headers
+        def curlHeaders = "-H \"X-Checksum-Deploy: true\""
+        if (sha1) {
+            curlHeaders += " -H \"X-Checksum-Sha1: ${sha1}\""
+        }
+        if (sha256) {
+            curlHeaders += " -H \"X-Checksum-Sha256: ${sha256}\""
+        }
+        if (md5) {
+            curlHeaders += " -H \"X-Checksum-Md5: ${md5}\""
+        }
+
+        def deployResult = sh(script: """
+            set +e
+            response=\$(curl -s -w "\\n%{http_code}" -X PUT -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} ${curlHeaders} '${deployUrl}' 2>&1)
+            echo "\$response"
+        """, returnStdout: true).trim()
+
+        def deployLines = deployResult.split('\n')
+        respCode = deployLines.length > 0 && deployLines[-1] ==~ /^\d{3}$/ ? deployLines[-1].toInteger() : 500
+        respText = deployLines.length > 1 ? deployLines[0..-2].join('\n') : ''
+
+        if (respCode != 200 && !respText) {
+            respText = "Failed to deploy by checksum: HTTP ${respCode}"
+        }
+    }
+
+    return [respCode, respText]
+}
+
+/**
+ * Recursively get all files in a directory
+ *
+ * @param artifactoryURL String, an URL to Artifactory
+ * @param dirPath String, a directory path including repository name
+ * @return List of file paths
+ */
+def getDirectoryFiles(String artifactoryURL, String dirPath, String credentialsId = 'artifactory') {
+    def files = []
+    def storageUrl = "${artifactoryURL}/api/storage/${dirPath}"
+
+    withCredentials([
+            [$class          : 'UsernamePasswordMultiBinding',
+             credentialsId   : credentialsId,
+             passwordVariable: 'ARTIFACTORY_PASSWORD',
+             usernameVariable: 'ARTIFACTORY_LOGIN']
+    ]) {
+        def result = sh(script: "bash -c \"curl -X GET -u \${ARTIFACTORY_LOGIN}:\${ARTIFACTORY_PASSWORD} '${storageUrl}'\"",
+                returnStdout: true).trim()
+
+        def storageInfo = new groovy.json.JsonSlurperClassic().parseText(result)
+        def children = storageInfo.get('children', [])
+
+        children.each { child ->
+            def childPath = "${dirPath}/${child.uri.replaceAll('^/', '')}"
+            if (child.folder) {
+                files.addAll(getDirectoryFiles(artifactoryURL, childPath))
+            } else {
+                files.add(childPath)
+            }
+        }
+    }
+    return files
 }
 
 /**