Good way to generate and deploy systemd unit via Jenkins

Hi folks, I want to ask for some advices regarding my topic.
I need to generate and deploy systemd units on remote host via pipeline
I came up with this solution, but I thinks it’s very bloated and a bit hard to read and maintain. It reads yml file and then transfer generated .service files to remote host

yml example
---
environments:
  staging:
    units:
      - name: example
        enabled: true
        description: example
        exec_start: example
  production:
    units:
      - name: example
        enabled: true
        description: example
        exec_start: example
pipeline stage
stage('Systemd Units Deployment') {
    steps {
        script {
            sshagent(credentials: ["${BUILD_USER_SSH_CREDS}"]) {
                def yamlData = readYaml file:'app/systemd_units.yaml'

                def environmentData = yamlData.environments[BUILD_ENVIRONMENT]
                def unitsInYaml = environmentData.units.collect { it.name }

                def unixDate = sh(script: "date +%s", returnStdout: true).trim()
                def tmpUnitsDir = "systemd_units_${unixDate}"
                sh "mkdir ${tmpUnitsDir}"

                def existingUnits = sh(script: "${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} 'ls /etc/systemd/user/*.service'", returnStdout: true).trim().split('\n').collect { it.split('/').last() }
                def unitsToRemove = existingUnits.findAll { !unitsInYaml.contains(it) }

                echoGreen("Deploying systemd units for environment: ${BUILD_ENVIRONMENT}")

                unitsToRemove.each { unit ->
                    echoGreen("Removing systemd unit: ${unit} from /etc/systemd/user/")
                    sh """
                        ${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} "
                            set -e
                            systemctl --user stop ${unit} || true && \
                            systemctl --user disable ${unit} || true && \
                            rm -f /etc/systemd/user/${unit} && \
                            systemctl --user daemon-reload
                        "
                    """
                }

                environmentData.units.each { unit ->
                    def remoteCommand = "${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} 'cat /etc/systemd/user/${unit.name}' 2>/dev/null || echo ''"
                    def existingUnitContent = sh(script: remoteCommand, returnStdout: true).stripIndent()

                    if (unit.enabled == false) {
                        echoGreen("Removing systemd unit: ${unit.name} from /etc/systemd/user/")
                        sh """
                            ${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} "
                                set -e
                                systemctl --user stop ${unit.name} || true && \
                                systemctl --user disable ${unit.name} || true && \
                                rm -f /etc/systemd/user/${unit.name} && \
                                systemctl --user daemon-reload
                            "
                        """
                    } else {
                        def newUnitContent = """\
                            [Unit]
                            After=network.target
                            Description=${unit.description}
                            
                            [Service]
                            Type=simple
                            ExecStart=${unit.exec_start}
                            Restart=always
                    
                            [Install]
                            WantedBy=default.target
                        """.stripIndent()

                        if (existingUnitContent != newUnitContent) {
                            echoGreen("Adding/updating systemd unit: ${unit.name} in /etc/systemd/user/")
                            echo "Description: ${unit.description}"
                            echo "ExecStart: ${unit.exec_start}"
                            writeFile file: "${tmpUnitsDir}/${unit.name}", text: newUnitContent
                            sh """
                                scp -r ${tmpUnitsDir} ${BUILD_USER}@${SERVICE_FULL_NAME}:/tmp
                                ${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} "
                                    set -e
                                    mv /tmp/${tmpUnitsDir}/${unit.name} /etc/systemd/user && \
                                    systemctl --user daemon-reload && \
                                    systemctl --user enable ${unit.name} && \
                                    systemctl --user restart ${unit.name}
                                "
                            """
                        } else {
                            echoGreen("Systemd unit ${unit.name} already up-to-date. Just restarting those.")
                            sh """
                                ${SSH} ${BUILD_USER}@${SERVICE_FULL_NAME} "
                                    set -e
                                    systemctl --user restart ${unit.name}
                                "
                            """
                        }
                    }
                }
            }
        }
    }
}

Maybe somebody can share their experience with such a task? Is my solution will work on long distances? Maybe there are some special software for that?

Hello and welcome to this community, @Anatrop. :wave:

To me, your solution seems to be a valid approach.

However, as you mentioned, it might be a bit hard to read and maintain due to its complexity. :person_shrugging:

Here are a few suggestions to maybe improve your current solution:

  • If possible, break down your code into smaller, reusable functions. This should make your code easier to read, test, and maintain.
  • Use Configuration Management Tools: Tools like Ansible, Chef, Puppet, or SaltStack are designed to automate the process of deploying, configuring, and managing servers. They can simplify the process of managing systemd units across multiple servers.
  • Use Templates for Systemd Units: Instead of generating the entire systemd unit file in your script, you could consider using a template file and replacing the necessary parts with your variables. This could make your code cleaner and easier to maintain.
  • You could add more error handling to your script. This may help you catch and handle any errors that might occur during the deployment process.
  • You might want to add more logging to your script. This could help you debug any issues that might occur during the deployment process.
  • Adding automated tests for your script would help you catch any issues or bugs before they affect your production environment.
1 Like