diff --git a/.changes/unreleased/BUG FIXES-20240425-104731.yaml b/.changes/unreleased/BUG FIXES-20240425-104731.yaml new file mode 100644 index 00000000..26f20704 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20240425-104731.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'wrapper: Fix wrapper to output to stdout and stderr immediately when data is + received' +time: 2024-04-25T10:47:31.19951-04:00 +custom: + Issue: "395" diff --git a/.github/workflows/data/delay/main.tf b/.github/workflows/data/delay/main.tf new file mode 100644 index 00000000..cbf7efbc --- /dev/null +++ b/.github/workflows/data/delay/main.tf @@ -0,0 +1,11 @@ +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + create_duration = "30s" +} + +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} diff --git a/.github/workflows/setup-terraform.yml b/.github/workflows/setup-terraform.yml index b8a3a3b1..e86fe213 100644 --- a/.github/workflows/setup-terraform.yml +++ b/.github/workflows/setup-terraform.yml @@ -304,7 +304,6 @@ jobs: id: plan run: terraform plan - terraform-stdout-wrapper: name: 'Terraform STDOUT' runs-on: ${{ matrix.os }} @@ -370,3 +369,33 @@ jobs: - name: Terraform Output to JQ id: output run: terraform output -json | jq '.pet.value' + + # This test has an artificial delay for testing the streaming of STDOUT + terraform-wrapper-delayed-apply: + name: 'Terraform Delayed Apply' + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + defaults: + run: + shell: bash + working-directory: ./.github/workflows/data/delay + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Terraform + uses: ./ + with: + terraform_wrapper: true + + - name: Terraform Init + run: terraform init + + - name: Terraform Format + run: terraform fmt -check + + - name: Terraform Apply + id: apply + run: terraform apply -auto-approve diff --git a/dist/index1.js b/dist/index1.js index 8f0f9fc6..c6f4bdd3 100755 --- a/dist/index1.js +++ b/dist/index1.js @@ -27599,13 +27599,18 @@ module.exports = { * console.log(listener.contents); */ class OutputListener { - constructor () { + constructor (streamWriter) { this._buff = []; + this._streamWriter = streamWriter; } get listener () { const listen = function listen (data) { this._buff.push(data); + + if (this._streamWriter) { + this._streamWriter.write(data); + } }; return listen.bind(this); } @@ -27946,9 +27951,9 @@ async function checkTerraform () { // This will fail if Terraform isn't found, which is what we want await checkTerraform(); - // Create listeners to receive output (in memory) as well - const stdout = new OutputListener(); - const stderr = new OutputListener(); + // Create listeners to receive output (in memory) + const stdout = new OutputListener(process.stdout); + const stderr = new OutputListener(process.stderr); const listeners = { stdout: stdout.listener, stderr: stderr.listener @@ -27963,10 +27968,6 @@ async function checkTerraform () { }; const exitCode = await exec(pathToCLI, args, options); - // Pass-through stdout/err as `exec` won't due to `silent: true` option - process.stdout.write(stdout.contents); - process.stderr.write(stderr.contents); - // Set outputs, result, exitcode, and stderr core.setOutput('stdout', stdout.contents); core.setOutput('stderr', stderr.contents); diff --git a/wrapper/lib/output-listener.js b/wrapper/lib/output-listener.js index 9686dbae..8323a5cb 100644 --- a/wrapper/lib/output-listener.js +++ b/wrapper/lib/output-listener.js @@ -20,13 +20,18 @@ * console.log(listener.contents); */ class OutputListener { - constructor () { + constructor (streamWriter) { this._buff = []; + this._streamWriter = streamWriter; } get listener () { const listen = function listen (data) { this._buff.push(data); + + if (this._streamWriter) { + this._streamWriter.write(data); + } }; return listen.bind(this); } diff --git a/wrapper/terraform.js b/wrapper/terraform.js index 20fec389..7350dc45 100755 --- a/wrapper/terraform.js +++ b/wrapper/terraform.js @@ -21,9 +21,9 @@ async function checkTerraform () { // This will fail if Terraform isn't found, which is what we want await checkTerraform(); - // Create listeners to receive output (in memory) as well - const stdout = new OutputListener(); - const stderr = new OutputListener(); + // Create listeners to receive output (in memory) + const stdout = new OutputListener(process.stdout); + const stderr = new OutputListener(process.stderr); const listeners = { stdout: stdout.listener, stderr: stderr.listener @@ -38,10 +38,6 @@ async function checkTerraform () { }; const exitCode = await exec(pathToCLI, args, options); - // Pass-through stdout/err as `exec` won't due to `silent: true` option - process.stdout.write(stdout.contents); - process.stderr.write(stderr.contents); - // Set outputs, result, exitcode, and stderr core.setOutput('stdout', stdout.contents); core.setOutput('stderr', stderr.contents); diff --git a/wrapper/test/output-listener.test.js b/wrapper/test/output-listener.test.js index 229c3cf0..daed9539 100644 --- a/wrapper/test/output-listener.test.js +++ b/wrapper/test/output-listener.test.js @@ -6,12 +6,31 @@ const OutputListener = require('../lib/output-listener'); describe('output-listener', () => { - it('receives and exposes data', () => { + it('receives and buffers data to .contents', () => { const listener = new OutputListener(); const listen = listener.listener; + listen(Buffer.from('foo')); listen(Buffer.from('bar')); listen(Buffer.from('baz')); + expect(listener.contents).toEqual('foobarbaz'); }); + + it('receives and writes data to stream immediately', () => { + const mockWrite = jest.fn(); + const listener = new OutputListener({ write: mockWrite }); + const listen = listener.listener; + + listen(Buffer.from('first write')); + expect(mockWrite.mock.lastCall[0]).toStrictEqual(Buffer.from('first write')); + + listen(Buffer.from('second write')); + expect(mockWrite.mock.lastCall[0]).toStrictEqual(Buffer.from('second write')); + + listen(Buffer.from('third write')); + expect(mockWrite.mock.lastCall[0]).toStrictEqual(Buffer.from('third write')); + + expect(mockWrite).toBeCalledTimes(3); + }); });