Skip to content

Commit

Permalink
fix(cli): "EACCES: Permission denied" on 'cdk init'
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rix0rrr committed Sep 19, 2022
1 parent 5c63751 commit 6d26492
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
};
26 changes: 12 additions & 14 deletions packages/aws-cdk/lib/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
Expand Down Expand Up @@ -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[]) => {
Expand All @@ -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);
Expand Down

0 comments on commit 6d26492

Please sign in to comment.