Jira & Confluence

[!NOTE|label:reference:]

jira

$ jiraName='jira.sample.com'
$ jiraID='STORY-1'

[!NOTE]

myself

[!NOTE|label:references:]

$ curl -s https://jira.sample.com/rest/api/2/myself
# or via `user?username=<id>`
$ curl -s https://jira.sample.com/rest/api/2/user?username=marslo
  • get info

    # get timezone
    $ curl -s https://jira.sample.com/rest/api/2/mypreferences?key=jira.user.timezone
    Asia/Shanghai
    
    # get locale
    $ curl --request GET \
           --url 'https://jira.sample.com/rest/api/2/mypreferences/locale' \
           --header 'Accept: application/json'
  • set info

    # set timezone
    $ curl --request PUT \
           --url 'https://jira.sample.com/rest/api/2/mypreferences?key={key}' \
           --header 'Accept: application/json' \
           --header 'Content-Type: application/json' \
           --data '"<string>"'
    
    # set local
    $ curl --request PUT \
           --url 'https://jira.sample.com/rest/api/2/mypreferences/locale' \
           --user 'email@example.com:<api_token>' \
           --header 'Accept: application/json' \
           --header 'Content-Type: application/json' \
           --data '{ "locale": "en_US" }'

check fields

$ curl -s \
       -k \
       -X GET https://${jiraName}/rest/api/2/issue/${jiraID} |
       jq --raw-output

check attachment

  • check attachment ID

    $ curl -s \
           -k \
           -X GET https://${jiraName}/rest/api/2/issue/${jiraID}?fields=attachment |
           jq --raw-output .fields.attachment[].id
  • get attachments download url

    $ curl -s \
           -k \
           -X GET https://${jiraName}/rest/api/2/issue/${jiraID}?fields=attachment |
           jq --raw-output .fields.attachment[].content
    • download all attachments in Jira

      -I replace-str Replace occurrences of replace-str in the initial-arguments with names read from standard in- put. Also, unquoted blanks do not terminate input items; instead the separator is the new- line character. Implies -x and -L 1.

      $ curl -s \
             -k \
             -X GET https://${jiraName}/rest/api/2/issue/${jiraID}?fields=attachment |
             jq --raw-output .fields.attachment[].content |
             xargs -I '{}' curl -sgOJL '{}'

list all projects

$ curl -fsSL -XGET https://jira.sample.com/rest/api/2/project |
  jq -r '.[] | [.key, .name] | join(" | ")' |
  column -s '|' -t

search issue by JQL

