feat: ported action definition from setup-terraform

Signed-off-by: Dmitry Kisler <admin@dkisler.com>
This commit is contained in:
Dmitry Kisler 2023-10-05 01:14:33 +02:00
parent c37e0c575a
commit 01bef202d2
No known key found for this signature in database
GPG key ID: 46C0A987D58548F6
19 changed files with 18728 additions and 3 deletions

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

View file

@ -0,0 +1,15 @@
name: 'Continuous Integration'
on:
push:
branches:
- main
pull_request:
jobs:
check-dist:
name: Check dist/ directory
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@a8533f184b279cfc1b2dd6a96ed2f097ccf81189
test:
name: Test
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@a8533f184b279cfc1b2dd6a96ed2f097ccf81189

5
.github/workflows/data/local/main.tf vendored Normal file
View file

@ -0,0 +1,5 @@
resource "null_resource" "null" {
triggers = {
value = timestamp()
}
}

316
.github/workflows/setup-terraform.yml vendored Normal file
View file

@ -0,0 +1,316 @@
name: 'Setup OpenTofu'
on:
push:
branches:
- main
pull_request:
# TODO: remove after manual tests
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
tofu-versions:
name: 'OpenTofu Versions'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# TODO: add move versions to test when the feature is added
tofu-versions: [latest]
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu - ${{ matrix['tofu-versions'] }}
uses: ./
with:
tofu_version: ${{ matrix['tofu-versions'] }}
- name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
if: ${{ matrix['tofu-versions'] != 'latest' }}
run: tofu version | grep ${{ matrix['tofu-versions']}}
- name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
if: ${{ matrix['tofu-versions'] == 'latest' }}
run: tofu version | grep 'OpenTofu v'
tofu-versions-no-wrapper:
name: 'OpenTofu Versions No Wrapper'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# TODO: add more versions to test when the semver feature is implemented
tofu-versions: [latest]
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu (no wrapper) - ${{ matrix['tofu-versions'] }}
uses: ./
with:
tofu_version: ${{ matrix['tofu-versions'] }}
tofu_wrapper: false
- name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
if: ${{ matrix['tofu-versions'] != 'latest' }}
run: tofu version | grep ${{ matrix['tofu-versions']}}
- name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
if: ${{ matrix['tofu-versions'] == 'latest' }}
run: tofu version | grep 'OpenTofu v'
# TODO: uncomment when the semver feature is implemented
# tofu-versions-constraints:
# name: 'OpenTofu Versions Constraints'
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [ubuntu-latest, windows-latest, macos-latest]
# tofu-versions: [~0.12, 0.12.x, <0.13.0]
# steps:
# - name: Checkout
# uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
#
# - name: Setup OpenTofu - ${{ matrix['tofu-versions'] }}
# uses: ./
# with:
# tofu_version: ${{ matrix['tofu-versions'] }}
#
# - name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
# run: tofu version | grep 'OpenTofu v0\.12'
# TODO: uncomment when the semver feature is implemented
# tofu-versions-constraints-no-wrapper:
# name: 'OpenTofu Versions Constraints No Wrapper'
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [ubuntu-latest, windows-latest, macos-latest]
# tofu-versions: [~0.12, 0.12.x, <0.13.0]
# steps:
# - name: Checkout
# uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
#
# - name: Setup OpenTofu (no wrapper) - ${{ matrix['tofu-versions'] }}
# uses: ./
# with:
# tofu_version: ${{ matrix['tofu-versions'] }}
# tofu_wrapper: false
#
# - name: Validate OpenTofu Version - ${{ matrix['tofu-versions'] }}
# run: tofu version | grep 'OpenTofu v0\.12'
tofu-credentials-cloud:
name: 'OpenTofu Cloud Credentials'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
TF_CLOUD_API_TOKEN: 'XXXXXXXXXXXXXX.atlasv1.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
with:
cli_config_credentials_token: ${{ env.TF_CLOUD_API_TOKEN }}
- name: Validate OpenTofu Credentials (Windows)
if: runner.os == 'Windows'
run: |
cat ${APPDATA}/terraform.rc | grep 'credentials "app.terraform.io"'
cat ${APPDATA}/terraform.rc | grep 'token = "${{ env.TF_CLOUD_API_TOKEN }}"'
- name: Validate Teraform Credentials (Linux & macOS)
if: runner.os != 'Windows'
run: |
cat ${HOME}/.terraformrc | grep 'credentials "app.terraform.io"'
cat ${HOME}/.terraformrc | grep 'token = "${{ env.TF_CLOUD_API_TOKEN }}"'
tofu-credentials-enterprise:
name: 'OpenTofu Enterprise Credentials'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
TF_CLOUD_API_TOKEN: 'XXXXXXXXXXXXXX.atlasv1.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
with:
cli_config_credentials_hostname: 'tofu.example.com'
cli_config_credentials_token: ${{ env.TF_CLOUD_API_TOKEN }}
- name: Validate OpenTofu Credentials (Windows)
if: runner.os == 'Windows'
run: |
cat ${APPDATA}/terraform.rc | grep 'credentials "tofu.example.com"'
cat ${APPDATA}/terraform.rc | grep 'token = "${{ env.TF_CLOUD_API_TOKEN }}"'
- name: Validate Teraform Credentials (Linux & macOS)
if: runner.os != 'Windows'
run: |
cat ${HOME}/.terraformrc | grep 'credentials "tofu.example.com"'
cat ${HOME}/.terraformrc | grep 'token = "${{ env.TF_CLOUD_API_TOKEN }}"'
tofu-credentials-none:
name: 'OpenTofu No Credentials'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
- name: Validate OpenTofu Credentials (Windows)
if: runner.os == 'Windows'
run: |
[[ -f ${APPDATA}/terraform.rc ]] || exit 0
- name: Validate Teraform Credentials (Linux & macOS)
if: runner.os != 'Windows'
run: |
[[ -f ${HOME}/.terraformrc ]] || exit 0
tofu-arguments:
name: 'OpenTofu Arguments'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
- name: Check No Arguments
run: tofu || exit 0
- name: Check Single Argument
run: tofu help || exit 0
- name: Check Single Argument Hyphen
run: tofu -help
- name: Check Single Argument Double Hyphen
run: tofu --help
- name: Check Single Argument Subcommand
run: tofu fmt -check
- name: Check Multiple Arguments Subcommand
run: tofu fmt -check -list=true -no-color
tofu-arguments-no-wrapper:
name: 'OpenTofu Arguments No Wrapper'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
with:
tofu_wrapper: false
- name: Check No Arguments
run: tofu || exit 0
- name: Check Single Argument
run: tofu help || exit 0
- name: Check Single Argument Hyphen
run: tofu -help
- name: Check Single Argument Double Hyphen
run: tofu --help
- name: Check Single Argument Subcommand
run: tofu fmt -check
- name: Check Multiple Arguments Subcommand
run: tofu fmt -check -list=true -no-color
tofu-run-local:
name: 'OpenTofu Run Local'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
defaults:
run:
working-directory: ./.github/workflows/data/local
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
- name: OpenTofu Init
shell: bash
run: tofu init
- name: OpenTofu Format
shell: bash
run: tofu fmt -check
- name: OpenTofu Plan
id: plan
shell: bash
run: tofu plan
- name: Print OpenTofu Plan
shell: bash
run: echo "${{ steps.plan.outputs.stdout }}"
tofu-run-local-no-wrapper:
name: 'OpenTofu Run Local No Wrapper'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
defaults:
run:
working-directory: ./.github/workflows/data/local
steps:
- name: Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup OpenTofu
uses: ./
with:
tofu_wrapper: false
- name: OpenTofu Init
shell: bash
run: tofu init
- name: OpenTofu Format
shell: bash
run: tofu fmt -check
- name: OpenTofu Plan
id: plan
shell: bash
run: tofu plan

