vouchington-tooling 0.0.9 → 0.0.11
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/README.md +21 -1
- package/dist/cli/commands/gha-artifacts-cleanup.d.mts +6 -0
- package/dist/cli/commands/gha-artifacts-cleanup.mjs +50 -0
- package/dist/cli/commands/http-origin.d.mts +2 -0
- package/dist/cli/commands/http-origin.mjs +14 -0
- package/dist/cli/index.mjs +11 -0
- package/dist/cli/parse-gha-artifacts-cleanup.d.mts +15 -0
- package/dist/cli/parse-gha-artifacts-cleanup.mjs +74 -0
- package/dist/cli/parse.d.mts +7 -2
- package/dist/cli/parse.mjs +36 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +9 -0
- package/dist/gha-artifacts-cleanup/classify.d.mts +14 -0
- package/dist/gha-artifacts-cleanup/classify.mjs +32 -0
- package/dist/gha-artifacts-cleanup/commands.d.mts +23 -0
- package/dist/gha-artifacts-cleanup/commands.mjs +55 -0
- package/dist/gha-artifacts-cleanup/github.d.mts +16 -0
- package/dist/gha-artifacts-cleanup/github.mjs +100 -0
- package/dist/gha-artifacts-cleanup/index.d.mts +8 -0
- package/dist/gha-artifacts-cleanup/index.mjs +4 -0
- package/dist/gha-artifacts-cleanup/plan.d.mts +17 -0
- package/dist/gha-artifacts-cleanup/plan.mjs +40 -0
- package/dist/gha-selected-files/index.d.mts +6 -0
- package/dist/gha-selected-files/index.mjs +38 -0
- package/dist/http-origin/index.d.mts +1 -0
- package/dist/http-origin/index.mjs +23 -0
- package/dist/index.d.mts +11 -2
- package/dist/index.mjs +7 -1
- package/dist/openapi-document/build-openapi-document.d.mts +11 -0
- package/dist/openapi-document/build-openapi-document.mjs +122 -0
- package/dist/openapi-document/build-openapi-query.d.mts +3 -0
- package/dist/openapi-document/build-openapi-query.mjs +53 -0
- package/dist/openapi-document/build-openapi-request.d.mts +7 -0
- package/dist/openapi-document/build-openapi-request.mjs +29 -0
- package/dist/openapi-document/build-openapi-response.d.mts +9 -0
- package/dist/openapi-document/build-openapi-response.mjs +111 -0
- package/dist/openapi-document/component-registry.d.mts +9 -0
- package/dist/openapi-document/component-registry.mjs +49 -0
- package/dist/openapi-document/contract-schema-canonical.d.mts +4 -0
- package/dist/openapi-document/contract-schema-canonical.mjs +23 -0
- package/dist/openapi-document/contract-schema-intersection-merge.d.mts +13 -0
- package/dist/openapi-document/contract-schema-intersection-merge.mjs +96 -0
- package/dist/openapi-document/contract-schema-to-openapi.d.mts +7 -0
- package/dist/openapi-document/contract-schema-to-openapi.mjs +71 -0
- package/dist/openapi-document/contract-schema-types.d.mts +43 -0
- package/dist/openapi-document/contract-schema-types.mjs +1 -0
- package/dist/openapi-document/index.d.mts +19 -0
- package/dist/openapi-document/index.mjs +12 -0
- package/dist/openapi-document/openapi-error-response.d.mts +2 -0
- package/dist/openapi-document/openapi-error-response.mjs +13 -0
- package/dist/openapi-document/openapi-route-helpers.d.mts +9 -0
- package/dist/openapi-document/openapi-route-helpers.mjs +61 -0
- package/dist/openapi-document/openapi-types.d.mts +80 -0
- package/dist/openapi-document/openapi-types.mjs +1 -0
- package/dist/openapi-document/operation-types.d.mts +35 -0
- package/dist/openapi-document/operation-types.mjs +11 -0
- package/dist/openapi-document/query-types.d.mts +35 -0
- package/dist/openapi-document/query-types.mjs +1 -0
- package/dist/openapi-document/schema-node-builders.d.mts +3 -0
- package/dist/openapi-document/schema-node-builders.mjs +6 -0
- package/dist/openapi-document/write-openapi.d.mts +9 -0
- package/dist/openapi-document/write-openapi.mjs +19 -0
- package/dist/pg-schema-snapshot/build-snapshot.d.mts +9 -0
- package/dist/pg-schema-snapshot/build-snapshot.mjs +61 -0
- package/dist/pg-schema-snapshot/catalog-objects.d.mts +34 -0
- package/dist/pg-schema-snapshot/catalog-objects.mjs +64 -0
- package/dist/pg-schema-snapshot/catalog-queries.d.mts +20 -0
- package/dist/pg-schema-snapshot/catalog-queries.mjs +31 -0
- package/dist/pg-schema-snapshot/catalog-shared.d.mts +3 -0
- package/dist/pg-schema-snapshot/catalog-shared.mjs +19 -0
- package/dist/pg-schema-snapshot/catalog-table-constraints.d.mts +14 -0
- package/dist/pg-schema-snapshot/catalog-table-constraints.mjs +54 -0
- package/dist/pg-schema-snapshot/catalog-table-indexes.d.mts +30 -0
- package/dist/pg-schema-snapshot/catalog-table-indexes.mjs +81 -0
- package/dist/pg-schema-snapshot/catalog-tables.d.mts +22 -0
- package/dist/pg-schema-snapshot/catalog-tables.mjs +48 -0
- package/dist/pg-schema-snapshot/file-safety.d.mts +4 -0
- package/dist/pg-schema-snapshot/file-safety.mjs +71 -0
- package/dist/pg-schema-snapshot/generate.d.mts +18 -0
- package/dist/pg-schema-snapshot/generate.mjs +95 -0
- package/dist/pg-schema-snapshot/index-rename-detect.d.mts +40 -0
- package/dist/pg-schema-snapshot/index-rename-detect.mjs +71 -0
- package/dist/pg-schema-snapshot/index.d.mts +10 -0
- package/dist/pg-schema-snapshot/index.mjs +6 -0
- package/dist/pg-schema-snapshot/markdown-files.d.mts +1 -0
- package/dist/pg-schema-snapshot/markdown-files.mjs +26 -0
- package/dist/pg-schema-snapshot/render-markdown.d.mts +8 -0
- package/dist/pg-schema-snapshot/render-markdown.mjs +113 -0
- package/dist/pg-schema-snapshot/render-tables.d.mts +2 -0
- package/dist/pg-schema-snapshot/render-tables.mjs +55 -0
- package/dist/pg-schema-snapshot/snapshot-build-utils.d.mts +2 -0
- package/dist/pg-schema-snapshot/snapshot-build-utils.mjs +15 -0
- package/dist/pg-schema-snapshot/snapshot.test-helpers.d.mts +11 -0
- package/dist/pg-schema-snapshot/snapshot.test-helpers.mjs +101 -0
- package/dist/pg-schema-snapshot/table-snapshot-columns.d.mts +3 -0
- package/dist/pg-schema-snapshot/table-snapshot-columns.mjs +33 -0
- package/dist/pg-schema-snapshot/table-snapshot-constraints.d.mts +7 -0
- package/dist/pg-schema-snapshot/table-snapshot-constraints.mjs +55 -0
- package/dist/pg-schema-snapshot/table-snapshot.d.mts +10 -0
- package/dist/pg-schema-snapshot/table-snapshot.mjs +99 -0
- package/dist/pg-schema-snapshot/types.d.mts +103 -0
- package/dist/pg-schema-snapshot/types.mjs +1 -0
- package/dist/process-line-buffer/index.d.mts +7 -0
- package/dist/process-line-buffer/index.mjs +28 -0
- package/dist/shared-context/{fake-git.test-helpers.d.mts → fake-git.d.mts} +1 -0
- package/dist/shared-context/{fake-git.test-helpers.mjs → fake-git.mjs} +4 -1
- package/dist/shared-context/index.d.mts +2 -0
- package/dist/shared-context/index.mjs +1 -0
- package/package.json +37 -1
- package/scripts/gha/diagnose-port-collision.sh +232 -0
- package/scripts/gha/prepare-trivy-db.sh +32 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export type CatalogQuery = <Row extends Record<string, unknown>>(sql: string, values?: readonly unknown[]) => Promise<{
|
|
2
|
+
rows: Row[];
|
|
3
|
+
}>;
|
|
4
|
+
export type PartitionPolicy = {
|
|
5
|
+
strategy: 'RANGE' | 'LIST -> RANGE';
|
|
6
|
+
key: string;
|
|
7
|
+
children: 'default' | 'monthly' | 'list-default-range';
|
|
8
|
+
retentionOwner: string | null;
|
|
9
|
+
accessClass: string;
|
|
10
|
+
};
|
|
11
|
+
export type SchemaGrowthMaps = {
|
|
12
|
+
partitionPolicies: ReadonlyMap<string, PartitionPolicy>;
|
|
13
|
+
unboundedUnpartitionedTables: ReadonlySet<string>;
|
|
14
|
+
};
|
|
15
|
+
export type SchemaColumnSnapshot = {
|
|
16
|
+
type: string;
|
|
17
|
+
nullable: boolean;
|
|
18
|
+
defaultExpression: string | null;
|
|
19
|
+
generatedExpression: string | null;
|
|
20
|
+
identity: 'always' | 'by default' | null;
|
|
21
|
+
generated: 'stored' | 'virtual' | null;
|
|
22
|
+
collation: string | null;
|
|
23
|
+
comment: string | null;
|
|
24
|
+
ordinalPosition: number;
|
|
25
|
+
};
|
|
26
|
+
export type SchemaKeyConstraintSnapshot = {
|
|
27
|
+
definition: string;
|
|
28
|
+
columns: string[];
|
|
29
|
+
};
|
|
30
|
+
export type SchemaForeignKeySnapshot = SchemaKeyConstraintSnapshot & {
|
|
31
|
+
referencedTable: string;
|
|
32
|
+
referencedColumns: string[];
|
|
33
|
+
onUpdate: 'no action' | 'restrict' | 'cascade' | 'set null' | 'set default';
|
|
34
|
+
onDelete: 'no action' | 'restrict' | 'cascade' | 'set null' | 'set default';
|
|
35
|
+
validated: boolean;
|
|
36
|
+
};
|
|
37
|
+
export type SchemaIndexKeySnapshot = {
|
|
38
|
+
column: string | null;
|
|
39
|
+
expression: string;
|
|
40
|
+
opclass: string;
|
|
41
|
+
descending: boolean;
|
|
42
|
+
nullsFirst: boolean;
|
|
43
|
+
};
|
|
44
|
+
export type SchemaIndexSnapshot = {
|
|
45
|
+
definition: string;
|
|
46
|
+
accessMethod: string;
|
|
47
|
+
unique: boolean;
|
|
48
|
+
primary: boolean;
|
|
49
|
+
constraintBacked: boolean;
|
|
50
|
+
valid: boolean;
|
|
51
|
+
ready: boolean;
|
|
52
|
+
keys: SchemaIndexKeySnapshot[];
|
|
53
|
+
includedColumns: string[];
|
|
54
|
+
predicate: string | null;
|
|
55
|
+
};
|
|
56
|
+
export type SchemaPhysicalPartitionSnapshot = {
|
|
57
|
+
strategy: 'hash' | 'list' | 'range';
|
|
58
|
+
key: string;
|
|
59
|
+
};
|
|
60
|
+
export type SchemaTableSnapshot = {
|
|
61
|
+
relationKind: 'table' | 'partitioned table';
|
|
62
|
+
columns: Record<string, SchemaColumnSnapshot>;
|
|
63
|
+
primaryKey: SchemaKeyConstraintSnapshot | null;
|
|
64
|
+
uniqueConstraints: Record<string, SchemaKeyConstraintSnapshot>;
|
|
65
|
+
checkConstraints: Record<string, string>;
|
|
66
|
+
foreignKeys: Record<string, SchemaForeignKeySnapshot>;
|
|
67
|
+
indexes: Record<string, SchemaIndexSnapshot>;
|
|
68
|
+
triggers: Record<string, string>;
|
|
69
|
+
comment: string | null;
|
|
70
|
+
physicalPartition: SchemaPhysicalPartitionSnapshot | null;
|
|
71
|
+
partition: PartitionPolicy | null;
|
|
72
|
+
growth: 'bounded' | 'unbounded';
|
|
73
|
+
};
|
|
74
|
+
export type SchemaViewSnapshot = {
|
|
75
|
+
definition: string;
|
|
76
|
+
comment: string | null;
|
|
77
|
+
materialized: boolean;
|
|
78
|
+
};
|
|
79
|
+
export type SchemaEnumSnapshot = {
|
|
80
|
+
values: string[];
|
|
81
|
+
};
|
|
82
|
+
export type SchemaExtensionSnapshot = {
|
|
83
|
+
version: string;
|
|
84
|
+
};
|
|
85
|
+
export type SchemaFunctionSnapshot = {
|
|
86
|
+
definition: string;
|
|
87
|
+
};
|
|
88
|
+
export type SchemaPolicySnapshot = {
|
|
89
|
+
table: string;
|
|
90
|
+
command: string;
|
|
91
|
+
pgRoles: string[];
|
|
92
|
+
using: string | null;
|
|
93
|
+
withCheck: string | null;
|
|
94
|
+
};
|
|
95
|
+
export type SchemaSnapshot = {
|
|
96
|
+
formatVersion: 2;
|
|
97
|
+
tables: Record<string, SchemaTableSnapshot>;
|
|
98
|
+
views: Record<string, SchemaViewSnapshot>;
|
|
99
|
+
enums: Record<string, SchemaEnumSnapshot>;
|
|
100
|
+
extensions: Record<string, SchemaExtensionSnapshot>;
|
|
101
|
+
functions: Record<string, SchemaFunctionSnapshot>;
|
|
102
|
+
policies: Record<string, SchemaPolicySnapshot>;
|
|
103
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const DEFAULT_TRUNCATED_LINE_MARKER = " [oversized line truncated] ";
|
|
2
|
+
export declare const DEFAULT_MAX_PENDING_LINE_LENGTH: number;
|
|
3
|
+
export declare function boundPendingLine(value: string, marker?: string, maxLength?: number): string;
|
|
4
|
+
export declare function splitCompleteLines(value: string): {
|
|
5
|
+
complete: string[];
|
|
6
|
+
pending: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const DEFAULT_TRUNCATED_LINE_MARKER = ' [oversized line truncated] ';
|
|
2
|
+
export const DEFAULT_MAX_PENDING_LINE_LENGTH = 64 * 1024;
|
|
3
|
+
export function boundPendingLine(value, marker = DEFAULT_TRUNCATED_LINE_MARKER, maxLength = DEFAULT_MAX_PENDING_LINE_LENGTH) {
|
|
4
|
+
if (value.length <= maxLength)
|
|
5
|
+
return value;
|
|
6
|
+
if (marker.length >= maxLength)
|
|
7
|
+
return marker;
|
|
8
|
+
const retainedLength = maxLength - marker.length;
|
|
9
|
+
const headLength = Math.floor(retainedLength / 2);
|
|
10
|
+
const tailLength = retainedLength - headLength;
|
|
11
|
+
return value.slice(0, headLength) + marker + value.slice(-tailLength);
|
|
12
|
+
}
|
|
13
|
+
export function splitCompleteLines(value) {
|
|
14
|
+
const complete = [];
|
|
15
|
+
let start = 0;
|
|
16
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
17
|
+
const character = value[index];
|
|
18
|
+
if (character !== '\n' && character !== '\r')
|
|
19
|
+
continue;
|
|
20
|
+
if (character === '\r' && index === value.length - 1)
|
|
21
|
+
break;
|
|
22
|
+
if (character === '\r' && value[index + 1] === '\n')
|
|
23
|
+
index += 1;
|
|
24
|
+
complete.push(value.slice(start, index + 1));
|
|
25
|
+
start = index + 1;
|
|
26
|
+
}
|
|
27
|
+
return { complete, pending: value.slice(start) };
|
|
28
|
+
}
|
|
@@ -8,4 +8,5 @@ export type FakeGitOptions = {
|
|
|
8
8
|
trackedFiles?: readonly string[];
|
|
9
9
|
};
|
|
10
10
|
export declare function installFakeGit({ binDir, isInsideWorkTree, lsFilesExitCode, lsFilesStderr, pathPrefix, repoRoot, trackedFiles, }: FakeGitOptions): void;
|
|
11
|
+
export declare function setFakeGitTrackedFiles(files: readonly string[]): void;
|
|
11
12
|
export declare function clearFakeGitEnv(): void;
|
|
@@ -15,7 +15,10 @@ export function installFakeGit({ binDir, isInsideWorkTree = true, lsFilesExitCod
|
|
|
15
15
|
process.env[FAKE_GIT_LS_FILES_STDERR] = lsFilesStderr;
|
|
16
16
|
if (repoRoot !== undefined)
|
|
17
17
|
process.env[FAKE_GIT_ROOT] = repoRoot;
|
|
18
|
-
|
|
18
|
+
setFakeGitTrackedFiles(trackedFiles);
|
|
19
|
+
}
|
|
20
|
+
export function setFakeGitTrackedFiles(files) {
|
|
21
|
+
process.env[FAKE_GIT_FILES] = [...files].toSorted().join('\n');
|
|
19
22
|
}
|
|
20
23
|
export function clearFakeGitEnv() {
|
|
21
24
|
process.env.FAKE_GIT_INSIDE = 'false';
|
|
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
const execFileAsync = promisify(execFile);
|
|
6
|
+
export { clearFakeGitEnv, installFakeGit, setFakeGitTrackedFiles } from './fake-git.mjs';
|
|
6
7
|
export function gitEnv() {
|
|
7
8
|
return Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith('GIT_')));
|
|
8
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -65,6 +65,36 @@
|
|
|
65
65
|
"import": "./dist/shared-context/index.mjs",
|
|
66
66
|
"default": "./dist/shared-context/index.mjs"
|
|
67
67
|
},
|
|
68
|
+
"./gha-selected-files": {
|
|
69
|
+
"types": "./dist/gha-selected-files/index.d.mts",
|
|
70
|
+
"import": "./dist/gha-selected-files/index.mjs",
|
|
71
|
+
"default": "./dist/gha-selected-files/index.mjs"
|
|
72
|
+
},
|
|
73
|
+
"./gha-artifacts-cleanup": {
|
|
74
|
+
"types": "./dist/gha-artifacts-cleanup/index.d.mts",
|
|
75
|
+
"import": "./dist/gha-artifacts-cleanup/index.mjs",
|
|
76
|
+
"default": "./dist/gha-artifacts-cleanup/index.mjs"
|
|
77
|
+
},
|
|
78
|
+
"./http-origin": {
|
|
79
|
+
"types": "./dist/http-origin/index.d.mts",
|
|
80
|
+
"import": "./dist/http-origin/index.mjs",
|
|
81
|
+
"default": "./dist/http-origin/index.mjs"
|
|
82
|
+
},
|
|
83
|
+
"./process-line-buffer": {
|
|
84
|
+
"types": "./dist/process-line-buffer/index.d.mts",
|
|
85
|
+
"import": "./dist/process-line-buffer/index.mjs",
|
|
86
|
+
"default": "./dist/process-line-buffer/index.mjs"
|
|
87
|
+
},
|
|
88
|
+
"./pg-schema-snapshot": {
|
|
89
|
+
"types": "./dist/pg-schema-snapshot/index.d.mts",
|
|
90
|
+
"import": "./dist/pg-schema-snapshot/index.mjs",
|
|
91
|
+
"default": "./dist/pg-schema-snapshot/index.mjs"
|
|
92
|
+
},
|
|
93
|
+
"./openapi-document": {
|
|
94
|
+
"types": "./dist/openapi-document/index.d.mts",
|
|
95
|
+
"import": "./dist/openapi-document/index.mjs",
|
|
96
|
+
"default": "./dist/openapi-document/index.mjs"
|
|
97
|
+
},
|
|
68
98
|
"./package.json": "./package.json"
|
|
69
99
|
},
|
|
70
100
|
"publishConfig": {
|
|
@@ -75,6 +105,12 @@
|
|
|
75
105
|
"prepack": "pnpm run build",
|
|
76
106
|
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
77
107
|
},
|
|
108
|
+
"dependencies": {
|
|
109
|
+
"picomatch": "4.0.5"
|
|
110
|
+
},
|
|
111
|
+
"devDependencies": {
|
|
112
|
+
"@types/picomatch": "^4.0.3"
|
|
113
|
+
},
|
|
78
114
|
"optionalDependencies": {
|
|
79
115
|
"@libpg-query/parser": "^18.0.0"
|
|
80
116
|
},
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
PORTS="${PORTS:-}"
|
|
4
|
+
OUTPUT_DIR="${OUTPUT_DIR:-${RUNNER_TEMP:-/tmp}/port-diagnostics}"
|
|
5
|
+
|
|
6
|
+
if [ "${PORT_DIAGNOSTICS_BOUNDED_CHILD:-}" != 1 ]; then
|
|
7
|
+
PORT_DIAGNOSTICS_BOUNDED_CHILD=1 python3 - \
|
|
8
|
+
"${PORT_DIAGNOSTICS_TIMEOUT_SECONDS:-45}" "$0" "$@" <<'PY'
|
|
9
|
+
import os
|
|
10
|
+
import signal
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
timeout_seconds = float(sys.argv[1])
|
|
16
|
+
if timeout_seconds <= 0 or timeout_seconds > 300:
|
|
17
|
+
raise ValueError
|
|
18
|
+
except ValueError:
|
|
19
|
+
timeout_seconds = 45
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
process = subprocess.Popen(
|
|
23
|
+
["bash", sys.argv[2], *sys.argv[3:]],
|
|
24
|
+
env=os.environ.copy(),
|
|
25
|
+
start_new_session=True,
|
|
26
|
+
)
|
|
27
|
+
except OSError as error:
|
|
28
|
+
print(f"::warning::Could not start port diagnostics: {error}", file=sys.stderr)
|
|
29
|
+
sys.exit(0)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
process.wait(timeout=timeout_seconds)
|
|
33
|
+
except subprocess.TimeoutExpired:
|
|
34
|
+
os.killpg(process.pid, signal.SIGTERM)
|
|
35
|
+
try:
|
|
36
|
+
process.wait(timeout=2)
|
|
37
|
+
except subprocess.TimeoutExpired:
|
|
38
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
39
|
+
process.wait()
|
|
40
|
+
print(
|
|
41
|
+
f"::warning::Port diagnostics collector exceeded {timeout_seconds:g}s; uploading partial evidence",
|
|
42
|
+
file=sys.stderr,
|
|
43
|
+
)
|
|
44
|
+
PY
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
usage() {
|
|
49
|
+
cat <<'USAGE'
|
|
50
|
+
Usage: diagnose-port-collision [--ports "2200 2216"] [--output-dir PATH]
|
|
51
|
+
|
|
52
|
+
Best-effort, non-masking diagnostics for a failed workflow that allocated localhost ports.
|
|
53
|
+
The script never returns a failure for an unavailable probe or an occupied port.
|
|
54
|
+
USAGE
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
while [ "$#" -gt 0 ]; do
|
|
58
|
+
case "$1" in
|
|
59
|
+
--ports)
|
|
60
|
+
[ "$#" -ge 2 ] || { usage >&2; exit 0; }
|
|
61
|
+
PORTS="$2"
|
|
62
|
+
shift
|
|
63
|
+
;;
|
|
64
|
+
--output-dir)
|
|
65
|
+
[ "$#" -ge 2 ] || { usage >&2; exit 0; }
|
|
66
|
+
OUTPUT_DIR="$2"
|
|
67
|
+
shift
|
|
68
|
+
;;
|
|
69
|
+
-h|--help)
|
|
70
|
+
usage
|
|
71
|
+
exit 0
|
|
72
|
+
;;
|
|
73
|
+
*)
|
|
74
|
+
echo "::warning::Ignoring unknown diagnostics argument: $1" >&2
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
shift
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
if ! mkdir -p "$OUTPUT_DIR" 2>/dev/null; then
|
|
81
|
+
echo "::warning::Could not create port diagnostics directory: $OUTPUT_DIR" >&2
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
REPORT="$OUTPUT_DIR/summary.txt"
|
|
86
|
+
LISTENERS="$OUTPUT_DIR/listeners.txt"
|
|
87
|
+
DOCKER_REPORT="$OUTPUT_DIR/docker.txt"
|
|
88
|
+
KERNEL_REPORT="$OUTPUT_DIR/kernel.txt"
|
|
89
|
+
RUNNER_REPORT="$OUTPUT_DIR/runner.txt"
|
|
90
|
+
|
|
91
|
+
write_report_header() {
|
|
92
|
+
local path="$1"
|
|
93
|
+
{
|
|
94
|
+
echo "generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unavailable)"
|
|
95
|
+
echo "workflow=${GITHUB_WORKFLOW:-unavailable}"
|
|
96
|
+
echo "job=${GITHUB_JOB:-unavailable}"
|
|
97
|
+
echo "run_id=${GITHUB_RUN_ID:-unavailable}"
|
|
98
|
+
echo "run_attempt=${GITHUB_RUN_ATTEMPT:-unavailable}"
|
|
99
|
+
echo "runner_name=${RUNNER_NAME:-unavailable}"
|
|
100
|
+
echo "runner_os=${RUNNER_OS:-unavailable}"
|
|
101
|
+
echo "workspace=${GITHUB_WORKSPACE:-unavailable}"
|
|
102
|
+
echo "allocated_ports=${PORTS:-none}"
|
|
103
|
+
} > "$path" 2>/dev/null || true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
write_report_header "$REPORT"
|
|
107
|
+
|
|
108
|
+
run_bounded() {
|
|
109
|
+
local seconds="$1"
|
|
110
|
+
shift
|
|
111
|
+
python3 - "$seconds" "$@" <<'PY'
|
|
112
|
+
import subprocess
|
|
113
|
+
import sys
|
|
114
|
+
try:
|
|
115
|
+
subprocess.run(sys.argv[2:], check=False, timeout=float(sys.argv[1]))
|
|
116
|
+
except subprocess.TimeoutExpired:
|
|
117
|
+
print(f'probe=timed-out after {sys.argv[1]}s', file=sys.stderr)
|
|
118
|
+
except OSError as error:
|
|
119
|
+
print(f'probe=unavailable ({error})', file=sys.stderr)
|
|
120
|
+
PY
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
valid_ports=()
|
|
124
|
+
invalid_ports=()
|
|
125
|
+
read -r -a requested_ports <<< "$PORTS"
|
|
126
|
+
for port in "${requested_ports[@]}"; do
|
|
127
|
+
case "$port" in
|
|
128
|
+
''|*[!0-9]*) invalid_ports+=("$port") ;;
|
|
129
|
+
*)
|
|
130
|
+
if [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
|
|
131
|
+
valid_ports+=("$port")
|
|
132
|
+
else
|
|
133
|
+
invalid_ports+=("$port")
|
|
134
|
+
fi
|
|
135
|
+
;;
|
|
136
|
+
esac
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
echo "requested_port_count=${#requested_ports[@]}"
|
|
141
|
+
echo "valid_port_count=${#valid_ports[@]}"
|
|
142
|
+
echo "invalid_ports=${invalid_ports[*]:-none}"
|
|
143
|
+
} >> "$REPORT" 2>/dev/null || true
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
echo "# Linux kernel port contract (best effort)"
|
|
147
|
+
if command -v sysctl >/dev/null 2>&1; then
|
|
148
|
+
echo "ip_local_reserved_ports=$(sysctl -n net.ipv4.ip_local_reserved_ports 2>/dev/null || echo unavailable)"
|
|
149
|
+
echo "ip_local_port_range=$(sysctl -n net.ipv4.ip_local_port_range 2>/dev/null || echo unavailable)"
|
|
150
|
+
else
|
|
151
|
+
echo "sysctl=unavailable"
|
|
152
|
+
fi
|
|
153
|
+
for path in /proc/sys/net/ipv4/ip_local_reserved_ports /proc/sys/net/ipv4/ip_local_port_range; do
|
|
154
|
+
if [ -r "$path" ]; then
|
|
155
|
+
echo "$path=$(tr -d '\n' < "$path" 2>/dev/null || true)"
|
|
156
|
+
fi
|
|
157
|
+
done
|
|
158
|
+
} > "$KERNEL_REPORT" 2>/dev/null || true
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
echo "# TCP socket diagnostics (best effort)"
|
|
162
|
+
lsof_available=false
|
|
163
|
+
ss_available=false
|
|
164
|
+
lsof_output=''
|
|
165
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
166
|
+
lsof_available=true
|
|
167
|
+
lsof_output=$(run_bounded 10 lsof -nP -iTCP 2>&1 || true)
|
|
168
|
+
elif command -v ss >/dev/null 2>&1; then
|
|
169
|
+
ss_available=true
|
|
170
|
+
fi
|
|
171
|
+
for port in "${valid_ports[@]}"; do
|
|
172
|
+
occupied=false
|
|
173
|
+
echo "port=$port"
|
|
174
|
+
if [ "$lsof_available" = true ]; then
|
|
175
|
+
if lsof_filtered=$(printf '%s\n' "$lsof_output" | awk -v port="$port" '
|
|
176
|
+
/^probe=/ { print; next }
|
|
177
|
+
$1 == "COMMAND" { header = $0; next }
|
|
178
|
+
{
|
|
179
|
+
local_endpoint = $9
|
|
180
|
+
sub(/->.*/, "", local_endpoint)
|
|
181
|
+
if (local_endpoint ~ (":" port "$")) {
|
|
182
|
+
if (!printed_header && header != "") {
|
|
183
|
+
print header
|
|
184
|
+
printed_header = 1
|
|
185
|
+
}
|
|
186
|
+
print
|
|
187
|
+
found = 1
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
END { exit found ? 0 : 1 }
|
|
191
|
+
'); then
|
|
192
|
+
occupied=true
|
|
193
|
+
fi
|
|
194
|
+
[ -z "$lsof_filtered" ] || printf '%s\n' "$lsof_filtered"
|
|
195
|
+
elif [ "$ss_available" = true ]; then
|
|
196
|
+
ss -ntp "( sport = :$port )" 2>&1 || true
|
|
197
|
+
if ss -nt "( sport = :$port )" 2>/dev/null | tail -n +2 | grep -q .; then
|
|
198
|
+
occupied=true
|
|
199
|
+
fi
|
|
200
|
+
else
|
|
201
|
+
echo "probe=unavailable (neither lsof nor ss is installed)"
|
|
202
|
+
fi
|
|
203
|
+
echo "status=$([ "$occupied" = true ] && echo occupied || echo free-or-unobserved)"
|
|
204
|
+
echo
|
|
205
|
+
done
|
|
206
|
+
} > "$LISTENERS" 2>/dev/null || true
|
|
207
|
+
|
|
208
|
+
{
|
|
209
|
+
echo "# Docker diagnostics (best effort)"
|
|
210
|
+
if command -v docker >/dev/null 2>&1; then
|
|
211
|
+
run_bounded 10 docker version --format 'server={{.Server.Version}}' 2>&1 || true
|
|
212
|
+
docker_ps_output=$(run_bounded 10 docker ps --no-trunc --format 'container={{.ID}} names={{.Names}} ports={{.Ports}}' 2>&1 || true)
|
|
213
|
+
for port in "${valid_ports[@]}"; do
|
|
214
|
+
echo "published_port=$port"
|
|
215
|
+
printf '%s\n' "$docker_ps_output" | awk -v port="$port" '/^probe=/ || $0 ~ "(^|[^0-9])" port "->"' || true
|
|
216
|
+
done
|
|
217
|
+
else
|
|
218
|
+
echo "docker=unavailable"
|
|
219
|
+
fi
|
|
220
|
+
} > "$DOCKER_REPORT" 2>/dev/null || true
|
|
221
|
+
|
|
222
|
+
{
|
|
223
|
+
echo "# Runner context (deliberately excludes the process environment)"
|
|
224
|
+
uname -a 2>&1 || true
|
|
225
|
+
hostname 2>&1 || true
|
|
226
|
+
id 2>&1 || true
|
|
227
|
+
echo "runner_temp=${RUNNER_TEMP:-unavailable}"
|
|
228
|
+
echo "github_actions=${GITHUB_ACTIONS:-unavailable}"
|
|
229
|
+
} > "$RUNNER_REPORT" 2>/dev/null || true
|
|
230
|
+
|
|
231
|
+
echo "::notice::Port diagnostics captured under $OUTPUT_DIR"
|
|
232
|
+
exit 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
mirror_repository="${TRIVY_DB_MIRROR_REPOSITORY:-mirror.gcr.io/aquasec/trivy-db:2}"
|
|
5
|
+
official_repository="${TRIVY_DB_OFFICIAL_REPOSITORY:-ghcr.io/aquasecurity/trivy-db:2}"
|
|
6
|
+
download_timeout="${TRIVY_DB_TIMEOUT:-75s}"
|
|
7
|
+
|
|
8
|
+
download_database() {
|
|
9
|
+
trivy image \
|
|
10
|
+
--download-db-only \
|
|
11
|
+
--no-progress \
|
|
12
|
+
--timeout "$download_timeout" \
|
|
13
|
+
--db-repository "$1"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
mirror_exit=0
|
|
17
|
+
download_database "$mirror_repository" || mirror_exit=$?
|
|
18
|
+
if [ "$mirror_exit" -eq 0 ]; then
|
|
19
|
+
echo "Trivy vulnerability database prepared from the Google mirror"
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "Trivy database mirror failed with exit ${mirror_exit}; retrying from official GHCR"
|
|
24
|
+
official_exit=0
|
|
25
|
+
download_database "$official_repository" || official_exit=$?
|
|
26
|
+
if [ "$official_exit" -eq 0 ]; then
|
|
27
|
+
echo "Trivy vulnerability database prepared from official GHCR"
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "Trivy vulnerability database download failed from the Google mirror (exit ${mirror_exit}) and official GHCR (exit ${official_exit})" >&2
|
|
32
|
+
exit "$official_exit"
|