-
Notifications
You must be signed in to change notification settings - Fork 201
Added a findLocks step to enumerate the available resources #307
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
0326c8f
67e44eb
cf0c2ae
e6c24ce
0c04017
e277423
b25322a
96895e8
48db2e0
3bd7d37
6127108
afb3a55
c25bd12
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 |
|---|---|---|
|
|
@@ -43,7 +43,7 @@ already defined in the Jenkins global configuration, an ephemeral resource is | |
| used: These resources only exist as long as any running build is referencing | ||
| them. | ||
|
|
||
| Examples: | ||
| #### Locking Examples | ||
|
|
||
| *Acquire lock* | ||
|
|
||
|
|
@@ -100,6 +100,38 @@ lock(resource: 'some_resource', skipIfLocked: true) { | |
| } | ||
| ``` | ||
|
|
||
| #### Finding Examples | ||
|
|
||
| *List all resources acquired by the current build* | ||
|
|
||
| ```groovy | ||
| lock(label:'printer') { | ||
| echo findLocks().name | ||
| } | ||
| ``` | ||
|
|
||
| *List all resources (locked or not) currently defined* | ||
|
|
||
| ```groovy | ||
| echo findLocks(build:'any').name | ||
| ``` | ||
|
|
||
| *List all resources (locked or not) with label `printer` excluding those with label `offline`* | ||
|
|
||
| ```groovy | ||
| echo findLocks(anyOfLabels:'printer', noneOfLabels:'offline', build:'any').name | ||
|
Contributor
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. How would this behave when
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. yes, the magic of groovy - if you |
||
| ``` | ||
| *List all resources (locked or not) with label `printer` and label `offline`* | ||
|
|
||
| ```groovy | ||
| echo findLocks(allOfLabels:'printer offline', build:'any').name | ||
| ``` | ||
| *List all resources (locked or not) with a name starting with `PRINTER`* | ||
|
|
||
| ```groovy | ||
| echo findLocks(matching:'^PRINTER.*', build:'any').name | ||
| ``` | ||
|
|
||
| Detailed documentation can be found as part of the | ||
| [Pipeline Steps](https://jenkins.io/doc/pipeline/steps/lockable-resources/) | ||
| documentation. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| package org.jenkins.plugins.lockableresources; | ||
|
|
||
| import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||
| import hudson.Extension; | ||
| import hudson.model.Run; | ||
| import hudson.model.TaskListener; | ||
| import java.io.Serializable; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.function.Predicate; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Collectors; | ||
| import org.apache.commons.lang.StringUtils; | ||
| import org.jenkinsci.plugins.workflow.steps.Step; | ||
| import org.jenkinsci.plugins.workflow.steps.StepContext; | ||
| import org.jenkinsci.plugins.workflow.steps.StepDescriptor; | ||
| import org.jenkinsci.plugins.workflow.steps.StepExecution; | ||
| import org.kohsuke.stapler.DataBoundConstructor; | ||
| import org.kohsuke.stapler.DataBoundSetter; | ||
|
|
||
| public class FindLocksStep extends Step implements Serializable { | ||
|
|
||
| private static final Logger LOGGER = Logger.getLogger(FindLocksStepExecution.class.getName()); | ||
| private static final long serialVersionUID = 148049840628540827L; | ||
| private static final String ANY_BUILD = "any"; | ||
|
|
||
| @CheckForNull | ||
| public String anyOfLabels = null; | ||
|
|
||
| @CheckForNull | ||
| public String noneOfLabels = null; | ||
|
|
||
| @CheckForNull | ||
| public String allOfLabels = null; | ||
|
|
||
| @CheckForNull | ||
| public String matching = null; | ||
|
|
||
| @CheckForNull | ||
| public String build = null; | ||
|
|
||
| @DataBoundSetter | ||
| public void setAnyOfLabels(String anyOfLabels) { | ||
| if (StringUtils.isNotBlank(anyOfLabels)) { | ||
| this.anyOfLabels = anyOfLabels; | ||
| } | ||
| } | ||
|
|
||
| @DataBoundSetter | ||
| public void setNoneOfLabels(String noneOfLabels) { | ||
| if (StringUtils.isNotBlank(noneOfLabels)) { | ||
| this.noneOfLabels = noneOfLabels; | ||
| } | ||
| } | ||
|
|
||
| @DataBoundSetter | ||
| public void setAllOfLabels(String allOfLabels) { | ||
| if (StringUtils.isNotBlank(allOfLabels)) { | ||
| this.allOfLabels = allOfLabels; | ||
| } | ||
| } | ||
|
|
||
| @DataBoundSetter | ||
| public void setMatching(String matching) { | ||
| if (StringUtils.isNotBlank(matching)) { | ||
| this.matching = matching; | ||
| } | ||
| } | ||
|
|
||
| @DataBoundSetter | ||
| public void setBuild(String build) { | ||
| if (StringUtils.isNotBlank(build)) { | ||
| this.build = build; | ||
| } | ||
| } | ||
|
|
||
| @DataBoundConstructor | ||
| public FindLocksStep() { | ||
| } | ||
|
|
||
| public Predicate<LockableResource> asPredicate(StepContext context) { | ||
| String currentJobName = null; | ||
| try { | ||
| Run run = context.get(Run.class); | ||
| currentJobName = run.getExternalizableId(); | ||
| } catch (Exception e) { | ||
| LOGGER.warning("Failed to resolve the current job"); | ||
| } | ||
| final String finalCurrentJobName = currentJobName; | ||
| return lockableResource -> { | ||
| if (StringUtils.isNotBlank(anyOfLabels)) { | ||
| List<String> anyLabelsList = Arrays.asList(anyOfLabels.split("\\s+")); | ||
| List<String> resourceLabels = Arrays.asList(lockableResource.getLabels().split("\\s+")); | ||
| if (anyLabelsList.stream().noneMatch(l -> resourceLabels.contains(l))) | ||
| return false; | ||
| } | ||
| if (StringUtils.isNotBlank(noneOfLabels)) { | ||
| List<String> noneOfLabelsList = Arrays.asList(noneOfLabels.split("\\s+")); | ||
| List<String> resourceLabels = Arrays.asList(lockableResource.getLabels().split("\\s+")); | ||
| if (noneOfLabelsList.stream().anyMatch(l -> resourceLabels.contains(l))) | ||
| return false; | ||
| } | ||
| if (StringUtils.isNotBlank(allOfLabels)) { | ||
| List<String> allOfLabelsList = Arrays.asList(allOfLabels.split("\\s+")); | ||
| List<String> resourceLabels = Arrays.asList(lockableResource.getLabels().split("\\s+")); | ||
| if (allOfLabelsList.stream().allMatch(l -> resourceLabels.contains(l)) == false) | ||
| return false; | ||
| } | ||
| if (StringUtils.isNotBlank(matching)) { | ||
| if (lockableResource.getName().matches(matching) == false) { | ||
| return false; | ||
| } | ||
| } | ||
| if (StringUtils.isNotBlank(build)) { | ||
| if (build.equals(ANY_BUILD) == false) { | ||
| if (StringUtils.isBlank(lockableResource.getBuildId())) { | ||
| // ignore unlocked resources | ||
| return false; | ||
| } | ||
| if (lockableResource.getBuildId().equals(build) == false) { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| // when build is blank, we restrict the search on the current build | ||
| if (finalCurrentJobName == null) { | ||
| // if we are not part of a job, filter out everything | ||
| return false; | ||
| } | ||
| if (StringUtils.isBlank(lockableResource.getBuildId())) { | ||
| // ignore unlocked resources | ||
| return false; | ||
| } | ||
| if (lockableResource.getBuildId().equals(finalCurrentJobName) == false) { | ||
| // ignore resources locked by another job | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
|
|
||
| @Extension | ||
| public static final class DescriptorImpl extends StepDescriptor { | ||
|
|
||
| @Override | ||
| public String getFunctionName() { | ||
| return "findLocks"; | ||
| } | ||
|
|
||
| @NonNull | ||
| @Override | ||
| public String getDisplayName() { | ||
| return "Find existing shared resource"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean takesImplicitBlockArgument() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<Class<?>> getRequiredContext() { | ||
| return Collections.singleton(TaskListener.class); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| List<String> desc = new ArrayList<>(); | ||
| if (StringUtils.isNotBlank(anyOfLabels)) { | ||
| desc.add("anyLabels:" + anyOfLabels); | ||
| } | ||
| if (StringUtils.isNotBlank(allOfLabels)) { | ||
| desc.add("allOfLabels:" + allOfLabels); | ||
| } | ||
| if (StringUtils.isNotBlank(noneOfLabels)) { | ||
| desc.add("noneOfLabels:" + noneOfLabels); | ||
| } | ||
| if (StringUtils.isNotBlank(matching)) { | ||
| desc.add("matching:" + matching); | ||
| } | ||
| if (StringUtils.isNotBlank(build)) { | ||
| desc.add("build:" + build); | ||
| } | ||
| if (desc.isEmpty()) { | ||
| return "all locked by current build"; | ||
| } | ||
| else { | ||
| return desc.stream().collect(Collectors.joining("; ")); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public StepExecution start(StepContext context) throws Exception { | ||
| return new FindLocksStepExecution(this, context); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package org.jenkins.plugins.lockableresources; | ||
|
|
||
| import java.io.Serializable; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Collectors; | ||
| import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; | ||
| import org.jenkinsci.plugins.workflow.steps.StepContext; | ||
|
|
||
| public class FindLocksStepExecution extends AbstractStepExecutionImpl implements Serializable { | ||
|
|
||
| private static final long serialVersionUID = -5757385070025969380L; | ||
| private static final Logger LOGGER = Logger.getLogger(FindLocksStepExecution.class.getName()); | ||
|
|
||
| private final FindLocksStep step; | ||
|
|
||
| public FindLocksStepExecution(FindLocksStep step, StepContext context) { | ||
| super(context); | ||
| this.step = step; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean start() throws Exception { | ||
| List<LockableResource> allResources = LockableResourcesManager.get().getResources(); | ||
|
|
||
| List<Map<String,Object>> resourcesAsMap = allResources.stream() | ||
| .filter(step.asPredicate(getContext())) | ||
| .map(FindLocksStepExecution::convertResourceToMap) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| getContext().onSuccess(resourcesAsMap); | ||
| return true; | ||
| } | ||
|
|
||
| private static Map<String, Object> convertResourceToMap(LockableResource lockableResource) { | ||
| Map<String, Object> lockAsMap = new HashMap<>(); | ||
| lockAsMap.put("name", lockableResource.getName()); | ||
| lockAsMap.put("labels", lockableResource.getLabels()); | ||
| lockAsMap.put("note", lockableResource.getNote()); | ||
| lockAsMap.put("description", lockableResource.getDescription()); | ||
| lockAsMap.put("reservedBy", lockableResource.getReservedBy()); | ||
| lockAsMap.put("reservedTimestamp", lockableResource.getReservedTimestamp()); | ||
| lockAsMap.put("queuedItemProject", lockableResource.getQueueItemProject()); | ||
| lockAsMap.put("buildName", lockableResource.getBuildName()); | ||
| lockAsMap.put("queuedItemId", lockableResource.getQueueItemId()); | ||
| lockAsMap.put("lockCause", lockableResource.getLockCause()); | ||
| lockAsMap.put("reservedByEmail", lockableResource.getReservedByEmail()); | ||
| return lockAsMap; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?jelly escape-by-default='true'?> | ||
| <j:jelly xmlns:j="jelly:core" | ||
| xmlns:f="/lib/form"> | ||
| <f:entry title="${%Resource Matches Expression}" field="matching"> | ||
| <f:textbox/> | ||
| </f:entry> | ||
| <f:entry title="${%Resource Labels Contain Any Of These Labels}" field="anyOfLabels"> | ||
| <f:textbox/> | ||
| </f:entry> | ||
| <f:entry title="${%Resource Labels Contain All Of These Labels}" field="allOfLabels"> | ||
| <f:textbox/> | ||
| </f:entry> | ||
| <f:entry title="${%Resource Labels Contain None Of These Labels}" field="noneOfLabels"> | ||
| <f:textbox/> | ||
| </f:entry> | ||
| <f:entry title="${%Build Id}" field="build"> | ||
| <f:textbox/> | ||
| </f:entry> | ||
| </j:jelly> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <div> | ||
| <p> | ||
| A list of space separated labels; | ||
| Only include resources that contain all the specified labels. | ||
| </p> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <div> | ||
| <p> | ||
| A list of space separated labels; | ||
| Include resources that contain any (one or more) of the specified labels. | ||
| </p> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <div> | ||
| <p> | ||
| A job id, ex. <code>myJob#32</code>; | ||
| Only resources locked by the specified build will be returned. Defaults to the | ||
| current job id. Specify <code>any</code> to search across all resources - locked | ||
| or not by any build | ||
| </p> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <div> | ||
| <p> | ||
| A regular expression; | ||
| Ignore any resource with a name that does not match the specified regular expression. | ||
| </p> | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <div> | ||
| <p> | ||
| A list of space separated labels; | ||
| Ignore resources that contain any (one or more) of the specified labels. | ||
| </p> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking of the repetitive "locked or not" comment, maybe filters for lock states (not-locked -
build:'none'perhaps?, reserved, stolen, ephemeral/persistent...) could be useful.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial concern was to go in a feature creep that would add complexity without fulfilling actual use cases. Do we want to search in notes? what about
reservedTimestamp- and do we want to allow before / after searches, etc. I don't mind adding a few, but I think the missing ones will be discovered and added through pull requests explaining which use case the new find option is fulfilling.For
reservedBy, I think it opens an interesting case where users can reserve resources and then execute jobs that work on their reserved resources, but apart from unreserving them (ex. with the update step), it would be difficult to allow a step to lock all the resources reserved by a user unless we allow locking resources reserved by a user when the job is being triggerred by that user for example.For stolen, ephemeral, these are easy to add, but I have the same concern about mapping these to actual use cases.
What I am hoping is that the combo of findLock/updateLock and the advanced lock filters will resolved most of the us cases by using labeling. If you really need to work on your reserved locks,
findLockson all locks, look for the ones you reserved, update the labels (ex.reserved_by_<username>) and then you can lock these specific locks by label.I do have a much narrower view on how this plugin might be used by the rest of the community, so I'll rely on your opinion on which fields are worth adding to the findLock step