72
.gitignore vendored Normal file
View file

@ -0,0 +1,72 @@
node_modules/
# Editors
.vscode
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Other Dependency directories
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# ./wrapper/dist gets included in top-level ./dist
wrapper/dist
# Jetbrains IDEs
.idea/
.iml

1
.husky/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run build && git add dist/

View file

@ -2,9 +2,9 @@
The `opentofu/setup-opentofu` action sets up OpenTofu CLI in your GitHub Actions workflow by:
- [ ] Downloading the latest version of OpenTofu CLI and adding it to the `PATH`.
- [ ] Configuring the [CLI configuration file](https://www.terraform.io/docs/commands/cli-config.html) with a Terraform Cloud/Enterprise hostname and API token.
- [ ] Installing a wrapper script to wrap subsequent calls of the `tofu` binary and expose its STDOUT, STDERR, and exit code as outputs named `stdout`, `stderr`, and `exitcode` respectively. (This can be optionally skipped if subsequent steps in the same job do not need to access the results of
- Downloading the latest version of OpenTofu CLI and adding it to the `PATH`.
- Configuring the [CLI configuration file](https://www.terraform.io/docs/commands/cli-config.html) with a Terraform Cloud/Enterprise hostname and API token.
- Installing a wrapper script to wrap subsequent calls of the `tofu` binary and expose its STDOUT, STDERR, and exit code as outputs named `stdout`, `stderr`, and `exitcode` respectively. (This can be optionally skipped if subsequent steps in the same job do not need to access the results of
OpenTofu commands.)
After you've used the action, subsequent steps in the same job can run arbitrary OpenTofu commands using [the GitHub Actions `run` syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun). This allows most OpenTofu commands to work exactly

7015
dist/index.js vendored Normal file

File diff suppressed because it is too large Load diff

4242
dist/index1.js vendored Executable file

File diff suppressed because it is too large Load diff

16
index.js Normal file
View file

@ -0,0 +1,16 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
const core = require('@actions/core');
const setup = require('./lib/setup-tofu');
(async () => {
try {
await setup();
} catch (error) {
core.setFailed(error.message);
}
})();

62
lib/releases.js Normal file
View file

@ -0,0 +1,62 @@
/**
* Copyright (c) OpenTofu
* SPDX-License-Identifier: MPL-2.0
*/
class Build {
constructor (name, url) {
this.name = name;
this.url = url;
}
}
class Release {
constructor (releaseMeta) {
this.version = releaseMeta.tag_name.replace('v', '');
this.builds = releaseMeta.assets.map(asset => new Build(asset.name, asset.browser_download_url));
}
getBuild (platform, arch) {
const requiredName = `tofu_${this.version}_${platform}_${arch}.zip`;
return this.builds.find(build => build.name === requiredName);
}
}
/**
* Fetches the top 30 releases sorted in desc order.
*
*/
async function fetchReleases () {
const url = 'https://api.github.com/repos/opentofu/opentofu/releases';
const resp = await fetch(url, {
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
});
if (!resp.ok) {
throw Error('failed fetching releases');
}
const releasesMeta = await resp.json();
return releasesMeta.map(releaseMeta => new Release(releaseMeta));
}
/**
* Fetches the release given the version.
*
* @param {string} version: Release version.
*/
async function getRelease (version) {
const releases = await fetchReleases();
return releases.find(release => release.version === version);
}
// Note that the export is defined as adaptor to replace hashicorp/js-releases
// See: https://github.com/hashicorp/setup-terraform/blob/e192cfcbae6c6ed207c277ed7624131996c9bf13/lib/setup-terraform.js#L15
module.exports = {
getRelease
};

170
lib/setup-tofu.js Normal file
View file

@ -0,0 +1,170 @@
/**
* Copyright (c) HashiCorp, Inc.
* Copyright (c) OpenTofu
* SPDX-License-Identifier: MPL-2.0
*/
// Node.js core
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
// External
const core = require('@actions/core');
const tc = require('@actions/tool-cache');
const io = require('@actions/io');
const releases = require('./releases');
// arch in [arm, x32, x64...] (https://nodejs.org/api/os.html#os_os_arch)
// return value in [amd64, 386, arm]
function mapArch (arch) {
const mappings = {
x32: '386',
x64: 'amd64'
};
return mappings[arch] || arch;
}
// os in [darwin, linux, win32...] (https://nodejs.org/api/os.html#os_os_platform)
// return value in [darwin, linux, windows]
function mapOS (os) {
const mappings = {
win32: 'windows'
};
return mappings[os] || os;
}
async function downloadCLI (url) {
core.debug(`Downloading OpenTofu CLI from ${url}`);
const pathToCLIZip = await tc.downloadTool(url);
let pathToCLI = '';
core.debug('Extracting OpenTofu CLI zip file');
if (os.platform().startsWith('win')) {
core.debug(`OpenTofu CLI Download Path is ${pathToCLIZip}`);
const fixedPathToCLIZip = `${pathToCLIZip}.zip`;
io.mv(pathToCLIZip, fixedPathToCLIZip);
core.debug(`Moved download to ${fixedPathToCLIZip}`);
pathToCLI = await tc.extractZip(fixedPathToCLIZip);
} else {
pathToCLI = await tc.extractZip(pathToCLIZip);
}
core.debug(`OpenTofu CLI path is ${pathToCLI}.`);
if (!pathToCLIZip || !pathToCLI) {
throw new Error(`Unable to download OpenTofu from ${url}`);
}
return pathToCLI;
}
async function installWrapper (pathToCLI) {
let source, target;
// If we're on Windows, then the executable ends with .exe
const exeSuffix = os.platform().startsWith('win') ? '.exe' : '';
// Rename tofu(.exe) to tofu-bin(.exe)
try {
source = [pathToCLI, `tofu${exeSuffix}`].join(path.sep);
target = [pathToCLI, `tofu-bin${exeSuffix}`].join(path.sep);
core.debug(`Moving ${source} to ${target}.`);
await io.mv(source, target);
} catch (e) {
core.error(`Unable to move ${source} to ${target}.`);
throw e;
}
// Install our wrapper as tofu
try {
source = path.resolve([__dirname, '..', 'wrapper', 'dist', 'index.js'].join(path.sep));
target = [pathToCLI, 'tofu'].join(path.sep);
core.debug(`Copying ${source} to ${target}.`);
await io.cp(source, target);
} catch (e) {
core.error(`Unable to copy ${source} to ${target}.`);
throw e;
}
// Export a new environment variable, so our wrapper can locate the binary
core.exportVariable('TOFU_CLI_PATH', pathToCLI);
}
// Add credentials to CLI Configuration File
// https://www.tofu.io/docs/commands/cli-config.html
async function addCredentials (credentialsHostname, credentialsToken, osPlat) {
// format HCL block
// eslint-disable
const creds = `
credentials "${credentialsHostname}" {
token = "${credentialsToken}"
}`.trim();
// eslint-enable
// default to OS-specific path
let credsFile = osPlat === 'win32'
? `${process.env.APPDATA}/terraform.rc`
: `${process.env.HOME}/.terraformrc`;
// override with TF_CLI_CONFIG_FILE environment variable
credsFile = process.env.TF_CLI_CONFIG_FILE ? process.env.TF_CLI_CONFIG_FILE : credsFile;
// get containing folder
const credsFolder = path.dirname(credsFile);
core.debug(`Creating ${credsFolder}`);
await io.mkdirP(credsFolder);
core.debug(`Adding credentials to ${credsFile}`);
await fs.writeFile(credsFile, creds);
}
async function run () {
try {
// Gather GitHub Actions inputs
const version = '1.6.0-alpha1';
// TODO: allow dynamic version selection once logic is ready
// const version = core.getInput('tofu_version');
const credentialsHostname = core.getInput('cli_config_credentials_hostname');
const credentialsToken = core.getInput('cli_config_credentials_token');
const wrapper = core.getInput('tofu_wrapper') === 'true';
// Gather OS details
const osPlatform = os.platform();
const osArch = os.arch();
core.debug(`Finding releases for OpenTofu version ${version}`);
const release = await releases.getRelease(version);
const platform = mapOS(osPlatform);
const arch = mapArch(osArch);
core.debug(`Getting build for OpenTofu version ${release.version}: ${platform} ${arch}`);
const build = release.getBuild(platform, arch);
if (!build) {
throw new Error(`OpenTofu version ${version} not available for ${platform} and ${arch}`);
}
// Download requested version
const pathToCLI = await downloadCLI(build.url);
// Install our wrapper
if (wrapper) {
await installWrapper(pathToCLI);
}
// Add to path
core.addPath(pathToCLI);
// Add credentials to file if they are provided
if (credentialsHostname && credentialsToken) {
await addCredentials(credentialsHostname, credentialsToken, osPlatform);
}
return release;
} catch (error) {
core.error(error);
throw error;
}
}
module.exports = run;

6630
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

40
package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "setup-opentofu",
"version": "0.0.1",
"description": "Setup OpenTofu CLI for GitHub Actions",
"license": "MPL-2.0",
"publisher": "OpenTofu",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/opentofu/setup-opentofu.git"
},
"scripts": {
"test": "semistandard --env jest && jest --coverage",
"lint": "semistandard --env jest --fix",
"build": "ncc build wrapper/tofu.js --out wrapper/dist && ncc build index.js --out dist",
"prepare": "husky install",
"format-check": "echo \"unimplemented for actions/reusable-workflows basic-validation\""
},
"keywords": [],
"author": "",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@actions/io": "^1.1.3",
"@actions/tool-cache": "^2.0.1"
},
"devDependencies": {
"@vercel/ncc": "^0.38.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
"nock": "^13.3.3",
"semistandard": "^17.0.0"
},
"semistandard": {
"ignore": [
"**/dist/**"
]
}
}

