Skip to content
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

Feat ansible playbook: Added Ansible Playbook Conversion step #262

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions convert/jenkinsjson/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ func collectStepsWithID(currentNode jenkinsjson.Node, stepWithIDList *[]StepWith
case "unarchive":
*stepWithIDList = append(*stepWithIDList, StepWithID{Step: jenkinsjson.ConvertUnarchive(currentNode, currentNode.ParameterMap), ID: id})

case "ansiblePlaybook":
*stepWithIDList = append(*stepWithIDList, StepWithID{Step: jenkinsjson.ConvertAnsiblePlaybook(currentNode, currentNode.ParameterMap), ID: id})

default:
placeholderStr := fmt.Sprintf("echo %q", "This is a place holder for: "+currentNode.AttributesMap["jenkins.pipeline.step.type"])
b, err := json.MarshalIndent(currentNode.ParameterMap, "", " ")
Expand Down
68 changes: 68 additions & 0 deletions convert/jenkinsjson/convertTestFiles/ansible/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
pipeline {
agent any

environment {
PATH = "/opt/homebrew/bin:$PATH"
VAULT_PASSWORD = 'secretpassword'
ANSIBLE_INVENTORY = 'inventory.ini'
ANSIBLE_PLAYBOOK = 'test-playbook.yml'
ANSIBLE_VAULT_TMP_PATH = '/tmp/vault-password.txt'
}
stages {
stage('Install Homebrew') {
steps {
script {
sh '''
if ! command -v brew &> /dev/null; then
echo "Homebrew not found. Installing..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
echo "Homebrew is already installed."
fi
'''
}
}
}

stage('Install Ansible') {
steps {
script {
sh 'brew install ansible'
}
}
}

stage('Run Ansible Playbook') {
steps {
ansiblePlaybook(
playbook: "${ANSIBLE_PLAYBOOK}",
become: true, // Enable privilege escalation
becomeUser: 'root', // Specify user for escalation
checkMode: false, // Disable check mode
colorized: true, // Enable colored output
credentialsId: '', // Optional credentials ID
disableHostKeyChecking: true, // Disable host key checking
dynamicInventory: false, // Use static inventory
extraVars: [ // Specify extra variables
var1: 'value1',
var2: 'value2'
],
extras: '--diff --timeout=30', // Additional flags
forks: 5, // Number of parallel processes
hostKeyChecking: false, // Alias for disableHostKeyChecking
installation: 'Default', // Specify installation
inventory: 'inventory.ini', // Inventory file path
inventoryContent: '', // Inline inventory content
limit: 'all', // Restrict to specific hosts
skippedTags: 'skip_tag1,skip_tag2', // Tags to skip
startAtTask: 'start_task_name', // Start execution at a specific task
sudo: false,
sudoUser: '',
tags: 'tag1,tag2', // Tags to execute
vaultCredentialsId: '', // Vault credentials ID
vaultTmpPath: "${ANSIBLE_VAULT_TMP_PATH}" // Path to vault password file
)
}
}
}
}
22 changes: 22 additions & 0 deletions convert/jenkinsjson/convertTestFiles/ansible/ansible.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- step:
identifier: ansibleplaybookb2fcf1
name: Ansible_Playbook
spec:
image: plugins/ansible
settings:
become: "true"
become_user: root
check: "false"
extra_vars: '{"var1":"value1","var2":"value2"}'
forks: "5"
inventory: inventory.ini
limit: localhost
list_hosts: ""
playbook: example-playbook.yml
skip_tags: debug
start_at_task: ""
tags: setup,deploy
vault_id: ""
vault_password: <secrets.getValue('vault-password')>
timeout: ""
type: Plugin
51 changes: 51 additions & 0 deletions convert/jenkinsjson/convertTestFiles/ansible/ansible_snippet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"spanId": "b2fcf14e7a77a8f7",
"traceId": "7d1ddf3bb4b373d3df9fbd587b36d037",
"parent": "ansible",
"all-info": "span(name: ansiblePlaybook, spanId: b2fcf14e7a77a8f7, parentSpanId: a9d15e0d263ba34b, traceId: 7d1ddf3bb4b373d3df9fbd587b36d037, attr: ci.pipeline.run.user:SYSTEM;harness-attribute:{\n \"forks\" : 5,\n \"hostKeyChecking\" : false,\n \"disableHostKeyChecking\" : true,\n \"startAtTask\" : \"\",\n \"dynamicInventory\" : false,\n \"extras\" : \"--diff --timeout=30\",\n \"credentialsId\" : \"\",\n \"inventory\" : \"localhost,\",\n \"becomeUser\" : \"root\",\n \"colorized\" : true,\n \"tags\" : \"setup,deploy\",\n \"inventoryContent\" : \"\",\n \"sudoUser\" : \"\",\n \"vaultCredentialsId\" : \"\",\n \"checkMode\" : false,\n \"skippedTags\" : \"debug\",\n \"installation\" : \"Default\",\n \"vaultTmpPath\" : \"/tmp/vault-password.txt\",\n \"limit\" : \"localhost\",\n \"extraVars\" : {\n \"var1\" : \"value1\",\n \"var2\" : \"value2\"\n },\n \"sudo\" : false,\n \"playbook\" : \"example-playbook.yml\",\n \"become\" : true\n};harness-attribute-extra-pip: com.cloudbees.workflow.rest.endpoints.FlowNodeAPI@6c0f4f7:com.cloudbees.workflow.rest.endpoints.FlowNodeAPI@6c0f4f7;harness-attribute-extra-pip: io.jenkins.plugins.opentelemetry.MigrateHarnessUrlChildAction@7982defe:io.jenkins.plugins.opentelemetry.MigrateHarnessUrlChildAction@7982defe;harness-attribute-extra-pip: org.jenkinsci.plugins.workflow.actions.TimingAction@35580470:org.jenkinsci.plugins.workflow.actions.TimingAction@35580470;harness-others:;jenkins.pipeline.step.id:27;jenkins.pipeline.step.name:Invoke an ansible playbook;jenkins.pipeline.step.plugin.name:ansible;jenkins.pipeline.step.plugin.version:403.v8d0ca_dcb_b_502;jenkins.pipeline.step.type:ansiblePlaybook;)",
"name": "ansible #95",
"attributesMap": {
"harness-others": "",
"jenkins.pipeline.step.name": "Invoke an ansible playbook",
"ci.pipeline.run.user": "SYSTEM",
"harness-attribute-extra-pip: io.jenkins.plugins.opentelemetry.MigrateHarnessUrlChildAction@7982defe": "io.jenkins.plugins.opentelemetry.MigrateHarnessUrlChildAction@7982defe",
"jenkins.pipeline.step.id": "27",
"harness-attribute-extra-pip: org.jenkinsci.plugins.workflow.actions.TimingAction@35580470": "org.jenkinsci.plugins.workflow.actions.TimingAction@35580470",
"jenkins.pipeline.step.type": "ansiblePlaybook",
"harness-attribute": "{\n \"forks\" : 5,\n \"hostKeyChecking\" : false,\n \"disableHostKeyChecking\" : true,\n \"startAtTask\" : \"\",\n \"dynamicInventory\" : false,\n \"extras\" : \"--diff --timeout=30\",\n \"credentialsId\" : \"\",\n \"inventory\" : \"localhost,\",\n \"becomeUser\" : \"root\",\n \"colorized\" : true,\n \"tags\" : \"setup,deploy\",\n \"inventoryContent\" : \"\",\n \"sudoUser\" : \"\",\n \"vaultCredentialsId\" : \"\",\n \"checkMode\" : false,\n \"skippedTags\" : \"debug\",\n \"installation\" : \"Default\",\n \"vaultTmpPath\" : \"/tmp/vault-password.txt\",\n \"limit\" : \"localhost\",\n \"extraVars\" : {\n \"var1\" : \"value1\",\n \"var2\" : \"value2\"\n },\n \"sudo\" : false,\n \"playbook\" : \"example-playbook.yml\",\n \"become\" : true\n}",
"jenkins.pipeline.step.plugin.name": "ansible",
"jenkins.pipeline.step.plugin.version": "403.v8d0ca_dcb_b_502",
"harness-attribute-extra-pip: com.cloudbees.workflow.rest.endpoints.FlowNodeAPI@6c0f4f7": "com.cloudbees.workflow.rest.endpoints.FlowNodeAPI@6c0f4f7"
},
"type": "Run Phase Span",
"parentSpanId": "a9d15e0d263ba34b",
"parameterMap": {
"forks": 5,
"hostKeyChecking": false,
"disableHostKeyChecking": true,
"startAtTask": "",
"dynamicInventory": false,
"extras": "--diff --timeout=30",
"credentialsId": "",
"inventory": "inventory.ini",
"becomeUser": "root",
"colorized": true,
"tags": "setup,deploy",
"inventoryContent": "",
"sudoUser": "",
"vaultCredentialsId": "",
"checkMode": false,
"skippedTags": "debug",
"installation": "Default",
"vaultTmpPath": "/tmp/vault-password.txt",
"limit": "localhost",
"extraVars": {
"var2": "value2",
"var1": "value1"
},
"sudo": false,
"playbook": "example-playbook.yml",
"become": true
},
"spanName": "ansiblePlaybook"
}
97 changes: 97 additions & 0 deletions convert/jenkinsjson/json/ansible.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package json