[!TIP]

  • format JQL

    [!TIP]

    $ jql='project = abc AND issuetype = release order by updated desc'
    $ jql=$(printf %s "${jql}" | jq -sRr @uri)
    $ jql="$(sed 's/ //g;s/AND/ AND /g;s/OR/ OR /g;s/IN/ IN /g;s/IS/ IS /g' <<< "${jql}")"
    $ jql="$(printf %s "${jql}" | jq -sRr @uri)"
    
    # i.e.:
    $ jql='project = abc AND issuetype = release'
    
    $ jql="$(sed 's/ //g;s/AND/ AND /g;s/OR/ OR /g;s/IN/ IN /g;s/IS/ IS /g' <<< "${jql}")"
    $ echo $jql
    project=abc AND issuetype=release
    
    $ jql="$(printf %s "${jql}" | jq -sRr @uri)"
    $ echo $jql
    project%3Dabc%20AND%20issuetype%3Drelease
  • api

    [!NOTE]

    • query parameters:

      • maxResults: integer

      • startAt: integer

      • validateQuery: string

      • fields: array<string>

      • expand: string

      • properties: array<string>

      • fieldsByKeys: boolean

      • sample: search?jql=${jql}&maxResults=100&startAt=0

    $ curl --silent \
           --insecure \
           --globoff \
           --netrc-file ~/.netrc \
           -XGET \
           "https://jira.sample.com/rest/api/2/search?jql=${jql}" |
      jq -r ${jqOpt}
    
    # i.e.:
    $ curlOpt='--silent --insecure --globoff --netrc-file ~/.netrc'
    $ url='https://jira.sample.com/rest/api/2'
    $ queryParams="startAt=0&maxResults=10"
    
    $ jql='project = ABC AND issuetype = Release ORDER BY updated ASC'          # copy from Jira website
    $ jql="$(printf %s "${jql}" | jq -sRr @uri)"
    
    $ curl "${curlOpt}" "${url}/search?jql=${jql}&${queryParams}" |
           jq -r '.issues[]' |
           jq -r '. | [.key, .fields.summary, .fields.status.name, .fields.issuetype.name, .fields.updated, .fields.created] | join("|")' |
           while IFS='|' read -r _key _summary _status _issuetype _updated _created; do
             echo "- [${_key}] - ${_summary}"
             echo "  -  status    : ${_status}"
             echo "  -  issuetype : ${_issuetype}"
             echo "  -  created   : ${_created}"
             echo "  -  updated   : ${_updated}"
           done
    $ curl --silent \
           --insecure \
           --globoff \
           --netrc-file ~/.netrc \
           -XGET \
           "https://jira.sample.com/rest/api/2/search?jql=${jql}" |
      jq -r ${jqOpt}
    
    # i.e.:
    $ curlOpt='--silent --insecure --globoff --netrc-file ~/.netrc'
    $ url='https://jira.sample.com/rest/api/2'
    $ queryParams="startAt=0&maxResults=10"
    $ jql='project = abc AND issuetype = release'          # copy from Jira website
    
    $ jql="$(sed 's/ //g;s/AND/ AND /g;s/OR/ OR /g;s/IN/ IN /g;s/IS/ IS /g' <<< "${jql}")"
    $ jql="$(printf %s "${jql}" | jq -sRr @uri)"
    
    $ curl "${curlOpt}" "${url}/search?jql=${jql}&${queryParams}" |
           jq -r '.issues[]' |
           jq -r '. | [.key, .fields.summary, .fields.status.name, .fields.issuetype.name, .fields.updated, .fields.created] | join("|")' |
           while IFS='|' read -r _key _summary _status _issuetype _updated _created; do
             echo "- [${_key}] - ${_summary}"
             echo "  -  status    : ${_status}"
             echo "  -  issuetype : ${_issuetype}"
             echo "  -  created   : ${_created}"
             echo "  -  updated   : ${_updated}"
           done

[get email address](Get Email Addresses For Users)

$ curl GET https://jira.sample.com/rest/api/2/user?key=JIRAUSER10100 |
  jq -r

api token

