diff --git a/build/jenkins/GDriveUpload b/build/jenkins/GDriveUpload index 0c1c158..8ad4a11 100644 --- a/build/jenkins/GDriveUpload +++ b/build/jenkins/GDriveUpload @@ -1,47 +1,46 @@ -// Jenkins Pipeline script to directly copy the zip file from an upstream job's workspace, -// derive build identifier from the zip filename, and upload to Google Drive with retention. +// Jenkins Pipeline script to find the latest zip file directly in the artifacts directory +// and upload it to Google Drive with retention. // Retention is based on the timestamp embedded in the filename and performed entirely in PowerShell. -// Designed to run on a Windows agent. +// Designed to run on a Windows agent, specifically on the machine where the main build creates artifacts. // Assumes rclone is installed and its directory is added to the system's PATH on the Windows agent. pipeline { - // Agent should be your Windows VM agent + // Agent should be the SAME Windows VM agent where the main build job runs + // and creates the artifacts in the MAIN_BUILD_ARTIFACTS_DIR. agent { label 'Win10-BuildMachine' } // Replace with your Windows agent label if different - // Removed parameters block as we don't need upstream job name/build number parameters anymore - environment { + // Google Drive settings GDRIVE_REMOTE = 'AzaionGoogleDrive:AzaionSuiteBuilds' // Your rclone remote name and path // Use a relative path within the workspace for the temporary directory on Windows - // This temporary directory will hold the copied zip file before upload + // This temporary directory is still useful for rclone's internal operations and cleanup TMP_UPLOAD_DIR = 'temp_upload' // Path to rclone.conf on the Windows agent. // Adjust this path if your rclone.conf is located elsewhere on the Windows VM. // Using a relative path from the workspace root is often best practice. RCLONE_CONFIG = 'rclone.conf' // Assuming rclone.conf is in the workspace root - // Define the FULL path to the upstream job's workspace on the SAME agent - // This assumes both jobs run on the same agent and you know the workspace path structure - UPSTREAM_WORKSPACE = 'C:\\Jenkins\\workspace\\AzaionSuite' // **Adjust this path if necessary** - - // Define the path to the zip file relative to the upstream workspace - // Based on your description: C:\Jenkins\workspace\AzaionSuite\suite\AzaionSuite.1.4.5-YYYYMMDD-HHMMSS.zip - // We'll use a wildcard to find the specific timestamped zip - UPSTREAM_ZIP_PATH_RELATIVE = 'suite\\*.zip' // **Reverted filter to only look for .zip** - // Number of latest files to keep on Google Drive FILES_TO_KEEP = 3 + + // Define the FULL path to the directory containing the zip files. + // This job will operate directly within this directory. + // ** IMPORTANT: Replace 'C:/Jenkins/workspace/AzaionSuite/suite' with the actual path ** + TARGET_ARTIFACTS_DIR = 'C:/Jenkins/workspace/AzaionSuite/suite' // <<== UPDATE THIS + + // Define the name of the latest zip file found as an environment variable + // This makes it easier to reference in later stages + LATEST_ZIP_FILENAME = '' // This will be set dynamically in the 'Find Latest Zip and Upload' stage } stages { - stage('Initialize') { + stage('Initialize Workspace') { steps { echo "Initializing workspace on Windows agent..." - // Use standard Windows PowerShell for directory creation and cleanup - // Ensure paths are quoted for safety + // Ensure paths are quoted for safety and use backslashes for Windows paths powershell """ - # Create temporary upload directory + # Create temporary upload directory within THIS job's workspace New-Item -ItemType Directory -Force -Path "${env:WORKSPACE}\\\\${env:TMP_UPLOAD_DIR}" # Clean up previous temporary files in the upload directory @@ -50,142 +49,114 @@ pipeline { } } - // Removed the 'Copy Last Stable Artifact' stage - - stage('Copy and Upload Build') { // Combined stage for copying, finding name, and conditional upload + stage('Find Latest Zip and Upload') { // Renamed stage steps { script { // Wrap steps in a script block - echo "Starting Copy and Upload Build stage..." + echo "Starting 'Find Latest Zip and Upload' stage." - def upstreamZipFullPath = null - def copiedZipFilename = null - def copiedZipFilePath = null - def expectedZipFilenameOnDrive = null + // Change directory to the target artifacts folder where the zip files are located + dir("${env.TARGET_ARTIFACTS_DIR}") { + echo "Operating in directory: ${pwd()}" - // --- Find the full path of the zip file in the upstream workspace --- - try { - echo "Attempting to find the zip file in upstream workspace: ${env.UPSTREAM_WORKSPACE}\\\\${env.UPSTREAM_ZIP_PATH_RELATIVE}" - // Use PowerShell to find the first file matching the pattern (.zip only) - // Use -ErrorAction Stop to ensure the error is caught by Groovy try/catch - // Ensure paths are quoted for safety - upstreamZipFullPath = powershell(script: "Get-ChildItem -Path \"${env:UPSTREAM_WORKSPACE}\\\\${env:UPSTREAM_ZIP_PATH_RELATIVE}\" -ErrorAction Stop | Select-Object -First 1 -ExpandProperty FullName", returnStdout: true).trim() + // Use PowerShell to find the latest zip file based on filename timestamp + // This logic is similar to the 'Archive Latest Zip' stage from the combined pipeline + def powershellOutput = powershell(script: """ + \$zipPattern = "AzaionSuite*-*-*.zip" # Pattern for zip files + Write-Host "Searching for latest zip file matching '\$zipPattern' in '\$PWD'..." - if (upstreamZipFullPath) { - echo "Found upstream file: ${upstreamZipFullPath}" - // Extract just the filename from the full path - copiedZipFilename = upstreamZipFullPath.substring(upstreamZipFullPath.lastIndexOf('\\') + 1) - echo "Derived filename: ${copiedZipFilename}" - // DEBUG: Show the derived filename explicitly - echo "DEBUG: Derived copiedZipFilename = '${copiedZipFilename}'" + # Find all zip files matching the pattern + # Use -ErrorAction Stop to fail if Get-ChildItem fails + \$zipFiles = Get-ChildItem -Path . -Filter \$zipPattern -ErrorAction Stop - // --- Removed Workaround: Remove trailing .zip if it's .zip.zip --- - copiedZipFilePath = "${env:WORKSPACE}\\\\${env:TMP_UPLOAD_DIR}\\\\${copiedZipFilename}" // Path where it will be copied in THIS workspace - expectedZipFilenameOnDrive = copiedZipFilename // The name on Google Drive is the same as the original filename + if (\$zipFiles.Count -eq 0) { + Write-Host "No zip files matching pattern '\$zipPattern' found in '\$PWD'." + # Exit PowerShell script with a non-zero code to fail the step + exit 1 + } + Write-Host "Found \$(\$zipFiles.Count) zip file(s)." + + # --- Sorting of ZIP Files by Filename Timestamp (in PowerShell) --- + Write-Host "Sorting ZIP files by filename timestamp (YYYYMMDD-HHMMSS)..." + + # Regex to extract the timestamp from the filename + \$timestampRegex = '.*-(\\d{8}-\\d{6})\\.zip' + + # Sort the files by extracting the timestamp and converting to DateTime for accurate sorting + # Sort-Object -Descending ensures newest are first + \$sortedZipFiles = \$zipFiles | Sort-Object -Descending { + \$name = \$_.Name + \$match = [regex]::Match(\$name, \$timestampRegex) + if (\$match.Success -and \$match.Groups.Count -gt 1) { + \$timestampStr = \$match.Groups[1].Value + # Attempt to parse the timestamp string into a DateTime object + try { + [DateTime]::ParseExact(\$timestampStr, "yyyyMMdd-HHmmss", \$null) + } catch { + Write-Host "Warning: Could not parse timestamp from filename '\$name': \$(\$_.Exception.Message)" + # Handle parsing errors - treat as the oldest possible date (e.g., 1/1/0001) + # This ensures unparseable dates are placed at the end (oldest) + [DateTime]::MinValue + } + } else { + Write-Host "Warning: Filename '\$name' does not match timestamp regex." + # Handle non-matching filenames - treat as the oldest possible date + [DateTime]::MinValue + # Exit the script here if you want to fail the stage on unparseable filenames + # exit 1 + } + } + # --- End Sorting --- + + # Get the name of the latest zip file (the first one after sorting descending) + \$latestZipFile = \$sortedZipFiles | Select-Object -First 1 + + Write-Host "Identified latest zip file: '\$latestZipFile.Name'" + + # Output the latest zip filename and set it as an environment variable for Jenkins + # This uses the Jenkins 'set context' feature + Write-Host "::SET-ENV::LATEST_ZIP_FILENAME=\$(\$latestZipFile.Name)" + + exit 0 # Exit PowerShell script successfully + + """ , returnStdout: true + ) // End powershell call + + // Parse the PowerShell output to find the latest zip filename and set the Groovy environment variable + def matcher = powershellOutput =~ /::SET-ENV::LATEST_ZIP_FILENAME=(.+)/ + def latestZipFilename = null + if (matcher.find()) { + latestZipFilename = matcher.group(1).trim() + env.LATEST_ZIP_FILENAME = latestZipFilename // Set the environment variable + echo "Groovy set LATEST_ZIP_FILENAME to: ${env.LATEST_ZIP_FILENAME}" } else { - // Updated error message to reflect looking for .zip only - error("No *.zip file found in the upstream workspace '${env.UPSTREAM_WORKSPACE}\\\\${env.UPSTREAM_ZIP_PATH_RELATIVE}'. Cannot proceed.") - return // Exit script block safely + error "Could not find the latest zip filename in the PowerShell output using marker." } - } catch (e) { - echo "Error finding upstream file: ${e.message}" - error("Failed to find the zip file in the upstream workspace.") - return // Exit script block safely - } - // --- End Find upstream zip file name --- - echo "Target copy path in this workspace: ${copiedZipFilePath}" - echo "Expected zip filename on Google Drive: ${expectedZipFilenameOnDrive}" + // Ensure the latest zip filename environment variable is set before attempting upload + if (env.LATEST_ZIP_FILENAME && env.LATEST_ZIP_FILENAME != '') { + echo "Identified latest zip file for upload: ${env.LATEST_ZIP_FILENAME}" + try { + // --- Upload the latest ZIP archive directly from the current directory --- + echo "Starting upload of '${env.LATEST_ZIP_FILENAME}' directly from '${pwd()}' to ${GDRIVE_REMOTE}..." + // Use standard Windows PowerShell to execute rclone (now in PATH) + // Ensure config path and remote path are quoted, and use backslashes for Windows paths + // The source path for rclone is the filename relative to the current directory (TARGET_ARTIFACTS_DIR) + powershell """ + rclone --config "${env:WORKSPACE}\\\\${env:RCLONE_CONFIG}" copy \"${env:LATEST_ZIP_FILENAME}\" \"${env:GDRIVE_REMOTE}\" + """ + echo "Finished uploading ${env.LATEST_ZIP_FILENAME}." - // --- Get list of existing zip files on Google Drive --- - def existingZipFiles = [] - try { - echo "Checking for existing zip files on Google Drive: ${GDRIVE_REMOTE}..." - // Use standard Windows PowerShell to execute rclone (now in PATH) - // Ensure config path and remote path are quoted, and use backslashes for Windows paths - def rcloneListCommand = """ - rclone --config "${env:WORKSPACE}\\\\${RCLONE_CONFIG}" lsjson "${GDRIVE_REMOTE}" - """ - def rcloneListOutput = powershell(script: rcloneListCommand, returnStdout: true).trim() ?: '[]' - - if (!rcloneListOutput.startsWith('[') || !rcloneListOutput.endsWith(']')) { - echo "Warning: rclone lsjson did not return a valid JSON array for existing files check. Output: ${rcloneListOutput}" - // Continue, but assume no existing files to be safe - existingZipFiles = [] - } else { - def allFilesJson = readJSON text: rcloneListOutput - // Filter for zip files and ensure Name exists - // Removed workaround application here - existingZipFiles = allFilesJson.findAll { it.Name?.endsWith(".zip") }.collect { it.Name } - // DEBUG: Print the exact list of existing zip files found (without workaround) - echo "DEBUG: Exact list of existing zip files on Google Drive: ${existingZipFiles}" - } - } catch (e) { - echo "An error occurred while checking existing files on Google Drive: ${e}" - // Continue, but assume no existing files to be safe - existingZipFiles = [] - } - // --- End getting existing files list --- - - // DEBUG: Print the exact expected zip filename being checked - echo "DEBUG: Checking for existence of expected zip file: ${expectedZipFilenameOnDrive}" - - // --- Manual Check if the zip file already exists on Google Drive --- - def fileExistsOnDrive = false - for (existingFile in existingZipFiles) { - // Compare the original filename to the list from Google Drive (without workaround) - if (existingFile == expectedZipFilenameOnDrive) { - fileExistsOnDrive = true - break // Found a match, no need to check further - } - } - // --- End Manual Check --- - - if (!fileExistsOnDrive) { // Use the result of the manual check - // If we reach here, the zip file does NOT exist on Google Drive, so proceed with copying and uploading - - echo "Zip file ${expectedZipFilenameOnDrive} does not exist on Google Drive. Proceeding with copying and uploading." - - try { - // --- Copy the zip file from the upstream workspace --- - // Use the original upstreamZipFullPath for the source path - echo "Copying zip file from '${upstreamZipFullPath}' to '${copiedZipFilePath}'..." - // DEBUG: Show source and destination paths for Copy-Item - echo "DEBUG: Copy-Item Source: '${upstreamZipFullPath}'" - echo "DEBUG: Copy-Item Destination: '${copiedZipFilePath}'" - // Use standard Windows PowerShell Copy-Item - // Ensure paths are quoted for safety and use backslashes - powershell "Copy-Item -Path \"${upstreamZipFullPath}\" -Destination \"${copiedZipFilePath}\" -Force" - echo "Successfully copied zip file." - - // --- Upload the copied ZIP archive to Google Drive --- - // Use the original filename for the upload source path - echo "Starting upload of '${copiedZipFilename}' to ${GDRIVE_REMOTE}..." - // DEBUG: Show source path for rclone copy - echo "DEBUG: rclone copy Source: '${copiedZipFilePath}'" - // Use standard Windows PowerShell to execute rclone (now in PATH) - powershell """ - rclone --config "${env:WORKSPACE}\\\\${RCLONE_CONFIG}" copy \"${copiedZipFilePath}\" \"${GDRIVE_REMOTE}\" - """ - echo "Finished uploading ${copiedZipFilename}." - - } catch (e) { - echo "ERROR processing build (copy/upload): ${e}" - // Consider adding a flag here to mark that at least one build failed processing - // This could be checked in a later post block to decide if the overall build should be marked as unstable - error("Failed to copy or upload build: ${e.message}") // Fail the stage on error - } finally { - // Clean up the copied zip file after upload attempt - // Use standard Windows PowerShell Remove-Item - echo "Cleaning up copied zip file: ${copiedZipFilePath}" - powershell "Remove-Item -Force \"${copiedZipFilePath}\" -ErrorAction SilentlyContinue" - } - } else { - // If the file DOES exist on Google Drive, print the skipping message - echo "Skipping upload: ${expectedZipFilenameOnDrive} already exists on Google Drive." - // No file was copied in this case, so no cleanup needed in finally - } + } catch (e) { + echo "ERROR uploading build: ${e}" + error("Failed to upload latest zip file: ${e.message}") // Fail the stage on error + } + // No cleanup needed in finally block here as the file is not copied to a temp location first + } else { + error "LATEST_ZIP_FILENAME environment variable was not set or was empty. Cannot upload." + } + } // End dir block } // end script } // end steps } // end stage @@ -195,7 +166,7 @@ pipeline { script { // Wrap script logic in a script block echo "Starting Google Drive retention process (using PowerShell)..." // Ensure rclone is installed and in PATH on the Windows agent, - // and the Jenkins agent user has read access to '${env:WORKSPACE}\\\\${RCLONE_CONFIG}'. + // and the Jenkins agent user has read access to '${env:WORKSPACE}\\\\${env.RCLONE_CONFIG}'. // PowerShell script block for retention logic powershell """ @@ -242,6 +213,8 @@ pipeline { Write-Host "Warning: Filename '\$name' does not match timestamp regex." # Handle non-matching filenames - treat as the oldest possible date [DateTime]::MinValue + # Exit the script here if you want to fail the stage on unparseable filenames + # exit 1 } } # --- End Sorting --- diff --git a/build/jenkins/zip b/build/jenkins/zip index 1f8f2e8..0e47f00 100644 --- a/build/jenkins/zip +++ b/build/jenkins/zip @@ -30,7 +30,7 @@ pipeline { // Define the name of the created zip file as an environment variable // This makes it easier to reference in later stages - CREATED_ZIP_FILENAME = '' // This will be set dynamically + CREATED_ZIP_FILENAME = '' // This will be set dynamically by capturing PowerShell output } stages { @@ -49,12 +49,13 @@ pipeline { // These are now defined as literals within the PowerShell script string // def exePattern = 'AzaionSuite*.exe' // def binPattern = 'AzaionSuite*.bin' - // Define defaultVersion here, used if version extraction fails + // Define defaultVersion here, used if no exe is found // def defaultVersion = '1.0.0' // Use a powershell step to perform file finding, filename extraction, timestamp generation, and zipping - powershell ''' + // Capture the output of the powershell script + def zipFilenameOutput = powershell returnStdout: true, script: ''' $ErrorActionPreference = "Stop" # Stop the script on any error $sevenZipExe = "$env:SEVEN_ZIP_PATH\\7z.exe" @@ -89,7 +90,7 @@ pipeline { # --- Zipping Logic --- - # Get current date and time in YYYYMMDD-HHmmss format + # Get current date and time inYYYYMMDD-HHmmss format $timestamp = (Get-Date -Format "yyyyMMdd-HHmmss") # Construct the zip filename using the base filename and timestamp @@ -124,13 +125,19 @@ pipeline { Write-Host "Zip archive created successfully by 7-Zip: $zipFilename" - # Output the zip filename and set it as an environment variable for Jenkins - # This uses the Jenkins 'set context' feature - Write-Host "::SET-ZIP-FILENAME::$zipFilename" - Write-Host "::SET-ENV::CREATED_ZIP_FILENAME=$zipFilename" + # Output the zip filename to standard output for the Groovy script to capture + # Write-Host "::SET-ZIP-FILENAME::$zipFilename" # Removed, using Write-Output instead + # Write-Host "::SET-ENV::CREATED_ZIP_FILENAME=$zipFilename" # Removed, using Write-Output instead + Write-Output $zipFilename exit 0 ''' // End powershell script + + // Capture the output and set the environment variable + // The PowerShell script outputs the zip filename as the last line + env.CREATED_ZIP_FILENAME = zipFilenameOutput.trim() + echo "Set CREATED_ZIP_FILENAME environment variable to: ${env.CREATED_ZIP_FILENAME}" + } // End dir block } } @@ -145,14 +152,14 @@ pipeline { // The zip filename was set as an environment variable in the previous stage def createdZipFilename = env.CREATED_ZIP_FILENAME - if (createdZipFilename) { + if (createdZipFilename && !createdZipFilename.trim().isEmpty()) { echo "Identified created zip file for archiving: ${createdZipFilename}" // Archive the created zip file using Jenkins built-in step // The zip file is created in the MAIN_BUILD_ARTIFACTS_DIR by the Batch script archiveArtifacts artifacts: "${createdZipFilename}", fingerprint: true echo "Archive step completed." } else { - error "CREATED_ZIP_FILENAME environment variable was not set. Cannot archive." + error "CREATED_ZIP_FILENAME environment variable was not set or was empty. Cannot archive." } } // End dir block }