Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions loops/issue-dev-loop/scripts/install-trusted-control-plane.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ async function main() {
}
const temporaryBundleRoot = `${targetBundleRoot}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`
const targetLoopRoot = path.join(temporaryBundleRoot, 'issue-dev-loop')
let cleanupBundleRoot = temporaryBundleRoot
try {
await mkdir(targetLoopRoot, { recursive: true })
await cp(path.join(sourceLoopRoot, 'scripts'), path.join(targetLoopRoot, 'scripts'), {
Expand Down Expand Up @@ -198,9 +199,11 @@ async function main() {
}
await chmod(path.join(targetLoopRoot, 'trusted-control-plane.json'), 0o444)
for (const directory of (await directories(temporaryBundleRoot)).reverse()) {
await chmod(directory, 0o555)
if (directory !== temporaryBundleRoot) await chmod(directory, 0o555)
}
await rename(temporaryBundleRoot, targetBundleRoot)
cleanupBundleRoot = targetBundleRoot
await chmod(targetBundleRoot, 0o555)
process.stdout.write(
`${JSON.stringify({
bundleRoot: targetBundleRoot,
Expand All @@ -210,13 +213,13 @@ async function main() {
)
} catch (error) {
try {
for (const directory of await directories(temporaryBundleRoot)) {
for (const directory of await directories(cleanupBundleRoot)) {
await chmod(directory, 0o755)
}
} catch {
// Best-effort permission restoration for an interrupted pre-rename install.
// Best-effort permission restoration for an interrupted install.
}
await rm(temporaryBundleRoot, { recursive: true, force: true })
await rm(cleanupBundleRoot, { recursive: true, force: true })
throw error
}
}
Expand Down
114 changes: 114 additions & 0 deletions loops/issue-dev-loop/tests/install-trusted-control-plane.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import assert from 'node:assert/strict'
import { execFile } from 'node:child_process'
import {
chmod,
cp,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rm,
stat,
writeFile,
} from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { promisify } from 'node:util'
import { fileURLToPath } from 'node:url'

const execFileAsync = promisify(execFile)
const testDirectory = path.dirname(fileURLToPath(import.meta.url))
const installerSource = path.resolve(
testDirectory,
'..',
'scripts',
'install-trusted-control-plane.mjs',
)

async function makeWritable(target) {
let targetStat
try {
targetStat = await lstat(target)
} catch (error) {
if (error?.code === 'ENOENT') return
throw error
}
if (!targetStat.isDirectory()) {
await chmod(target, 0o644)
return
}
await chmod(target, 0o755)
for (const entry of await readdir(target)) {
await makeWritable(path.join(target, entry))
}
}

async function git(repositoryRoot, args) {
return execFileAsync('git', args, { cwd: repositoryRoot })
}

test('installer atomically publishes a read-only trusted control plane', async (context) => {
const fixtureRoot = await mkdtemp(path.join(os.tmpdir(), 'echo-ui-control-plane-install-'))
context.after(async () => {
await makeWritable(fixtureRoot)
await rm(fixtureRoot, { recursive: true, force: true })
})

const repositoryRoot = path.join(fixtureRoot, 'repository')
const loopRoot = path.join(repositoryRoot, 'loops', 'issue-dev-loop')
const installer = path.join(loopRoot, 'scripts', 'install-trusted-control-plane.mjs')
const target = path.join(fixtureRoot, 'trusted-control-plane')
const canonicalTarget = path.join(await realpath(fixtureRoot), 'trusted-control-plane')
await Promise.all([
mkdir(path.dirname(installer), { recursive: true }),
mkdir(path.join(loopRoot, 'triggers'), { recursive: true }),
mkdir(path.join(repositoryRoot, 'loops', '_shared', 'owner-channel'), { recursive: true }),
])
await Promise.all([
cp(installerSource, installer),
writeFile(path.join(loopRoot, 'triggers', 'detect-work.mjs'), 'export {}\n', 'utf8'),
writeFile(
path.join(repositoryRoot, 'loops', '_shared', 'owner-channel', 'channel.json'),
'{}\n',
'utf8',
),
])

await git(repositoryRoot, ['init', '--initial-branch=dev'])
await git(repositoryRoot, ['add', '.'])
await git(repositoryRoot, [
'-c',
'user.name=Echo UI Test',
'-c',
'[email protected]',
'commit',
'-m',
'fixture',
])
const { stdout: sourceCommitOutput } = await git(repositoryRoot, ['rev-parse', 'HEAD'])
const sourceCommit = sourceCommitOutput.trim()
await git(repositoryRoot, ['update-ref', 'refs/remotes/origin/dev', sourceCommit])

const { stdout } = await execFileAsync(process.execPath, [installer, '--target', target], {
cwd: repositoryRoot,
})

const result = JSON.parse(stdout)
assert.equal(result.bundleRoot, canonicalTarget)
assert.equal(result.controlPlaneRoot, path.join(canonicalTarget, 'issue-dev-loop'))
assert.equal(result.sourceCommit, sourceCommit)
assert.equal((await stat(target)).mode & 0o777, 0o555)
assert.equal((await stat(path.join(target, 'issue-dev-loop'))).mode & 0o777, 0o555)

const manifest = JSON.parse(
await readFile(
path.join(target, 'issue-dev-loop', 'trusted-control-plane.json'),
'utf8',
),
)
assert.equal(manifest.bundleRoot, canonicalTarget)
assert.equal(manifest.sourceCommit, sourceCommit)
})