Skip to content

Commit 37d97c4

Browse files
feat(filesystem): add stub for chmod to filesystem wrapper (#5647)
## Problem Changing the permissions of a file is currently not supported by the file system wrapper. ## Solution Implement a stub chmod that does nothing on web, and uses `fs` to modify permissions in non-web environments. --- <!--- REMINDER: Ensure that your PR meets the guidelines in CONTRIBUTING.md --> License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Justin M. Keyes <jmkeyes@amazon.com>
1 parent fde2fe5 commit 37d97c4

File tree

9 files changed

+78
-33
lines changed

9 files changed

+78
-33
lines changed

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { AuthUtil } from '../../codewhisperer'
2424
import { isWeb } from '../../shared/extensionGlobals'
2525
import { getUserAgent } from '../../shared/telemetry/util'
2626
import { isAmazonInternalOs } from '../../shared/vscode/env'
27+
import { fs as fs2 } from '../../shared/fs/fs'
2728

2829
function getProjectPaths() {
2930
const workspaceFolders = vscode.workspace.workspaceFolders
@@ -266,7 +267,7 @@ export class LspController {
266267
if (!downloadNodeOk) {
267268
return false
268269
}
269-
fs.chmodSync(nodeRuntimeTempPath, 0o755)
270+
await fs2.chmod(nodeRuntimeTempPath, 0o755)
270271
fs.moveSync(nodeRuntimeTempPath, nodeRuntimePath)
271272
return true
272273
} catch (e) {

packages/core/src/awsService/ec2/sshKeyPair.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
import { fs } from '../../shared'
6-
import { chmodSync } from 'fs'
76
import { ToolkitError } from '../../shared/errors'
87
import { tryRun } from '../../shared/utilities/pathFind'
98
import { Timeout } from '../../shared/utilities/timeoutUtils'
@@ -39,7 +38,7 @@ export class SshKeyPair {
3938
if (!keyGenerated) {
4039
throw new ToolkitError('ec2: Unable to generate ssh key pair')
4140
}
42-
chmodSync(keyPath, 0o600)
41+
await fs.chmod(keyPath, 0o600)
4342
}
4443
/**
4544
* Attempts to generate an ssh key pair. Returns true if successful, false otherwise.

packages/core/src/shared/fs/fs.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import vscode from 'vscode'
66
import os from 'os'
77
import { promises as nodefs, constants as nodeConstants, WriteFileOptions } from 'fs'
8+
import { chmod } from 'fs/promises'
89
import { isCloud9 } from '../extensionUtilities'
910
import _path from 'path'
1011
import {
@@ -357,6 +358,20 @@ export class FileSystem {
357358
return await vfs.stat(path)
358359
}
359360

361+
/**
362+
* Change permissions on file. Note that this will do nothing on browser.
363+
* @param uri file whose permissions should be set.
364+
* @param mode new permissions in octal notation.
365+
* More info: https://nodejs.org/api/fs.html#fspromiseschmodpath-mode
366+
* Examples: https://www.geeksforgeeks.org/node-js-fspromises-chmod-method/
367+
*/
368+
async chmod(uri: vscode.Uri | string, mode: number): Promise<void> {
369+
if (!this.isWeb) {
370+
const path = toUri(uri)
371+
await chmod(path.fsPath, mode)
372+
}
373+
}
374+
360375
/**
361376
* Deletes a file or directory. It is not an error if the file/directory does not exist, unless
362377
* its parent directory is not listable (executable).

packages/core/src/shared/sam/debugger/csharpSamDebug.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { chmod, ensureDir, writeFile } from 'fs-extra'
6+
import { ensureDir, writeFile } from 'fs-extra'
77
import * as os from 'os'
88
import * as path from 'path'
99
import {
@@ -24,6 +24,7 @@ import { getLogger } from '../../logger'
2424
import * as vscode from 'vscode'
2525
import * as nls from 'vscode-nls'
2626
import globals from '../../extensionGlobals'
27+
import fs from '../../fs/fs'
2728
const localize = nls.loadMessageBundle()
2829

2930
/**
@@ -203,7 +204,7 @@ async function downloadInstallScript(debuggerPath: string): Promise<string> {
203204
}
204205

205206
await writeFile(installScriptPath, installScript, 'utf8')
206-
await chmod(installScriptPath, 0o700)
207+
await fs.chmod(installScriptPath, 0o700)
207208

208209
return installScriptPath
209210
}

packages/core/src/shared/sam/debugger/goSamDebug.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ async function makeInstallScript(debuggerPath: string, isWindows: boolean): Prom
224224
script += `go build -o "${delvePath}" "${delveRepo}/cmd/dlv"\n`
225225

226226
await fs.writeFile(installScriptPath, script, 'utf8')
227-
await fs.chmod(installScriptPath, 0o755)
227+
await fs2.chmod(installScriptPath, 0o755)
228228

229229
return { path: installScriptPath, options: installOptions }
230230
}

packages/core/src/test/awsService/ec2/sshKeyPair.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
*/
55
import * as vscode from 'vscode'
66
import assert from 'assert'
7+
import nodefs from 'fs'
78
import * as sinon from 'sinon'
89
import * as path from 'path'
10+
import * as os from 'os'
911
import { SshKeyPair } from '../../../awsService/ec2/sshKeyPair'
1012
import { createTestWorkspaceFolder, installFakeClock } from '../../testUtil'
1113
import { InstalledClock } from '@sinonjs/fake-timers'
1214
import { ChildProcess } from '../../../shared/utilities/processUtils'
13-
import { fs } from '../../../shared'
15+
import { fs, globals } from '../../../shared'
1416

1517
describe('SshKeyUtility', async function () {
1618
let temporaryDirectory: string
@@ -50,6 +52,13 @@ describe('SshKeyUtility', async function () {
5052
assert.notStrictEqual(beforeContent, afterContent)
5153
})
5254

55+
it('sets permission of the file to read/write owner', async function () {
56+
if (!globals.isWeb && os.platform() !== 'win32') {
57+
const result = nodefs.statSync(keyPair.getPrivateKeyPath())
58+
assert.strictEqual(result.mode & 0o777, 0o600)
59+
}
60+
})
61+
5362
it('defaults to ed25519 key type', async function () {
5463
const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', keyPath])
5564
const result = await process.run()

packages/core/src/test/shared/fs/fs.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import vscode from 'vscode'
88
import * as path from 'path'
99
import * as utils from 'util'
1010
import { existsSync, mkdirSync, promises as nodefs, readFileSync } from 'fs'
11+
import { stat } from 'fs/promises'
1112
import nodeFs from 'fs'
1213
import fs, { FileSystem } from '../../../shared/fs/fs'
1314
import * as os from 'os'
@@ -385,6 +386,23 @@ describe('FileSystem', function () {
385386
})
386387
})
387388

389+
describe('chmod()', async function () {
390+
it('changes permissions when not on web, otherwise does not throw', async function () {
391+
const filePath = await testFolder.write('test.txt', 'hello world', { mode: 0o777 })
392+
await fs.chmod(filePath, 0o644)
393+
// chmod doesn't exist on windows, non-unix permission system.
394+
if (!globals.isWeb && os.platform() !== 'win32') {
395+
const result = await stat(filePath)
396+
assert.strictEqual(result.mode & 0o777, 0o644)
397+
}
398+
})
399+
400+
it('throws if no file exists', async function () {
401+
const filePath = testFolder.pathFrom('thisDoesNotExist.txt')
402+
await assert.rejects(() => fs.chmod(filePath, 0o644))
403+
})
404+
})
405+
388406
describe('rename()', async () => {
389407
it('renames a file', async () => {
390408
const oldPath = await testFolder.write('oldFile.txt', 'hello world')

packages/core/src/test/shared/utilities/processUtils.test.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import assert from 'assert'
77
import * as fs from 'fs-extra'
8+
import { fs as fs2 } from '../../../shared'
89
import * as os from 'os'
910
import * as path from 'path'
1011
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities'
@@ -80,7 +81,7 @@ describe('ChildProcess', async function () {
8081
if (process.platform !== 'win32') {
8182
it('runs and captures stdout - unix', async function () {
8283
const scriptFile = path.join(tempFolder, 'test-script.sh')
83-
writeShellFile(scriptFile)
84+
await writeShellFile(scriptFile)
8485

8586
const childProcess = new ChildProcess(scriptFile)
8687

@@ -89,7 +90,7 @@ describe('ChildProcess', async function () {
8990

9091
it('errs when starting twice - unix', async function () {
9192
const scriptFile = path.join(tempFolder, 'test-script.sh')
92-
writeShellFile(scriptFile)
93+
await writeShellFile(scriptFile)
9394

9495
const childProcess = new ChildProcess(scriptFile)
9596

@@ -120,7 +121,7 @@ describe('ChildProcess', async function () {
120121
writeBatchFile(command)
121122
} else {
122123
command = path.join(subfolder, 'test script.sh')
123-
writeShellFile(command)
124+
await writeShellFile(command)
124125
}
125126

126127
const childProcess = new ChildProcess(command)
@@ -140,14 +141,17 @@ describe('ChildProcess', async function () {
140141
describe('Extra options', function () {
141142
let childProcess: ChildProcess
142143

143-
beforeEach(function () {
144+
beforeEach(async function () {
144145
const isWindows = process.platform === 'win32'
145146
const command = path.join(tempFolder, `test-script.${isWindows ? 'bat' : 'sh'}`)
146147

147148
if (isWindows) {
148149
writeBatchFile(command, ['@echo %1', '@echo %2', '@echo "%3"', 'SLEEP 20', 'exit 1'].join(os.EOL))
149150
} else {
150-
writeShellFile(command, ['echo $1', 'echo $2', 'echo "$3"', 'sleep 20', 'exit 1'].join(os.EOL))
151+
await writeShellFile(
152+
command,
153+
['echo $1', 'echo $2', 'echo "$3"', 'sleep 20', 'exit 1'].join(os.EOL)
154+
)
151155
}
152156

153157
childProcess = new ChildProcess(command, ['1', '2'], { collect: false })
@@ -277,7 +281,7 @@ describe('ChildProcess', async function () {
277281
if (process.platform !== 'win32') {
278282
it('detects running processes and successfully stops a running process - Unix', async function () {
279283
const scriptFile = path.join(tempFolder, 'test-script.sh')
280-
writeShellFileWithDelays(scriptFile)
284+
await writeShellFileWithDelays(scriptFile)
281285

282286
const childProcess = new ChildProcess('sh', [scriptFile])
283287
const result = childProcess.run()
@@ -291,7 +295,7 @@ describe('ChildProcess', async function () {
291295

292296
it('can stop() previously stopped processes - Unix', async function () {
293297
const scriptFile = path.join(tempFolder, 'test-script.sh')
294-
writeShellFileWithDelays(scriptFile)
298+
await writeShellFileWithDelays(scriptFile)
295299

296300
const childProcess = new ChildProcess(scriptFile)
297301

@@ -331,17 +335,17 @@ describe('ChildProcess', async function () {
331335
fs.writeFileSync(filename, `@echo OFF${os.EOL}echo hi`)
332336
}
333337

334-
function writeShellFile(filename: string, contents = 'echo hi'): void {
338+
async function writeShellFile(filename: string, contents = 'echo hi'): Promise<void> {
335339
fs.writeFileSync(filename, `#!/bin/sh\n${contents}`)
336-
fs.chmodSync(filename, 0o744)
340+
await fs2.chmod(filename, 0o744)
337341
}
338342

339-
function writeShellFileWithDelays(filename: string): void {
343+
async function writeShellFileWithDelays(filename: string): Promise<void> {
340344
const file = `
341345
echo hi
342346
sleep 20
343347
echo bye`
344-
writeShellFile(filename, file)
348+
await writeShellFile(filename, file)
345349
}
346350
})
347351

packages/core/src/test/testUtil.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import assert from 'assert'
7-
import * as nodeFsSync from 'fs'
87
import * as path from 'path'
98
import * as vscode from 'vscode'
109
import * as FakeTimers from '@sinonjs/fake-timers'
@@ -14,10 +13,9 @@ import globals from '../shared/extensionGlobals'
1413
import { waitUntil } from '../shared/utilities/timeoutUtils'
1514
import { MetricName, MetricShapes } from '../shared/telemetry/telemetry'
1615
import { keys, selectFrom } from '../shared/utilities/tsUtils'
17-
import fs2 from '../shared/fs/fs'
16+
import fs from '../shared/fs/fs'
1817
import { DeclaredCommand } from '../shared/vscode/commands2'
1918
import { mkdirSync, existsSync } from 'fs'
20-
import * as nodefs from 'fs/promises'
2119
import { randomBytes } from 'crypto'
2220
import request from '../shared/request'
2321
import { stub } from 'sinon'
@@ -33,15 +31,15 @@ export async function toFile(o: any, filepath: string | vscode.Uri) {
3331
const file = typeof filepath === 'string' ? filepath : filepath.fsPath
3432
const text = o === undefined ? '' : o.toString()
3533
const dir = path.dirname(file)
36-
await fs2.mkdir(dir)
37-
await fs2.writeFile(file, text)
34+
await fs.mkdir(dir)
35+
await fs.writeFile(file, text)
3836
}
3937

4038
/**
4139
* Gets the contents of `filepath` as UTF-8 encoded string.
4240
*/
4341
export async function fromFile(filepath: string): Promise<string> {
44-
return fs2.readFileAsString(filepath)
42+
return fs.readFileAsString(filepath)
4543
}
4644

4745
/** Gets the full path to the Toolkit source root on this machine. */
@@ -97,7 +95,7 @@ export class TestFolder {
9795
await toFile(content ?? '', filePath)
9896

9997
if (options?.mode !== undefined) {
100-
await nodefs.chmod(filePath, options.mode)
98+
await fs.chmod(filePath, options.mode)
10199
}
102100

103101
return filePath
@@ -144,7 +142,7 @@ export async function createTestWorkspaceFolder(name?: string, subDir?: string):
144142
testTempDirs.push(tempFolder)
145143
const finalWsFolder = subDir === undefined ? tempFolder : path.join(tempFolder, subDir)
146144
if (subDir !== undefined && subDir.length > 0) {
147-
await nodefs.mkdir(finalWsFolder, { recursive: true })
145+
await fs.mkdir(finalWsFolder)
148146
}
149147
return {
150148
uri: vscode.Uri.file(finalWsFolder),
@@ -157,7 +155,7 @@ export async function createTestFile(fileName: string): Promise<vscode.Uri> {
157155
const tempFolder = await makeTemporaryToolkitFolder()
158156
testTempDirs.push(tempFolder) // ensures this is deleted at the end
159157
const tempFilePath = path.join(tempFolder, fileName)
160-
await fs2.writeFile(tempFilePath, '')
158+
await fs.writeFile(tempFilePath, '')
161159
return vscode.Uri.file(tempFilePath)
162160
}
163161

@@ -205,7 +203,7 @@ export async function createTestWorkspace(
205203

206204
do {
207205
const tempFilePath = path.join(workspace.uri.fsPath, `${fileNamePrefix}${n}${fileNameSuffix}`)
208-
await fs2.writeFile(tempFilePath, fileContent)
206+
await fs.writeFile(tempFilePath, fileContent)
209207
} while (--n > 0)
210208

211209
return workspace
@@ -236,7 +234,7 @@ export function assertEqualPaths(actual: string, expected: string, message?: str
236234
* Asserts that UTF-8 contents of `file` are equal to `expected`.
237235
*/
238236
export async function assertFileText(file: string, expected: string, message?: string | Error) {
239-
const actualContents = await fs2.readFileAsString(file)
237+
const actualContents = await fs.readFileAsString(file)
240238
assert.strictEqual(actualContents, expected, message)
241239
}
242240

@@ -250,12 +248,12 @@ export async function tickPromise<T>(promise: Promise<T>, clock: FakeTimers.Inst
250248
* Creates an executable file (including any parent directories) with the given contents.
251249
*/
252250
export async function createExecutableFile(filepath: string, contents: string): Promise<void> {
253-
await fs2.mkdir(path.dirname(filepath))
251+
await fs.mkdir(path.dirname(filepath))
254252
if (process.platform === 'win32') {
255-
await fs2.writeFile(filepath, `@echo OFF$\r\n${contents}\r\n`)
253+
await fs.writeFile(filepath, `@echo OFF$\r\n${contents}\r\n`)
256254
} else {
257-
await fs2.writeFile(filepath, `#!/bin/sh\n${contents}`)
258-
nodeFsSync.chmodSync(filepath, 0o744)
255+
await fs.writeFile(filepath, `#!/bin/sh\n${contents}`)
256+
await fs.chmod(filepath, 0o744)
259257
}
260258
}
261259

0 commit comments

Comments
 (0)