Skip to content

Commit 9a70f75

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

File tree

2 files changed

+262
-32
lines changed

2 files changed

+262
-32
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: 175 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import os from 'os'
1212
import fs from '../../../shared/fs/fs'
1313
import vscode from 'vscode'
1414
import { getComputeEnvType } from '../../../shared/telemetry/util'
15+
import * as globals from '../../../shared/extensionGlobals'
1516

1617
describe('env', function () {
1718
// create a sinon sandbox instance and instantiate in a beforeEach
@@ -97,18 +98,183 @@ describe('env', function () {
9798
assert.strictEqual(isBeta(), expected)
9899
})
99100

100-
it('isAmazonLinux2', function () {
101-
sandbox.stub(process, 'platform').value('linux')
102-
const versionStub = stubOsVersion('5.10.220-188.869.amzn2int.x86_64')
101+
describe('isAmazonLinux2', function () {
102+
let fsExistsStub: sinon.SinonStub
103+
let fsReadFileStub: sinon.SinonStub
104+
let isWebStub: sinon.SinonStub
105+
let platformStub: sinon.SinonStub
106+
let osReleaseStub: sinon.SinonStub
107+
let requireStub: sinon.SinonStub
108+
109+
beforeEach(function () {
110+
// Default stubs
111+
platformStub = sandbox.stub(process, 'platform').value('linux')
112+
osReleaseStub = stubOsVersion('5.10.220-188.869.amzn2int.x86_64')
113+
isWebStub = sandbox.stub(globals, 'isWeb').returns(false)
114+
115+
// Mock fs module
116+
const fsMock = {
117+
existsSync: sandbox.stub().returns(false),
118+
readFileSync: sandbox.stub().returns(''),
119+
}
120+
fsExistsStub = fsMock.existsSync
121+
fsReadFileStub = fsMock.readFileSync
122+
123+
// Stub the global require function to return our mocked fs
124+
requireStub = sandbox.stub(global, 'require').callThrough()
125+
requireStub.withArgs('fs').returns(fsMock)
126+
})
103127

104-
// Test with actual AL2 kernel
105-
assert.strictEqual(isAmazonLinux2(), true)
128+
it('returns false in web environment', function () {
129+
isWebStub.returns(true)
130+
assert.strictEqual(isAmazonLinux2(), false)
131+
})
132+
133+
it('returns false in SageMaker environment with SAGEMAKER_APP_TYPE', function () {
134+
sandbox.stub(process.env, 'SAGEMAKER_APP_TYPE').value('JupyterLab')
135+
assert.strictEqual(isAmazonLinux2(), false)
136+
})
106137

107-
versionStub.returns('5.10.236-227.928.amzn2.x86_64')
108-
assert.strictEqual(isAmazonLinux2(), true)
138+
it('returns false in SageMaker environment with SM_APP_TYPE', function () {
139+
sandbox.stub(process.env, 'SM_APP_TYPE').value('JupyterLab')
140+
assert.strictEqual(isAmazonLinux2(), false)
141+
})
142+
143+
it('returns false in SageMaker environment with SERVICE_NAME', function () {
144+
sandbox.stub(process.env, 'SERVICE_NAME').value('SageMakerUnifiedStudio')
145+
assert.strictEqual(isAmazonLinux2(), false)
146+
})
109147

110-
versionStub.returns('5.10.220-188.869.NOT_INTERNAL.x86_64')
111-
assert.strictEqual(isAmazonLinux2(), false)
148+
it('returns false when /etc/os-release indicates Ubuntu in container', function () {
149+
fsExistsStub.returns(true)
150+
fsReadFileStub.returns(`
151+
NAME="Ubuntu"
152+
VERSION="20.04.6 LTS (Focal Fossa)"
153+
ID=ubuntu
154+
ID_LIKE=debian
155+
PRETTY_NAME="Ubuntu 20.04.6 LTS"
156+
VERSION_ID="20.04"
157+
`)
158+
159+
// Even with AL2 kernel (host is AL2), should return false (container is Ubuntu)
160+
assert.strictEqual(isAmazonLinux2(), false)
161+
})
162+
163+
it('returns false when /etc/os-release indicates Amazon Linux 2023', function () {
164+
fsExistsStub.returns(true)
165+
fsReadFileStub.returns(`
166+
NAME="Amazon Linux"
167+
VERSION="2023"
168+
ID="amzn"
169+
ID_LIKE="fedora"
170+
VERSION_ID="2023"
171+
PLATFORM_ID="platform:al2023"
172+
PRETTY_NAME="Amazon Linux 2023"
173+
`)
174+
175+
assert.strictEqual(isAmazonLinux2(), false)
176+
})
177+
178+
it('returns true when /etc/os-release indicates Amazon Linux 2', function () {
179+
fsExistsStub.returns(true)
180+
fsReadFileStub.returns(`
181+
NAME="Amazon Linux 2"
182+
VERSION="2"
183+
ID="amzn"
184+
ID_LIKE="centos rhel fedora"
185+
VERSION_ID="2"
186+
PRETTY_NAME="Amazon Linux 2"
187+
`)
188+
189+
assert.strictEqual(isAmazonLinux2(), true)
190+
})
191+
192+
it('returns true when /etc/os-release has ID="amzn" and VERSION_ID="2"', function () {
193+
fsExistsStub.returns(true)
194+
fsReadFileStub.returns(`
195+
NAME="Amazon Linux"
196+
VERSION="2"
197+
ID="amzn"
198+
VERSION_ID="2"
199+
`)
200+
201+
assert.strictEqual(isAmazonLinux2(), true)
202+
})
203+
204+
it('returns false when /etc/os-release indicates CentOS', function () {
205+
fsExistsStub.returns(true)
206+
fsReadFileStub.returns(`
207+
NAME="CentOS Linux"
208+
VERSION="7 (Core)"
209+
ID="centos"
210+
ID_LIKE="rhel fedora"
211+
VERSION_ID="7"
212+
`)
213+
214+
// Even with AL2 kernel
215+
assert.strictEqual(isAmazonLinux2(), false)
216+
})
217+
218+
it('falls back to kernel check when /etc/os-release does not exist', function () {
219+
fsExistsStub.returns(false)
220+
221+
// Test with AL2 kernel
222+
assert.strictEqual(isAmazonLinux2(), true)
223+
224+
// Test with non-AL2 kernel
225+
osReleaseStub.returns('5.10.220-188.869.NOT_INTERNAL.x86_64')
226+
assert.strictEqual(isAmazonLinux2(), false)
227+
})
228+
229+
it('falls back to kernel check when /etc/os-release read fails', function () {
230+
fsExistsStub.returns(true)
231+
fsReadFileStub.throws(new Error('Permission denied'))
232+
233+
// Should fall back to kernel check
234+
assert.strictEqual(isAmazonLinux2(), true)
235+
})
236+
237+
it('returns true with .amzn2. kernel pattern', function () {
238+
fsExistsStub.returns(false)
239+
osReleaseStub.returns('5.10.236-227.928.amzn2.x86_64')
240+
assert.strictEqual(isAmazonLinux2(), true)
241+
})
242+
243+
it('returns true with .amzn2int. kernel pattern', function () {
244+
fsExistsStub.returns(false)
245+
osReleaseStub.returns('5.10.220-188.869.amzn2int.x86_64')
246+
assert.strictEqual(isAmazonLinux2(), true)
247+
})
248+
249+
it('returns false with non-AL2 kernel', function () {
250+
fsExistsStub.returns(false)
251+
osReleaseStub.returns('5.15.0-91-generic')
252+
assert.strictEqual(isAmazonLinux2(), false)
253+
})
254+
255+
it('returns false on non-Linux platforms', function () {
256+
platformStub.value('darwin')
257+
fsExistsStub.returns(false)
258+
assert.strictEqual(isAmazonLinux2(), false)
259+
260+
platformStub.value('win32')
261+
assert.strictEqual(isAmazonLinux2(), false)
262+
})
263+
264+
it('returns false when container OS is different from host OS', function () {
265+
// Scenario: Host is AL2 (kernel shows AL2) but container is Ubuntu
266+
fsExistsStub.returns(true)
267+
fsReadFileStub.returns(`
268+
NAME="Ubuntu"
269+
VERSION="22.04"
270+
ID=ubuntu
271+
VERSION_ID="22.04"
272+
`)
273+
osReleaseStub.returns('5.10.220-188.869.amzn2int.x86_64') // AL2 kernel from host
274+
275+
// Should trust container OS over kernel
276+
assert.strictEqual(isAmazonLinux2(), false)
277+
})
112278
})
113279

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

0 commit comments

Comments
 (0)