View file

@ -0,0 +1,41 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
/**
* Acts as a listener for @actions/exec, by capturing STDOUT and STDERR
* streams, and exposing them via a contents attribute.
*
* @example
* // Instantiate a new listener
* const listener = new OutputListener();
* // Register listener against STDOUT stream
* await exec.exec('ls', ['-ltr'], {
* listeners: {
* stdout: listener.listener
* }
* });
* // Log out STDOUT contents
* console.log(listener.contents);
*/
class OutputListener {
constructor () {
this._buff = [];
}
get listener () {
const listen = function listen (data) {
this._buff.push(data);
};
return listen.bind(this);
}
get contents () {
return this._buff
.map(chunk => chunk.toString())
.join('');
}
}
module.exports = OutputListener;

14
wrapper/lib/tofu-bin.js Normal file
View file

@ -0,0 +1,14 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
const os = require('os');
const path = require('path');
module.exports = (() => {
// If we're on Windows, then the executable ends with .exe
const exeSuffix = os.platform().startsWith('win') ? '.exe' : '';
return [process.env.TOFU_CLI_PATH, `tofu-bin${exeSuffix}`].join(path.sep);
})();

View file

@ -0,0 +1,17 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
const OutputListener = require('../lib/output-listener');
describe('output-listener', () => {
it('receives and exposes data', () => {
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');
});
});

