@docupace/pipeline 1.0.122-connect.0 → 1.0.123-xray.0
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.
- package/.gitignore +103 -0
- package/CHANGELOG.md +6 -0
- package/bitbucket-pipelines.yml +267 -0
- package/package.json +4 -2
- package/pnpm-workspace.yaml +3 -0
- package/src/next/util.js +25 -6
- package/src/tenable/args.js +152 -0
- package/src/tenable/args.test.js +226 -0
- package/src/tenable/cli.js +24 -0
- package/src/tenable/docker.js +38 -0
- package/src/tenable/fsops.js +15 -0
- package/src/tenable/scan.js +249 -0
- package/src/tenable/scan.test.js +284 -0
- package/src/xray/cli.js +37 -0
- package/src/xray/notify.js +107 -0
- package/src/xray/notify.test.js +161 -0
- package/src/xray/report.js +194 -0
- package/src/xray/report.test.js +278 -0
- package/src/xray/slack.js +14 -0
package/.gitignore
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# IDE
|
|
4
|
+
.idea
|
|
5
|
+
*.iml
|
|
6
|
+
# Dependencies
|
|
7
|
+
/node_modules
|
|
8
|
+
/.pnp
|
|
9
|
+
.pnp.js
|
|
10
|
+
|
|
11
|
+
# Testing
|
|
12
|
+
/coverage
|
|
13
|
+
|
|
14
|
+
# Next.js
|
|
15
|
+
/.next/
|
|
16
|
+
/out/
|
|
17
|
+
|
|
18
|
+
# Production
|
|
19
|
+
/build
|
|
20
|
+
/dist
|
|
21
|
+
|
|
22
|
+
# Misc
|
|
23
|
+
.DS_Store
|
|
24
|
+
*.pem
|
|
25
|
+
cypress/screenshots/
|
|
26
|
+
|
|
27
|
+
# Debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
|
|
32
|
+
# Local env files
|
|
33
|
+
.env*.local
|
|
34
|
+
|
|
35
|
+
# Vercel
|
|
36
|
+
.vercel
|
|
37
|
+
|
|
38
|
+
# Typescript
|
|
39
|
+
*.tsbuildinfo
|
|
40
|
+
next-env.d.ts
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Logs
|
|
44
|
+
logs
|
|
45
|
+
*.log
|
|
46
|
+
|
|
47
|
+
# Runtime data
|
|
48
|
+
pids
|
|
49
|
+
*.pid
|
|
50
|
+
*.seed
|
|
51
|
+
|
|
52
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
53
|
+
lib-cov
|
|
54
|
+
|
|
55
|
+
# Coverage directory used by tools like istanbul
|
|
56
|
+
coverage
|
|
57
|
+
|
|
58
|
+
# nyc test coverage
|
|
59
|
+
.nyc_output
|
|
60
|
+
|
|
61
|
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
62
|
+
.grunt
|
|
63
|
+
|
|
64
|
+
# node-waf configuration
|
|
65
|
+
.lock-wscript
|
|
66
|
+
|
|
67
|
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
68
|
+
build/Release
|
|
69
|
+
|
|
70
|
+
# Dependency directories
|
|
71
|
+
node_modules
|
|
72
|
+
jspm_packages
|
|
73
|
+
|
|
74
|
+
# Optional npm cache directory
|
|
75
|
+
.npm
|
|
76
|
+
|
|
77
|
+
# Optional REPL history
|
|
78
|
+
.node_repl_history
|
|
79
|
+
.next
|
|
80
|
+
|
|
81
|
+
scratch
|
|
82
|
+
|
|
83
|
+
# visual studio code
|
|
84
|
+
.vscode/
|
|
85
|
+
|
|
86
|
+
src/gql/graphql.js
|
|
87
|
+
.yalc/
|
|
88
|
+
yalc.lock
|
|
89
|
+
|
|
90
|
+
.last-run.json
|
|
91
|
+
|
|
92
|
+
test-results
|
|
93
|
+
coverage-reports
|
|
94
|
+
.sonar
|
|
95
|
+
.v8-coverage
|
|
96
|
+
/assets
|
|
97
|
+
tests/lib/data/auth.json
|
|
98
|
+
|
|
99
|
+
failed-tests.json
|
|
100
|
+
|
|
101
|
+
/tmp
|
|
102
|
+
|
|
103
|
+
.claude
|
package/CHANGELOG.md
CHANGED
package/bitbucket-pipelines.yml
CHANGED
|
@@ -292,6 +292,273 @@ definitions:
|
|
|
292
292
|
tenable-scan:
|
|
293
293
|
- step: *tenable-scan-step
|
|
294
294
|
|
|
295
|
+
# Uploads and scans one or more app targets with Veracode:
|
|
296
|
+
# import: pipeline-shared-scripts:master:veracode-scan
|
|
297
|
+
#
|
|
298
|
+
# Required: VERACODE_API_ID, VERACODE_API_KEY, VERACODE_TARGETS
|
|
299
|
+
# VERACODE_TARGETS="label:appname:path1,path2,...[;label2:appname2:...]"
|
|
300
|
+
# (an app profile named <appname> must already exist in the Veracode portal)
|
|
301
|
+
# Optional: VERACODE_BUILD_CMD — eval'd before packaging, so this step is
|
|
302
|
+
# self-contained and needs no separate build step in the consumer pipeline.
|
|
303
|
+
veracode-scan:
|
|
304
|
+
- step:
|
|
305
|
+
name: Veracode Upload and Scan
|
|
306
|
+
runs-on:
|
|
307
|
+
- self.hosted
|
|
308
|
+
- linux.shell
|
|
309
|
+
- onprem
|
|
310
|
+
script:
|
|
311
|
+
- |
|
|
312
|
+
if [ -z "${VERACODE_API_ID:-}" ] || [ -z "${VERACODE_API_KEY:-}" ]; then
|
|
313
|
+
echo "ERROR: VERACODE_API_ID / VERACODE_API_KEY not set — cannot run Veracode scan." >&2
|
|
314
|
+
exit 1
|
|
315
|
+
fi
|
|
316
|
+
if [ -z "${VERACODE_TARGETS:-}" ]; then
|
|
317
|
+
echo "ERROR: VERACODE_TARGETS not set — cannot run Veracode scan." >&2
|
|
318
|
+
echo " Expected: label:appname:path1,path2,...[;label2:appname2:path1,...]" >&2
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
- |
|
|
322
|
+
# VERACODE_BUILD_CMD lets a consumer produce whatever build artifacts
|
|
323
|
+
# Veracode's SAST needs (compiled bundles, etc.) without a separate
|
|
324
|
+
# pre-build step in the consumer's own pipeline — the same
|
|
325
|
+
# self-contained approach the Xray step above uses for its Docker build.
|
|
326
|
+
if [ -n "${VERACODE_BUILD_CMD:-}" ]; then
|
|
327
|
+
echo "Running VERACODE_BUILD_CMD..."
|
|
328
|
+
eval "${VERACODE_BUILD_CMD}"
|
|
329
|
+
fi
|
|
330
|
+
- |
|
|
331
|
+
# Cached across builds on self-hosted runners; downloaded once.
|
|
332
|
+
if [ ! -f veracode-wrapper/VeracodeJavaAPI.jar ]; then
|
|
333
|
+
mkdir -p veracode-wrapper
|
|
334
|
+
curl -sSL "https://repo1.maven.org/maven2/com/veracode/vosp/api/wrappers/vosp-api-wrappers-java/26.3.19.0/vosp-api-wrappers-java-26.3.19.0.jar" \
|
|
335
|
+
-o veracode-wrapper/VeracodeJavaAPI.jar
|
|
336
|
+
fi
|
|
337
|
+
- |
|
|
338
|
+
# Each target is zipped and submitted as its own uploadandscan call,
|
|
339
|
+
# so one repo with several apps (e.g. a frontend and a backend) gets
|
|
340
|
+
# separate Veracode profiles instead of one scan covering everything.
|
|
341
|
+
SHORT_SHA="$(printf '%s' "${BITBUCKET_COMMIT:-local}" | cut -c1-7)"
|
|
342
|
+
failed=0
|
|
343
|
+
OLD_IFS="${IFS}"
|
|
344
|
+
IFS=';'
|
|
345
|
+
set -- ${VERACODE_TARGETS}
|
|
346
|
+
IFS="${OLD_IFS}"
|
|
347
|
+
for target in "$@"; do
|
|
348
|
+
[ -z "${target}" ] && continue
|
|
349
|
+
label="$(printf '%s' "${target}" | cut -d: -f1)"
|
|
350
|
+
appname="$(printf '%s' "${target}" | cut -d: -f2)"
|
|
351
|
+
paths="$(printf '%s' "${target}" | cut -d: -f3)"
|
|
352
|
+
[ -z "${label}" ] && label="app"
|
|
353
|
+
slug="$(printf '%s' "${label}" | tr -c 'A-Za-z0-9._-' '-')"
|
|
354
|
+
|
|
355
|
+
if [ -z "${appname}" ] || [ -z "${paths}" ]; then
|
|
356
|
+
echo "Target '${label}' is missing an appname or paths in VERACODE_TARGETS — skipping." >&2
|
|
357
|
+
failed=1
|
|
358
|
+
continue
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
zip_file="veracode-${slug}.zip"
|
|
362
|
+
rm -f "${zip_file}"
|
|
363
|
+
OLD_IFS2="${IFS}"
|
|
364
|
+
IFS=','
|
|
365
|
+
set -- ${paths}
|
|
366
|
+
IFS="${OLD_IFS2}"
|
|
367
|
+
zip -r "${zip_file}" "$@"
|
|
368
|
+
|
|
369
|
+
# -createprofile false — the app profile must already exist in the
|
|
370
|
+
# Veracode portal. -autoscan true starts the scan once prescan passes.
|
|
371
|
+
java -jar veracode-wrapper/VeracodeJavaAPI.jar \
|
|
372
|
+
-vid "${VERACODE_API_ID}" \
|
|
373
|
+
-vkey "${VERACODE_API_KEY}" \
|
|
374
|
+
-action uploadandscan \
|
|
375
|
+
-appname "${appname}" \
|
|
376
|
+
-createprofile false \
|
|
377
|
+
-autoscan true \
|
|
378
|
+
-filepath "${zip_file}" \
|
|
379
|
+
-version "sha-${SHORT_SHA}-${label}" || { failed=1; echo "ERROR: Veracode scan submission failed for '${label}'." >&2; }
|
|
380
|
+
|
|
381
|
+
rm -f "${zip_file}"
|
|
382
|
+
done
|
|
383
|
+
[ "${failed}" -ne 0 ] && exit 1
|
|
384
|
+
exit 0
|
|
385
|
+
|
|
386
|
+
# Runs a SonarQube analysis against the current branch, fire-and-forget
|
|
387
|
+
# (no quality-gate enforcement): import: pipeline-shared-scripts:master:sonar-scan
|
|
388
|
+
#
|
|
389
|
+
# Required: SONAR_TOKEN (soft-skips the whole step if unset)
|
|
390
|
+
# Optional: SONAR_HOST_URL (default https://sonarcloud.io), SONAR_DEFAULT_BRANCH
|
|
391
|
+
# (default "main").
|
|
392
|
+
#
|
|
393
|
+
# Single project (default): SONAR_PROJECT_DIR (default ".", the dir holding
|
|
394
|
+
# sonar-project.properties), SONAR_COVERAGE_CMD (eval'd before scanning),
|
|
395
|
+
# SONAR_COVERAGE_REPORT_PATH (if set, required to exist and be non-empty
|
|
396
|
+
# afterward).
|
|
397
|
+
#
|
|
398
|
+
# Multiple projects in one repo (e.g. a frontend and a backend, each its own
|
|
399
|
+
# sonar.projectKey): set SONAR_TARGETS instead of SONAR_PROJECT_DIR —
|
|
400
|
+
# "label:projectDir;label2:projectDir2" (semicolon between targets, mirrors
|
|
401
|
+
# VERACODE_TARGETS' delimiter convention). Each label's own coverage
|
|
402
|
+
# command/report path can be set via SONAR_COVERAGE_CMD_<LABEL> /
|
|
403
|
+
# SONAR_COVERAGE_REPORT_PATH_<LABEL> (label upper-cased, non-alphanumerics
|
|
404
|
+
# replaced with "_" — label "frontend" reads SONAR_COVERAGE_CMD_FRONTEND),
|
|
405
|
+
# falling back to the global SONAR_COVERAGE_CMD/SONAR_COVERAGE_REPORT_PATH
|
|
406
|
+
# when no per-target one is set. A failed target is reported and scanning
|
|
407
|
+
# continues on to the rest; the step fails afterward if any target failed —
|
|
408
|
+
# same pattern as VERACODE_TARGETS.
|
|
409
|
+
#
|
|
410
|
+
# depth: full — Sonar reads git blame to attribute New Code; a shallow clone
|
|
411
|
+
# only sees the tip commit and silently treats everything as new.
|
|
412
|
+
sonar-scan: &sonar-scan-pipeline
|
|
413
|
+
- step: &sonar-scan-step
|
|
414
|
+
name: SonarQube Scan
|
|
415
|
+
runs-on:
|
|
416
|
+
- self.hosted
|
|
417
|
+
- linux.shell
|
|
418
|
+
- onprem
|
|
419
|
+
clone:
|
|
420
|
+
depth: full
|
|
421
|
+
script:
|
|
422
|
+
- |
|
|
423
|
+
if [ -z "${SONAR_TOKEN:-}" ]; then
|
|
424
|
+
echo "Skipping SonarQube scan: SONAR_TOKEN not set"
|
|
425
|
+
exit 0
|
|
426
|
+
fi
|
|
427
|
+
- &sonar-scan-script |
|
|
428
|
+
export DOCKER_HOST=""
|
|
429
|
+
unset DOCKER_CONTEXT
|
|
430
|
+
SONAR_HOST="${SONAR_HOST_URL:-https://sonarcloud.io}"
|
|
431
|
+
DEFAULT_BRANCH="${SONAR_DEFAULT_BRANCH:-main}"
|
|
432
|
+
WORKDIR="$(pwd)"
|
|
433
|
+
|
|
434
|
+
# Bitbucket's built-ins: BITBUCKET_BRANCH / BITBUCKET_PR_ID /
|
|
435
|
+
# BITBUCKET_PR_DESTINATION_BRANCH. An upload with no branch parameter
|
|
436
|
+
# is filed against the project's default branch, so scanning a
|
|
437
|
+
# feature branch without one would silently overwrite that branch's
|
|
438
|
+
# analysis and quality-gate status. Resolved once — every target in
|
|
439
|
+
# this run shares the same branch/PR context.
|
|
440
|
+
BRANCH="${BITBUCKET_BRANCH:-}"
|
|
441
|
+
PR_ID="${BITBUCKET_PR_ID:-}"
|
|
442
|
+
PR_BASE="${BITBUCKET_PR_DESTINATION_BRANCH:-}"
|
|
443
|
+
BRANCH_DOCKER_ARGS=""
|
|
444
|
+
BRANCH_SONAR_ARGS=""
|
|
445
|
+
|
|
446
|
+
if [ -n "${PR_ID}" ]; then
|
|
447
|
+
BRANCH_DOCKER_ARGS="-e PR_ID=${PR_ID}"
|
|
448
|
+
BRANCH_SONAR_ARGS="-Dsonar.pullrequest.key=${PR_ID}"
|
|
449
|
+
[ -n "${BRANCH}" ] && BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.pullrequest.branch=${BRANCH}"
|
|
450
|
+
[ -n "${PR_BASE}" ] && BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.pullrequest.base=${PR_BASE}"
|
|
451
|
+
elif [ -n "${BRANCH}" ] && [ "${BRANCH}" != "${DEFAULT_BRANCH}" ]; then
|
|
452
|
+
BRANCH_DOCKER_ARGS="-e BRANCH=${BRANCH}"
|
|
453
|
+
BRANCH_SONAR_ARGS="-Dsonar.branch.name=${BRANCH}"
|
|
454
|
+
elif [ -n "${BITBUCKET_TAG:-}" ] || [ "${BRANCH}" = "${DEFAULT_BRANCH}" ]; then
|
|
455
|
+
echo "Sonar: analysing as the default branch (${DEFAULT_BRANCH})"
|
|
456
|
+
else
|
|
457
|
+
echo "ERROR: cannot tell which branch this analysis belongs to — none of BITBUCKET_PR_ID, BITBUCKET_BRANCH or BITBUCKET_TAG is set." >&2
|
|
458
|
+
echo " Refusing to upload: SonarCloud would file it against '${DEFAULT_BRANCH}' and overwrite that branch's analysis." >&2
|
|
459
|
+
exit 1
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
if [ "${SONAR_WAIT_FOR_GATE:-false}" = "true" ]; then
|
|
463
|
+
BRANCH_SONAR_ARGS="${BRANCH_SONAR_ARGS} -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=${SONAR_QUALITYGATE_TIMEOUT:-300}"
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Looks up "${1}_<SLUG>" if set, else prints nothing — POSIX sh has no
|
|
467
|
+
# ${!indirect} expansion, so this shells out through eval instead.
|
|
468
|
+
target_var() {
|
|
469
|
+
eval "printf '%s' \"\${$1_${2}:-}\""
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
# Each target is its own SonarCloud project — like Veracode's per-app
|
|
473
|
+
# profiles, a repo with several components (frontend/backend) needs
|
|
474
|
+
# its own sonar.projectKey per component, not one shared analysis.
|
|
475
|
+
failed=0
|
|
476
|
+
OLD_IFS="${IFS}"
|
|
477
|
+
IFS=';'
|
|
478
|
+
set -- ${SONAR_TARGETS:-default:${SONAR_PROJECT_DIR:-.}}
|
|
479
|
+
IFS="${OLD_IFS}"
|
|
480
|
+
|
|
481
|
+
for target in "$@"; do
|
|
482
|
+
[ -z "${target}" ] && continue
|
|
483
|
+
label="$(printf '%s' "${target}" | cut -d: -f1)"
|
|
484
|
+
project_dir="$(printf '%s' "${target}" | cut -d: -f2)"
|
|
485
|
+
[ -z "${label}" ] && label="default"
|
|
486
|
+
[ -z "${project_dir}" ] && project_dir="."
|
|
487
|
+
slug="$(printf '%s' "${label}" | tr -c 'A-Za-z0-9' '_' | tr 'a-z' 'A-Z')"
|
|
488
|
+
|
|
489
|
+
coverage_cmd="$(target_var SONAR_COVERAGE_CMD "${slug}")"
|
|
490
|
+
[ -z "${coverage_cmd}" ] && coverage_cmd="${SONAR_COVERAGE_CMD:-}"
|
|
491
|
+
coverage_path="$(target_var SONAR_COVERAGE_REPORT_PATH "${slug}")"
|
|
492
|
+
[ -z "${coverage_path}" ] && coverage_path="${SONAR_COVERAGE_REPORT_PATH:-}"
|
|
493
|
+
|
|
494
|
+
# SONAR_COVERAGE_CMD(_<LABEL>) lets a consumer produce a coverage
|
|
495
|
+
# report before the scan runs, without a separate pre-scan step —
|
|
496
|
+
# the same self-contained approach as VERACODE_BUILD_CMD above.
|
|
497
|
+
# SONAR_COVERAGE_REPORT_PATH(_<LABEL>), if set, is required to exist
|
|
498
|
+
# afterward: a run with no coverage report still "succeeds" but
|
|
499
|
+
# silently reports 0% coverage.
|
|
500
|
+
if [ -n "${coverage_cmd}" ]; then
|
|
501
|
+
echo "[${label}] Running coverage command..."
|
|
502
|
+
eval "${coverage_cmd}" || echo "WARN: coverage command exited non-zero for '${label}'; checking for the report anyway."
|
|
503
|
+
fi
|
|
504
|
+
if [ -n "${coverage_path}" ] && [ ! -s "${coverage_path}" ]; then
|
|
505
|
+
echo "ERROR: ${coverage_path} missing or empty for target '${label}' — Sonar would report 0% coverage." >&2
|
|
506
|
+
failed=1
|
|
507
|
+
continue
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
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}"
|
|
511
|
+
SONAR_ARGS="sonar-scanner -Dsonar.host.url=${SONAR_HOST} -Dproject.settings=/workspace/${project_dir}/sonar-project.properties ${BRANCH_SONAR_ARGS}"
|
|
512
|
+
|
|
513
|
+
echo "[${label}] Running: docker ${DOCKER_ARGS} sonarsource/sonar-scanner-cli:latest ${SONAR_ARGS}"
|
|
514
|
+
docker ${DOCKER_ARGS} sonarsource/sonar-scanner-cli:latest ${SONAR_ARGS} || {
|
|
515
|
+
failed=1
|
|
516
|
+
echo "ERROR: Sonar scan failed for target '${label}'." >&2
|
|
517
|
+
}
|
|
518
|
+
done
|
|
519
|
+
|
|
520
|
+
[ "${failed}" -ne 0 ] && exit 1
|
|
521
|
+
exit 0
|
|
522
|
+
|
|
523
|
+
# Enforces the SonarQube quality gate on a PR into SONAR_GATE_BRANCH (default
|
|
524
|
+
# "main"); skips for PRs into any other branch:
|
|
525
|
+
# import: pipeline-shared-scripts:master:sonar-pr-gate
|
|
526
|
+
#
|
|
527
|
+
# Required: SONAR_TOKEN (hard-fails if unset and the gate applies to this PR)
|
|
528
|
+
# Optional: same as sonar-scan, plus SONAR_GATE_BRANCH (default "main") and
|
|
529
|
+
# SONAR_QUALITYGATE_TIMEOUT (default 300 seconds).
|
|
530
|
+
sonar-pr-gate:
|
|
531
|
+
- step:
|
|
532
|
+
name: SonarQube Quality Gate
|
|
533
|
+
runs-on:
|
|
534
|
+
- self.hosted
|
|
535
|
+
- linux.shell
|
|
536
|
+
- onprem
|
|
537
|
+
clone:
|
|
538
|
+
depth: full
|
|
539
|
+
script:
|
|
540
|
+
- |
|
|
541
|
+
DEST="${BITBUCKET_PR_DESTINATION_BRANCH:-}"
|
|
542
|
+
GATE_BRANCH="${SONAR_GATE_BRANCH:-main}"
|
|
543
|
+
|
|
544
|
+
if [ -z "${DEST}" ]; then
|
|
545
|
+
echo "ERROR: BITBUCKET_PR_DESTINATION_BRANCH not set — refusing to run the PR quality gate blind." >&2
|
|
546
|
+
exit 1
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
if [ "${DEST}" != "${GATE_BRANCH}" ]; then
|
|
550
|
+
echo "Skipping Sonar quality gate: PR targets '${DEST}', not '${GATE_BRANCH}'"
|
|
551
|
+
exit 0
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
if [ -z "${SONAR_TOKEN:-}" ]; then
|
|
555
|
+
echo "ERROR: SONAR_TOKEN not set — cannot enforce the Sonar quality gate for a PR into ${GATE_BRANCH}." >&2
|
|
556
|
+
exit 1
|
|
557
|
+
fi
|
|
558
|
+
|
|
559
|
+
export SONAR_WAIT_FOR_GATE=true
|
|
560
|
+
- *sonar-scan-script
|
|
561
|
+
|
|
295
562
|
steps:
|
|
296
563
|
- step: &version-step
|
|
297
564
|
runs-on:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docupace/pipeline",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.123-xray.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,9 @@
|
|
|
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"
|
|
15
|
+
"dp-micro-cli": "src/micro/cli.js",
|
|
16
|
+
"dp-xray-cli": "src/xray/cli.js",
|
|
17
|
+
"dp-tenable-cli": "src/tenable/cli.js"
|
|
16
18
|
},
|
|
17
19
|
"keywords": [
|
|
18
20
|
"bash"
|
package/src/next/util.js
CHANGED
|
@@ -84,22 +84,41 @@ export async function getChangedAppPackages() {
|
|
|
84
84
|
return appPackages;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function maskCreds(url) {
|
|
88
|
+
return url.replace(/:[^:@/]+@/, ':***@');
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
export async function setGitInfo() {
|
|
88
92
|
console.log(`setGitInfo: SET_GITOPS_USER=${process.env.SET_GITOPS_USER || '(unset)'}`);
|
|
89
93
|
if (process.env.SET_GITOPS_USER) {
|
|
90
94
|
const before = (await $`git remote get-url origin`).stdout.trim();
|
|
91
|
-
console.log(`setGitInfo: origin before = ${before}`);
|
|
95
|
+
console.log(`setGitInfo: origin before = ${maskCreds(before)}`);
|
|
92
96
|
|
|
93
97
|
await $`git config --global user.email "gitops@docupace.com"`;
|
|
94
98
|
await $`git config --global user.name "Gitops"`;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
}
|
|
98
115
|
|
|
99
116
|
const after = (await $`git remote get-url origin`).stdout.trim();
|
|
100
|
-
console.log(`setGitInfo: origin after = ${after}`);
|
|
117
|
+
console.log(`setGitInfo: origin after = ${maskCreds(after)}`);
|
|
101
118
|
} else {
|
|
102
119
|
const current = (await $`git remote get-url origin`).stdout.trim();
|
|
103
|
-
console.log(
|
|
120
|
+
console.log(
|
|
121
|
+
`setGitInfo: SET_GITOPS_USER not set, leaving origin as-is = ${maskCreds(current)}`,
|
|
122
|
+
);
|
|
104
123
|
}
|
|
105
124
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
export function buildScanArgs({
|
|
97
|
+
slug,
|
|
98
|
+
scanTarget,
|
|
99
|
+
codeBranch,
|
|
100
|
+
commit,
|
|
101
|
+
codeUser,
|
|
102
|
+
codeRepoUrl,
|
|
103
|
+
minSeverity,
|
|
104
|
+
runInfo,
|
|
105
|
+
failOnMinSeverity = '',
|
|
106
|
+
}) {
|
|
107
|
+
const args = [
|
|
108
|
+
'container-image',
|
|
109
|
+
'scan',
|
|
110
|
+
'--archive-path',
|
|
111
|
+
'/tmp/tenable/image.tar',
|
|
112
|
+
'--name',
|
|
113
|
+
scanTarget,
|
|
114
|
+
'--code-branch',
|
|
115
|
+
codeBranch,
|
|
116
|
+
'--code-commit-hash',
|
|
117
|
+
commit,
|
|
118
|
+
'--code-commit-user',
|
|
119
|
+
codeUser,
|
|
120
|
+
'--code-repository-url',
|
|
121
|
+
codeRepoUrl,
|
|
122
|
+
'--min-severity',
|
|
123
|
+
minSeverity,
|
|
124
|
+
'--output-file-formats',
|
|
125
|
+
'Json,Md,JUnit',
|
|
126
|
+
'--output-path',
|
|
127
|
+
'/tmp/tenable/out',
|
|
128
|
+
'--output-file-name',
|
|
129
|
+
`${slug}-tenable`,
|
|
130
|
+
'--no-color',
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
if (runInfo?.includeInScanArgs) {
|
|
134
|
+
args.push(
|
|
135
|
+
'--pipeline-run-id',
|
|
136
|
+
runInfo.runId,
|
|
137
|
+
'--pipeline-run-trigger',
|
|
138
|
+
runInfo.runTrigger,
|
|
139
|
+
'--pipeline-run-url',
|
|
140
|
+
runInfo.runUrl,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (failOnMinSeverity) {
|
|
145
|
+
args.push('--fail-on-min-severity', failOnMinSeverity);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return args;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const SEPARATOR =
|
|
152
|
+
'==============================================================';
|