import (
"fmt"
"strconv"
"strings"

harness "github.com/drone/spec/dist/go"
)

// ConvertAnsiblePlaybook creates a Harness step for Ansible Playbook plugin.
func ConvertAnsiblePlaybook(node Node, arguments map[string]interface{}) *harness.Step {
playbook, _ := arguments["playbook"].(string)
become, _ := arguments["become"].(bool)
becomeUser, _ := arguments["becomeUser"].(string)
checkMode, _ := arguments["checkMode"].(bool)
hostKeyChecking, _ := arguments["hostKeyChecking"].(string)
inventory, _ := arguments["inventory"].(string)
limit, _ := arguments["limit"].(string)
skippedTags, _ := arguments["skippedTags"].(string)
startAtTask, _ := arguments["startAtTask"].(string)
tags, _ := arguments["tags"].(string)
vaultCredentialsId, _ := arguments["vaultCredentialsId"].(string)

var forks int
if value, ok := arguments["forks"]; ok {
switch v := value.(type) {
case float64:
forks = int(v) // JSON numbers are often float64
fmt.Println("forks is float64, converted to int:", forks)
case int:
forks = v
fmt.Println("forks is int:", forks)
case string:
// Convert string to int
if intValue, err := strconv.Atoi(v); err == nil {
forks = intValue
fmt.Println("forks is string, converted to int:", forks)
} else {
fmt.Println("Failed to convert forks from string to int:", err)
}
default:
fmt.Println("forks has an unexpected type:", v)
forks = 0 // Default value if type assertion fails
}
} else {
fmt.Println("forks key not found in arguments")
}

// Handle extraVars
var extraVars string
if value, ok := arguments["extraVars"]; ok {
switch v := value.(type) {
case []string:
// Already in correct format
extraVars = strings.Join(v, " ")
case map[string]interface{}:
// Convert map to "key=value" strings
var extraVarsList []string
for key, val := range v {
extraVarsList = append(extraVarsList, fmt.Sprintf("%s=%v", key, val))
}
extraVars = strings.Join(extraVarsList, ",")
default:
fmt.Println("Unexpected type for extraVars:", v)
}
} else {
fmt.Println("extraVars key not found in arguments")
}

convertAnsiblePlaybook := &harness.Step{
Name: "Ansible_Playbook",
Type: "plugin",
Id: SanitizeForId(node.SpanName, node.SpanId),
Spec: &harness.StepPlugin{
Image: "plugins/ansible",
With: map[string]interface{}{
"playbook": playbook,
"become": become,
"become_user": becomeUser,
"check": checkMode,
"extra_vars": extraVars,
"forks": forks,
"list_hosts": hostKeyChecking,
"inventory": inventory,
"limit": limit,
"skip_tags": skippedTags,
"start_at_task": startAtTask,
"tags": tags,
"vault_id": vaultCredentialsId,
"vault_password": "<secrets.getValue('vault-password')>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vault_password is optional and should be called only when seen in the trace - this can go under if statement

},
},
}

return convertAnsiblePlaybook
}
46 changes: 46 additions & 0 deletions convert/jenkinsjson/json/ansible_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package json

