@docupace/pipeline 1.0.123-xray.3 → 1.0.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -119,8 +119,8 @@ scan_one() {
119
119
  "--code-commit-hash" "${COMMIT}"
120
120
  "--code-commit-user" "${CODE_USER}"
121
121
  "--code-repository-url" "${CODE_REPO_URL}"
122
- "--min-severity" "$(printf '%s' "${MIN_SEVERITY}" | tr 'A-Z' 'a-z')"
123
- "--output-file-formats" "json,md,junit"
122
+ "--min-severity" "${MIN_SEVERITY}"
123
+ "--output-file-formats" "Json,Md,JUnit"
124
124
  "--output-path" "/tmp/tenable/out"
125
125
  "--output-file-name" "${slug}-tenable"
126
126
  "--no-color"
@@ -134,24 +134,15 @@ scan_one() {
134
134
  )
135
135
  fi
136
136
  if [[ -n "${TENABLE_FAIL_ON_MIN_SEVERITY:-}" ]]; then
137
- scan_args+=("--fail-on-min-severity" "$(printf '%s' "${TENABLE_FAIL_ON_MIN_SEVERITY}" | tr 'A-Z' 'a-z')")
137
+ scan_args+=("--fail-on-min-severity" "${TENABLE_FAIL_ON_MIN_SEVERITY}")
138
138
  fi
139
139
 
140
140
  echo "Running Tenable scan for '${slug}' (min-severity=${MIN_SEVERITY}${TENABLE_FAIL_ON_MIN_SEVERITY:+, fail-on=${TENABLE_FAIL_ON_MIN_SEVERITY}})..."
141
- # --entrypoint is not optional: Tenable's own GitHub action builds FROM this image
142
- # solely to replace the entrypoint with a script that runs `tenable container-image
143
- # scan "$@"`, so the image's own entrypoint is not that binary. Handing the
144
- # subcommand to whatever it does start fails silently — exit 0, no output, no
145
- # reports. The binary also takes its output destination from the environment (the
146
- # action passes no --output-path flag at all), so it is set both ways.
147
141
  docker run --rm \
148
142
  --workdir /tmp/tenable \
149
143
  --volume "${work}:/tmp/tenable" \
150
144
  --env TENABLE_API_TOKEN \
151
145
  --env TENABLE_API_URL \
152
- --env "TENABLE_OUTPUT_PATH=/tmp/tenable/out" \
153
- --env "TENABLE_OUTPUT_FILE_NAME=${slug}-tenable" \
154
- --entrypoint "${TENABLE_SCANNER_ENTRYPOINT:-tenable}" \
155
146
  --pull=always \
156
147
  "${SCANNER_IMAGE}" "${scan_args[@]}"
157
148
  local scan_exit=$?
@@ -8,11 +8,33 @@ definitions:
8
8
  caches:
9
9
  pnpm: ~/.pnpm-store
10
10
 
11
- # Pipelines under `definitions.pipelines` are the ones other repos can pull in
12
- # with `import: pipeline-shared-scripts:master:<name>`. Names under
13
- # `pipelines.custom` below are local to this repo and are NOT importable.
11
+ steps:
12
+ - step: &version-step
13
+ runs-on:
14
+ - self.hosted
15
+ - linux.shell
16
+ - onprem
17
+ name: On Merge
18
+ script:
19
+ - npm config set //registry.npmjs.org/:_authToken $DOCUPACE_NPM_TOKEN
20
+ - pnpm install --frozen-lockfile
21
+ - export HOME=/tmp
22
+ - node src/changeset/cli.js version-commit-tag --install-latest
23
+
24
+ # Bitbucket's cross-repo `import: pipeline-shared-scripts:master:<name>`
25
+ # resolves against definitions.pipelines.<name> in the exporting repo, and
26
+ # extracts just that one target subtree — so each entry here must be fully
27
+ # self-contained: a YAML alias pointing at an anchor defined outside of it
28
+ # (in a sibling entry here, or anywhere else in this file) resolves to
29
+ # nothing once extracted in isolation, and the imported step comes back
30
+ # "empty, null, or missing" on the consumer side. Content needed in more
31
+ # than one of these (the Tenable step, the Sonar scan script) is therefore
32
+ # duplicated instead of shared via anchor/alias. pipelines.custom below
33
+ # aliases into these (safe: same-document, not crossing the export
34
+ # boundary) purely so this repo can also run each one manually from its
35
+ # own Pipelines UI.
14
36
  pipelines:
15
- xray-scan: &xray-scan-pipeline
37
+ xray-scan: &def-xray-scan
16
38
  - step:
17
39
  name: JFrog Xray Scan (dependencies + Docker image)
18
40
  runs-on:
@@ -235,15 +257,15 @@ definitions:
235
257
  artifacts:
236
258
  - xray-report/**
237
259
 
238
- - step: &tenable-scan-step
260
+ - step:
239
261
  name: Tenable Cloud Security Scan (container image)
240
262
  runs-on:
241
263
  - self.hosted
242
264
  - linux.shell
243
265
  - onprem
244
266
  script:
245
- # bin/tenable-scan.sh lives in this shared repo; an import only brings
246
- # the YAML across, so fetch it the same way the Xray step does.
267
+ # bin/tenable-scan.sh lives in this shared repo, but an import only
268
+ # brings the YAML across — so fetch it the same way the Xray step does.
247
269
  - |
248
270
  TENABLE_BIN=""
249
271
  if [ -f ./bin/tenable-scan.sh ]; then
@@ -289,8 +311,56 @@ definitions:
289
311
 
290
312
  # Runs the Tenable image scan on its own, for repos that want it separately
291
313
  # from the Xray scan: import: pipeline-shared-scripts:master:tenable-scan
292
- tenable-scan:
293
- - step: *tenable-scan-step
314
+ tenable-scan: &def-tenable-scan
315
+ - step:
316
+ name: Tenable Cloud Security Scan (container image)
317
+ runs-on:
318
+ - self.hosted
319
+ - linux.shell
320
+ - onprem
321
+ script:
322
+ - |
323
+ TENABLE_BIN=""
324
+ if [ -f ./bin/tenable-scan.sh ]; then
325
+ TENABLE_BIN=./bin
326
+ else
327
+ DEST=/tmp/pipeline-shared-scripts
328
+ rm -rf "${DEST}"
329
+ LABELS=""; URLS=""
330
+ add_src() { LABELS="${LABELS}${LABELS:+|}$1"; URLS="${URLS}${URLS:+|}$2"; }
331
+ [ -n "${SCRIPTS_REPO:-}" ] && add_src "SCRIPTS_REPO" "${SCRIPTS_REPO}"
332
+ if [ -n "${BITBUCKET_USERNAME:-}" ] && [ -n "${BITBUCKET_APP_PASSWORD:-}" ]; then
333
+ add_src "BITBUCKET_USERNAME+APP_PASSWORD" "https://${BITBUCKET_USERNAME}:${BITBUCKET_APP_PASSWORD}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
334
+ fi
335
+ [ -n "${BITBUCKET_STEP_OAUTH_TOKEN:-}" ] && add_src "step OAuth token" "https://x-token-auth:${BITBUCKET_STEP_OAUTH_TOKEN}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
336
+ add_src "ssh" "git@bitbucket.org:dp-cloud/pipeline-shared-scripts.git"
337
+ i=1
338
+ while :; do
339
+ label="$(printf '%s' "${LABELS}" | cut -d'|' -f${i})"
340
+ url="$(printf '%s' "${URLS}" | cut -d'|' -f${i})"
341
+ [ -z "${label}" ] && break
342
+ echo "Fetching shared scripts via ${label}..."
343
+ if GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes" \
344
+ git clone --depth 1 --quiet "${url}" "${DEST}" 2>/dev/null; then
345
+ TENABLE_BIN="${DEST}/bin"
346
+ echo "Fetched shared scripts via ${label}."
347
+ break
348
+ fi
349
+ rm -rf "${DEST}"
350
+ i=$((i + 1))
351
+ done
352
+ fi
353
+ if [ -z "${TENABLE_BIN}" ]; then
354
+ echo "Could not fetch shared scripts (tried: $(printf '%s' "${LABELS}" | tr '|' ' ')) — skipping Tenable scan."
355
+ exit 0
356
+ fi
357
+ if [ ! -f "${TENABLE_BIN}/tenable-scan.sh" ]; then
358
+ echo "tenable-scan.sh not present in ${TENABLE_BIN} — skipping Tenable scan."
359
+ exit 0
360
+ fi
361
+ bash "${TENABLE_BIN}/tenable-scan.sh"
362
+ artifacts:
363
+ - tenable-report/**
294
364
 
295
365
  # Uploads and scans one or more app targets with Veracode:
296
366
  # import: pipeline-shared-scripts:master:veracode-scan
@@ -300,7 +370,7 @@ definitions:
300
370
  # (an app profile named <appname> must already exist in the Veracode portal)
301
371
  # Optional: VERACODE_BUILD_CMD — eval'd before packaging, so this step is
302
372
  # self-contained and needs no separate build step in the consumer pipeline.
303
- veracode-scan:
373
+ veracode-scan: &def-veracode-scan
304
374
  - step:
305
375
  name: Veracode Upload and Scan
306
376
  runs-on:
@@ -409,8 +479,8 @@ definitions:
409
479
  #
410
480
  # depth: full — Sonar reads git blame to attribute New Code; a shallow clone
411
481
  # only sees the tip commit and silently treats everything as new.
412
- sonar-scan: &sonar-scan-pipeline
413
- - step: &sonar-scan-step
482
+ sonar-scan: &def-sonar-scan
483
+ - step:
414
484
  name: SonarQube Scan
415
485
  runs-on:
416
486
  - self.hosted
@@ -424,7 +494,7 @@ definitions:
424
494
  echo "Skipping SonarQube scan: SONAR_TOKEN not set"
425
495
  exit 0
426
496
  fi
427
- - &sonar-scan-script |
497
+ - |
428
498
  export DOCKER_HOST=""
429
499
  unset DOCKER_CONTEXT
430
500
  SONAR_HOST="${SONAR_HOST_URL:-https://sonarcloud.io}"
@@ -527,7 +597,7 @@ definitions:
527
597
  # Required: SONAR_TOKEN (hard-fails if unset and the gate applies to this PR)
528
598
  # Optional: same as sonar-scan, plus SONAR_GATE_BRANCH (default "main") and
529
599
  # SONAR_QUALITYGATE_TIMEOUT (default 300 seconds).
530
- sonar-pr-gate:
600
+ sonar-pr-gate: &def-sonar-pr-gate
531
601
  - step:
532
602
  name: SonarQube Quality Gate
533
603
  runs-on:
@@ -557,27 +627,106 @@ definitions:
557
627
  fi
558
628
 
559
629
  export SONAR_WAIT_FOR_GATE=true
560
- - *sonar-scan-script
630
+ - |
631
+ export DOCKER_HOST=""
632
+ unset DOCKER_CONTEXT
633
+ SONAR_HOST="${SONAR_HOST_URL:-https://sonarcloud.io}"
634
+ DEFAULT_BRANCH="${SONAR_DEFAULT_BRANCH:-main}"
635
+ WORKDIR="$(pwd)"
561
636
 
562
- steps:
563
- - step: &version-step
564
- runs-on:
565
- - self.hosted
566
- - linux.shell
567
- - onprem
568
- name: On Merge
569
- script:
570
- - npm config set //registry.npmjs.org/:_authToken $DOCUPACE_NPM_TOKEN
571
- - pnpm install --frozen-lockfile
572
- - export HOME=/tmp
573
- - node src/changeset/cli.js version-commit-tag --install-latest
637
+ # Bitbucket's built-ins: BITBUCKET_BRANCH / BITBUCKET_PR_ID /
638
+ # BITBUCKET_PR_DESTINATION_BRANCH. An upload with no branch parameter
639
+ # is filed against the project's default branch, so scanning a
640
+ # feature branch without one would silently overwrite that branch's
641
+ # analysis and quality-gate status. Resolved once — every target in
642
+ # this run shares the same branch/PR context.
643
+ BRANCH="${BITBUCKET_BRANCH:-}"
644
+ PR_ID="${BITBUCKET_PR_ID:-}"
645
+ PR_BASE="${BITBUCKET_PR_DESTINATION_BRANCH:-}"
646
+ BRANCH_DOCKER_ARGS=""
647
+ BRANCH_SONAR_ARGS=""
648
+
649
+ if [ -n "${PR_ID}" ]; then
650
+ BRANCH_DOCKER_ARGS="-e PR_ID=${PR_ID}"
651
+ BRANCH_SONAR_ARGS="-Dsonar.pullrequest.key=${PR_ID}"
652
+ [ -n "${BRANCH}" ] && BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.pullrequest.branch=${BRANCH}"
653
+ [ -n "${PR_BASE}" ] && BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.pullrequest.base=${PR_BASE}"
654
+ elif [ -n "${BRANCH}" ] && [ "${BRANCH}" != "${DEFAULT_BRANCH}" ]; then
655
+ BRANCH_DOCKER_ARGS="-e BRANCH=${BRANCH}"
656
+ BRANCH_SONAR_ARGS="-Dsonar.branch.name=${BRANCH}"
657
+ elif [ -n "${BITBUCKET_TAG:-}" ] || [ "${BRANCH}" = "${DEFAULT_BRANCH}" ]; then
658
+ echo "Sonar: analysing as the default branch (${DEFAULT_BRANCH})"
659
+ else
660
+ echo "ERROR: cannot tell which branch this analysis belongs to — none of BITBUCKET_PR_ID, BITBUCKET_BRANCH or BITBUCKET_TAG is set." >&2
661
+ echo " Refusing to upload: SonarCloud would file it against '${DEFAULT_BRANCH}' and overwrite that branch's analysis." >&2
662
+ exit 1
663
+ fi
664
+
665
+ if [ "${SONAR_WAIT_FOR_GATE:-false}" = "true" ]; then
666
+ BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=${SONAR_QUALITYGATE_TIMEOUT:-300}"
667
+ fi
668
+
669
+ # Looks up "${1}_<SLUG>" if set, else prints nothing — POSIX sh has no
670
+ # ${!indirect} expansion, so this shells out through eval instead.
671
+ target_var() {
672
+ eval "printf '%s' \"\${$1_${2}:-}\""
673
+ }
674
+
675
+ # Each target is its own SonarCloud project — like Veracode's per-app
676
+ # profiles, a repo with several components (frontend/backend) needs
677
+ # its own sonar.projectKey per component, not one shared analysis.
678
+ failed=0
679
+ OLD_IFS="${IFS}"
680
+ IFS=';'
681
+ set -- ${SONAR_TARGETS:-default:${SONAR_PROJECT_DIR:-.}}
682
+ IFS="${OLD_IFS}"
683
+
684
+ for target in "$@"; do
685
+ [ -z "${target}" ] && continue
686
+ label="$(printf '%s' "${target}" | cut -d: -f1)"
687
+ project_dir="$(printf '%s' "${target}" | cut -d: -f2)"
688
+ [ -z "${label}" ] && label="default"
689
+ [ -z "${project_dir}" ] && project_dir="."
690
+ slug="$(printf '%s' "${label}" | tr -c 'A-Za-z0-9' '_' | tr 'a-z' 'A-Z')"
691
+
692
+ coverage_cmd="$(target_var SONAR_COVERAGE_CMD "${slug}")"
693
+ [ -z "${coverage_cmd}" ] && coverage_cmd="${SONAR_COVERAGE_CMD:-}"
694
+ coverage_path="$(target_var SONAR_COVERAGE_REPORT_PATH "${slug}")"
695
+ [ -z "${coverage_path}" ] && coverage_path="${SONAR_COVERAGE_REPORT_PATH:-}"
696
+
697
+ if [ -n "${coverage_cmd}" ]; then
698
+ echo "[${label}] Running coverage command..."
699
+ eval "${coverage_cmd}" || echo "WARN: coverage command exited non-zero for '${label}'; checking for the report anyway."
700
+ fi
701
+ if [ -n "${coverage_path}" ] && [ ! -s "${coverage_path}" ]; then
702
+ echo "ERROR: ${coverage_path} missing or empty for target '${label}' — Sonar would report 0% coverage." >&2
703
+ failed=1
704
+ continue
705
+ fi
706
+
707
+ DOCKER_ARGS="run --rm --platform linux/amd64 -e SONAR_HOST_URL=${SONAR_HOST} -e SONAR_TOKEN ${BRANCH_DOCKER_ARGS} -v ${WORKDIR}:/workspace -w /workspace/${project_dir}"
708
+ SONAR_ARGS="sonar-scanner -Dsonar.host.url=${SONAR_HOST} -Dproject.settings=/workspace/${project_dir}/sonar-project.properties ${BRANCH_SONAR_ARGS}"
709
+
710
+ echo "[${label}] Running: docker ${DOCKER_ARGS} sonarsource/sonar-scanner-cli:latest ${SONAR_ARGS}"
711
+ docker ${DOCKER_ARGS} sonarsource/sonar-scanner-cli:latest ${SONAR_ARGS} || {
712
+ failed=1
713
+ echo "ERROR: Sonar scan failed for target '${label}'." >&2
714
+ }
715
+ done
716
+
717
+ [ "${failed}" -ne 0 ] && exit 1
718
+ exit 0
574
719
 
575
720
  pipelines:
576
- # Lets this repo run the shared scans against itself from the Bitbucket UI.
721
+ # These simply alias the definitions.pipelines entries above (safe:
722
+ # same-document reuse, not a cross-repo import) so this repo can also run
723
+ # each shared pipeline manually from its own Pipelines UI.
577
724
  custom:
578
- xray-scan: *xray-scan-pipeline
579
- tenable-scan:
580
- - step: *tenable-scan-step
725
+ xray-scan: *def-xray-scan
726
+ tenable-scan: *def-tenable-scan
727
+ veracode-scan: *def-veracode-scan
728
+ sonar-scan: *def-sonar-scan
729
+ sonar-pr-gate: *def-sonar-pr-gate
581
730
 
582
731
  pull-requests:
583
732
  '**':
@@ -618,4 +767,4 @@ pipelines:
618
767
  - pnpm install --frozen-lockfile
619
768
  - 'npx eslint . --rule "no-unused-vars: warn" --rule "no-useless-escape: warn" --color'
620
769
  - pnpm test
621
- - pnpm publish --no-git-checks
770
+ - pnpm publish --no-git-checks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docupace/pipeline",
3
- "version": "1.0.123-xray.3",
3
+ "version": "1.0.124",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,10 +12,7 @@
12
12
  "dp-next-cli": "src/next/cli.js",
13
13
  "dp-auth0-cli": "src/auth0/cli.js",
14
14
  "dp-merge-cli": "src/merge/cli.js",
15
- "dp-micro-cli": "src/micro/cli.js",
16
- "dp-xray-cli": "src/xray/cli.js",
17
- "dp-tenable-cli": "src/tenable/cli.js",
18
- "dp-veracode-cli": "src/veracode/cli.js"
15
+ "dp-micro-cli": "src/micro/cli.js"
19
16
  },
20
17
  "keywords": [
21
18
  "bash"
@@ -94,6 +94,10 @@ export async function uploadToCanny(releaseNote, labelIDs) {
94
94
 
95
95
  const body = JSON.stringify(bodyPayload);
96
96
 
97
+ console.log(
98
+ `📤 Posting to Canny: ${JSON.stringify({ ...bodyPayload, apiKey: undefined }, null, 2)}`,
99
+ );
100
+
97
101
  // Create a new changelog entry in Canny
98
102
  const createResponse = await fetch(
99
103
  'https://canny.io/api/v1/entries/create',
@@ -1,24 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { fileURLToPath } from 'url';
4
3
  import { $ } from 'zx';
5
4
 
6
5
  import { setGitInfo } from '../next/util.js';
7
6
  import { versionStatus } from './version.js';
8
7
 
9
- const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
10
- const PACKAGE_VERSION = JSON.parse(
11
- fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'),
12
- ).version;
13
-
14
8
  export async function validateChangesets() {
15
- console.log(`validateChangesets: @docupace/pipeline version = ${PACKAGE_VERSION}`);
16
9
  await setGitInfo();
17
10
  const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
18
- console.log(`validateChangesets: destBranch = ${destBranch}`);
19
- console.log(`validateChangesets: fetching origin ${destBranch}:${destBranch}`);
20
11
  await $`git fetch origin ${destBranch}:${destBranch}`;
21
- console.log('validateChangesets: fetch succeeded');
22
12
  let releases;
23
13
 
24
14
  try {
package/src/next/util.js CHANGED
@@ -84,41 +84,12 @@ export async function getChangedAppPackages() {
84
84
  return appPackages;
85
85
  }
86
86
 
87
- function maskCreds(url) {
88
- return url.replace(/:[^:@/]+@/, ':***@');
89
- }
90
-
91
87
  export async function setGitInfo() {
92
- console.log(`setGitInfo: SET_GITOPS_USER=${process.env.SET_GITOPS_USER || '(unset)'}`);
93
88
  if (process.env.SET_GITOPS_USER) {
94
- const before = (await $`git remote get-url origin`).stdout.trim();
95
- console.log(`setGitInfo: origin before = ${maskCreds(before)}`);
96
-
97
89
  await $`git config --global user.email "gitops@docupace.com"`;
98
90
  await $`git config --global user.name "Gitops"`;
99
-
100
- // Some self-hosted runners rewrite `git@bitbucket.org:` (SSH) to a bare,
101
- // credential-less `https://bitbucket.org/` via a git `insteadOf` rule, so an
102
- // SSH-form origin silently loses its identity at fetch/push time. Use an
103
- // HTTPS URL with the token embedded instead — insteadOf doesn't touch it.
104
- const token =
105
- process.env.BITBUCKET_STEP_OAUTH_TOKEN || process.env.BITBUCKET_TOKEN;
106
- if (!token) {
107
- console.log(
108
- 'setGitInfo: no BITBUCKET_STEP_OAUTH_TOKEN/BITBUCKET_TOKEN available, leaving origin as-is',
109
- );
110
- } else {
111
- const repoUrl = `https://x-token-auth:${token}@bitbucket.org/dp-cloud/${process.env.BITBUCKET_REPO_SLUG}.git`;
112
- console.log(`Setting new origin: ${maskCreds(repoUrl)}`);
113
- await $`git remote set-url origin ${repoUrl}`;
114
- }
115
-
116
- const after = (await $`git remote get-url origin`).stdout.trim();
117
- console.log(`setGitInfo: origin after = ${maskCreds(after)}`);
118
- } else {
119
- const current = (await $`git remote get-url origin`).stdout.trim();
120
- console.log(
121
- `setGitInfo: SET_GITOPS_USER not set, leaving origin as-is = ${maskCreds(current)}`,
122
- );
91
+ const repoUrl = `git@bitbucket.org:dp-cloud/${process.env.BITBUCKET_REPO_SLUG}.git`;
92
+ console.log(`Setting new origin: ${repoUrl}`);
93
+ await $`git remote set-url origin ${repoUrl}`;
123
94
  }
124
95
  }
@@ -1,3 +0,0 @@
1
- allowBuilds:
2
- esbuild: true
3
- unrs-resolver: true
@@ -1,157 +0,0 @@
1
- // Pure parsing/formatting logic for the Tenable scan step, ported from
2
- // bin/tenable-scan.sh. No fs/docker calls here so it's unit-testable.
3
-
4
- // Mirrors `tr -c 'A-Za-z0-9._-' '-'`: every character outside that set is
5
- // replaced with '-' (no squeezing of consecutive replacements).
6
- export function slugify(label) {
7
- return label.replace(/[^A-Za-z0-9._-]/g, '-');
8
- }
9
-
10
- // Splits `str` on `sep` into exactly `n` fields, folding any extra fields
11
- // into the last one — matching `read a b c d <<< "$str"` semantics, where a
12
- // trailing variable absorbs everything left over.
13
- function splitFixed(str, sep, n) {
14
- const parts = str.split(sep);
15
- if (parts.length <= n) {
16
- return [...parts, ...Array(n - parts.length).fill('')];
17
- }
18
- return [...parts.slice(0, n - 1), parts.slice(n - 1).join(sep)];
19
- }
20
-
21
- // One "label:auditDir:buildContext:dockerfile" entry from SCAN_TARGETS.
22
- // auditDir belongs to the Xray step and is ignored here. A target with no
23
- // build context is returned with skipped: true rather than dropped, so the
24
- // caller can log why it was skipped (matching the bash script's behavior).
25
- export function parseScanTargets(scanTargetsStr) {
26
- return (scanTargetsStr || '')
27
- .split(';')
28
- .map((t) => t.trim())
29
- .filter(Boolean)
30
- .map((raw) => {
31
- const [rawLabel, , rawContext, rawDockerfile] = splitFixed(raw, ':', 4);
32
- const label = rawLabel || 'app';
33
- const slug = slugify(label);
34
- if (!rawContext) {
35
- return { label, slug, buildContext: '', dockerfile: '', skipped: true };
36
- }
37
- return {
38
- label,
39
- slug,
40
- buildContext: rawContext,
41
- dockerfile: rawDockerfile || `${rawContext}/Dockerfile`,
42
- skipped: false,
43
- };
44
- });
45
- }
46
-
47
- export function defaultScanTargets(buildContext, dockerfilePath) {
48
- const ctx = buildContext || '.';
49
- return `app::${ctx}:${dockerfilePath || `${ctx}/Dockerfile`}`;
50
- }
51
-
52
- export function buildCodeMetadata({
53
- commit = 'local',
54
- branch = '',
55
- prDestinationBranch = '',
56
- workspace = 'unknown',
57
- repoSlug = 'unknown',
58
- gitAuthorName = '',
59
- } = {}) {
60
- return {
61
- shortSha: commit.slice(0, 7),
62
- codeBranch: branch || prDestinationBranch || 'unknown',
63
- codeRepoUrl: `https://bitbucket.org/${workspace}/${repoSlug}`,
64
- codeUser: gitAuthorName || 'bitbucket-pipelines',
65
- };
66
- }
67
-
68
- // The scanner requires --pipeline-run-url as soon as any pipeline flag is
69
- // supplied, so the trio (id/trigger/url) travels together or not at all —
70
- // includeInScanArgs tells the caller which case applies.
71
- export function buildPipelineRunInfo({
72
- runId = '',
73
- runTrigger = '',
74
- runUrl = '',
75
- prId = '',
76
- buildNumber = '',
77
- workspace = 'unknown',
78
- repoSlug = 'unknown',
79
- } = {}) {
80
- const resolvedRunId = runId || buildNumber || '';
81
- const resolvedTrigger = runTrigger || (prId ? 'pull_request' : '') || 'push';
82
- const resolvedUrl =
83
- runUrl ||
84
- (buildNumber
85
- ? `https://bitbucket.org/${workspace}/${repoSlug}/pipelines/results/${buildNumber}`
86
- : '');
87
-
88
- return {
89
- runId: resolvedRunId,
90
- runTrigger: resolvedTrigger,
91
- runUrl: resolvedUrl,
92
- includeInScanArgs: Boolean(resolvedRunId) && Boolean(resolvedUrl),
93
- };
94
- }
95
-
96
- // The scanner's documented vocabularies are lowercase — severities
97
- // information|low|medium|high|critical, formats cyclonedx|json|junit|md — and it
98
- // does not correct a case mismatch or complain about one: it just exports nothing.
99
- // Callers set these from env vars that read naturally capitalised ("Medium"), so
100
- // normalise here rather than making every caller remember.
101
- export function buildScanArgs({
102
- slug,
103
- scanTarget,
104
- codeBranch,
105
- commit,
106
- codeUser,
107
- codeRepoUrl,
108
- minSeverity,
109
- runInfo,
110
- failOnMinSeverity = '',
111
- }) {
112
- const args = [
113
- 'container-image',
114
- 'scan',
115
- '--archive-path',
116
- '/tmp/tenable/image.tar',
117
- '--name',
118
- scanTarget,
119
- '--code-branch',
120
- codeBranch,
121
- '--code-commit-hash',
122
- commit,
123
- '--code-commit-user',
124
- codeUser,
125
- '--code-repository-url',
126
- codeRepoUrl,
127
- '--min-severity',
128
- String(minSeverity).toLowerCase(),
129
- '--output-file-formats',
130
- 'json,md,junit',
131
- '--output-path',
132
- '/tmp/tenable/out',
133
- '--output-file-name',
134
- `${slug}-tenable`,
135
- '--no-color',
136
- ];
137
-
138
- if (runInfo?.includeInScanArgs) {
139
- args.push(
140
- '--pipeline-run-id',
141
- runInfo.runId,
142
- '--pipeline-run-trigger',
143
- runInfo.runTrigger,
144
- '--pipeline-run-url',
145
- runInfo.runUrl,
146
- );
147
- }
148
-
149
- if (failOnMinSeverity) {
150
- args.push('--fail-on-min-severity', failOnMinSeverity.toLowerCase());
151
- }
152
-
153
- return args;
154
- }
155
-
156
- export const SEPARATOR =
157
- '==============================================================';