Merge main
# Conflicts: # modules/minio/module.json
This commit is contained in:
@@ -9,6 +9,11 @@
|
||||
# image it RUNS in — the second must not carry a compiler. Declared in module.json's `build.on`.
|
||||
ARG BUILD_BASE
|
||||
ARG RUNTIME_BASE
|
||||
ARG MC_CLI
|
||||
|
||||
# Named so the final stage's COPY --from can reference a stage, not an ARG — the legacy builder
|
||||
# this host still runs doesn't expand ARGs inside COPY --from, only inside FROM.
|
||||
FROM ${MC_CLI} AS mccli
|
||||
|
||||
FROM ${BUILD_BASE} AS build
|
||||
# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own
|
||||
@@ -22,6 +27,13 @@ RUN node /app/node_modules/typescript/bin/tsc client.ts tools/index.ts provision
|
||||
|
||||
FROM ${RUNTIME_BASE}
|
||||
COPY --from=build /app/modules/minio/dist /app/modules/minio/dist
|
||||
# The provisioner shells out to mc to actually create buckets and service accounts on the running
|
||||
# minio server — mc itself was never in this runtime image, only in minio's own. Silently retried
|
||||
# "spawn mc ENOENT" forever: a requirement was granted at the control-plane level without ever
|
||||
# materializing the credential on minio. /usr/bin/mc there is a symlink to the real binary, mcli —
|
||||
# both copied so the symlink resolves.
|
||||
COPY --from=mccli /usr/bin/mcli /usr/bin/mcli
|
||||
COPY --from=mccli /usr/bin/mc /usr/bin/mc
|
||||
# Every serve-time entrypoint, loaded by the runtime in serve mode: tools and events serve, and a
|
||||
# provider's provisioner runs its reconcile loop in the same process, with the broker connected —
|
||||
# the convention novox/hq issues 060/061 settled.
|
||||
|
||||
+17
-4
@@ -125,6 +125,18 @@ export class MinioClient {
|
||||
throw new Error(`minio bucketExists ${bucket}: ${status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a consumer's access key, with exactly this secret, reaches its bucket: a HEAD of the
|
||||
* bucket signed as the consumer, the way it signs. Read-only. `false` when the key is unknown, the
|
||||
* secret wrong, access denied or the bucket gone; any other answer rejects (novox/hq issue 120).
|
||||
*/
|
||||
async canReachAs(bucket: string, accessKey: string, secretKey: string): Promise<boolean> {
|
||||
const { status } = await this.request("HEAD", `/${bucket}`, {}, { accessKey, secretKey });
|
||||
if (status === 200) return true;
|
||||
if (status === 403 || status === 404) return false;
|
||||
throw new Error(`minio HEAD ${bucket} as ${accessKey}: ${status}`);
|
||||
}
|
||||
|
||||
async createBucket(bucket: string): Promise<void> {
|
||||
const { status, text } = await this.request("PUT", `/${bucket}`);
|
||||
// 200 created; 409 BucketAlreadyOwnedByYou — idempotent, a re-provision must not fail.
|
||||
@@ -251,6 +263,7 @@ export class MinioClient {
|
||||
method: string,
|
||||
path: string,
|
||||
query: Record<string, string> = {},
|
||||
as: { accessKey: string; secretKey: string } = { accessKey: this.rootUser, secretKey: this.rootPassword },
|
||||
): Promise<{ status: number; headers: Headers; text: string }> {
|
||||
const { amzDate, dateStamp } = this.stamp();
|
||||
const host = new URL(this.baseUrl).host;
|
||||
@@ -262,8 +275,8 @@ export class MinioClient {
|
||||
const canonicalRequest = [method, encodedPath, canonicalQuery, canonicalHeaders, signedHeaders, payloadHash].join("\n");
|
||||
const scope = `${dateStamp}/${this.region}/s3/aws4_request`;
|
||||
const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256hex(canonicalRequest)].join("\n");
|
||||
const signature = hmac(this.signingKey(dateStamp), stringToSign).toString("hex");
|
||||
const authorization = `AWS4-HMAC-SHA256 Credential=${this.rootUser}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
const signature = hmac(this.signingKey(dateStamp, as.secretKey), stringToSign).toString("hex");
|
||||
const authorization = `AWS4-HMAC-SHA256 Credential=${as.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
|
||||
const url = `${this.baseUrl}${encodedPath}${canonicalQuery ? `?${canonicalQuery}` : ""}`;
|
||||
const res = await fetch(url, {
|
||||
@@ -275,8 +288,8 @@ export class MinioClient {
|
||||
return { status: res.status, headers: res.headers, text };
|
||||
}
|
||||
|
||||
private signingKey(dateStamp: string): Buffer {
|
||||
const kDate = hmac(`AWS4${this.rootPassword}`, dateStamp);
|
||||
private signingKey(dateStamp: string, secretKey: string = this.rootPassword): Buffer {
|
||||
const kDate = hmac(`AWS4${secretKey}`, dateStamp);
|
||||
const kRegion = hmac(kDate, this.region);
|
||||
const kService = hmac(kRegion, "s3");
|
||||
return hmac(kService, "aws4_request");
|
||||
|
||||
@@ -103,9 +103,19 @@
|
||||
"name": "minio",
|
||||
"image": "docker.io/pgsty/minio@sha256:b6bfe7239bfc83fb90d31612d9704d86039dd714f7904b3f1ad68f211e602372",
|
||||
"network": "minio-net",
|
||||
"args": ["server", "/data", "--console-address", ":9001"],
|
||||
"env-file": ["/var/lib/minio/root.env"],
|
||||
"ports": ["9000", "9001"],
|
||||
"args": [
|
||||
"server",
|
||||
"/data",
|
||||
"--console-address",
|
||||
":9001"
|
||||
],
|
||||
"env-file": [
|
||||
"/var/lib/minio/root.env"
|
||||
],
|
||||
"ports": [
|
||||
"9000",
|
||||
"9001"
|
||||
],
|
||||
"volumes": [
|
||||
"/var/lib/minio-store:/data",
|
||||
"/var/lib/minio/root.secret:/run/secrets/root:ro"
|
||||
@@ -148,6 +158,10 @@
|
||||
"arg": "RUNTIME_BASE",
|
||||
"module": "mesh-tools",
|
||||
"artifact": "runtime"
|
||||
},
|
||||
{
|
||||
"arg": "MC_CLI",
|
||||
"image": "docker.io/pgsty/minio@sha256:b6bfe7239bfc83fb90d31612d9704d86039dd714f7904b3f1ad68f211e602372"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@novox/mesh-sdk": "^0.1.0"
|
||||
"@novox/mesh-sdk": "^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
|
||||
@@ -53,6 +53,12 @@ runProvisioner("s3-bucket", {
|
||||
|
||||
await announce("module.minio.bucket.removed", { bucket, accessKey: p.as });
|
||||
},
|
||||
|
||||
// Asked every minute by the harness: whether the backend still holds this consumer exactly as
|
||||
// the mesh gave it, so a login lost behind the provisioner's back is made again (novox/hq issue 120).
|
||||
async holds(p: Provision): Promise<boolean> {
|
||||
return minio.canReachAs(bucketFor(p.as), p.as, p.password);
|
||||
},
|
||||
});
|
||||
|
||||
/** Emit best-effort: a broker hiccup is logged and dropped, never allowed to throw back and fail a
|
||||
|
||||
Reference in New Issue
Block a user