-
Notifications
You must be signed in to change notification settings - Fork 1
Add Docker-based Tofu and Ansible operations #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d10df16
76eba50
7460aed
afc29c0
be1a487
87e160c
61a5552
b3c3e4a
28ca749
6d8cd9f
a0c17cd
9f69bef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| // 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") | ||
| } | ||
|
|
||
| // Properly escape ansible arguments to prevent command injection | ||
| def escapedAnsibleArgs = ansibleArgs.collect { arg -> | ||
| // Escape any double quotes and backslashes in the argument | ||
| arg.replace('\\', '\\\\').replace('"', '\\"') | ||
| } | ||
|
|
||
| def ansibleCommand = "cd ${config.dir} && ${escapedAnsibleArgs.join(' ')}" | ||
|
|
||
| def workspace = steps.pwd() | ||
| def globalConfig = new config() | ||
| def platform = globalConfig.getDockerPlatform() | ||
| def dockerCommand = "docker run --rm --platform ${platform} -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}" | ||
|
|
||
| // Properly escape inventory path to prevent command injection | ||
| def escapedDir = config.dir.replace('\\', '\\\\').replace('"', '\\"') | ||
| def escapedInventory = config.inventory.replace('\\', '\\\\').replace('"', '\\"') | ||
|
|
||
| def validateCommand = "cd ${escapedDir} && ansible-inventory -i ${escapedInventory} --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)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another nit, if config.container is just the container name, maybe we could generate one and return it or just name it config.container_name. Looks good anyway if you don't want to change this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not understanding how this makes it better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe it doesn't, I guess this well within the subjective realm so this is only a suggestion. My thought was that you could prefer an alternative that has less arguments. |
||
| 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}" } | ||
| } | ||
|
|
||
| // Properly escape ansible arguments to prevent command injection | ||
| def escapedAnsibleArgs = ansibleArgs.collect { arg -> | ||
| // Escape any double quotes and backslashes in the argument | ||
| arg.replace('\\', '\\\\').replace('"', '\\"') | ||
| } | ||
|
|
||
| def dockerCommand = [ | ||
| "docker run --rm", | ||
| envArgs.join(' '), | ||
| "-v ${config.dir}:/workspace", | ||
| "-w /workspace", | ||
| config.container, | ||
| "-c", | ||
| "\"${escapedAnsibleArgs.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" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // Centralized configuration management for Jenkins shared library | ||
|
|
||
| // Default configuration values | ||
| def getDefaultConfig() { | ||
| return [ | ||
| // Docker configuration | ||
| docker: [ | ||
| images: [ | ||
| infraTools: env.RANCHER_INFRA_TOOLS_IMAGE ?: 'rancher-infra-tools:latest' | ||
| ], | ||
| platform: env.DOCKER_PLATFORM ?: 'linux/amd64', | ||
| defaultEnvFile: env.DOCKER_DEFAULT_ENV_FILE ?: '.env' | ||
| ], | ||
|
|
||
| // Testing configuration | ||
| testing: [ | ||
| defaultTags: env.TEST_DEFAULT_TAGS ?: 'validation', | ||
| defaultTimeout: env.TEST_DEFAULT_TIMEOUT ?: '60m', | ||
| defaultResultsXML: env.TEST_DEFAULT_RESULTS_XML ?: 'results.xml', | ||
| defaultResultsJSON: env.TEST_DEFAULT_RESULTS_JSON ?: 'results.json' | ||
| ], | ||
|
|
||
| // Naming conventions | ||
| naming: [ | ||
| containerSuffix: env.CONTAINER_SUFFIX ?: 'test', | ||
| imagePrefix: env.IMAGE_PREFIX ?: 'rancher-validation-' | ||
| ], | ||
|
|
||
| // UI/Display configuration | ||
| ui: [ | ||
| colorMapName: env.COLOR_MAP_NAME ?: 'XTerm', | ||
| defaultFg: env.DEFAULT_FG_COLOR ? env.DEFAULT_FG_COLOR.toInteger() : 2, | ||
| defaultBg: env.DEFAULT_BG_COLOR ? env.DEFAULT_BG_COLOR.toInteger() : 1 | ||
| ], | ||
|
|
||
| // Path configuration | ||
| paths: [ | ||
| defaultDir: env.DEFAULT_DIR ?: '.', | ||
| sshDir: env.SSH_DIR ?: '.ssh', | ||
| validationDir: env.VALIDATION_DIR ?: 'validation' | ||
| ] | ||
| ] | ||
| } | ||
|
|
||
| // Get specific configuration section | ||
| def getConfig(String section) { | ||
| def config = getDefaultConfig() | ||
| return config[section] ?: [:] | ||
| } | ||
|
|
||
| // Get specific configuration value with fallback | ||
| def getConfigValue(String section, String key, def defaultValue = null) { | ||
| def config = getDefaultConfig() | ||
| def sectionConfig = config[section] ?: [:] | ||
| return sectionConfig[key] ?: defaultValue | ||
| } | ||
|
|
||
| // Merge user configuration with defaults | ||
| def mergeConfig(Map userConfig = [:]) { | ||
| def defaultConfig = getDefaultConfig() | ||
|
|
||
| // Deep merge configuration | ||
| return deepMerge(defaultConfig, userConfig) | ||
| } | ||
|
|
||
| // Deep merge two maps recursively | ||
| def deepMerge(Map target, Map source) { | ||
| source.each { key, value -> | ||
| if (value instanceof Map && target[key] instanceof Map) { | ||
| target[key] = deepMerge(target[key], value) | ||
| } else { | ||
| target[key] = value | ||
| } | ||
| } | ||
| return target | ||
| } | ||
|
|
||
| // Validate configuration | ||
| def validateConfig(Map config) { | ||
| def errors = [] | ||
|
|
||
| // Validate required Docker configuration | ||
| if (!config.docker?.images?.infraTools) { | ||
| errors.add("Docker infra tools image is required") | ||
| } | ||
|
|
||
| if (!config.docker?.platform) { | ||
| errors.add("Docker platform is required") | ||
| } | ||
|
|
||
| // Validate required testing configuration | ||
| if (!config.testing?.defaultTags) { | ||
| errors.add("Default test tags are required") | ||
| } | ||
|
|
||
| if (!config.testing?.defaultTimeout) { | ||
| errors.add("Default test timeout is required") | ||
| } | ||
|
|
||
| if (errors) { | ||
| error "Configuration validation failed: ${errors.join(', ')}" | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| // Get Docker image name | ||
| def getDockerImage(String imageType = 'infraTools') { | ||
| def dockerConfig = getConfig('docker') | ||
| return dockerConfig.images[imageType] | ||
| } | ||
|
|
||
| // Get Docker platform | ||
| def getDockerPlatform() { | ||
| return getConfigValue('docker', 'platform') | ||
| } | ||
|
|
||
| // Get default test configuration | ||
| def getTestConfig() { | ||
| return getConfig('testing') | ||
| } | ||
|
|
||
| // Get naming configuration | ||
| def getNamingConfig() { | ||
| return getConfig('naming') | ||
| } | ||
|
|
||
| // Get UI configuration | ||
| def getUIConfig() { | ||
| return getConfig('ui') | ||
| } | ||
|
|
||
| // Get path configuration | ||
| def getPathConfig() { | ||
| return getConfig('paths') | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.