Class ActivationService

java.lang.Object
io.jenkins.plugins.batchcontrol.policy.ActivationService

@Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public final class ActivationService extends Object
Activation approval (SPEC item 6a, D-39): whether a run-controlled job may run unattended, and the single entry point for every ActivationRequest state transition and every ActivationState write. No other class changes either.

The activation lives in the plugin store, outside the job's config.xml, so none of the configuration write paths (web form, REST, CLI, script, JCasC) can activate a job. It is changed only by an approved ACTIVATE/HOLD request and, once, by the upgrade seeding (seedExistingJobs()).

Concurrency: as in RunRequestService, every load, validate, transition and persist sequence runs under one ReentrantLock, so a transition is an effective compare-and-set. isActivated(Job) takes no lock: the queue gate calls it while Jenkins holds the queue lock, and it is answered from an in-memory cache (filled from the store on first use per job and kept in step by every write here).

Failure families: IllegalArgumentException for input validation, IllegalStateException for wrong-state transitions, AccessDeniedException (403) for authorization refusals.

  • Method Summary

    Modifier and Type
    Method
    Description
    static Item
    The item whose activation governs item (D-46c, D-82): the item itself, or for a computed child its nearest computed-folder ancestor, or for a job's sub-item (a matrix configuration, a Maven module) its parent job, resolved again while the item reached is itself such a child (a repository project of an organization folder is carried by the organization folder, which is the item that was created and can be requested on; a configuration of a matrix project inside a multibranch project by that project).
    void
    Ends, as INVALIDATED, every PENDING request that missed an invalidation while its file could not be read or written (invalidatePending(java.lang.String, boolean, java.lang.String)) and can now be, when the invalidation applies to the job it names; forgets the others.
    approve(String id, String comment)
    Approves a PENDING request and applies it: ACTIVATE marks the job activated and writes an ChangeType.ACTIVATED record, HOLD marks it not activated and writes a ChangeType.HELD record.
    boolean
    D-38b: whether the current user may cancel request: its requester holding BatchControl/Request on the item, or a Manage holder.
    Cancels a PENDING request; requester or a Manage holder only (SPEC item 7 rule).
    boolean
    D-38b: whether the current user may change the approvers of request: its requester holding BatchControl/Request on the item.
    boolean
    D-38b: whether the current user could request an activation or hold of item: the permission checks of create(Item, ActivationRequest.Action, String, List), namely BatchControl/Request and Item/Read on the item.
    changeApprovers(String id, List<String> newApprovers)
    Replaces the designated approver set of a PENDING request; requester only (D-26, D-37).
    countPendingFor(org.springframework.security.core.Authentication auth)
    D-61: the PENDING activation and hold requests that concern auth, read from the open-request index only (no history scan): those awaiting their decision as a designated approver holding Jenkins-level BatchControl/Approve, else their own.
    create(Item job, ActivationRequest.Action action, String reason, List<String> approvers)
    Creates a PENDING activation or hold request for the job.
    create(Job<?,?> job, ActivationRequest.Action action, String reason, List<String> approvers)
    void
    Expires PENDING requests past pendingTimeoutHours, the run-request timeout.
    get()
     
    getState(Item item)
    The stored activation state of an item (a job or a computed folder), or null.
    getState(Job<?,?> job)
    Job form of getState(Item).
    getState(String jobFullName)
    The stored activation state of an item, or null if none is stored.
    static Job<?,?>
    governingJob(Job<?,?> job)
    D-82: the job whose approval property governs job at the queue gate: the job itself, or for a sub-item the topmost job among its parents (a configuration's matrix project).
    boolean
    hasOwnRequests(org.springframework.security.core.Authentication auth)
    D-38b: whether auth filed at least one activation or hold request, in any status (the requester always sees their own, P-09).
    boolean
    holdAfterMove(Item item, String oldFullName, String mover)
    D-59a (SPEC 6a): item was moved from oldFullName by mover, a user without Overall/Administer, while run control and change control were on.
    e2e-04 FD-07, security-25 S-25-03: the part of a refusal record's coalescing key that changes when the item's activation ends: never-activated, held-<epoch millis>, or unknown when the state cannot be read.
    boolean
    Whether an approved activation is stored for this very item.
    boolean
    isActivated(Job<?,?> job)
    Job form of isActivated(Item).
    static boolean
    D-32: a job its parent computes for itself (a multibranch branch job, an organization folder's repositories) is not controlled and needs no activation.
    static boolean
    D-82: whether item is a sub-item of a job (a multi-configuration project's configuration, a Maven module): its parent is itself a job.
    static boolean
    Whether the item carries an activation of its own: a job or a computed folder that is not itself a computed child (D-46) nor a job's sub-item (D-82).
    All stored activation requests, in creation order.
    listAwaitingDecision(org.springframework.security.core.Authentication auth)
    The PENDING requests counted as PendingCount.getAwaitingDecision() by countPendingFor(Authentication), in creation order (oldest, the one closest to its timeout, first): empty unless auth holds Jenkins-level BatchControl/Approve.
    The PENDING activation and hold requests, in creation order (the approval inbox).
    The PENDING requests on which userId is a designated approver.
    The PENDING requests of one job, in creation order.
    Loads an activation request by id, or null.
    boolean
    The queue gate's activation input (SPEC item 6a, D-46): whether the job may start on a cause that is not a human submission.
    void
    D-36: sends NotificationEvent.EXPIRING once per PENDING request close to its timeout.
    void
    An item was created (SPEC item 6a, D-45, D-46).
    reject(String id, String comment)
    Rejects a PENDING request; the comment is mandatory (SPEC item 5 rule).
    void
    relocate(String oldFullName, String newFullName)
    A job was renamed or moved: its activation follows it, and PENDING requests on the old name end INVALIDATED, as run requests do (D-21: the approver reviewed another identity).
    void
    remove(String fullName, boolean withDescendants)
    A job was deleted: its activation is removed and its PENDING requests end INVALIDATED, so a later job of the same name starts not activated and cannot be activated by a request that was about another job.
    int
    One-time upgrade seeding: when activations/.schema is absent, every job that exists now (other than a computed child, D-32) is recorded as activated with activatedBy = upgrade and one ChangeType.ACTIVATED record, and then the marker is written.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • get

      public static ActivationService get()
    • isActivated

      public boolean isActivated(Item item)
      Whether an approved activation is stored for this very item. The answer is truthful: a computed child, which nobody activates, reports not activated; whether it may run is mayRunUnattended(Job), which asks its computed-folder ancestor (D-46c).

      Fails closed: a state that cannot be read, one bound to another directory than the item's current one (a re-created item under an old name, S-13-09), or one whose directory marker is missing or cannot be read now (D-80), counts as not activated.

    • holdEpoch

      public String holdEpoch(Item item)
      e2e-04 FD-07, security-25 S-25-03: the part of a refusal record's coalescing key that changes when the item's activation ends: never-activated, held-<epoch millis>, or unknown when the state cannot be read. Answered from the activation cache that isActivated(Item) fills, so the queue gate does not read the disk; never throws.
    • isActivated

      public boolean isActivated(Job<?,?> job)
      Job form of isActivated(Item).
    • mayRunUnattended

      public boolean mayRunUnattended(Job<?,?> job)
      The queue gate's activation input (SPEC item 6a, D-46): whether the job may start on a cause that is not a human submission. A computed child asks the item that carries its activation (activationSubject(hudson.model.Item)); any other job asks itself.
    • activationSubject

      public static Item activationSubject(Item item)
      The item whose activation governs item (D-46c, D-82): the item itself, or for a computed child its nearest computed-folder ancestor, or for a job's sub-item (a matrix configuration, a Maven module) its parent job, resolved again while the item reached is itself such a child (a repository project of an organization folder is carried by the organization folder, which is the item that was created and can be requested on; a configuration of a matrix project inside a multibranch project by that project).
    • isSubject

      public static boolean isSubject(Item item)
      Whether the item carries an activation of its own: a job or a computed folder that is not itself a computed child (D-46) nor a job's sub-item (D-82).
    • isSubItem

      public static boolean isSubItem(Item item)
      D-82: whether item is a sub-item of a job (a multi-configuration project's configuration, a Maven module): its parent is itself a job. It has no activation or approval state of its own; the queue gate applies its parent job's (governingJob(hudson.model.Job<?, ?>)).
    • governingJob

      public static Job<?,?> governingJob(Job<?,?> job)
      D-82: the job whose approval property governs job at the queue gate: the job itself, or for a sub-item the topmost job among its parents (a configuration's matrix project).
    • getState

      public ActivationState getState(String jobFullName)
      The stored activation state of an item, or null if none is stored.
    • getState

      public ActivationState getState(Item item)
      The stored activation state of an item (a job or a computed folder), or null.
    • getState

      public ActivationState getState(Job<?,?> job)
      Job form of getState(Item).
    • load

      public ActivationRequest load(String id)
      Loads an activation request by id, or null.
    • list

      public List<ActivationRequest> list()
      All stored activation requests, in creation order.
    • hasOwnRequests

      public boolean hasOwnRequests(org.springframework.security.core.Authentication auth)
      D-38b: whether auth filed at least one activation or hold request, in any status (the requester always sees their own, P-09). Answered from the store's in-memory index; no item is visited.
    • canRequest

      public boolean canRequest(Item item)
      D-38b: whether the current user could request an activation or hold of item: the permission checks of create(Item, ActivationRequest.Action, String, List), namely BatchControl/Request and Item/Read on the item. Screens use it to show the request form; create still checks for real.
    • canCancel

      public boolean canCancel(ActivationRequest request)
      D-38b: whether the current user may cancel request: its requester holding BatchControl/Request on the item, or a Manage holder. Permission only.
    • canChangeApprovers

      public boolean canChangeApprovers(ActivationRequest request)
      D-38b: whether the current user may change the approvers of request: its requester holding BatchControl/Request on the item. Permission only.
    • listPending

      public List<ActivationRequest> listPending()
      The PENDING activation and hold requests, in creation order (the approval inbox).
    • listPendingFor

      public List<ActivationRequest> listPendingFor(String userId)
      The PENDING requests on which userId is a designated approver.
    • countPendingFor

      public PendingCount countPendingFor(org.springframework.security.core.Authentication auth)
      D-61: the PENDING activation and hold requests that concern auth, read from the open-request index only (no history scan): those awaiting their decision as a designated approver holding Jenkins-level BatchControl/Approve, else their own. Every counted request is visible to auth under P-09. The single source for the tab badge and the section.
    • listAwaitingDecision

      public List<ActivationRequest> listAwaitingDecision(org.springframework.security.core.Authentication auth)
      The PENDING requests counted as PendingCount.getAwaitingDecision() by countPendingFor(Authentication), in creation order (oldest, the one closest to its timeout, first): empty unless auth holds Jenkins-level BatchControl/Approve.
    • listPendingForJob

      public List<ActivationRequest> listPendingForJob(String jobFullName)
      The PENDING requests of one job, in creation order.
    • create

      public ActivationRequest create(Job<?,?> job, ActivationRequest.Action action, String reason, List<String> approvers)
    • create

      public ActivationRequest create(Item job, ActivationRequest.Action action, String reason, List<String> approvers)
      Creates a PENDING activation or hold request for the job. The requester is the current authentication and needs BatchControl/Request plus Item/Read on the job (D-39). Validation as for run requests: a reason of at most 4000 characters and a designated approver set that passes the SPEC item 3 rules, including the job's own approver list (D-37).

      ACTIVATE is refused for a job that is already activated and HOLD for a job that is not, since neither could change anything. A computed child (D-32) is not controlled and cannot be the subject of a request.

    • approve

      public ActivationRequest approve(String id, String comment)
      Approves a PENDING request and applies it: ACTIVATE marks the job activated and writes an ChangeType.ACTIVATED record, HOLD marks it not activated and writes a ChangeType.HELD record. The caller must be a designated approver holding BatchControl/Approve (or the administrator self-approval path), exactly as for a run request. A request past its pending timeout is expired instead; a request whose job no longer exists is invalidated.
    • reject

      public ActivationRequest reject(String id, String comment)
      Rejects a PENDING request; the comment is mandatory (SPEC item 5 rule). Nothing else changes.
    • cancel

      public ActivationRequest cancel(String id)
      Cancels a PENDING request; requester or a Manage holder only (SPEC item 7 rule).
    • changeApprovers

      public ActivationRequest changeApprovers(String id, List<String> newApprovers)
      Replaces the designated approver set of a PENDING request; requester only (D-26, D-37). Recorded as (previous set, new set, changed by, time).
    • expireOverduePending

      public void expireOverduePending()
      Expires PENDING requests past pendingTimeoutHours, the run-request timeout. Called by the periodic work; the truth stays the clock comparison (approve(java.lang.String, java.lang.String) expires a late request on its own).
    • notifyExpiring

      public void notifyExpiring()
      D-36: sends NotificationEvent.EXPIRING once per PENDING request close to its timeout. Each request is handled on its own: one that cannot be read or written is logged and tried again on the next run, and never keeps the others from their notice.
    • relocate

      public void relocate(String oldFullName, String newFullName)
      A job was renamed or moved: its activation follows it, and PENDING requests on the old name end INVALIDATED, as run requests do (D-21: the approver reviewed another identity). Runs regardless of the switches; it is bookkeeping, not control.
    • holdAfterMove

      public boolean holdAfterMove(Item item, String oldFullName, String mover)
      D-59a (SPEC 6a): item was moved from oldFullName by mover, a user without Overall/Administer, while run control and change control were on. It starts over like a newly created item: it is no longer activated, and a ChangeType.HELD record naming the move and the mover is written. Called after the move completed (the state has already followed the item through relocate(java.lang.String, java.lang.String)). A computed child or a job's sub-item carries no activation of its own (D-46, D-82) and is skipped; a computed folder, which carries its children's, is held. The D-34 configuration lock is applied by the caller. The caller checks the switches.
      Returns:
      whether the item was handled (a job or computed folder that is not a computed child)
    • remove

      public void remove(String fullName, boolean withDescendants)
      A job was deleted: its activation is removed and its PENDING requests end INVALIDATED, so a later job of the same name starts not activated and cannot be activated by a request that was about another job. For a deleted folder the same applies to everything stored below it.
    • onItemCreated

      public void onItemCreated(Item item)
      An item was created (SPEC item 6a, D-45, D-46). For an item that carries its own activation (a job or a computed folder that is not a computed child):
      • run control on: an explicit not-activated state is stored (S-13-06), so a retried seeding never activates it and nothing left under the name is inherited (S-13-04);
      • run control off: it is recorded as activated by ActivationState.UNCONTROLLED with an ChangeType.ACTIVATED record (S-13-10), so turning run control on later never stops a schedule created in between.
      A computed child or a job's sub-item (D-82) carries no state of its own; anything stored under its name is discarded. The state is bound to the item's directory marker (S-13-09).
    • seedExistingJobs

      public int seedExistingJobs()
      One-time upgrade seeding: when activations/.schema is absent, every job that exists now (other than a computed child, D-32) is recorded as activated with activatedBy = upgrade and one ChangeType.ACTIVATED record, and then the marker is written. With the marker present this does nothing, so it never re-seeds, not even after the activation files are removed. A job that already has a stored state is left alone, so a seeding interrupted before the marker was written completes without duplicates.

      Seeding does not depend on the switches: installing with run control off and turning it on later must not stop an existing schedule either. With run control off the state has no effect.

      Must run with every job visible: it is called from an initializer, which runs as SYSTEM.

      Returns:
      the number of jobs seeded, or -1 when the marker was already present
    • isComputedChild

      public static boolean isComputedChild(Item item)
      D-32: a job its parent computes for itself (a multibranch branch job, an organization folder's repositories) is not controlled and needs no activation. Same test as the D-31/D-34 default in listener.ItemChangeListener.
    • applyMissedInvalidations

      public void applyMissedInvalidations()
      Ends, as INVALIDATED, every PENDING request that missed an invalidation while its file could not be read or written (invalidatePending(java.lang.String, boolean, java.lang.String)) and can now be, when the invalidation applies to the job it names; forgets the others. Run by the periodic work every minute; a request that still cannot be read stays for the next run.