[!NOTE|label:references:]

  • list all tokens

    [!NOTE]

    • to list all tokens for single user:

      $ curl -v -XGET \
             https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token/ |
        jq -r
    • to list tokens for all users:

      $ curl -v \
             -X GET \
             https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/tokensByFilter |
        jq -r
    $ curl -s -D- \
           -XGET \
           -H "Content-Type: application/json"  \
           https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
      sed '/^\s*$/,$!d;//d' |
      jq -r
    • get id, created, last access, and valid timestamp

      function epoch2timestamp() {
        [[ 0 != $1 ]] && echo $(date -d @$(( $1/1000 )) +%FT%T.%3N%Z) || echo "0";
      }
      
      $ while read -r _id _created _lastAccess _validUntil; do
          echo "${_id} $(epoch2timestamp ${_created}) $(epoch2timestamp ${_lastAccess}) $(epoch2timestamp ${_validUntil})";
        done < <( curl -s -D- \
                       -XGET \
                       -H "Content-Type: application/json" \
                       https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
                  sed '/^\s*$/,$!d;//d' |
                  jq -r '.content[] | [ .id, .created, .lastAccessed, .validUntil ] | join( "\t" )'
                ) |
        column -t
      537  2024-01-31T21:32:51.000PST  2024-03-26T23:30:30.000PDT  2024-07-31T21:32:51.000PDT
      579  2024-03-26T23:19:16.000PDT  0                           2024-09-26T23:19:16.000PDT
      
      # with header
      $ ( echo "ID CREATED LASTACCESS VALIDUNTIL";
          while read -r _id _created _lastAccess _validUntil; do
            echo "${_id} $(epoch2timestamp ${_created}) $(epoch2timestamp ${_lastAccess}) $(epoch2timestamp ${_validUntil})";
          done < <( curl -s -D- \
                         -XGET \
                         -H "Content-Type: application/json" \
                         https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
                    sed '/^\s*$/,$!d;//d' |
                    jq -r '.content[] | [ .id, .created, .lastAccessed, .validUntil ] | join( "\t" )'
                  )
        ) | column -t
      ID   CREATED                     LASTACCESS                  VALIDUNTIL
      537  2024-01-31T21:32:51.000PST  2024-03-26T23:33:33.000PDT  2024-07-31T21:32:51.000PDT
      579  2024-03-26T23:19:16.000PDT  0                           2024-09-26T23:19:16.000PDT
      • or

        $ curl -s -D- \
               -XGET \
               -H "Content-Type: application/json" \
               https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
          sed '/^\s*$/,$!d;//d' |
          jq -r '.content[] | [ .created, .lastAccessed, .validUntil ] | join("\n")' |
          xargs -r -I{} bash -c "et=\"{}\"; date -d @\$(( \${et}/1000 )) +%c"
        Mon 18 Dec 2023 10:25:58 AM PST
        Tue 26 Mar 2024 10:21:30 PM PDT
        Tue 18 Jun 2024 10:25:58 AM PDT
        
        # or
        $ while read -r _d; do
            date -d @$(( ${_d}/1000 )) +%c;
          done < <( curl -s -D- \
                         -XGET \
                         -H "Content-Type: application/json" \
                         https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
                    sed '/^\s*$/,$!d;//d' |
                    jq -r '.content[] | [ .created, .lastAccessed, .validUntil ] | join("\n")'
                  )
        Mon 18 Dec 2023 10:25:58 AM PST
        Tue 26 Mar 2024 10:14:58 PM PDT
        Tue 18 Jun 2024 10:25:58 AM PDT
  • create token

    [!TIP]

    • expiration keywords

      • tokenValidityTimeInMonths

      • tokenExpirationDateTime

      • tokenExpirationDateTimeMillis

    $ curl -v -d '{"tokenDescription":"<token-description>"}' \
           -X POST \
           --header "Content-Type: application/json" \
           https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token
    • create new token with expiration time

      [!NOTE|label:references:]

      • with tokenValidityTimeInMonths

        $ curl -v \
               -d '{"tokenDescription":"<token-description>", "tokenValidityTimeInMonths" : 1}' \
               -X POST \
               --header "Content-Type: application/json" \
               https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token
      • with tokenExpirationDateTime

        $ curl -v \
               -d '{"tokenDescription":"Custom expiration", "tokenExpirationDateTime" : "2020-10-19T10:29:00.000+02:00"}' \
               -X POST \
               --header "Content-Type: application/json" \
               https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token
      $ curl -s \
             -d '{"tokenDescription":"marslo-token-api-test", "tokenValidityTimeInMonths" : 6}' \
             -X POST \
             --header "Content-Type: application/json" \
             https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
       jq -r .plainTextToken
      NdxEToKfDsjUE7tct1ePP6erE1xdDsEAa64BOT
    • create token for another users

      $ curl -v \
             -d '{"tokenDescription":"token for another user", "tokenForUserKey":"JIRAUSER10105"}' \
             POST \
             --header "Content-Type: application/json" \
             https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token
      
      # or with token validity time
      $ curl -v \
             -d '{"tokenDescription":"token for another user", "tokenForUserKey":"JIRAUSER10105","tokenValidityTimeInMonths":12}' \
             POST \
             --header "Content-Type: application/json" \
             https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token
  • update token description

    $ curl -v \
           -d '{"tokenDescription":"Updated token description"}' \
           -X PATCH \
           --header "Content-Type: application/json" \
           https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token/<token-id>
  • delete token

    [!NOTE|label:references:]

    $ curl -v \
           -X DELETE \
           https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/token/<token-id>
    # list before token deleted
    $ ( echo "ID CREATED LASTACCESS VALIDUNTIL";
        while read -r _id _created _lastAccess _validUntil; do
          echo "${_id} $(epoch2timestamp ${_created}) $(epoch2timestamp ${_lastAccess}) $(epoch2timestamp ${_validUntil})";
        done < <( curl -s -D- \
                       -XGET \
                       -H "Content-Type: application/json" \
                       https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
                  sed '/^\s*$/,$!d;//d' |
                  jq -r '.content[] | [ .id, .created, .lastAccessed, .validUntil ] | join( "\t" )'
                )
      ) | column -t
    ID   CREATED                     LASTACCESS                  VALIDUNTIL
    537  2024-01-31T21:32:51.000PST  2024-03-26T23:40:02.000PDT  2024-07-31T21:32:51.000PDT
    579  2024-03-26T23:19:16.000PDT  0                           2024-09-26T23:19:16.000PDT
    580  2024-03-26T23:36:02.000PDT  0                           2024-09-26T23:36:02.000PDT
    581  2024-03-26T23:36:11.000PDT  0                           2024-09-26T23:36:11.000PDT
    
    # delete
    $ curl -s -D- \
           -X DELETE \
           https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token/581
    HTTP/2 200
    date: Wed, 27 Mar 2024 06:41:06 GMT
    content-type: application/json;charset=UTF-8
    x-arequestid: 1421x248002x2
    x-anodeid: jiraprod5
    referrer-policy: strict-origin-when-cross-origin
    x-xss-protection: 1; mode=block
    x-content-type-options: nosniff
    x-frame-options: SAMEORIGIN
    content-security-policy: sandbox
    strict-transport-security: max-age=31536000
    set-cookie: JSESSIONID=731380C1F17B2F9A59E211B0442AAFFB; Path=/; Secure; HttpOnly
    x-seraph-loginreason: OK
    set-cookie: atlassian.xsrf.token=A8KN-1NAU-M55V-EQSR_1811ae8601112c05430589e686032924599718c9_lin; Path=/; Secure; SameSite=None
    x-asessionid: k5q153
    x-ausername: marslo
    cache-control: no-cache, no-store, no-transform
    
    true
    
    # curl without header
    $ curl -s \
           -X DELETE \
           https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token/580
    true
    
    # verify
    $ ( echo "ID CREATED LASTACCESS VALIDUNTIL";
        while read -r _id _created _lastAccess _validUntil; do
          echo "${_id} $(epoch2timestamp ${_created}) $(epoch2timestamp ${_lastAccess}) $(epoch2timestamp ${_validUntil})";
        done < <( curl -s -D- \
                       -XGET \
                       -H "Content-Type: application/json" \
                       https://jira.sample.com/rest/de.resolution.apitokenauth/latest/user/token |
                  sed '/^\s*$/,$!d;//d' |
                  jq -r '.content[] | [ .id, .created, .lastAccessed, .validUntil ] | join( "\t" )'
                )
      ) | column -t
    ID   CREATED                     LASTACCESS                  VALIDUNTIL
    537  2024-01-31T21:32:51.000PST  2024-03-26T23:44:32.000PDT  2024-07-31T21:32:51.000PDT
    579  2024-03-26T23:19:16.000PDT  0                           2024-09-26T23:19:16.000PDT
