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

Add ability to "offline" a resource through Declarative pipeline code #689

Open
asadniazi opened this issue Jul 30, 2024 · 3 comments
Open
Labels

Comments

@asadniazi
Copy link

asadniazi commented Jul 30, 2024

What feature do you want to see added?

Currently there is no easy way to offline/reserve/unreserve a resource if it crashes through the Groovy declarative pipeline code. The subsequent builds try to use the same resource and fail. Someone has to reserve that resource manually through UI so that the other jobs do not pick the same resource. This maybe possible using the Java libraries but it is not easy to do. There should be an out of the box step like the "lock" stop to reserve, unreserve, offline resource in the declarative pipeline code.

Upstream changes

No response

Are you interested in contributing this feature?

@PayBas
Copy link
Contributor

PayBas commented Jul 30, 2024

This is not natively supported, but since I required the same functionality, I made a Jenkins shared library (https://www.jenkins.io/doc/book/pipeline/shared-libraries/) method for this:

/**
 * params:
 *     action       - method to call
 *     resourceName - name of lockable resource
 *     textParam    - text-string to pass to set ReservedBy, Note or Steal text
 */
def String call(String action, String resourceName, String textParam = "") {

    if (resourceName == null || !resourceName.trim()) {
        echo "WARNING: no or empty lockable resource name provided! Aborting"
        return ""
    }
    def lockManager = org.jenkins.plugins.lockableresources.LockableResourcesManager.get()
    def lockableResource = lockManager.fromName(resourceName)
    def retVal = ""

    if (lockableResource != null && !lockableResource.isEphemeral()) {
        switch(action) {
            case "getReservedBy":
                def reservedBy = lockableResource.getReservedBy()
                if (reservedBy != null && reservedBy) {
                    retVal = lockableResource.getReservedBy()
                    echo "Resource: ${resourceName}, ReservedBy: ${retVal}"
                } else {
                    echo "Lockable resource: [${resourceName}] currently not reserved."
                }
                break
            case "setReservedBy":
                if (lockManager.reserve([lockableResource], textParam)) {
                    echo "Resource: ${resourceName}, ReservedBy: ${textParam}"
                } else {
                    echo "Lockable resource: [${resourceName}] could not be reserved by: '${textParam}'"
                }
                break
            case "unreserve":
                lockManager.unreserve([lockableResource])
                break
            case "steal":
                lockManager.steal([lockableResource], textParam)
                break
            case "getNote":
                retVal = lockableResource.getNote()
                echo "Resource: ${resourceName}, Note: ${retVal}"
                break
            case "setNote":
                retVal = lockableResource.setNote(textParam)
                break
            case "isLocked":
                retVal = lockableResource.isLocked()
                echo "Resource: ${resourceName}, Locked: ${retVal}"
                break
            case "quarantine":
                // Checks if the resource is already reserved, if not steal the lock
                def reservedBy = lockableResource.getReservedBy()
                if (reservedBy == null) {
                    echo "Reserving lockable resource [${resourceName}] for troubleshooting and manual repair"
                    lockableResource.setNote("FAILED build: <a href='${BUILD_URL}' target='_blank'>${JOB_BASE_NAME} : ${BUILD_DISPLAY_NAME}</a>")
                    lockManager.steal([lockableResource], textParam ?: 'Jenkins auto-quarantine')
                } else {
                    echo "Lockable Resource: [${resourceName}] has already been reserved by: '${reservedBy}'"
                }
                break
            default:
                echo "Unsupported action requested!"
        }
    } else {
         echo "WARNING: Lockable resource [${resourceName}] not found or ephemeral!"
    }
    return retVal
}

Which can be called from a declarative pipeline using:

#!/usr/bin/env groovy
pipeline {
  agent none
  options {
    lock(resource: 'some-resource', variable: 'RESOURCE')
  }
  stages {
    stage('Do stuff') {
      agent any
      steps {
        echo "Hi there!"
        error("Force an error")
      }
      post {
        failure {
          script {
            manageLockableResources("quarantine", env.RESOURCE, currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')[0]?.userName ?: 'Jenkins')
          }
        }
      }
    }
  }
}

Note that you might have to tweak this pipeline a bit. I had to remove 99% in order to share this, so I haven't tested it.

@asadniazi
Copy link
Author

asadniazi commented Aug 1, 2024

@PayBas Cool, this looks good. I'll give it a try once I get a chance and confirm if it works for me. Thanks!

@mPokornyETM
Copy link
Contributor

This will not works when you have a big amount of resources or many acces to it. Because you can not synchronized the groovy && java operatiosn and you will raise concurent modification exception. The best way is to extend LRM for 'missing' methods.

@mPokornyETM mPokornyETM added the good first issue Good for newcomers label Sep 15, 2024
@mPokornyETM mPokornyETM added this to the Feature committed milestone Sep 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants