diff --git a/engine.Dockerfile b/engine.Dockerfile index 271581bb9..62d0980aa 100644 --- a/engine.Dockerfile +++ b/engine.Dockerfile @@ -19,12 +19,17 @@ RUN <<-``` fi chmod +x /usr/bin/yq - # Install hcledit (note: arm64 version might not be available, defaulting to amd64) - curl -L https://github.com/minamijoyo/hcledit/releases/download/v0.2.15/hcledit_0.2.15_linux_amd64.tar.gz -o /tmp/hcledit_0.2.15_linux_amd64.tar.gz - tar -C /usr/bin -xf /tmp/hcledit_0.2.15_linux_amd64.tar.gz - chmod +x /usr/bin/hcledit - rm /tmp/hcledit_0.2.15_linux_amd64.tar.gz + # Install hcledit with architecture-specific binary + if [ "$TARGETARCH" = "arm64" ]; then + curl -L https://github.com/minamijoyo/hcledit/releases/download/v0.2.18/hcledit_0.2.18_linux_arm64.tar.gz -o /tmp/hcledit_0.2.18.tar.gz + else + curl -L https://github.com/minamijoyo/hcledit/releases/download/v0.2.18/hcledit_0.2.18_linux_amd64.tar.gz -o /tmp/hcledit_0.2.18.tar.gz + fi + tar -C /usr/bin -xf /tmp/hcledit_0.2.18.tar.gz + chmod +x /usr/bin/hcledit + rm /tmp/hcledit_0.2.18.tar.gz + # Install AWS CLI with architecture-specific package if [ "$TARGETARCH" = "arm64" ]; then curl -L https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip -o awscliv2.zip diff --git a/packages/openops/src/lib/command-wrapper.ts b/packages/openops/src/lib/command-wrapper.ts index b3c29cae3..5f2987314 100644 --- a/packages/openops/src/lib/command-wrapper.ts +++ b/packages/openops/src/lib/command-wrapper.ts @@ -15,6 +15,11 @@ export async function executeCommand( const fullCommand = `${command} ${args.join(' ')}`; logger.debug('Execute command', { command: fullCommand }); + const env: NodeJS.ProcessEnv = {}; + if (process.env['HOME']) { + env['HOME'] = process.env['HOME']; + } + const childProcess = spawn(command, args); return await getResult(childProcess, fullCommand); @@ -25,6 +30,10 @@ export async function executeFile( args: string[], envVariables: any, ): Promise { + if (!envVariables['HOME'] && process.env['HOME']) { + envVariables.HOME = process.env['HOME']; + } + const options: ExecFileOptions = { env: envVariables, }; diff --git a/packages/openops/test/command-wrapper.test.ts b/packages/openops/test/command-wrapper.test.ts index 488fa7377..aaae696ca 100644 --- a/packages/openops/test/command-wrapper.test.ts +++ b/packages/openops/test/command-wrapper.test.ts @@ -19,8 +19,15 @@ jest.mock('node:child_process', () => { import { executeCommand, executeFile } from '../src/lib/command-wrapper'; +const originalEnv = process.env; + describe('Execute Command', () => { + beforeEach(() => { + process.env = { ...originalEnv }; + }); + afterEach(() => { + process.env = originalEnv; jest.clearAllMocks(); }); @@ -91,10 +98,44 @@ describe('Execute Command', () => { ]); }, ); + + it('should handle missing stdout or stderr gracefully', async () => { + mockSpawn.mockImplementation(() => ({ + stdout: null, + stderr: null, + on: jest.fn((event, callback) => { + if (event === 'close') { + callback(0); + } + }), + })); + + const result = await executeCommand('command', []); + + expect(result).toEqual({ + stdOut: '', + stdError: '', + exitCode: 0, + }); + }); + + it('should not add HOME to env if it is not in process.env', async () => { + delete process.env['HOME']; + mockMockSpawn('ok', '', 0); + + await executeCommand('command', []); + + expect(mockSpawn).toHaveBeenCalledWith('command', []); + }); }); describe('Execute File', () => { + beforeEach(() => { + process.env = { ...originalEnv }; + }); + afterEach(() => { + process.env = originalEnv; jest.clearAllMocks(); }); @@ -116,7 +157,11 @@ describe('Execute File', () => { expect(mockExecFile).toHaveBeenCalledWith( 'command', ['parameter 1', 'parameter 2'], - { env: { VAR1: 'var1' } }, + { + env: expect.objectContaining({ + VAR1: 'var1', + }), + }, ); }); @@ -138,7 +183,11 @@ describe('Execute File', () => { expect(mockExecFile).toHaveBeenCalledWith( 'command', ['parameter 1', 'parameter 2'], - { env: { VAR1: 'var1' } }, + { + env: expect.objectContaining({ + VAR1: 'var1', + }), + }, ); }); @@ -167,7 +216,11 @@ describe('Execute File', () => { expect(mockExecFile).toHaveBeenCalledWith( 'command', ['parameter 1', 'parameter 2'], - { env: { VAR1: 'var1' } }, + { + env: expect.objectContaining({ + VAR1: 'var1', + }), + }, ); }, ); @@ -182,7 +235,7 @@ describe('Execute File', () => { 'command', ['--arg'], expect.objectContaining({ - env: { VAR: 'value' }, + env: expect.objectContaining({ VAR: 'value' }), maxBuffer: 5 * 1024 * 1024, }), ); @@ -197,9 +250,34 @@ describe('Execute File', () => { expect(mockExecFile).toHaveBeenCalledWith( 'command', ['--arg'], - expect.not.objectContaining({ maxBuffer: expect.any(Number) }), + expect.not.objectContaining({ maxBuffer: expect.anything() }), ); }); + + it('should use provided HOME if it exists in envVariables', async () => { + mockMockExecFile('ok', '', 0); + + await executeFile('command', [], { HOME: '/custom/home' }); + + expect(mockExecFile).toHaveBeenCalledWith( + 'command', + [], + expect.objectContaining({ + env: { HOME: '/custom/home' }, + }), + ); + }); + + it('should not add HOME to env if it is not in process.env and not provided in envVariables', async () => { + delete process.env['HOME']; + mockMockExecFile('ok', '', 0); + + await executeFile('command', [], { VAR1: 'var1' }); + + expect(mockExecFile).toHaveBeenCalledWith('command', [], { + env: { VAR1: 'var1' }, + }); + }); }); function mockReadableStream(data: string) {