PARAMETERVALUECOMMENT

userFilter

valid user key

read above how to get a user key for a name or by email address; if you want to filter for more than one user, repeat that parameter for as many users you want to filter for

descriptionFilter

search term

string to search for in all token descriptions

notValidAfter

epoch Unix timestamp

tokens not valid anymore after that date/ time in milliseconds

tokenScope

integer

0 = no scope (all pre 1.5.0 tokens), 1 = read-only, 2 = read/ write

fromCreated

epoch Unix timestamp

-

untilCreated

epoch Unix timestamp

-

fromLastUsed

epoch Unix timestamp

-

untilLastUsed

epoch Unix timestamp

-

fromExpiresDuring

epoch Unix timestamp

-

untilExpiresDuring

epoch Unix timestamp

-

  • list token expired after certain time

    $ curl "https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/tokensByFilter?notValidAfter=1688918956972" |
      jq -r
    
    # with limit and page
    $ curl "https://jira-or-confluence.sample.com/rest/de.resolution.apitokenauth/latest/user/tokensByFilter?page=0&limit=1&notValidAfter=1688918956972" |
      jq -r

[!NOTE|label:references:]

$ openssl genrsa -out jira_privatekey.pem 1024
$ openssl req -newkey rsa:1024 -x509 -key jira_privatekey.pem -out jira_publickey.cer -days 365
$ openssl pkcs8 -topk8 -nocrypt -in jira_privatekey.pem -out jira_privatekey.pcks8
$ openssl x509 -pubkey -noout -in jira_publickey.cer  > jira_publickey.pem

