@docupace/pipeline 1.0.123-xray.2 → 1.0.123

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=$?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docupace/pipeline",
3
- "version": "1.0.123-xray.2",
3
+ "version": "1.0.123",
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"
@@ -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
  }
package/.gitignore DELETED
@@ -1,103 +0,0 @@
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
@@ -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
- '==============================================================';
@@ -1,226 +0,0 @@
1
- import { describe, expect, test } from '@jest/globals';
2
- import {
3
- slugify,
4
- parseScanTargets,
5
- defaultScanTargets,
6
- buildCodeMetadata,
7
- buildPipelineRunInfo,
8
- buildScanArgs,
9
- } from './args.js';
10
-
11
- describe('slugify', () => {
12
- test('replaces every non [A-Za-z0-9._-] character with a dash', () => {
13
- expect(slugify('my app!')).toBe('my-app-');
14
- expect(slugify('my/app')).toBe('my-app');
15
- expect(slugify('already-safe_v1.2')).toBe('already-safe_v1.2');
16
- });
17
- });
18
-
19
- describe('parseScanTargets', () => {
20
- test('parses label:auditDir:context:dockerfile entries, ignoring auditDir', () => {
21
- const targets = parseScanTargets(
22
- 'web:deps:services/web:services/web/Dockerfile.prod;worker::services/worker:',
23
- );
24
-
25
- expect(targets).toEqual([
26
- {
27
- label: 'web',
28
- slug: 'web',
29
- buildContext: 'services/web',
30
- dockerfile: 'services/web/Dockerfile.prod',
31
- skipped: false,
32
- },
33
- {
34
- label: 'worker',
35
- slug: 'worker',
36
- buildContext: 'services/worker',
37
- dockerfile: 'services/worker/Dockerfile',
38
- skipped: false,
39
- },
40
- ]);
41
- });
42
-
43
- test('defaults an empty label to "app" and slugifies it', () => {
44
- const [target] = parseScanTargets(':deps:ctx:Dockerfile');
45
- expect(target.label).toBe('app');
46
- expect(target.slug).toBe('app');
47
- });
48
-
49
- test('marks a target with no build context as skipped', () => {
50
- const [target] = parseScanTargets('web:deps::Dockerfile');
51
- expect(target).toEqual({
52
- label: 'web',
53
- slug: 'web',
54
- buildContext: '',
55
- dockerfile: '',
56
- skipped: true,
57
- });
58
- });
59
-
60
- test('ignores blank entries between semicolons', () => {
61
- expect(parseScanTargets('web:deps:ctx:Dockerfile;;')).toHaveLength(1);
62
- });
63
-
64
- test('empty string produces no targets', () => {
65
- expect(parseScanTargets('')).toEqual([]);
66
- });
67
- });
68
-
69
- describe('defaultScanTargets', () => {
70
- test('builds a single app target from context + dockerfile', () => {
71
- expect(defaultScanTargets('.', './Dockerfile')).toBe('app::.:./Dockerfile');
72
- });
73
-
74
- test('defaults the dockerfile path to <context>/Dockerfile when unset', () => {
75
- expect(defaultScanTargets('services/web', '')).toBe(
76
- 'app::services/web:services/web/Dockerfile',
77
- );
78
- });
79
-
80
- test('defaults the context to "." when unset', () => {
81
- expect(defaultScanTargets('', '')).toBe('app::.:./Dockerfile');
82
- });
83
- });
84
-
85
- describe('buildCodeMetadata', () => {
86
- test('derives a 7-char short SHA and falls back branch/user', () => {
87
- const meta = buildCodeMetadata({
88
- commit: 'abcdef1234567',
89
- branch: '',
90
- prDestinationBranch: 'main',
91
- workspace: 'dp-cloud',
92
- repoSlug: 'my-repo',
93
- gitAuthorName: '',
94
- });
95
-
96
- expect(meta).toEqual({
97
- shortSha: 'abcdef1',
98
- codeBranch: 'main',
99
- codeRepoUrl: 'https://bitbucket.org/dp-cloud/my-repo',
100
- codeUser: 'bitbucket-pipelines',
101
- });
102
- });
103
-
104
- test('prefers branch over PR destination branch, and a real author name', () => {
105
- const meta = buildCodeMetadata({
106
- commit: 'abcdef1234567',
107
- branch: 'feature/x',
108
- prDestinationBranch: 'main',
109
- gitAuthorName: 'Matt Fields',
110
- });
111
- expect(meta.codeBranch).toBe('feature/x');
112
- expect(meta.codeUser).toBe('Matt Fields');
113
- });
114
- });
115
-
116
- describe('buildPipelineRunInfo', () => {
117
- test('defaults trigger to push and builds a run URL from the build number', () => {
118
- const info = buildPipelineRunInfo({
119
- buildNumber: '42',
120
- workspace: 'dp-cloud',
121
- repoSlug: 'my-repo',
122
- });
123
-
124
- expect(info).toEqual({
125
- runId: '42',
126
- runTrigger: 'push',
127
- runUrl: 'https://bitbucket.org/dp-cloud/my-repo/pipelines/results/42',
128
- includeInScanArgs: true,
129
- });
130
- });
131
-
132
- test('defaults trigger to pull_request when a PR id is present', () => {
133
- const info = buildPipelineRunInfo({ buildNumber: '42', prId: '7' });
134
- expect(info.runTrigger).toBe('pull_request');
135
- });
136
-
137
- test('explicit TENABLE_PIPELINE_RUN_* values win over the Bitbucket-derived ones', () => {
138
- const info = buildPipelineRunInfo({
139
- runId: 'custom-1',
140
- runTrigger: 'manual',
141
- runUrl: 'https://example.com/run/1',
142
- buildNumber: '42',
143
- });
144
- expect(info).toEqual({
145
- runId: 'custom-1',
146
- runTrigger: 'manual',
147
- runUrl: 'https://example.com/run/1',
148
- includeInScanArgs: true,
149
- });
150
- });
151
-
152
- test('does not include the trio in scan args when no build number/id is available', () => {
153
- const info = buildPipelineRunInfo({});
154
- expect(info.includeInScanArgs).toBe(false);
155
- });
156
- });
157
-
158
- describe('buildScanArgs', () => {
159
- const base = {
160
- slug: 'web',
161
- scanTarget: 'my-repo-web:tenable-abc1234',
162
- codeBranch: 'main',
163
- commit: 'abcdef1234567',
164
- codeUser: 'Matt Fields',
165
- codeRepoUrl: 'https://bitbucket.org/dp-cloud/my-repo',
166
- minSeverity: 'Medium',
167
- runInfo: { includeInScanArgs: false },
168
- };
169
-
170
- test('builds the base scanner CLI args', () => {
171
- expect(buildScanArgs(base)).toEqual([
172
- 'container-image',
173
- 'scan',
174
- '--archive-path',
175
- '/tmp/tenable/image.tar',
176
- '--name',
177
- 'my-repo-web:tenable-abc1234',
178
- '--code-branch',
179
- 'main',
180
- '--code-commit-hash',
181
- 'abcdef1234567',
182
- '--code-commit-user',
183
- 'Matt Fields',
184
- '--code-repository-url',
185
- 'https://bitbucket.org/dp-cloud/my-repo',
186
- '--min-severity',
187
- 'medium',
188
- '--output-file-formats',
189
- 'json,md,junit',
190
- '--output-path',
191
- '/tmp/tenable/out',
192
- '--output-file-name',
193
- 'web-tenable',
194
- '--no-color',
195
- ]);
196
- });
197
-
198
- test('appends the pipeline-run trio only when includeInScanArgs is true', () => {
199
- const args = buildScanArgs({
200
- ...base,
201
- runInfo: {
202
- includeInScanArgs: true,
203
- runId: '42',
204
- runTrigger: 'push',
205
- runUrl: 'https://bitbucket.org/dp-cloud/my-repo/pipelines/results/42',
206
- },
207
- });
208
- expect(args).toEqual(
209
- expect.arrayContaining([
210
- '--pipeline-run-id',
211
- '42',
212
- '--pipeline-run-trigger',
213
- 'push',
214
- '--pipeline-run-url',
215
- 'https://bitbucket.org/dp-cloud/my-repo/pipelines/results/42',
216
- ]),
217
- );
218
- });
219
-
220
- test('appends --fail-on-min-severity only when set', () => {
221
- expect(buildScanArgs(base)).not.toContain('--fail-on-min-severity');
222
- expect(buildScanArgs({ ...base, failOnMinSeverity: 'High' })).toEqual(
223
- expect.arrayContaining(['--fail-on-min-severity', 'high']),
224
- );
225
- });
226
- });
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { $ } from 'zx';
5
- import { runTenableScan } from './scan.js';
6
-
7
- const program = new Command();
8
-
9
- program
10
- .command('scan')
11
- .description('Run the Tenable Cloud Security container image scan')
12
- .action(async () => {
13
- let gitAuthorName = '';
14
- const result = await $({
15
- nothrow: true,
16
- quiet: true,
17
- })`git log -1 --pretty=format:%an`;
18
- if (result.exitCode === 0) gitAuthorName = result.stdout.trim();
19
-
20
- const { exitCode } = await runTenableScan({ gitAuthorName });
21
- process.exit(exitCode);
22
- });
23
-
24
- program.parse(process.argv);
@@ -1,45 +0,0 @@
1
- import { $ } from 'zx';
2
-
3
- // Thin wrappers around the docker CLI so scan.js can inject a fake in tests.
4
- // Every call uses nothrow: a non-zero exit is meaningful data here (a failed
5
- // build, or the scanner itself reporting findings), not an exceptional case.
6
- export const docker = {
7
- async buildxAvailable() {
8
- const result = await $({
9
- nothrow: true,
10
- quiet: true,
11
- })`docker buildx version`;
12
- return result.exitCode === 0;
13
- },
14
-
15
- async build(args) {
16
- const result = await $({ nothrow: true })`docker build ${args}`;
17
- return { ok: result.exitCode === 0 };
18
- },
19
-
20
- async save(image, tarPath) {
21
- const result = await $({
22
- nothrow: true,
23
- })`docker save ${image} -o ${tarPath}`;
24
- return { ok: result.exitCode === 0 };
25
- },
26
-
27
- // The entrypoint is overridden rather than trusted. Tenable's own GitHub action
28
- // builds FROM this image purely to replace the entrypoint with a script whose
29
- // first line is `tenable container-image scan "$@"`, which means the base image's
30
- // own entrypoint is not that binary. Passing the subcommand as bare arguments
31
- // hands them to whatever the image does start, and the failure is silent: exit 0,
32
- // no output, no reports.
33
- async run({ image, args, volume, workdir, env, entrypoint }) {
34
- const envFlags = env.flatMap((name) => ['--env', name]);
35
- const entrypointFlags = entrypoint ? ['--entrypoint', entrypoint] : [];
36
- const result = await $({
37
- nothrow: true,
38
- })`docker run --rm --workdir ${workdir} --volume ${volume} ${envFlags} ${entrypointFlags} --pull=always ${image} ${args}`;
39
- return result.exitCode;
40
- },
41
-
42
- async rmi(image) {
43
- await $({ nothrow: true, quiet: true })`docker rmi -f ${image}`;
44
- },
45
- };