Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions vars/ansible.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Ansible operations for Jenkins pipelines (Docker-based)

// Get the Docker image to use for ansible commands
def _getImage() {
return 'rancher-infra-tools:latest'
}

// Run an Ansible playbook
// [ dir: string, inventory: string, playbook: string, extraVars?: Map, tags?: string, limit?: string, verbose?: bool ]
def runPlaybook(Map config) {
if (!(config.dir && config.inventory && config.playbook)) {
error 'Directory, inventory, and playbook must be provided.'
}

steps.echo "Running Ansible playbook: ${config.playbook}"

def ansibleArgs = [
"ansible-playbook",
"-i ${config.inventory}",
config.playbook
]

// Add extra variables if provided
if (config.extraVars) {
def extraVarsStr = config.extraVars.collect { k, v ->
"${k}=${v}"
}.join(' ')
ansibleArgs.add("--extra-vars \"${extraVarsStr}\"")
}

// Add tags if provided
if (config.tags) {
ansibleArgs.add("--tags ${config.tags}")
}

// Add limit if provided
if (config.limit) {
ansibleArgs.add("--limit ${config.limit}")
}

// Add verbosity if requested
if (config.verbose) {
ansibleArgs.add("-vvv")
}

def ansibleCommand = "cd ${config.dir} && ${ansibleArgs.join(' ')}"

def workspace = steps.pwd()
def dockerCommand = "docker run --rm --platform linux/amd64 -v ${workspace}:/workspace -v ${workspace}/.ssh:/root/.ssh:ro -w /workspace ${_getImage()} sh -c \"${ansibleCommand}\""

def status = steps.sh(script: dockerCommand, returnStatus: true)

if (status != 0) {
error "Ansible playbook execution failed with status ${status}"
}

steps.echo "Ansible playbook completed successfully"
}

// Write variables to Ansible inventory group_vars
// [ path: string, content: string ]
def writeInventoryVars(Map config) {
if (!(config.path && config.content)) {
error 'Path and content must be provided.'
}

steps.echo "Writing Ansible variables to ${config.path}"

try {
steps.writeFile file: config.path, text: config.content
steps.echo "Ansible variables written successfully"
} catch (e) {
error "Failed to write Ansible variables: ${e.message}"
}
}

// Validate Ansible inventory
// [ dir: string, inventory: string ]
def validateInventory(Map config) {
if (!(config.dir && config.inventory)) {
error 'Directory and inventory must be provided.'
}

steps.echo "Validating Ansible inventory: ${config.inventory}"

def validateCommand = "cd ${config.dir} && ansible-inventory -i ${config.inventory} --list > /dev/null"

def status = steps.sh(script: validateCommand, returnStatus: true)

if (status != 0) {
steps.echo "Warning: Ansible inventory validation failed"
return false
}

steps.echo "Ansible inventory validated successfully"
return true
}

// Run ansible-playbook in a container
// [ container: string, dir: string, inventory: string, playbook: string, extraVars?: Map, tags?: string, envVars?: Map ]
def runPlaybookInContainer(Map config) {
if (!(config.container && config.dir && config.inventory && config.playbook)) {
error 'Container, directory, inventory, and playbook must be provided.'
}

steps.echo "Running Ansible playbook in container: ${config.container}"

def ansibleArgs = [
"ansible-playbook",
"-i ${config.inventory}",
config.playbook
]

if (config.extraVars) {
def extraVarsStr = config.extraVars.collect { k, v ->
"${k}=${v}"
}.join(' ')
ansibleArgs.add("--extra-vars \"${extraVarsStr}\"")
}

if (config.tags) {
ansibleArgs.add("--tags ${config.tags}")
}

def envArgs = []
if (config.envVars) {
envArgs = config.envVars.collect { k, v -> "-e ${k}=${v}" }
}

def dockerCommand = [
"docker run --rm",
envArgs.join(' '),
"-v ${config.dir}:/workspace",
"-w /workspace",
config.container,
"-c",
"\"${ansibleArgs.join(' ')}\""
].join(' ')

def status = steps.sh(script: dockerCommand, returnStatus: true)

if (status != 0) {
error "Ansible playbook in container failed with status ${status}"
}

steps.echo "Ansible playbook in container completed successfully"
}
174 changes: 174 additions & 0 deletions vars/infrastructure.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// High-level infrastructure helpers for Jenkins pipelines