icons and priority

priority

[!NOTE|label:references:]

$ for _i in "blocker.png" "blocker.svg" "critical.png" "critical.svg" "high.png" "high.svg" "highest.png" "highest.svg" "low.png" "low.svg" "lowest.png" "lowest.svg" "major.png" "major.svg" "medium.png" "medium.svg" "minor.png" "minor.svg" "trivial.png" "trivial.svg"; do
    echo "--> ${_i}"
    curl -O https://jira-trigger-plugin.atlassian.net/images/icons/priorities/${_i}
  done

confluence

$ confluenceName='confluence.domain.com'
$ pageID='143765713'

$ curl -s https://${confluenceName}/rest/api/user/current | jq -r

get info

$ curl -s -X GET https://${confluenceName}/rest/api/content/${pageID} | jq --raw-output
  • get space

    $ curl -s -X GET https://${confluenceName}/rest/api/content/${pageID} | jq .space.key
  • get title

    $ curl -s -X GET https://${confluenceName}/rest/api/content/${pageID} | jq .title
  • get page history

    $ curl -s -X GET https://${confluenceName}/rest/api/content/${pageID} | jq .version.number
    • get next version

      currentVer=$(curl -s -X GET https://${confluenceName}/rest/api/content/${pageID} | jq .version.number)
      newVer=$((currentVer+1))

publish to confluence

[!NOTE|label:references:]

$ url="https://${confluenceName}/rest/api/content/${pageID}"
$ page=$(curl -s ${url})
$ space=$(echo "${page}" | jq .space.key)
$ title=$(echo "${page}" | jq .title)
$ currentVer=$(echo "${page}" | jq .version.number)
$ newVer=$((currentVer+1))

$ cat > a.json << EOF
{
  "id": "${pageID}",
  "type": "page",
  "title": ${title},
  "space": {"key": ${space}},
  "body": {
    "storage": {
      "value": "<h1>Hi confluence</h1>",
      "representation": "storage"
    }
  },
  "version": {"number":${newVer}}
}
EOF

$ curl -s \
       -i \
       -X PUT \
       -H 'Content-Type: application/json' \
       --data "$(cat a.json)" \
       https://${confluenceName}/rest/api/content/${pageID}

plugins

Multiexcerpt

ACLI

[!NOTE|label:references:]

running with docker images

$ $ docker run -ti bobswiftapps/acli:latest acli -a getClientInfo
# or
$ docker run -ti bobswiftapps/acli:latest /bin/bash
bash-4.4# acli -a getClientInfo

# with env
$ cat .env
examplegear=jira -s https://examplegear.atlassian.net -u anonymous
examplegear_confluence=confluence -s https://examplegear.atlassian.net/wiki -u anonymous
$ docker run --env-file=.env -ti bobswiftapps/acli:latest /bin/bash
bash-5.0# acli $examplegear -a getServerInfo
# or
docker run -e examplegear='jira -s https://examplegear.atlassian.net -u anonymous' -ti bobswiftapps/acli:latest /bin/bash
bash-4.4# acli ${examplegear} -a getServerInfo

# with acli.properties
$ docker run -v ${PWD}/acli.properties:/opt/acli/acli.properties \
         -ti bobswiftapps/acli:latest
# or prior to version 11.0.0, use:
$ docker run -v ${PWD}/acli.properties:/opt/atlassian-cli/acli.properties \
         -ti bobswiftapps/acli:latest
# or with `ACLI_CONFIG` env
$ docker run -v ${PWD}/acli.properties:/tmp/acli.properties \
         -e ACLI_CONFIG=/tmp/acli.properties \
         -ti bobswiftapps/acli:latest \
         acli -a getServerInfo --outputFormat 2

$ acli system setSecureProperty --name my.secret --secret -
Enter secure value: <secret value prompt>
Secure properties file does not yet exist. Creating...
Enter new secure properties password: <new password prompt>
Confirm secure properties password: <new password prompt>
Secure properties file created.
Value for key 'foo' set in secure properties file.

Last updated