@aopslabs/aops 0.2.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/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parseEnv } from 'node:util';
|
|
5
|
+
import { communityProcessRuntime, } from './community-docker-adapter.js';
|
|
6
|
+
import { CommunityCommandAbortedError, throwIfCommunityCommandAborted, } from './community-command-abort.js';
|
|
7
|
+
export const COMMUNITY_NATIVE_POSTGRES_CONTRACT_PATH = 'community-postgres.json';
|
|
8
|
+
const SHA256 = /^sha256:[a-f0-9]{64}$/;
|
|
9
|
+
const IMAGE_REF = /^ghcr\.io\/eeemzs\/aops-community-base-postgres:17-bookworm@(sha256:[a-f0-9]{64})$/;
|
|
10
|
+
const SAFE_NAME = /^[a-z0-9][a-z0-9_.-]{0,179}$/;
|
|
11
|
+
const NAMESPACE = /^[a-f0-9]{12}$/;
|
|
12
|
+
const SECRET = /^[A-Za-z0-9_-]{32,}$/;
|
|
13
|
+
const DATABASE_NAME = /^[a-z][a-z0-9_]{0,62}$/;
|
|
14
|
+
const MAX_CONTRACT_BYTES = 65_536;
|
|
15
|
+
const MAX_SECRET_BYTES = 4_096;
|
|
16
|
+
const LABEL_PROFILE = 'io.aopslab.aops-community.profile';
|
|
17
|
+
const LABEL_INSTANCE = 'io.aopslab.aops-community.instance';
|
|
18
|
+
const LABEL_NAMESPACE = 'io.aopslab.aops-community.namespace';
|
|
19
|
+
const LABEL_SECRET_SHA256 = 'io.aopslab.aops-community.secret-sha256';
|
|
20
|
+
const PROFILE = 'native-container-postgres';
|
|
21
|
+
const DEFAULT_READY_TIMEOUT_MS = 90_000;
|
|
22
|
+
const READY_POLL_MS = 500;
|
|
23
|
+
const POSTGRES_DATA_MOUNT = '/var/lib/postgresql/data';
|
|
24
|
+
const POSTGRES_PORT_KEY = '5432/tcp';
|
|
25
|
+
const SAFE_ENV_INSPECT_FORMAT = '{{range .Config.Env}}{{if eq (index (split . "=") 0) "POSTGRES_DB"}}{{println .}}{{end}}{{if eq (index (split . "=") 0) "POSTGRES_USER"}}{{println .}}{{end}}{{end}}';
|
|
26
|
+
export const communityNativePostgresRuntime = {
|
|
27
|
+
run: (invocation) => communityProcessRuntime.run(invocation),
|
|
28
|
+
sleep: (milliseconds, signal) => {
|
|
29
|
+
throwIfCommunityCommandAborted(signal);
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const onAbort = () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
signal?.removeEventListener('abort', onAbort);
|
|
34
|
+
reject(new CommunityCommandAbortedError());
|
|
35
|
+
};
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
signal?.removeEventListener('abort', onAbort);
|
|
38
|
+
resolve();
|
|
39
|
+
}, milliseconds);
|
|
40
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
41
|
+
if (signal?.aborted)
|
|
42
|
+
onAbort();
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
function sha256(content) {
|
|
47
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
48
|
+
}
|
|
49
|
+
function isWithin(root, candidate) {
|
|
50
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
51
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
52
|
+
}
|
|
53
|
+
function exactKeys(input, expected, code) {
|
|
54
|
+
const actual = Object.keys(input).sort();
|
|
55
|
+
const wanted = [...expected].sort();
|
|
56
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
57
|
+
throw new Error(code);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function redactDiagnostic(value) {
|
|
61
|
+
return value
|
|
62
|
+
.replace(/(postgres(?:ql)?:\/\/[^:\s/@]+:)[^@\s/]+@/gi, '$1[REDACTED]@')
|
|
63
|
+
.replace(/((?:password|passwd|secret|token)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
|
|
64
|
+
.slice(-1_000);
|
|
65
|
+
}
|
|
66
|
+
function parseContract(value) {
|
|
67
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
68
|
+
throw new Error('community_native_postgres_contract_invalid');
|
|
69
|
+
}
|
|
70
|
+
const input = value;
|
|
71
|
+
exactKeys(input, [
|
|
72
|
+
'schemaVersion', 'profile', 'engine', 'majorVersion', 'imageRef', 'imageDigest',
|
|
73
|
+
'database', 'username', 'containerPort', 'dataMount', 'bindHost',
|
|
74
|
+
], 'community_native_postgres_contract_schema_invalid');
|
|
75
|
+
const imageMatch = IMAGE_REF.exec(String(input.imageRef));
|
|
76
|
+
if (input.schemaVersion !== 1 || input.profile !== PROFILE || input.engine !== 'postgresql' ||
|
|
77
|
+
input.majorVersion !== 17 || !imageMatch || !SHA256.test(String(input.imageDigest)) ||
|
|
78
|
+
imageMatch[1] !== input.imageDigest || input.database !== 'aops' || input.username !== 'aops' ||
|
|
79
|
+
input.containerPort !== 5432 || input.dataMount !== '/var/lib/postgresql/data' ||
|
|
80
|
+
input.bindHost !== '127.0.0.1')
|
|
81
|
+
throw new Error('community_native_postgres_contract_schema_invalid');
|
|
82
|
+
return Object.freeze(input);
|
|
83
|
+
}
|
|
84
|
+
export function inspectCommunityNativePostgresContract(sourceRoot) {
|
|
85
|
+
const resolvedRoot = path.resolve(sourceRoot);
|
|
86
|
+
let rootStats;
|
|
87
|
+
try {
|
|
88
|
+
rootStats = lstatSync(resolvedRoot);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new Error('community_native_postgres_source_root_invalid');
|
|
92
|
+
}
|
|
93
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
94
|
+
throw new Error('community_native_postgres_source_root_invalid');
|
|
95
|
+
}
|
|
96
|
+
const realRoot = realpathSync(resolvedRoot);
|
|
97
|
+
const candidate = path.resolve(realRoot, COMMUNITY_NATIVE_POSTGRES_CONTRACT_PATH);
|
|
98
|
+
if (!isWithin(realRoot, candidate))
|
|
99
|
+
throw new Error('community_native_postgres_contract_path_escape');
|
|
100
|
+
let stats;
|
|
101
|
+
try {
|
|
102
|
+
stats = lstatSync(candidate);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error('community_native_postgres_contract_file_invalid');
|
|
106
|
+
}
|
|
107
|
+
if (!stats.isFile() || stats.isSymbolicLink() || stats.size < 1 || stats.size > MAX_CONTRACT_BYTES) {
|
|
108
|
+
throw new Error('community_native_postgres_contract_file_invalid');
|
|
109
|
+
}
|
|
110
|
+
const contractRef = realpathSync(candidate);
|
|
111
|
+
if (!isWithin(realRoot, contractRef))
|
|
112
|
+
throw new Error('community_native_postgres_contract_path_escape');
|
|
113
|
+
const content = readFileSync(contractRef);
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(content.toString('utf8'));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new Error('community_native_postgres_contract_json_invalid');
|
|
120
|
+
}
|
|
121
|
+
return { contract: parseContract(parsed), contractRef, contractSha256: sha256(content) };
|
|
122
|
+
}
|
|
123
|
+
export function assertCommunityNativePostgresState(value) {
|
|
124
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
125
|
+
throw new Error('community_native_postgres_state_invalid');
|
|
126
|
+
}
|
|
127
|
+
const input = value;
|
|
128
|
+
exactKeys(input, [
|
|
129
|
+
'mode', 'contractRef', 'contractSha256', 'imageRef', 'imageDigest', 'namespace',
|
|
130
|
+
'containerName', 'volumeName', 'secretRef', 'secretSha256', 'host', 'port',
|
|
131
|
+
], 'community_native_postgres_state_schema_invalid');
|
|
132
|
+
const imageMatch = IMAGE_REF.exec(String(input.imageRef));
|
|
133
|
+
if (input.mode !== 'container' || !path.isAbsolute(String(input.contractRef)) ||
|
|
134
|
+
!SHA256.test(String(input.contractSha256)) || !imageMatch ||
|
|
135
|
+
!SHA256.test(String(input.imageDigest)) || imageMatch[1] !== input.imageDigest ||
|
|
136
|
+
!NAMESPACE.test(String(input.namespace)) || !SAFE_NAME.test(String(input.containerName)) ||
|
|
137
|
+
!SAFE_NAME.test(String(input.volumeName)) || !path.isAbsolute(String(input.secretRef)) ||
|
|
138
|
+
!SHA256.test(String(input.secretSha256)) ||
|
|
139
|
+
input.host !== '127.0.0.1' || !Number.isSafeInteger(input.port) || input.port < 1 || input.port > 65_535)
|
|
140
|
+
throw new Error('community_native_postgres_state_schema_invalid');
|
|
141
|
+
return input;
|
|
142
|
+
}
|
|
143
|
+
function readSecret(secretRef) {
|
|
144
|
+
const resolved = path.resolve(secretRef);
|
|
145
|
+
let stats;
|
|
146
|
+
try {
|
|
147
|
+
stats = lstatSync(resolved);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
throw new Error('community_native_postgres_secret_invalid');
|
|
151
|
+
}
|
|
152
|
+
if (!stats.isFile() || stats.isSymbolicLink() || stats.size < 1 || stats.size > MAX_SECRET_BYTES) {
|
|
153
|
+
throw new Error('community_native_postgres_secret_invalid');
|
|
154
|
+
}
|
|
155
|
+
const content = readFileSync(resolved);
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = parseEnv(content.toString('utf8'));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
throw new Error('community_native_postgres_secret_invalid');
|
|
162
|
+
}
|
|
163
|
+
exactKeys(parsed, ['POSTGRES_DB', 'POSTGRES_USER', 'POSTGRES_PASSWORD'], 'community_native_postgres_secret_invalid');
|
|
164
|
+
const password = String(parsed.POSTGRES_PASSWORD ?? '');
|
|
165
|
+
if (parsed.POSTGRES_DB !== 'aops' || parsed.POSTGRES_USER !== 'aops' || !SECRET.test(password)) {
|
|
166
|
+
throw new Error('community_native_postgres_secret_invalid');
|
|
167
|
+
}
|
|
168
|
+
return { database: 'aops', username: 'aops', password, secretSha256: sha256(content) };
|
|
169
|
+
}
|
|
170
|
+
function ensureSecret(secretRef, createSecret) {
|
|
171
|
+
if (existsSync(secretRef)) {
|
|
172
|
+
return readSecret(secretRef);
|
|
173
|
+
}
|
|
174
|
+
const secret = createSecret();
|
|
175
|
+
if (!SECRET.test(secret))
|
|
176
|
+
throw new Error('community_native_postgres_generated_secret_invalid');
|
|
177
|
+
mkdirSync(path.dirname(secretRef), { recursive: true, mode: 0o700 });
|
|
178
|
+
writeFileSync(secretRef, `POSTGRES_DB=aops\nPOSTGRES_USER=aops\nPOSTGRES_PASSWORD=${secret}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
179
|
+
return readSecret(secretRef);
|
|
180
|
+
}
|
|
181
|
+
function assertSecretFingerprint(secretRef, expectedSecretSha256) {
|
|
182
|
+
const secret = readSecret(secretRef);
|
|
183
|
+
if (secret.secretSha256 !== expectedSecretSha256) {
|
|
184
|
+
throw new Error('community_native_postgres_secret_identity_mismatch');
|
|
185
|
+
}
|
|
186
|
+
return secret;
|
|
187
|
+
}
|
|
188
|
+
function dockerInvocation(args, signal) {
|
|
189
|
+
return { command: 'docker', args, env: {}, signal };
|
|
190
|
+
}
|
|
191
|
+
async function runDocker(runtime, args, operation, signal) {
|
|
192
|
+
throwIfCommunityCommandAborted(signal);
|
|
193
|
+
const result = await runtime.run(dockerInvocation(args, signal));
|
|
194
|
+
throwIfCommunityCommandAborted(signal);
|
|
195
|
+
if (result.exitCode !== 0) {
|
|
196
|
+
throw new Error(`community_native_postgres_${operation}_failed:${result.exitCode ?? result.signal ?? 'unknown'}:${redactDiagnostic(result.stderr)}`);
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
function isMissing(result) {
|
|
201
|
+
return result.exitCode !== 0 && /No such (?:container|object|volume)/i.test(result.stderr);
|
|
202
|
+
}
|
|
203
|
+
async function inspectLabels(runtime, kind, name, signal) {
|
|
204
|
+
const format = kind === 'container' ? '{{json .Config.Labels}}' : '{{json .Labels}}';
|
|
205
|
+
throwIfCommunityCommandAborted(signal);
|
|
206
|
+
const result = await runtime.run(dockerInvocation([kind, 'inspect', '--format', format, name], signal));
|
|
207
|
+
throwIfCommunityCommandAborted(signal);
|
|
208
|
+
if (isMissing(result))
|
|
209
|
+
return null;
|
|
210
|
+
if (result.exitCode !== 0) {
|
|
211
|
+
throw new Error(`community_native_postgres_${kind}_inspect_failed:${redactDiagnostic(result.stderr)}`);
|
|
212
|
+
}
|
|
213
|
+
let parsed;
|
|
214
|
+
try {
|
|
215
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw new Error(`community_native_postgres_${kind}_labels_invalid`);
|
|
219
|
+
}
|
|
220
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
221
|
+
throw new Error(`community_native_postgres_${kind}_labels_invalid`);
|
|
222
|
+
}
|
|
223
|
+
return parsed;
|
|
224
|
+
}
|
|
225
|
+
async function inspectContainerJson(runtime, name, format, operation, signal) {
|
|
226
|
+
const result = await runDocker(runtime, ['container', 'inspect', '--format', format, name], operation, signal);
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(result.stdout.trim());
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
throw new Error(`community_native_postgres_${operation}_invalid`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function assertOwnedLabels(labels, state, kind) {
|
|
235
|
+
if (labels[LABEL_PROFILE] !== PROFILE || labels[LABEL_INSTANCE] !== state.instanceName ||
|
|
236
|
+
labels[LABEL_NAMESPACE] !== state.namespace || labels[LABEL_SECRET_SHA256] !== state.secretSha256)
|
|
237
|
+
throw new Error(`community_native_postgres_${kind}_ownership_conflict`);
|
|
238
|
+
}
|
|
239
|
+
function identity(instanceName, instanceRoot) {
|
|
240
|
+
const namespace = createHash('sha256').update(path.resolve(instanceRoot)).digest('hex').slice(0, 12);
|
|
241
|
+
return {
|
|
242
|
+
namespace,
|
|
243
|
+
containerName: `aops-community-${instanceName}-${namespace}-postgres`,
|
|
244
|
+
volumeName: `aops-community-${instanceName}-${namespace}-postgres-data`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function sameResolvedPath(left, right) {
|
|
248
|
+
const resolvedLeft = path.resolve(left);
|
|
249
|
+
const resolvedRight = path.resolve(right);
|
|
250
|
+
return process.platform === 'win32'
|
|
251
|
+
? resolvedLeft.toLowerCase() === resolvedRight.toLowerCase()
|
|
252
|
+
: resolvedLeft === resolvedRight;
|
|
253
|
+
}
|
|
254
|
+
export function assertCommunityNativePostgresInstanceState(params) {
|
|
255
|
+
const state = assertCommunityNativePostgresState(params.state);
|
|
256
|
+
const expected = identity(params.instanceName, params.instanceRoot);
|
|
257
|
+
const expectedSecretRef = path.join(path.resolve(params.instanceRoot), 'runtime', 'native-postgres.env');
|
|
258
|
+
if (state.namespace !== expected.namespace || state.containerName !== expected.containerName ||
|
|
259
|
+
state.volumeName !== expected.volumeName || !sameResolvedPath(state.secretRef, expectedSecretRef))
|
|
260
|
+
throw new Error('community_native_postgres_state_identity_mismatch');
|
|
261
|
+
assertSecretFingerprint(state.secretRef, state.secretSha256);
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
264
|
+
function ownershipArgs(instanceName, namespace, secretSha256) {
|
|
265
|
+
return [
|
|
266
|
+
'--label', `${LABEL_PROFILE}=${PROFILE}`,
|
|
267
|
+
'--label', `${LABEL_INSTANCE}=${instanceName}`,
|
|
268
|
+
'--label', `${LABEL_NAMESPACE}=${namespace}`,
|
|
269
|
+
'--label', `${LABEL_SECRET_SHA256}=${secretSha256}`,
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
async function verifyContainer(runtime, state, instanceName, signal, verifyPersistedPort = true) {
|
|
273
|
+
const labels = await inspectLabels(runtime, 'container', state.containerName, signal);
|
|
274
|
+
if (!labels)
|
|
275
|
+
return false;
|
|
276
|
+
assertOwnedLabels(labels, {
|
|
277
|
+
namespace: state.namespace,
|
|
278
|
+
secretSha256: state.secretSha256,
|
|
279
|
+
instanceName,
|
|
280
|
+
}, 'container');
|
|
281
|
+
const image = await runDocker(runtime, ['container', 'inspect', '--format', '{{.Config.Image}}', state.containerName], 'container_image_inspect', signal);
|
|
282
|
+
if (image.stdout.trim() !== state.imageRef)
|
|
283
|
+
throw new Error('community_native_postgres_container_image_drift');
|
|
284
|
+
const mounts = await inspectContainerJson(runtime, state.containerName, '{{json .Mounts}}', 'container_mount_inspect', signal);
|
|
285
|
+
if (!Array.isArray(mounts) || mounts.length !== 1 || !mounts[0] || typeof mounts[0] !== 'object' ||
|
|
286
|
+
mounts[0].Type !== 'volume' || mounts[0].Name !== state.volumeName ||
|
|
287
|
+
mounts[0].Destination !== POSTGRES_DATA_MOUNT || mounts[0].RW !== true)
|
|
288
|
+
throw new Error('community_native_postgres_container_mount_drift');
|
|
289
|
+
const portBindings = await inspectContainerJson(runtime, state.containerName, '{{json .HostConfig.PortBindings}}', 'container_port_binding_inspect', signal);
|
|
290
|
+
if (!portBindings || typeof portBindings !== 'object' || Array.isArray(portBindings)) {
|
|
291
|
+
throw new Error('community_native_postgres_container_port_binding_drift');
|
|
292
|
+
}
|
|
293
|
+
const bindingKeys = Object.keys(portBindings);
|
|
294
|
+
const binding = portBindings[POSTGRES_PORT_KEY];
|
|
295
|
+
if (bindingKeys.length !== 1 || bindingKeys[0] !== POSTGRES_PORT_KEY || !Array.isArray(binding) ||
|
|
296
|
+
binding.length !== 1 || !binding[0] || typeof binding[0] !== 'object' ||
|
|
297
|
+
binding[0].HostIp !== '127.0.0.1')
|
|
298
|
+
throw new Error('community_native_postgres_container_port_binding_drift');
|
|
299
|
+
const configuredHostPort = binding[0].HostPort;
|
|
300
|
+
const hostPort = configuredHostPort === '' ? null : Number(configuredHostPort);
|
|
301
|
+
if (typeof configuredHostPort !== 'string' ||
|
|
302
|
+
(hostPort !== null && (!Number.isSafeInteger(hostPort) || hostPort < 1 || hostPort > 65_535)) ||
|
|
303
|
+
(verifyPersistedPort && hostPort !== null && hostPort !== state.port))
|
|
304
|
+
throw new Error('community_native_postgres_container_port_binding_drift');
|
|
305
|
+
if (verifyPersistedPort && await mappedPort(runtime, state, signal) !== state.port) {
|
|
306
|
+
throw new Error('community_native_postgres_container_port_binding_drift');
|
|
307
|
+
}
|
|
308
|
+
const restartPolicy = await inspectContainerJson(runtime, state.containerName, '{{json .HostConfig.RestartPolicy}}', 'container_restart_policy_inspect', signal);
|
|
309
|
+
if (!restartPolicy || typeof restartPolicy !== 'object' || Array.isArray(restartPolicy) ||
|
|
310
|
+
restartPolicy.Name !== 'unless-stopped' || restartPolicy.MaximumRetryCount !== 0)
|
|
311
|
+
throw new Error('community_native_postgres_container_restart_policy_drift');
|
|
312
|
+
const safeEnv = await runDocker(runtime, ['container', 'inspect', '--format', SAFE_ENV_INSPECT_FORMAT, state.containerName], 'container_safe_env_inspect', signal);
|
|
313
|
+
const safeEnvLines = safeEnv.stdout.trim().split(/\r?\n/).filter(Boolean).sort();
|
|
314
|
+
if (safeEnvLines.length !== 2 || safeEnvLines[0] !== 'POSTGRES_DB=aops' ||
|
|
315
|
+
safeEnvLines[1] !== 'POSTGRES_USER=aops')
|
|
316
|
+
throw new Error('community_native_postgres_container_safe_env_drift');
|
|
317
|
+
const health = await inspectContainerJson(runtime, state.containerName, '{{json .Config.Healthcheck}}', 'container_healthcheck_inspect', signal);
|
|
318
|
+
if (!health || typeof health !== 'object' || Array.isArray(health) ||
|
|
319
|
+
!Array.isArray(health.Test) || health.Test.length !== 2 || health.Test[0] !== 'CMD-SHELL' ||
|
|
320
|
+
health.Test[1] !== 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' ||
|
|
321
|
+
health.Interval !== 5_000_000_000 || health.Timeout !== 5_000_000_000 ||
|
|
322
|
+
health.Retries !== 20 || health.StartPeriod !== 10_000_000_000 ||
|
|
323
|
+
(health.StartInterval !== undefined && health.StartInterval !== 0))
|
|
324
|
+
throw new Error('community_native_postgres_container_healthcheck_drift');
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
async function containerState(runtime, name, signal) {
|
|
328
|
+
const result = await runDocker(runtime, ['container', 'inspect', '--format', '{{json .State}}', name], 'state_inspect', signal);
|
|
329
|
+
let parsed;
|
|
330
|
+
try {
|
|
331
|
+
parsed = JSON.parse(result.stdout.trim());
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
throw new Error('community_native_postgres_container_state_invalid');
|
|
335
|
+
}
|
|
336
|
+
const status = String(parsed?.Status ?? '');
|
|
337
|
+
const health = parsed?.Health?.Status == null ? null : String(parsed.Health.Status);
|
|
338
|
+
if (!['created', 'running', 'exited', 'dead', 'restarting'].includes(status)) {
|
|
339
|
+
throw new Error('community_native_postgres_container_state_invalid');
|
|
340
|
+
}
|
|
341
|
+
return { status, health };
|
|
342
|
+
}
|
|
343
|
+
async function mappedPort(runtime, state, signal) {
|
|
344
|
+
const result = await runDocker(runtime, ['container', 'port', state.containerName, '5432/tcp'], 'port_inspect', signal);
|
|
345
|
+
const lines = result.stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
346
|
+
if (lines.length !== 1)
|
|
347
|
+
throw new Error('community_native_postgres_port_mapping_invalid');
|
|
348
|
+
const match = /^127\.0\.0\.1:(\d{1,5})$/.exec(lines[0]);
|
|
349
|
+
const port = Number(match?.[1]);
|
|
350
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
351
|
+
throw new Error('community_native_postgres_port_mapping_invalid');
|
|
352
|
+
}
|
|
353
|
+
return port;
|
|
354
|
+
}
|
|
355
|
+
async function waitReady(runtime, state, timeoutMs, signal) {
|
|
356
|
+
const deadline = Date.now() + timeoutMs;
|
|
357
|
+
while (Date.now() < deadline) {
|
|
358
|
+
throwIfCommunityCommandAborted(signal);
|
|
359
|
+
const observed = await containerState(runtime, state.containerName, signal);
|
|
360
|
+
if (observed.status === 'running' && observed.health === 'healthy') {
|
|
361
|
+
const port = await mappedPort(runtime, state, signal);
|
|
362
|
+
const smoke = await runDocker(runtime, [
|
|
363
|
+
'container', 'exec', state.containerName,
|
|
364
|
+
'psql', '-U', 'aops', '-d', 'aops', '--tuples-only', '--no-align', '--command', 'SELECT 1',
|
|
365
|
+
], 'data_smoke', signal);
|
|
366
|
+
if (smoke.stdout.trim() !== '1')
|
|
367
|
+
throw new Error('community_native_postgres_data_smoke_failed');
|
|
368
|
+
return port;
|
|
369
|
+
}
|
|
370
|
+
if (observed.status === 'exited' || observed.status === 'dead' || observed.health === 'unhealthy') {
|
|
371
|
+
throw new Error(`community_native_postgres_unhealthy:${observed.status}:${observed.health ?? 'unknown'}`);
|
|
372
|
+
}
|
|
373
|
+
await runtime.sleep(READY_POLL_MS, signal);
|
|
374
|
+
throwIfCommunityCommandAborted(signal);
|
|
375
|
+
}
|
|
376
|
+
throw new Error('community_native_postgres_ready_timeout');
|
|
377
|
+
}
|
|
378
|
+
export async function setupCommunityNativePostgres(params) {
|
|
379
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
380
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
381
|
+
const inspected = inspectCommunityNativePostgresContract(params.sourceRoot);
|
|
382
|
+
const names = identity(params.instanceName, params.instanceRoot);
|
|
383
|
+
const secretRef = path.join(path.resolve(params.runtimeRoot), 'native-postgres.env');
|
|
384
|
+
const secret = ensureSecret(secretRef, params.createSecret ?? (() => randomBytes(32).toString('base64url')));
|
|
385
|
+
const seed = {
|
|
386
|
+
mode: 'container',
|
|
387
|
+
contractRef: inspected.contractRef,
|
|
388
|
+
contractSha256: inspected.contractSha256,
|
|
389
|
+
imageRef: inspected.contract.imageRef,
|
|
390
|
+
imageDigest: inspected.contract.imageDigest,
|
|
391
|
+
namespace: names.namespace,
|
|
392
|
+
containerName: names.containerName,
|
|
393
|
+
volumeName: names.volumeName,
|
|
394
|
+
secretRef,
|
|
395
|
+
secretSha256: secret.secretSha256,
|
|
396
|
+
host: '127.0.0.1',
|
|
397
|
+
port: 1,
|
|
398
|
+
};
|
|
399
|
+
await runDocker(runtime, ['image', 'pull', seed.imageRef], 'image_pull', params.signal);
|
|
400
|
+
const volumeLabels = await inspectLabels(runtime, 'volume', seed.volumeName, params.signal);
|
|
401
|
+
if (volumeLabels) {
|
|
402
|
+
assertOwnedLabels(volumeLabels, {
|
|
403
|
+
namespace: seed.namespace,
|
|
404
|
+
secretSha256: seed.secretSha256,
|
|
405
|
+
instanceName: params.instanceName,
|
|
406
|
+
}, 'volume');
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
await runDocker(runtime, [
|
|
410
|
+
'volume', 'create', ...ownershipArgs(params.instanceName, seed.namespace, seed.secretSha256), seed.volumeName,
|
|
411
|
+
], 'volume_create', params.signal);
|
|
412
|
+
const createdLabels = await inspectLabels(runtime, 'volume', seed.volumeName, params.signal);
|
|
413
|
+
if (!createdLabels)
|
|
414
|
+
throw new Error('community_native_postgres_volume_create_unverified');
|
|
415
|
+
assertOwnedLabels(createdLabels, {
|
|
416
|
+
namespace: seed.namespace,
|
|
417
|
+
secretSha256: seed.secretSha256,
|
|
418
|
+
instanceName: params.instanceName,
|
|
419
|
+
}, 'volume');
|
|
420
|
+
}
|
|
421
|
+
const existing = await inspectLabels(runtime, 'container', seed.containerName, params.signal);
|
|
422
|
+
let created = false;
|
|
423
|
+
if (existing) {
|
|
424
|
+
assertOwnedLabels(existing, {
|
|
425
|
+
namespace: seed.namespace,
|
|
426
|
+
secretSha256: seed.secretSha256,
|
|
427
|
+
instanceName: params.instanceName,
|
|
428
|
+
}, 'container');
|
|
429
|
+
const verified = await verifyContainer(runtime, seed, params.instanceName, params.signal, false);
|
|
430
|
+
if (!verified)
|
|
431
|
+
throw new Error('community_native_postgres_container_missing');
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
await runDocker(runtime, [
|
|
435
|
+
'container', 'create', '--name', seed.containerName,
|
|
436
|
+
...ownershipArgs(params.instanceName, seed.namespace, seed.secretSha256),
|
|
437
|
+
'--restart', 'unless-stopped',
|
|
438
|
+
'--publish', '127.0.0.1::5432',
|
|
439
|
+
'--mount', `type=volume,source=${seed.volumeName},target=/var/lib/postgresql/data`,
|
|
440
|
+
'--env-file', seed.secretRef,
|
|
441
|
+
'--health-cmd', 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"',
|
|
442
|
+
'--health-interval', '5s', '--health-timeout', '5s', '--health-retries', '20',
|
|
443
|
+
'--health-start-period', '10s',
|
|
444
|
+
seed.imageRef,
|
|
445
|
+
], 'container_create', params.signal);
|
|
446
|
+
created = true;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
if (created && !await verifyContainer(runtime, seed, params.instanceName, params.signal, false)) {
|
|
450
|
+
throw new Error('community_native_postgres_container_create_unverified');
|
|
451
|
+
}
|
|
452
|
+
await runDocker(runtime, ['container', 'start', seed.containerName], 'container_start', params.signal);
|
|
453
|
+
const port = await waitReady(runtime, seed, params.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS, params.signal);
|
|
454
|
+
return assertCommunityNativePostgresState({ ...seed, port });
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (created) {
|
|
458
|
+
try {
|
|
459
|
+
await runtime.run(dockerInvocation(['container', 'rm', '--force', seed.containerName], params.signal));
|
|
460
|
+
}
|
|
461
|
+
catch { }
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
export async function startCommunityNativePostgres(params) {
|
|
467
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
468
|
+
const state = assertCommunityNativePostgresInstanceState(params);
|
|
469
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
470
|
+
if (!await verifyContainer(runtime, state, params.instanceName, params.signal)) {
|
|
471
|
+
throw new Error('community_native_postgres_container_missing:run_server_setup_--apply');
|
|
472
|
+
}
|
|
473
|
+
readSecret(state.secretRef);
|
|
474
|
+
await runDocker(runtime, ['container', 'start', state.containerName], 'container_start', params.signal);
|
|
475
|
+
const port = await waitReady(runtime, state, params.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS, params.signal);
|
|
476
|
+
if (port !== state.port)
|
|
477
|
+
throw new Error('community_native_postgres_port_mapping_drift:run_server_setup_--apply');
|
|
478
|
+
}
|
|
479
|
+
export async function stopCommunityNativePostgres(params) {
|
|
480
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
481
|
+
const state = assertCommunityNativePostgresInstanceState(params);
|
|
482
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
483
|
+
if (!await verifyContainer(runtime, state, params.instanceName, params.signal)) {
|
|
484
|
+
throw new Error('community_native_postgres_container_missing');
|
|
485
|
+
}
|
|
486
|
+
const observed = await containerState(runtime, state.containerName, params.signal);
|
|
487
|
+
if (observed.status === 'exited' || observed.status === 'created')
|
|
488
|
+
return 'already-stopped';
|
|
489
|
+
await runDocker(runtime, ['container', 'stop', '--time', '15', state.containerName], 'container_stop', params.signal);
|
|
490
|
+
return 'stopped';
|
|
491
|
+
}
|
|
492
|
+
export async function removeCommunityNativePostgresContainer(params) {
|
|
493
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
494
|
+
const state = assertCommunityNativePostgresInstanceState(params);
|
|
495
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
496
|
+
if (!await verifyContainer(runtime, state, params.instanceName, params.signal))
|
|
497
|
+
return 'missing';
|
|
498
|
+
await runDocker(runtime, ['container', 'rm', '--force', state.containerName], 'container_remove', params.signal);
|
|
499
|
+
return 'removed';
|
|
500
|
+
}
|
|
501
|
+
export async function removeCommunityNativePostgresContainerForReset(params) {
|
|
502
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
503
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
504
|
+
const expected = identity(params.instanceName, params.instanceRoot);
|
|
505
|
+
const containerLabels = await inspectLabels(runtime, 'container', expected.containerName, params.signal);
|
|
506
|
+
if (!containerLabels) {
|
|
507
|
+
return { container: 'missing', containerName: expected.containerName, volumeName: expected.volumeName };
|
|
508
|
+
}
|
|
509
|
+
const secretRef = path.join(path.resolve(params.instanceRoot), 'runtime', 'native-postgres.env');
|
|
510
|
+
const secret = readSecret(secretRef);
|
|
511
|
+
const ownedState = {
|
|
512
|
+
namespace: expected.namespace,
|
|
513
|
+
secretSha256: secret.secretSha256,
|
|
514
|
+
instanceName: params.instanceName,
|
|
515
|
+
};
|
|
516
|
+
assertOwnedLabels(containerLabels, ownedState, 'container');
|
|
517
|
+
const volumeLabels = await inspectLabels(runtime, 'volume', expected.volumeName, params.signal);
|
|
518
|
+
if (volumeLabels) {
|
|
519
|
+
assertOwnedLabels(volumeLabels, ownedState, 'volume');
|
|
520
|
+
}
|
|
521
|
+
await runDocker(runtime, ['container', 'rm', '--force', expected.containerName], 'container_remove', params.signal);
|
|
522
|
+
return { container: 'removed', containerName: expected.containerName, volumeName: expected.volumeName };
|
|
523
|
+
}
|
|
524
|
+
export async function removeCommunityNativeManagedPostgres(params) {
|
|
525
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
526
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
527
|
+
const expected = identity(params.instanceName, params.instanceRoot);
|
|
528
|
+
const containerLabels = await inspectLabels(runtime, 'container', expected.containerName, params.signal);
|
|
529
|
+
const volumeLabels = await inspectLabels(runtime, 'volume', expected.volumeName, params.signal);
|
|
530
|
+
if (!containerLabels && !volumeLabels) {
|
|
531
|
+
return {
|
|
532
|
+
container: 'missing',
|
|
533
|
+
volume: 'missing',
|
|
534
|
+
containerName: expected.containerName,
|
|
535
|
+
volumeName: expected.volumeName,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const secretRef = path.join(path.resolve(params.instanceRoot), 'runtime', 'native-postgres.env');
|
|
539
|
+
const secret = readSecret(secretRef);
|
|
540
|
+
const ownedState = {
|
|
541
|
+
namespace: expected.namespace,
|
|
542
|
+
secretSha256: secret.secretSha256,
|
|
543
|
+
instanceName: params.instanceName,
|
|
544
|
+
};
|
|
545
|
+
if (containerLabels)
|
|
546
|
+
assertOwnedLabels(containerLabels, ownedState, 'container');
|
|
547
|
+
if (volumeLabels)
|
|
548
|
+
assertOwnedLabels(volumeLabels, ownedState, 'volume');
|
|
549
|
+
if (containerLabels) {
|
|
550
|
+
await runDocker(runtime, ['container', 'rm', '--force', expected.containerName], 'container_remove', params.signal);
|
|
551
|
+
}
|
|
552
|
+
if (volumeLabels) {
|
|
553
|
+
await runDocker(runtime, ['volume', 'rm', expected.volumeName], 'volume_remove', params.signal);
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
container: containerLabels ? 'removed' : 'missing',
|
|
557
|
+
volume: volumeLabels ? 'removed' : 'missing',
|
|
558
|
+
containerName: expected.containerName,
|
|
559
|
+
volumeName: expected.volumeName,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
export async function inspectCommunityNativePostgres(params) {
|
|
563
|
+
throwIfCommunityCommandAborted(params.signal);
|
|
564
|
+
const state = assertCommunityNativePostgresInstanceState(params);
|
|
565
|
+
const runtime = params.runtime ?? communityNativePostgresRuntime;
|
|
566
|
+
if (!await verifyContainer(runtime, state, params.instanceName, params.signal)) {
|
|
567
|
+
return {
|
|
568
|
+
container: 'missing', health: 'not-checked', imageRef: state.imageRef,
|
|
569
|
+
containerName: state.containerName, volumeName: state.volumeName, host: state.host, port: state.port,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const observed = await containerState(runtime, state.containerName, params.signal);
|
|
573
|
+
const health = observed.health === 'healthy' || observed.health === 'starting' || observed.health === 'unhealthy'
|
|
574
|
+
? observed.health
|
|
575
|
+
: 'not-checked';
|
|
576
|
+
const container = observed.status === 'running'
|
|
577
|
+
? (health === 'unhealthy' ? 'unhealthy' : 'running')
|
|
578
|
+
: observed.status === 'created' ? 'created' : 'stopped';
|
|
579
|
+
return {
|
|
580
|
+
container, health, imageRef: state.imageRef, containerName: state.containerName,
|
|
581
|
+
volumeName: state.volumeName, host: state.host, port: state.port,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
export function buildCommunityNativePostgresUrl(state, database = 'aops') {
|
|
585
|
+
const validated = assertCommunityNativePostgresState(state);
|
|
586
|
+
if (!DATABASE_NAME.test(database))
|
|
587
|
+
throw new Error('community_native_postgres_database_name_invalid');
|
|
588
|
+
const secret = assertSecretFingerprint(validated.secretRef, validated.secretSha256);
|
|
589
|
+
const url = new URL('postgresql://127.0.0.1/');
|
|
590
|
+
url.username = secret.username;
|
|
591
|
+
url.password = secret.password;
|
|
592
|
+
url.port = String(validated.port);
|
|
593
|
+
url.pathname = `/${database === 'aops' ? secret.database : database}`;
|
|
594
|
+
url.searchParams.set('sslmode', 'disable');
|
|
595
|
+
return url.toString();
|
|
596
|
+
}
|