Skip to content

Commit fe7d550

Browse files
committed
fix: adding some improvements to isAmazonLinux2 check
1 parent 0982bce commit fe7d550

File tree

2 files changed

+312
-33
lines changed

2 files changed

+312
-33
lines changed

packages/core/src/shared/vscode/env.ts

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,34 @@ export function hasSageMakerEnvVars(): boolean {
149149
* This function detects if we're actually running on AL2, not just if the host is AL2.
150150
* In containerized environments, we check the container's OS, not the host's.
151151
*
152-
* Example: `5.10.220-188.869.amzn2int.x86_64` or `5.10.236-227.928.amzn2.x86_64` (Cloud Dev Machine)
152+
* Detection Process (in order):
153+
* 1. Returns false for web environments (browser-based)
154+
* 2. Returns false for SageMaker environments (even if host is AL2)
155+
* 3. Checks `/etc/image-id` for Amazon Linux specific identification
156+
* - Most reliable method for Amazon Linux systems
157+
* - Contains `image_name="Amazon Linux 2"` for AL2
158+
* - Returns false for Amazon Linux 2023 or other versions
159+
* 4. Checks `/etc/os-release` with fallback to `/usr/lib/os-release`
160+
* - Standard Linux OS identification files
161+
* - Explicitly checks for and rejects Amazon Linux 2023
162+
* - Looks for `ID="amzn"` and `VERSION_ID="2"` for AL2
163+
* 5. Falls back to kernel version check as last resort
164+
* - Checks for `.amzn2.` or `.amzn2int.` in kernel release
165+
* - Only used if file-based detection fails or confirms AL2
166+
*
167+
* This approach ensures correct detection in:
168+
* - Containerized environments (detects container OS, not host)
169+
* - Web/browser environments (returns false)
170+
* - Amazon Linux 2023 systems (properly distinguished from AL2)
171+
* - SageMaker environments (returns false)
172+
*
173+
* References:
174+
* - https://docs.aws.amazon.com/linux/al2/ug/ident-amazon-linux-specific.html
175+
* - https://docs.aws.amazon.com/linux/al2/ug/ident-os-release.html
176+
*
177+
* Example kernel versions:
178+
* - `5.10.220-188.869.amzn2int.x86_64` (internal AL2)
179+
* - `5.10.236-227.928.amzn2.x86_64` (Cloud Dev Machine)
153180
*/
154181
export function isAmazonLinux2() {
155182
// Skip AL2 detection for web environments
@@ -164,44 +191,81 @@ export function isAmazonLinux2() {
164191
return false
165192
}
166193

194+
// Only proceed with file checks on Linux platforms
195+
if (process.platform !== 'linux') {
196+
return false
197+
}
198+
167199
// For containerized environments, check the actual container OS
168200
// not the host kernel version
169201
try {
170202
const fs = require('fs')
171-
if (fs.existsSync('/etc/os-release')) {
172-
const osRelease = fs.readFileSync('/etc/os-release', 'utf8')
173203

174-
// Check if this is Amazon Linux 2023 (not AL2)
175-
if (osRelease.includes('VERSION_ID="2023"') || osRelease.includes('PLATFORM_ID="platform:al2023"')) {
176-
// This is Amazon Linux 2023, not AL2
177-
return false
204+
// First, try Amazon Linux specific file /etc/image-id. This is the most reliable way to identify Amazon Linux
205+
if (fs.existsSync('/etc/image-id')) {
206+
try {
207+
const imageId = fs.readFileSync('/etc/image-id', 'utf8')
208+
// Check if this is Amazon Linux 2 (not 2023 or other versions)
209+
// Example content: image_name="Amazon Linux 2"
210+
if (imageId.includes('image_name="Amazon Linux 2"')) {
211+
return true
212+
}
213+
// If it's Amazon Linux but not version 2, return false
214+
if (imageId.includes('image_name="Amazon Linux')) {
215+
return false
216+
}
217+
} catch (e) {
218+
// Continue to other checks if we can't read the file
219+
getLogger().error(`Checking for Amazon Linux specific file /etc/image-id failed with error: ${e}`)
178220
}
221+
}
179222

180-
// Check if this is actually Amazon Linux 2
181-
// Must be specifically version 2, not 2023 or other versions
182-
const isAL2 =
183-
osRelease.includes('Amazon Linux 2') ||
184-
(osRelease.includes('ID="amzn"') && osRelease.includes('VERSION_ID="2"'))
185-
186-
// If we found os-release file, trust its content over kernel version
187-
if (!isAL2) {
188-
// Explicitly not AL2 based on os-release
189-
return false
223+
// Check /etc/os-release with fallback to /usr/lib/os-release as per https://docs.aws.amazon.com/linux/al2/ug/ident-os-release.html
224+
const osReleasePaths = ['/etc/os-release', '/usr/lib/os-release']
225+
for (const osReleasePath of osReleasePaths) {
226+
if (fs.existsSync(osReleasePath)) {
227+
try {
228+
const osRelease = fs.readFileSync(osReleasePath, 'utf8')
229+
230+
// Check if this is Amazon Linux 2023 (not AL2)
231+
if (
232+
osRelease.includes('VERSION_ID="2023"') ||
233+
osRelease.includes('PLATFORM_ID="platform:al2023"')
234+
) {
235+
// This is Amazon Linux 2023, not AL2
236+
return false
237+
}
238+
239+
// Check if this is actually Amazon Linux 2
240+
// Must be specifically version 2, not 2023 or other versions
241+
const isAL2 =
242+
osRelease.includes('Amazon Linux 2') ||
243+
(osRelease.includes('ID="amzn"') && osRelease.includes('VERSION_ID="2"'))
244+
245+
// If we found os-release file, trust its content over kernel version
246+
if (!isAL2) {
247+
// Explicitly not AL2 based on os-release
248+
return false
249+
}
250+
// If it is AL2 according to os-release, continue to kernel check for confirmation
251+
break // Found and processed os-release, no need to check fallback
252+
} catch (e) {
253+
// Continue to next path or fallback check
254+
getLogger().error(`Checking for Amazon Linux 2023 (not AL2) failed with error: ${e}`)
255+
}
190256
}
191-
// If it is AL2 according to os-release, continue to kernel check for confirmation
192257
}
193258
} catch (e) {
194-
// If we can't read the file, fall back to the os.release() check
259+
// If we can't read the files, fall back to the os.release() check
195260
// This might happen in some restricted environments
196261
getLogger().error(`Checking the current environment failed with error: ${e}`)
197262
}
198263

199264
// Check kernel version as a fallback or confirmation
200-
// This should only be trusted if we couldn't determine from /etc/os-release
201-
// or if /etc/os-release confirmed it's AL2
265+
// This should only be trusted if we couldn't determine from files above
266+
// or if files confirmed it's AL2
202267
const kernelRelease = os.release()
203-
const hasAL2Kernel =
204-
(kernelRelease.includes('.amzn2int.') || kernelRelease.includes('.amzn2.')) && process.platform === 'linux'
268+
const hasAL2Kernel = kernelRelease.includes('.amzn2int.') || kernelRelease.includes('.amzn2.')
205269

206270
return hasAL2Kernel
207271
}

packages/core/src/test/shared/vscode/env.test.ts

Lines changed: 225 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55

66
import assert from 'assert'
77
import path from 'path'
8-
import { isCloudDesktop, getEnvVars, getServiceEnvVarConfig, isAmazonLinux2, isBeta } from '../../../shared/vscode/env'
8+
import {
9+
isCloudDesktop,
10+
getEnvVars,
11+
getServiceEnvVarConfig,
12+
isAmazonLinux2,
13+
isBeta,
14+
hasSageMakerEnvVars,
15+
} from '../../../shared/vscode/env'
916
import { ChildProcess } from '../../../shared/utilities/processUtils'
1017
import * as sinon from 'sinon'
1118
import os from 'os'
1219
import fs from '../../../shared/fs/fs'
1320
import vscode from 'vscode'
1421
import { getComputeEnvType } from '../../../shared/telemetry/util'
22+
import * as globals from '../../../shared/extensionGlobals'
1523

1624
describe('env', function () {
1725
// create a sinon sandbox instance and instantiate in a beforeEach
@@ -97,18 +105,225 @@ describe('env', function () {
97105
assert.strictEqual(isBeta(), expected)
98106
})
99107

100-
it('isAmazonLinux2', function () {
101-
sandbox.stub(process, 'platform').value('linux')
102-
const versionStub = stubOsVersion('5.10.220-188.869.amzn2int.x86_64')
108+
describe('isAmazonLinux2', function () {
109+
let fsExistsStub: sinon.SinonStub
110+
let fsReadFileStub: sinon.SinonStub
111+
let isWebStub: sinon.SinonStub
112+
let platformStub: sinon.SinonStub
113+
let osReleaseStub: sinon.SinonStub
114+
let moduleLoadStub: sinon.SinonStub
115+
116+
beforeEach(function () {
117+
// Default stubs
118+
platformStub = sandbox.stub(process, 'platform').value('linux')
119+
osReleaseStub = stubOsVersion('5.10.220-188.869.amzn2int.x86_64')
120+
isWebStub = sandbox.stub(globals, 'isWeb').returns(false)
121+
122+
// Mock fs module
123+
const fsMock = {
124+
existsSync: sandbox.stub().returns(false),
125+
readFileSync: sandbox.stub().returns(''),
126+
}
127+
fsExistsStub = fsMock.existsSync
128+
fsReadFileStub = fsMock.readFileSync
129+
130+
// Stub Module._load to intercept require calls
131+
const Module = require('module')
132+
moduleLoadStub = sandbox.stub(Module, '_load').callThrough()
133+
moduleLoadStub.withArgs('fs').returns(fsMock)
134+
})
135+
136+
it('returns false in web environment', function () {
137+
isWebStub.returns(true)
138+
assert.strictEqual(isAmazonLinux2(), false)
139+
})
140+
141+
it('returns false in SageMaker environment with SAGEMAKER_APP_TYPE', function () {
142+
sandbox.stub(process.env, 'SAGEMAKER_APP_TYPE').value('JupyterLab')
143+
assert.strictEqual(isAmazonLinux2(), false)
144+
})
145+
146+
it('returns false in SageMaker environment with SM_APP_TYPE', function () {
147+
sandbox.stub(process.env, 'SM_APP_TYPE').value('JupyterLab')
148+
assert.strictEqual(isAmazonLinux2(), false)
149+
})
150+
151+
it('returns false in SageMaker environment with SERVICE_NAME', function () {
152+
sandbox.stub(process.env, 'SERVICE_NAME').value('SageMakerUnifiedStudio')
153+
assert.strictEqual(isAmazonLinux2(), false)
154+
})
155+
156+
it('returns false when /etc/os-release indicates Ubuntu in container', function () {
157+
fsExistsStub.returns(true)
158+
fsReadFileStub.returns(`
159+
NAME="Ubuntu"
160+
VERSION="20.04.6 LTS (Focal Fossa)"
161+
ID=ubuntu
162+
ID_LIKE=debian
163+
PRETTY_NAME="Ubuntu 20.04.6 LTS"
164+
VERSION_ID="20.04"
165+
`)
166+
167+
// Even with AL2 kernel (host is AL2), should return false (container is Ubuntu)
168+
assert.strictEqual(isAmazonLinux2(), false)
169+
})
170+
171+
it('returns false when /etc/os-release indicates Amazon Linux 2023', function () {
172+
fsExistsStub.returns(true)
173+
fsReadFileStub.returns(`
174+
NAME="Amazon Linux"
175+
VERSION="2023"
176+
ID="amzn"
177+
ID_LIKE="fedora"
178+
VERSION_ID="2023"
179+
PLATFORM_ID="platform:al2023"
180+
PRETTY_NAME="Amazon Linux 2023"
181+
`)
182+
183+
assert.strictEqual(isAmazonLinux2(), false)
184+
})
185+
186+
it('returns true when /etc/os-release indicates Amazon Linux 2', function () {
187+
fsExistsStub.returns(true)
188+
fsReadFileStub.returns(`
189+
NAME="Amazon Linux 2"
190+
VERSION="2"
191+
ID="amzn"
192+
ID_LIKE="centos rhel fedora"
193+
VERSION_ID="2"
194+
PRETTY_NAME="Amazon Linux 2"
195+
`)
196+
197+
assert.strictEqual(isAmazonLinux2(), true)
198+
})
199+
200+
it('returns true when /etc/os-release has ID="amzn" and VERSION_ID="2"', function () {
201+
fsExistsStub.returns(true)
202+
fsReadFileStub.returns(`
203+
NAME="Amazon Linux"
204+
VERSION="2"
205+
ID="amzn"
206+
VERSION_ID="2"
207+
`)
208+
209+
assert.strictEqual(isAmazonLinux2(), true)
210+
})
211+
212+
it('returns false when /etc/os-release indicates CentOS', function () {
213+
fsExistsStub.returns(true)
214+
fsReadFileStub.returns(`
215+
NAME="CentOS Linux"
216+
VERSION="7 (Core)"
217+
ID="centos"
218+
ID_LIKE="rhel fedora"
219+
VERSION_ID="7"
220+
`)
221+
222+
// Even with AL2 kernel
223+
assert.strictEqual(isAmazonLinux2(), false)
224+
})
225+
226+
it('falls back to kernel check when /etc/os-release does not exist', function () {
227+
fsExistsStub.returns(false)
103228

104-
// Test with actual AL2 kernel
105-
assert.strictEqual(isAmazonLinux2(), true)
229+
// Test with AL2 kernel
230+
assert.strictEqual(isAmazonLinux2(), true)
106231

107-
versionStub.returns('5.10.236-227.928.amzn2.x86_64')
108-
assert.strictEqual(isAmazonLinux2(), true)
232+
// Test with non-AL2 kernel
233+
osReleaseStub.returns('5.10.220-188.869.NOT_INTERNAL.x86_64')
234+
assert.strictEqual(isAmazonLinux2(), false)
235+
})
236+
237+
it('falls back to kernel check when /etc/os-release read fails', function () {
238+
fsExistsStub.returns(true)
239+
fsReadFileStub.throws(new Error('Permission denied'))
240+
241+
// Should fall back to kernel check
242+
assert.strictEqual(isAmazonLinux2(), true)
243+
})
244+
245+
it('returns true with .amzn2. kernel pattern', function () {
246+
fsExistsStub.returns(false)
247+
osReleaseStub.returns('5.10.236-227.928.amzn2.x86_64')
248+
assert.strictEqual(isAmazonLinux2(), true)
249+
})
250+
251+
it('returns true with .amzn2int. kernel pattern', function () {
252+
fsExistsStub.returns(false)
253+
osReleaseStub.returns('5.10.220-188.869.amzn2int.x86_64')
254+
assert.strictEqual(isAmazonLinux2(), true)
255+
})
256+
257+
it('returns false with non-AL2 kernel', function () {
258+
fsExistsStub.returns(false)
259+
osReleaseStub.returns('5.15.0-91-generic')
260+
assert.strictEqual(isAmazonLinux2(), false)
261+
})
262+
263+
it('returns false on non-Linux platforms', function () {
264+
platformStub.value('darwin')
265+
fsExistsStub.returns(false)
266+
assert.strictEqual(isAmazonLinux2(), false)
267+
268+
platformStub.value('win32')
269+
assert.strictEqual(isAmazonLinux2(), false)
270+
})
271+
272+
it('returns false when container OS is different from host OS', function () {
273+
// Scenario: Host is AL2 (kernel shows AL2) but container is Ubuntu
274+
fsExistsStub.returns(true)
275+
fsReadFileStub.returns(`
276+
NAME="Ubuntu"
277+
VERSION="22.04"
278+
ID=ubuntu
279+
VERSION_ID="22.04"
280+
`)
281+
osReleaseStub.returns('5.10.220-188.869.amzn2int.x86_64') // AL2 kernel from host
282+
283+
// Should trust container OS over kernel
284+
assert.strictEqual(isAmazonLinux2(), false)
285+
})
286+
})
287+
288+
describe('hasSageMakerEnvVars', function () {
289+
afterEach(function () {
290+
// Clean up environment variables
291+
delete process.env.SAGEMAKER_APP_TYPE
292+
delete process.env.SAGEMAKER_INTERNAL_IMAGE_URI
293+
delete process.env.STUDIO_LOGGING_DIR
294+
delete process.env.SM_APP_TYPE
295+
delete process.env.SM_INTERNAL_IMAGE_URI
296+
delete process.env.SERVICE_NAME
297+
})
298+
299+
it('returns true when SAGEMAKER_APP_TYPE is set', function () {
300+
process.env.SAGEMAKER_APP_TYPE = 'JupyterLab'
301+
assert.strictEqual(hasSageMakerEnvVars(), true)
302+
})
303+
304+
it('returns true when SM_APP_TYPE is set', function () {
305+
process.env.SM_APP_TYPE = 'JupyterLab'
306+
assert.strictEqual(hasSageMakerEnvVars(), true)
307+
})
109308

110-
versionStub.returns('5.10.220-188.869.NOT_INTERNAL.x86_64')
111-
assert.strictEqual(isAmazonLinux2(), false)
309+
it('returns true when SERVICE_NAME is SageMakerUnifiedStudio', function () {
310+
process.env.SERVICE_NAME = 'SageMakerUnifiedStudio'
311+
assert.strictEqual(hasSageMakerEnvVars(), true)
312+
})
313+
314+
it('returns true when STUDIO_LOGGING_DIR contains /var/log/studio', function () {
315+
process.env.STUDIO_LOGGING_DIR = '/var/log/studio/logs'
316+
assert.strictEqual(hasSageMakerEnvVars(), true)
317+
})
318+
319+
it('returns false when no SageMaker env vars are set', function () {
320+
assert.strictEqual(hasSageMakerEnvVars(), false)
321+
})
322+
323+
it('returns false when SERVICE_NAME is set but not SageMakerUnifiedStudio', function () {
324+
process.env.SERVICE_NAME = 'SomeOtherService'
325+
assert.strictEqual(hasSageMakerEnvVars(), false)
326+
})
112327
})
113328

114329
it('isCloudDesktop', async function () {

0 commit comments

Comments
 (0)