59
wrapper/tofu.js Executable file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env node
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
const io = require('@actions/io');
const core = require('@actions/core');
const { exec } = require('@actions/exec');
const OutputListener = require('./lib/output-listener');
const pathToCLI = require('./lib/tofu-bin');
async function checkTofu () {
// Setting check to `true` will cause `which` to throw if tofu isn't found
const check = true;
return io.which(pathToCLI, check);
}
(async () => {
// This will fail if tofu isn't found, which is what we want
await checkTofu();
// Create listeners to receive output (in memory) as well
const stdout = new OutputListener();
const stderr = new OutputListener();
const listeners = {
stdout: stdout.listener,
stderr: stderr.listener
};
// Execute tofu and capture output
const args = process.argv.slice(2);
const options = {
listeners,
ignoreReturnCode: true
};
const exitCode = await exec(pathToCLI, args, options);
core.debug(`OpenTofu exited with code ${exitCode}.`);
core.debug(`stdout: ${stdout.contents}`);
core.debug(`stderr: ${stderr.contents}`);
core.debug(`exitcode: ${exitCode}`);
// Set outputs, result, exitcode, and stderr
core.setOutput('stdout', stdout.contents);
core.setOutput('stderr', stderr.contents);
core.setOutput('exitcode', exitCode.toString(10));
if (exitCode === 0 || exitCode === 2) {
// A exitCode of 0 is considered a success
// An exitCode of 2 may be returned when the '-detailed-exitcode' option
// is passed to plan. This denotes Success with non-empty
// diff (changes present).
return;
}
// A non-zero exitCode is considered an error
core.setFailed(`OpenTofu exited with code ${exitCode}.`);
})();