Skip to main content

Making a Pipeline

Assuming you have the container running, you should be able to set up a playbook. Before this, you should set up the link with Github. I don't remember exactly how I did that either, so I found a guide. Turns out it's easy to checkout, it was the part about the Repo needing credentials that was hard. I remember it took a little while to get the Github SSH keys all set up, but now that I have them it's trivial. Here is an old playbook I wrote before learning how to use Ansible to set up my first PXE server. Since this was before I figured out Ansible, I was just using Jenkins to automate my shell commands. Once I learned how to use Ansible, I abandoned this way of using Jenkins. I haven't ran this pipeline in a long time, but I believe it did work in the end. 

Jenkinsfile.old_pxe
pipeline {
    agent any


    environment {
        DEBIAN_IP = 'debian_machine_ip'
        SSH_CREDENTIALS_ID = 'jenkins-ssh-key'
        PXE_AUTH = 'PXE_AUTH'
    }


    stages {


        stage('Install Packages') {
            steps {
                script {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh-keygen -f "/root/.ssh/known_hosts" -R "${params.host_ip}"
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            echo "samba-common samba-common/workgroup string  WORKGROUP" | debconf-set-selections
                            echo "samba-common samba-common/dhcp boolean true" | debconf-set-selections
                            echo "samba-common samba-common/do_debconf boolean true" | debconf-set-selections
                            apt update --yes
                            apt upgrade --yes
                            apt install --yes isc-dhcp-server curl jq tftpd-hpa apache2 syslinux-common net-tools samba pwgen cifs-utils unzip
                        
                        """
                    }
                }
            }
        }


        stage('Configure DHCP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop isc-dhcp-server
                            curl -o /etc/dhcp/dhcpd.conf -L https://$PXE_AUTH@mattifactory.com/dhcp/dhcpd.conf
                            curl -o /etc/default/isc-dhcp-server -L https://$PXE_AUTH@mattifactory.com/dhcp/isc-dhcp-server
                            systemctl start isc-dhcp-server
                        """
                    }
                }
            }
        }


        stage('Configure TFTP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop tftpd-hpa
                            mkdir -p /srv/tftp
                            chown -R tftp:tftp /srv/tftp
                            chmod -R 777 /srv/tftp
                            curl -o /etc/default/tftpd-hpa -L https://$PXE_AUTH@mattifactory.com/dhcp/tftpd-hpa
                            systemctl start tftpd-hpa
                        """
                    }
                }
            }
        }


        stage('Configure HTTP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop apache2
                            mkdir -p /var/www/html/debian-installer/amd64
                            cd /var/www/html/debian-installer/amd64
                            wget -q https://$PXE_AUTH@mattifactory.com/dhcp/netboot.tar.gz
                            tar -xzf netboot.tar.gz
                            systemctl start apache2
                        """
                    }
                }
            }
        }


        stage('Configure PXE Boot Configuration') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            cp /var/www/html/debian-installer/amd64/pxelinux.0 /srv/tftp/
                            cp /usr/lib/syslinux/modules/bios/* /srv/tftp/
                            cp -R /var/www/html/debian-installer/amd64/debian-installer /srv/tftp/
                            mkdir -p /srv/tftp/pxelinux.cfg
                            curl -o /srv/tftp/debian-installer/amd64/linux -L https://$PXE_AUTH@mattifactory.com/dhcp/linux
                            curl -o /srv/tftp/debian-installer/amd64/initrd.gz -L https://$PXE_AUTH@mattifactory.com/dhcp/initrd.gz
                            curl -o /srv/tftp/debian-installer/amd64/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/debian-installer/amd64/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/debian-installer/amd64/grub/grub.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/grub.cfg
                            curl -o /var/www/html/preseed.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/preseed.cfg
                            curl -o /srv/tftp/preseed.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/preseed.cfg
                        """
                    }
                }
            }
        }


        stage('Configure SMB & Hostname & Reboot') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            mkdir -p /media/share
                            chmod 777 /media/share 
                            systemctl stop smbd.service
                            curl -o /etc/samba/smb.conf -L https://$PXE_AUTH@mattifactory.com/smb/smb.conf
                            systemctl start smbd.service
                            echo cosmos-pxe > /etc/hostname
                            echo 127.0.0.1 cosmos-pxe >> /etc/hosts
                            sleep 2
                            reboot now

                        """
                    }
                }
            }
        }
    }
}

 

With that old example of how to use Jenkins without Ansible behind us, here is a simple Jenkisfile that first injects the Ansible SSH key into a new NanoPi Device and runs a super simple playbook. Ansible requires an inventory file; assuming you're not actually keeping an Ansible inventory file, then you need to generate an inventory file. This is usually done by script ran in the Jenkinsfile. This inventory.sh script takes a list of IPs and creates an inventory file that ansible-playbook can read among other things.  This is less important than what the Jenkinsfile shows; specifically this shows how to run commands with variables, how the SSH key works, and how to run a very simply Ansible playbook. 

I recently updated the dynamic inventory generation script to have a few more options for passing user and group, as well as prohibiting lists of endpoints. This is because I want to open my Jenkins up a bit, and I don't want people running pipelines on my servers. It did make the script a lot more complicated.

Jenkinsfile
pipeline {
    agent any
    
    // Define parameters
    parameters {
        string(name: 'host_ip', description: 'Target System Address')
    }

    environment {
        ANSIBLE_FORCE_COLOR = '1'
        jenkins_public_key = credentials('jenkins_public_key')        
    }

    options {
        ansiColor('xterm')
    }

    stages {

        stage('Inject Auth Key') {
            steps {
                script{
                    // clear ssh keys
                    echo "Target IP: ${params.host_ip}"
                    
                    sh """
                    ssh-keygen -f "/root/.ssh/known_hosts" -R "${params.host_ip}"
                    """
                    
                    sh """
                    echo Copy public key to pi home dir
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo ${env.jenkins_public_key} > /home/pi/authorized_keys"
                    """

                    sh """
                    echo Make sure /root/.ssh exists
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S mkdir -p /root/.ssh/"
                    """
                    
                    sh """
                    echo Move public key to root
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S mv /home/pi/authorized_keys /root/.ssh/authorized_keys"
                    """
                    
                    sh """
                    echo Restrict permissions on file
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S chmod -R 600 /root/.ssh/"
                    """
                    
                    sh """
                    echo Set owner to root
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S chown -R root:root /root/.ssh/"
                    """
                }
            }
        }

        stage('Generate Inventory File') {
            steps {
                // Generate the dynamic inventory file
                sh """
                jenkins_group=\$(echo ${env.BUILD_USER_GROUPS} |  sed 's/,/\\n/g' | grep Jenkins | head -n 1)
                jenkins_user=\$(echo ${env.BUILD_USER})
                cd /var/jenkins_home/ansible
                chmod +x /var/jenkins_home/ansible/inventory/inventory.sh
                /var/jenkins_home/ansible/inventory/inventory.sh -s -g \$jenkins_group -u \$jenkins_user -i ${params.host_ip} 

                """
            }
        }

        stage('Ansible Check') {
            steps {
                sh """
                echo ${params.host_ip}
                hash=\$(echo -n ${params.host_ip} | md5sum | cut -c 1-8)
                inventory_file="/var/jenkins_home/ansible/.inv/inventory-\$hash.yml"

                cd /var/jenkins_home/ansible

                ansible-playbook -i \$inventory_file \
                    /var/jenkins_home/ansible/playbooks/pi-init.yaml --ssh-common-args='-o StrictHostKeyChecking=no' 
                
                """
            }
        }

    }


    post {
        always {
            // Remove dynamic Inventory file
            sh """
            hash=\$(echo -n "${params.host_ip}" | md5sum | cut -c 1-8)
            inventory_file="/var/jenkins_home/ansible/.inv/inventory-\$hash.yml"
            rm \$inventory_file
            
            """
        }
    }

}
pi-init.yaml
---
- name: Ansible Test
  hosts: all
  become: yes

  # this is meant just as a tiny playbook to run after the public key is injected with jenkins
  tasks: 
  # Check System Architecture
  - name: Check CPU Arch
    shell: "dpkg --print-architecture"
    register: cpu_architecture_output

  - name: Display cpu_architecture_output variable
    debug: 
      msg: "{{ cpu_architecture_output.stdout_lines[0] }}"

...
inventory.sh
#!/bin/bash

# Dynamic inventory generation script ansible

# Function to display usage
usage() {
    echo "Usage: $0 -i IP_LIST -u JENKINS_USER -g JENKINS_GROUP [-s] [-v]"
    echo "Options:"
    echo "  -i IP_LIST       Comma-separated list of IPs"
    echo "  -u JENKINS_USER  Jenkins user for SSH access"
    echo "  -g JENKINS_GROUP Jenkins group for SSH access"
    echo "  -s               Set variable to true if more than one IP is passed"
    echo "  -v               Display Ansible Version"
    exit 1
}

# Initialize variables with default values
skip=false
more_than_one=false
display_version=false

# Parse command line options
while getopts ":i:u:g:sv" opt; do
  case ${opt} in
    i ) # process option i
        IP_LIST=$OPTARG
        ;;
    u ) # process option u
        JENKINS_USER=$OPTARG
        ;;
    g ) # process option g
        JENKINS_GROUP=$OPTARG
        ;;
    s ) # process option s
        skip=true
        ;;
    v ) # process option v
        display_version=true
        ;;
    \? ) usage
        ;;
  esac
done
shift $((OPTIND -1))
# Check if all required options are provided
if [ -z "$IP_LIST" ] || [ -z "$JENKINS_USER" ] || [ -z "$JENKINS_GROUP" ]; then
    usage
fi

if $display_version; then
    echo "Showing ansible version"
    ansible --version
fi

# Generate an 8-character hash from the IP list
hash=$(echo -n "$IP_LIST" | md5sum | cut -c 1-8)
echo "IP List:"
echo $IP_LIST
echo $hash

# Define the inventory file path with the hash
inventory_file="/var/jenkins_home/ansible/.inv/inventory-$hash.yml"

if $skip; then
    IFS=',' read -ra IPS <<< "$IP_LIST"
    if [ ${#IPS[@]} -gt 1 ]; then
        more_than_one=true
    fi
fi

if $skip; then
    echo "Single host option set"
    if $more_than_one; then
        echo "IP list provided, inventory will be emptied"
        IP_LIST=""
    fi
fi

# Initialize the YAML inventory content
inventory_content="---
all:
  hosts:
"

# Loop through each IP in the comma-separated list
IFS=',' read -ra IPS <<< "$IP_LIST"
for IP in "${IPS[@]}"; do
    inventory_content+="    ${IP}:
      ansible_user: root
"
done

inventory_content+="  vars:
    ansible_connection: ssh
    ansible_ssh_private_key_file: /var/jenkins_home/jenkins_key
    ansible_python_interpreter: /usr/bin/python3
    jenkins_user: '${JENKINS_USER}'
    jenkins_group: '${JENKINS_GROUP}'
"

# Write the inventory content to the file
echo "$inventory_content" > $inventory_file

echo "Inventory file created at $inventory_file with the following content:"
cat $inventory_file

image.png

This is how to configure this to pull from Github:

image.png
image.png
image.png