import (
"testing"

harness "github.com/drone/spec/dist/go"
"github.com/google/go-cmp/cmp"
)

func TestConvertAnsiblePlaybook(t *testing.T) {
var tests []runner
tests = append(tests, prepare(t, "/ansible/ansible_snippet", &harness.Step{
Id: "ansiblePlaybookb2fcf1",
Name: "Ansible_Playbook",
Type: "plugin",
Spec: &harness.StepPlugin{
Image: "plugins/ansible",
With: map[string]interface{}{
"become": true,
"become_user": "root",
"check": false,
"extra_vars": "var2=value2,var1=value1",
"forks": 5,
"inventory": "inventory.ini",
"limit": "localhost",
"list_hosts": "",
"playbook": "example-playbook.yml",
"skip_tags": "debug",
"start_at_task": "",
"tags": "setup,deploy",
"vault_id": "",
"vault_password": "<secrets.getValue('vault-password')>",
},
},
}))

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ConvertAnsiblePlaybook(tt.input, tt.input.ParameterMap)

if diff := cmp.Diff(got, tt.want); diff != "" {
t.Errorf("ConvertAnsiblePlaybook() mismatch (-want +got):\n%s", diff)
}
})
}
}
64 changes: 63 additions & 1 deletion samples/jenkins/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ pipeline {
DOCKER_IMAGE = 'user1/testimage'
DOCKER_TAG = 'latest'
ARTIFACTORY_SERVER = 'artifactory-id'
//Ansible Playbook
PATH = "/opt/homebrew/bin:$PATH"
VAULT_PASSWORD = 'secretpassword'
ANSIBLE_INVENTORY = 'inventory.ini'
ANSIBLE_PLAYBOOK = 'test-playbook.yml'
ANSIBLE_VAULT_TMP_PATH = '/tmp/vault-password.txt'
}