// Write configuration file with variable substitutions
// [ path: string, content: string, substitutions?: Map ]
def writeConfig(Map config) {
if (!(config.path && config.content)) {
error 'Path and content must be provided.'
}

steps.echo "Writing configuration to ${config.path}"

def processedContent = config.content

// Apply substitutions if provided
if (config.substitutions) {
config.substitutions.each { key, value ->
processedContent = processedContent.replace("\${${key}}", value.toString())
}
}

try {
// Ensure directory exists
def dirPath = config.path.substring(0, config.path.lastIndexOf('/'))
steps.sh "mkdir -p ${dirPath}"

steps.writeFile file: config.path, text: processedContent
steps.echo "Configuration written successfully to ${config.path}"
} catch (e) {
error "Failed to write configuration: ${e.message}"
}
}

// Decode and write SSH key from base64, and generate public key
// [ keyContent: string, keyName: string, dir?: string ]
def writeSshKey(Map config) {
if (!(config.keyContent && config.keyName)) {
error 'SSH key content and name must be provided.'
}

def sshDir = config.dir ?: '.ssh'

steps.echo "Writing SSH key: ${config.keyName}"

try {
// Create SSH directory if it doesn't exist
steps.sh "mkdir -p ${sshDir}"

// Decode base64 key content
def decoded = new String(config.keyContent.decodeBase64())

// Write private key file
def keyPath = "${sshDir}/${config.keyName}"
steps.writeFile file: keyPath, text: decoded

// Set proper permissions for private key
steps.sh "chmod 600 ${keyPath}"

// Generate public key from private key
// Strip extension (e.g., .pem) and add .pub
def keyBaseName = config.keyName.replaceAll(/\.[^.]+$/, '')
def pubKeyPath = "${sshDir}/${keyBaseName}.pub"
steps.sh "ssh-keygen -y -f ${keyPath} > ${pubKeyPath}"
steps.sh "chmod 644 ${pubKeyPath}"

steps.echo "SSH key pair written successfully: ${keyPath} and ${pubKeyPath}"
return keyPath
} catch (e) {
error "Failed to write SSH key: ${e.message}"
}
}

// Generate unique workspace name
// [ prefix?: string, includeTimestamp?: bool ]
def generateWorkspaceName(Map config = [:]) {
def prefix = config.prefix ?: 'jenkins_workspace'
def buildNumber = env.BUILD_NUMBER ?: 'unknown'

if (config.includeTimestamp != false) {
def timestamp = new Date().format('yyyyMMddHHmmss')
return "${prefix}_${buildNumber}_${timestamp}"
}

return "${prefix}_${buildNumber}"
}

// Parse YAML-like content and apply environment variable substitutions
// [ content: string, envVars: Map ]
def parseAndSubstituteVars(Map config) {
if (!config.content) {
error 'Content must be provided.'
}

def processedContent = config.content

if (config.envVars) {
config.envVars.each { key, value ->
// Replace both ${VAR} and $VAR patterns
processedContent = processedContent.replaceAll(/\$\{${key}\}/, value.toString())
processedContent = processedContent.replaceAll(/\$${key}(?![a-zA-Z0-9_])/, value.toString())
}
}

return processedContent
}

// Create directory structure
// [ paths: List<String> ]
def createDirectories(Map config) {
if (!config.paths) {
error 'Paths must be provided.'
}

config.paths.each { path ->
steps.echo "Creating directory: ${path}"
steps.sh "mkdir -p ${path}"
}
}

// Clean up workspace artifacts
// [ paths: List<String>, force?: bool ]
def cleanupArtifacts(Map config) {
if (!config.paths) {
error 'Paths must be provided.'
}

steps.echo "Cleaning up artifacts"

def forceFlag = config.force ? '-f' : ''

config.paths.each { path ->
try {
steps.sh "rm -rf ${forceFlag} ${path}"
steps.echo "Removed: ${path}"
} catch (e) {
steps.echo "Warning: Could not remove ${path}: ${e.message}"
}
}
}

// Archive workspace name for later use
// [ workspaceName: string, fileName?: string ]
def archiveWorkspaceName(Map config) {
if (!config.workspaceName) {
error 'Workspace name must be provided.'
}

def fileName = config.fileName ?: 'workspace_name.txt'

steps.echo "Archiving workspace name: ${config.workspaceName}"

try {
steps.writeFile file: fileName, text: config.workspaceName
steps.archiveArtifacts artifacts: fileName, fingerprint: true
steps.echo "Workspace name archived to ${fileName}"
} catch (e) {
error "Failed to archive workspace name: ${e.message}"
}
}

// Extract archived workspace name
// [ fileName?: string ]
def getArchivedWorkspaceName(Map config = [:]) {
def fileName = config.fileName ?: 'workspace_name.txt'

steps.echo "Retrieving archived workspace name from ${fileName}"

try {
def workspaceName = steps.readFile(fileName).trim()
steps.echo "Retrieved workspace name: ${workspaceName}"
return workspaceName
} catch (e) {
error "Failed to retrieve workspace name: ${e.message}"
}
}
Loading