fix(KAN-58): accept empty unversioned bucket response
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 55s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m23s
CI/CD / test_and_check_app (pull_request) Successful in 6m34s
CI/CD / Publish immutable image (pull_request) Skipped

This commit is contained in:
2026-09-27 12:05:11 +02:00
parent b0cc9dad14
commit f2e1bd6a0d
2 changed files with 49 additions and 2 deletions
+20 -1
View File
@@ -94,7 +94,26 @@ export async function publishMacos({
...args,
]);
}
const versioning = JSON.parse(s3('get-bucket-versioning', []));
const versioningOutput = s3('get-bucket-versioning', [
'--output',
'json',
]).trim();
let versioning;
try {
// AWS CLI prints nothing for a successful empty VersioningConfiguration.
versioning = JSON.parse(versioningOutput || '{}');
} catch {
throw new Error('Invalid JSON from S3 bucket versioning check.');
}
if (
!versioning ||
typeof versioning !== 'object' ||
Array.isArray(versioning)
) {
throw new Error(
'Invalid S3 bucket versioning response: expected an object.',
);
}
if (versioning.Status) {
throw new Error(
'Use an unversioned release bucket: Hetzner does not support conditional writes on versioned buckets.',
+29 -1
View File
@@ -38,7 +38,8 @@ function fixture(t) {
const key = value('--key');
events.push({ operation, key });
if (operation === 'get-bucket-versioning') {
return '{}';
assert.equal(value('--output'), 'json');
return '';
}
if (operation === 'put-object') {
assert.equal(value('--if-none-match'), '*');
@@ -103,6 +104,33 @@ test('uploads immutable files and writes completion record after public verifica
});
});
for (const response of ['', ' \n\t', '{}']) {
test(`unversioned bucket response ${JSON.stringify(response)} permits a verified upload`, async (t) => {
const f = fixture(t);
const run = f.run;
f.run = (command, args) => {
const result = run(command, args);
return args.includes('get-bucket-versioning') ? response : result;
};
await publishMacos(f);
assert.equal(f.objects.size, 3);
assert.equal(f.events.at(-1).operation, 'public-get');
assert(f.events.at(-1).key.endsWith('/release.json'));
});
}
for (const response of ['{', 'null', '[]', '"Enabled"', 'false']) {
test(`invalid versioning response ${response} aborts before any upload`, async (t) => {
const f = fixture(t);
f.run = (_command, args) => {
assert(args.includes('get-bucket-versioning'));
return response;
};
await assert.rejects(publishMacos(f), /Invalid .*versioning/);
assert.equal(f.objects.size, 0);
});
}
test('retry accepts byte-identical objects without replacing them', async (t) => {
const f = fixture(t);
await publishMacos(f);