stages {
stage('Plugin Build') {
steps {
Expand Down Expand Up @@ -878,5 +884,61 @@ line3'''
gatlingArchive() // or archiveArtifacts(artifacts: 'target/gatling/index.html')
}
}
//Ansible-Playbook
stage('Install Homebrew') {
steps {
script {
sh '''
if ! command -v brew &> /dev/null; then
echo "Homebrew not found. Installing..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
echo "Homebrew is already installed."
fi
'''
}
}
}

stage('Install Ansible') {
steps {
script {
sh 'brew install ansible'
}
}
}

stage('Run Ansible Playbook') {
steps {
ansiblePlaybook(
playbook: "${ANSIBLE_PLAYBOOK}",
become: true, // Enable privilege escalation
becomeUser: 'root', // Specify user for escalation
checkMode: false, // Disable check mode
colorized: true, // Enable colored output
credentialsId: '', // Optional credentials ID
disableHostKeyChecking: true, // Disable host key checking
dynamicInventory: false, // Use static inventory
extraVars: [ // Specify extra variables
var1: 'value1',
var2: 'value2'
],
extras: '--diff --timeout=30', // Additional flags
forks: 5, // Number of parallel processes
hostKeyChecking: false, // Alias for disableHostKeyChecking
installation: 'Default', // Specify installation
inventory: 'inventory.ini', // Inventory file path
inventoryContent: '', // Inline inventory content
limit: 'all', // Restrict to specific hosts
skippedTags: 'skip_tag1,skip_tag2', // Tags to skip
startAtTask: 'start_task_name', // Start execution at a specific task
sudo: false,
sudoUser: '',
tags: 'tag1,tag2', // Tags to execute
vaultCredentialsId: '', // Vault credentials ID
vaultTmpPath: "${ANSIBLE_VAULT_TMP_PATH}" // Path to vault password file
)
}
}
}
}