Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapper: Write stdout/stderr data to stream when received #410

Merged
merged 6 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/BUG FIXES-20240425-104731.yaml
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 11 additions & 0 deletions .github/workflows/data/delay/main.tf
Original file line number Diff line number Diff line change
@@ -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]
}
31 changes: 30 additions & 1 deletion .github/workflows/setup-terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ jobs:
id: plan
run: terraform plan


terraform-stdout-wrapper:
name: 'Terraform STDOUT'
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -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
17 changes: 9 additions & 8 deletions dist/index1.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion wrapper/lib/output-listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
austinvalle marked this conversation as resolved.
Show resolved Hide resolved
}
};
return listen.bind(this);
}
Expand Down
10 changes: 3 additions & 7 deletions wrapper/terraform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
21 changes: 20 additions & 1 deletion wrapper/test/output-listener.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Loading