From 6d2649256e89c1cd615da5df58ef17b0aff22874 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 19 Sep 2022 14:09:11 +0200 Subject: [PATCH] fix(cli): "EACCES: Permission denied" on 'cdk init' Historically, `cdk init` used to create a dedicated temporary directory for hook scripts and copy `*.hook.*` scripts into there. In #21049, the logic was changed to create that temporary directory inside the CLI source directory. If that CLI source directory is mounted in a read-only location (say, `/usr/lib/node_modules`) then that directory could not be created and `cdk init` would fail. It looks like historically we might copy and postprocess hook scripts so that they could have variables replaced... but given that hook scripts are code, they could just read the variables directly, so we don't have to copy them into a temporary directory at all: we can directly run them from the source location. Fixes #22090. --- .../app/csharp/add-project.hook.ts | 9 ++++--- .../app/fsharp/add-project.hook.ts | 9 ++++--- .../sample-app/csharp/add-project.hook.ts | 9 ++++--- .../sample-app/fsharp/add-project.hook.ts | 9 ++++--- packages/aws-cdk/lib/init.ts | 26 +++++++++---------- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/aws-cdk/lib/init-templates/app/csharp/add-project.hook.ts b/packages/aws-cdk/lib/init-templates/app/csharp/add-project.hook.ts index ad074041fab32..4903c8776c60c 100644 --- a/packages/aws-cdk/lib/init-templates/app/csharp/add-project.hook.ts +++ b/packages/aws-cdk/lib/init-templates/app/csharp/add-project.hook.ts @@ -2,12 +2,13 @@ import * as path from 'path'; import { InvokeHook } from '../../../init'; import { shell } from '../../../os'; -export const invoke: InvokeHook = async (targetDirectory: string) => { - const slnPath = path.join(targetDirectory, 'src', '%name.PascalCased%.sln'); - const csprojPath = path.join(targetDirectory, 'src', '%name.PascalCased%', '%name.PascalCased%.csproj'); +export const invoke: InvokeHook = async (targetDirectory: string, context) => { + const pname = context.placeholder('name.PascalCased'); + const slnPath = path.join(targetDirectory, 'src', `${pname}.sln`); + const csprojPath = path.join(targetDirectory, 'src', pname, `${pname}.csproj`); try { await shell(['dotnet', 'sln', slnPath, 'add', csprojPath]); } catch (e) { - throw new Error(`Could not add project %name.PascalCased%.csproj to solution %name.PascalCased%.sln. ${e.message}`); + throw new Error(`Could not add project ${pname}.csproj to solution ${pname}.sln. ${e.message}`); } }; diff --git a/packages/aws-cdk/lib/init-templates/app/fsharp/add-project.hook.ts b/packages/aws-cdk/lib/init-templates/app/fsharp/add-project.hook.ts index e3cae76dba992..1298c91432636 100644 --- a/packages/aws-cdk/lib/init-templates/app/fsharp/add-project.hook.ts +++ b/packages/aws-cdk/lib/init-templates/app/fsharp/add-project.hook.ts @@ -2,12 +2,13 @@ import * as path from 'path'; import { InvokeHook } from '../../../init'; import { shell } from '../../../os'; -export const invoke: InvokeHook = async (targetDirectory: string) => { - const slnPath = path.join(targetDirectory, 'src', '%name.PascalCased%.sln'); - const fsprojPath = path.join(targetDirectory, 'src', '%name.PascalCased%', '%name.PascalCased%.fsproj'); +export const invoke: InvokeHook = async (targetDirectory: string, context) => { + const pname = context.placeholder('name.PascalCased'); + const slnPath = path.join(targetDirectory, 'src', `${pname}.sln`); + const fsprojPath = path.join(targetDirectory, 'src', pname, `${pname}.fsproj`); try { await shell(['dotnet', 'sln', slnPath, 'add', fsprojPath]); } catch (e) { - throw new Error(`Could not add project %name.PascalCased%.fsproj to solution %name.PascalCased%.sln. ${e.message}`); + throw new Error(`Could not add project ${pname}.fsproj to solution ${pname}.sln. ${e.message}`); } }; diff --git a/packages/aws-cdk/lib/init-templates/sample-app/csharp/add-project.hook.ts b/packages/aws-cdk/lib/init-templates/sample-app/csharp/add-project.hook.ts index ad074041fab32..4903c8776c60c 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/csharp/add-project.hook.ts +++ b/packages/aws-cdk/lib/init-templates/sample-app/csharp/add-project.hook.ts @@ -2,12 +2,13 @@ import * as path from 'path'; import { InvokeHook } from '../../../init'; import { shell } from '../../../os'; -export const invoke: InvokeHook = async (targetDirectory: string) => { - const slnPath = path.join(targetDirectory, 'src', '%name.PascalCased%.sln'); - const csprojPath = path.join(targetDirectory, 'src', '%name.PascalCased%', '%name.PascalCased%.csproj'); +export const invoke: InvokeHook = async (targetDirectory: string, context) => { + const pname = context.placeholder('name.PascalCased'); + const slnPath = path.join(targetDirectory, 'src', `${pname}.sln`); + const csprojPath = path.join(targetDirectory, 'src', pname, `${pname}.csproj`); try { await shell(['dotnet', 'sln', slnPath, 'add', csprojPath]); } catch (e) { - throw new Error(`Could not add project %name.PascalCased%.csproj to solution %name.PascalCased%.sln. ${e.message}`); + throw new Error(`Could not add project ${pname}.csproj to solution ${pname}.sln. ${e.message}`); } }; diff --git a/packages/aws-cdk/lib/init-templates/sample-app/fsharp/add-project.hook.ts b/packages/aws-cdk/lib/init-templates/sample-app/fsharp/add-project.hook.ts index e3cae76dba992..1298c91432636 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/fsharp/add-project.hook.ts +++ b/packages/aws-cdk/lib/init-templates/sample-app/fsharp/add-project.hook.ts @@ -2,12 +2,13 @@ import * as path from 'path'; import { InvokeHook } from '../../../init'; import { shell } from '../../../os'; -export const invoke: InvokeHook = async (targetDirectory: string) => { - const slnPath = path.join(targetDirectory, 'src', '%name.PascalCased%.sln'); - const fsprojPath = path.join(targetDirectory, 'src', '%name.PascalCased%', '%name.PascalCased%.fsproj'); +export const invoke: InvokeHook = async (targetDirectory: string, context) => { + const pname = context.placeholder('name.PascalCased'); + const slnPath = path.join(targetDirectory, 'src', `${pname}.sln`); + const fsprojPath = path.join(targetDirectory, 'src', pname, `${pname}.fsproj`); try { await shell(['dotnet', 'sln', slnPath, 'add', fsprojPath]); } catch (e) { - throw new Error(`Could not add project %name.PascalCased%.fsproj to solution %name.PascalCased%.sln. ${e.message}`); + throw new Error(`Could not add project ${pname}.fsproj to solution ${pname}.sln. ${e.message}`); } }; diff --git a/packages/aws-cdk/lib/init.ts b/packages/aws-cdk/lib/init.ts index e5ecef421cde4..ba6555e26d62d 100644 --- a/packages/aws-cdk/lib/init.ts +++ b/packages/aws-cdk/lib/init.ts @@ -20,6 +20,11 @@ export interface HookContext { * This makes token substitution available to non-`.template` files. */ readonly substitutePlaceholdersIn: SubstitutePlaceholders; + + /** + * Return a single placeholder + */ + placeholder(name: string): string; } export type InvokeHook = (targetDirectory: string, context: HookContext) => Promise; @@ -119,7 +124,6 @@ export class InitTemplate { }; const sourceDirectory = path.join(this.basePath, language); - const hookTempDirectory = path.join(this.basePath, `${HOOK_DIR_PREFIX}-${projectInfo.name}`); const hookContext: HookContext = { substitutePlaceholdersIn: async (...fileNames: string[]) => { @@ -129,33 +133,27 @@ export class InitTemplate { await fs.writeFile(fullPath, this.expand(template, language, projectInfo)); } }, + placeholder: (ph: string) => this.expand(`%${ph}%`, language, projectInfo), }; - try { - await fs.mkdir(hookTempDirectory); - await this.installFiles(sourceDirectory, targetDirectory, hookTempDirectory, language, projectInfo); - await this.applyFutureFlags(targetDirectory); - await this.invokeHooks(hookTempDirectory, targetDirectory, hookContext); - } catch (e) { - warning(`Unable to create ${projectInfo.name}: ${e.message}`); - } finally { - await fs.remove(hookTempDirectory); - } + await this.installFiles(sourceDirectory, targetDirectory, language, projectInfo); + await this.applyFutureFlags(targetDirectory); + await this.invokeHooks(sourceDirectory, targetDirectory, hookContext); } - private async installFiles(sourceDirectory: string, targetDirectory: string, hookTempDirectory: string, language:string, project: ProjectInfo) { + private async installFiles(sourceDirectory: string, targetDirectory: string, language:string, project: ProjectInfo) { for (const file of await fs.readdir(sourceDirectory)) { const fromFile = path.join(sourceDirectory, file); const toFile = path.join(targetDirectory, this.expand(file, language, project)); if ((await fs.stat(fromFile)).isDirectory()) { await fs.mkdir(toFile); - await this.installFiles(fromFile, toFile, hookTempDirectory, language, project); + await this.installFiles(fromFile, toFile, language, project); continue; } else if (file.match(/^.*\.template\.[^.]+$/)) { await this.installProcessed(fromFile, toFile.replace(/\.template(\.[^.]+)$/, '$1'), language, project); continue; } else if (file.match(/^.*\.hook\.(d.)?[^.]+$/)) { - await this.installProcessed(fromFile, path.join(hookTempDirectory, file), language, project); + // Ignore continue; } else { await fs.copy(fromFile, toFile);