Compare commits

..

No commits in common. "main" and "1.0.0" have entirely different histories.
main ... 1.0.0

20 changed files with 5800 additions and 56771 deletions

View file

@ -1,19 +1,19 @@
name: "Continuous Integration"
name: 'Continuous Integration'
on:
push:
branches:
- main
- main
pull_request:
jobs:
check-dist:
name: Check dist/ directory
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@2826fb8353263a138210fc017301ce5767a9c0d4
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@967035ce963867fb956a309c9b67512314bc7c1f
with:
node-version: "20.19.1"
node-version: "20.x"
test:
name: Test
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@2826fb8353263a138210fc017301ce5767a9c0d4
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@967035ce963867fb956a309c9b67512314bc7c1f
with:
node-version: "20.19.1"
node-version: "20.x"

View file

@ -1 +0,0 @@
latest

View file

@ -1,21 +0,0 @@
name: Retag
on:
push:
tags:
- 'v[1-2].[0-9]+.[0-9]+*'
defaults:
run:
shell: bash
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
set -o pipefail
git tag --force "$(grep '^v[1-2]\.[0-9]\+\.[0-9]\+\(.*\)\?$' <<< ${GITHUB_REF#refs/*/} | sed 's/^v\([1-2]\)\..*/v\1/')"
git push --tag --force

View file

@ -10,36 +10,14 @@ defaults:
run:
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
tofu-version-files:
name: 'OpenTofu Version Files'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
tofu-version-files: ['./.github/workflows/data/local/.opentofu-version']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup OpenTofu - ${{ matrix['tofu-version-files'] }}
uses: ./
with:
tofu_version_file: ${{ matrix['tofu-version-files'] }}
tofu_wrapper: false
- name: Validate that OpenTofu was installed
run: tofu version | grep 'OpenTofu v'
tofu-versions:
name: 'OpenTofu Versions'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
tofu-versions: [1.6.0, latest]
tofu-versions: [1.6.0-alpha1, latest]
tofu-wrapper: [true, false]
steps:
- name: Checkout
@ -99,7 +77,6 @@ jobs:
tofu-wrapper: [true, false]
defaults:
run:
shell: bash
working-directory: ./.github/workflows/data/local
steps:
- name: Checkout
@ -204,3 +181,4 @@ jobs:
if: runner.os != 'Windows'
run: |
[[ -f ${HOME}/.tofurc ]] || exit 0

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## Unreleased
Initial release. It provides feature parity with the [setup-terraform](https://github.com/hashicorp/setup-terraform) action.

View file

@ -4,9 +4,4 @@
# We currently do not have any specific code owners
# In the future, we will have a Github team of global code owners of the entire package
# Later on, we will start splitting up the responsibilities, and packages will be assigned more specific code owners
* @opentofu/maintainers
# The last matching pattern takes the most precedence for CODEOWNERS. CODEOWNERS does not have fine-grained control so we will
# just match whole changes for these specific files, but @diofeher is responsible for taking care of the Dependabot updates
# in this repository.
package*.json @diofeher
# * @opentofu-code-owners

10
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,10 @@
# Code of Conduct
We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
<!-- TODO: Decide who will handle Code of Conduct reports and replace [INSERT EMAIL ADDRESS]
with an email address in the paragraph below. We recommend using a mailing list to handle reports.
If your project isn't prepared to handle reports, remove the project email address and just have
reporters send to conduct@cncf.io.
-->
Please contact contact@opentf.org in order to report violations of the Code of Conduct.

View file

@ -1,12 +1,18 @@
# GitHub Action for setting up OpenTofu
# setup-opentofu
> [!NOTE]
> This is a community-maintained repository. The OpenTofu team does not fix non-critical bugs or add features, but is happy to review community pull requests.
The `opentofu/setup-opentofu` action sets up OpenTofu CLI in your GitHub Actions workflow by:
> [!TIP]
> Having trouble with exit codes or the output format? Try setting the `tofu_wrapper` setting to `false`.
- Downloading the latest version of OpenTofu CLI and adding it to the `PATH`.
- Configuring the [CLI configuration file](https://opentofu.org/docs/cli/config/config-file/) 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.)
The `opentofu/setup-opentofu` action sets up OpenTofu CLI in GitHub Actions.
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
like they do on your local command line.
## Experimental Status
By using the software in this repository (the "Software"), you acknowledge that: (1) the Software is still in development, may change, and has not been released as a commercial product by OpenTofu; (2) the Software is provided on an "as-is" basis, and may include bugs, errors, or other issues; (3) the Software is NOT INTENDED FOR PRODUCTION USE, use of the Software may result in unexpected results, loss of data, or other unexpected results, and OpenTofu disclaims any and all liability resulting from use of the Software.
## Usage
@ -16,34 +22,23 @@ The default configuration installs the latest version of OpenTofu CLI and instal
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
```
A specific version of OpenTofu CLI can be installed:
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
with:
tofu_version: 1.6.0
tofu_version: 1.6.0-alpha1
```
You can also specify the version in a file (e.g., `.opentofu-version`):
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
with:
tofu_version_file: .opentofu-version
```
Supported version syntax is the same as for the `tofu_version` input. If both `tofu_version` and `tofu_version_file` are provided, the version number in the file takes precedence.
Credentials for Terraform Cloud ([app.terraform.io](https://app.terraform.io/)) can be configured:
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
```
@ -52,7 +47,7 @@ Credentials for Terraform Enterprise (TFE) can be configured:
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
with:
cli_config_credentials_hostname: 'tofu.example.com'
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
@ -62,7 +57,7 @@ The wrapper script installation can be skipped by setting the `tofu_wrapper` var
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
with:
tofu_wrapper: false
```
@ -71,7 +66,7 @@ Subsequent steps can access outputs when the wrapper script is installed:
```yaml
steps:
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
- run: tofu init
@ -99,7 +94,7 @@ permissions:
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
- name: OpenTofu fmt
id: fmt
@ -167,7 +162,7 @@ permissions:
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: opentofu/setup-opentofu@v1
- uses: opentofu/setup-opentofu
- name: OpenTofu fmt
id: fmt
@ -259,12 +254,9 @@ The action supports the following inputs:
for available range specifications). Examples are: `<1.6.0-beta`, `~1.6.0-alpha`, `1.6.0-alpha2` (all three installing
the latest available `1.6.0-alpha2` version). Prerelease versions can be specified and a range will stay within the
given tag such as `beta` or `rc`. If no version is given, it will default to `latest`.
- `tofu_version_file` - (optional) Path to a file containing the OpenTofu version to install. Supported version syntax
is the same as for the `tofu_version` input. Takes precedence over `tofu_version` if both are provided.
- `tofu_wrapper` - (optional) Whether to install a wrapper to wrap subsequent calls of
the `tofu` binary and expose its STDOUT, STDERR, and exit code as outputs
named `stdout`, `stderr`, and `exitcode` respectively. Defaults to `true`.
- `github_token` - (optional) Override the GitHub token read from the environment variable. Defaults to the value of the `GITHUB_TOKEN` environment variable unless running on Forgejo or Gitea.
## Outputs
@ -279,3 +271,6 @@ to `true`, the following outputs are available for subsequent steps that call th
[Mozilla Public License v2.0](LICENSE)
## Code of Conduct
[Code of Conduct](CODE_OF_CONDUCT.md)

View file

@ -13,17 +13,10 @@ inputs:
description: 'The version of OpenTofu CLI to install. If no version is given, it will default to `latest`.'
default: 'latest'
required: false
tofu_version_file:
description: 'Path to a file containing the OpenTofu version to install. Takes precedence over `tofu_version` if both are provided.'
required: false
tofu_wrapper:
description: 'Whether or not to install a wrapper to wrap subsequent calls of the `tofu` binary and expose its STDOUT, STDERR, and exit code as outputs named `stdout`, `stderr`, and `exitcode` respectively. Defaults to `true`.'
default: 'true'
required: false
github_token:
description: 'API token for GitHub to increase the rate limit. Defaults to the GITHUB_TOKEN environment variable unless running on Forgejo/Gitea.'
default: ''
required: false
outputs:
stdout:
description: 'The STDOUT stream of the call to the `tofu` binary.'
@ -36,8 +29,8 @@ outputs:
value: '126'
branding:
icon: 'box'
color: 'yellow'
icon: 'terminal'
color: 'purple'
runs:
using: 'node20'
main: 'dist/index.js'

31671
dist/index.js vendored

File diff suppressed because one or more lines are too long

26810
dist/index1.js vendored

File diff suppressed because one or more lines are too long

View file

@ -4,16 +4,16 @@
*/
class Build {
constructor (version, name) {
constructor (name, url) {
this.name = name;
this.url = 'https://github.com/opentofu/opentofu/releases/download/v' + version + '/' + name;
this.url = url;
}
}
class Release {
constructor (releaseMeta) {
this.version = releaseMeta.id.replace('v', '');
this.builds = releaseMeta.files.map(asset => new Build(this.version, asset));
this.version = releaseMeta.tag_name.replace('v', '');
this.builds = releaseMeta.assets.map(asset => new Build(asset.name, asset.browser_download_url));
}
getBuild (platform, arch) {
@ -27,39 +27,29 @@ class Release {
*
* @return {Array<Release>} Releases.
*/
async function fetchReleases (githubToken) {
const hc = require('@actions/http-client');
const userAgent = 'opentofu/setup-opentofu';
async function fetchReleases () {
const url = 'https://api.github.com/repos/opentofu/opentofu/releases';
const http = new hc.HttpClient(userAgent);
const resp = await fetch(url, {
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
});
const url = 'https://get.opentofu.org/tofu/api.json';
const headers = {
Accept: 'application/json'
};
const resp = await http.get(url, headers);
if (resp.message.statusCode !== hc.HttpCodes.OK) {
throw new Error('failed fetching releases (' + resp.message.statusCode + ')');
if (!resp.ok) {
throw new Error('failed fetching releases');
}
const body = await resp.readBody();
const releasesMeta = JSON.parse(body);
const releasesMeta = await resp.json();
/**
* @type {Array}
*/
const versions = releasesMeta.versions;
return versions.map(releaseMeta => new Release(releaseMeta));
return releasesMeta.map(releaseMeta => new Release(releaseMeta));
}
const semver = require('semver');
async function findLatestVersion (versions) {
return versions.filter((v) => semver.prerelease(v) === null).sort((a, b) => semver.rcompare(a, b))[0];
return versions.sort((a, b) => semver.rcompare(a, b))[0];
}
async function findLatestVersionInRange (versions, range) {
@ -70,11 +60,10 @@ async function findLatestVersionInRange (versions, range) {
* Fetches the release given the version.
*
* @param {string} version: Release version.
* @param {string} githubToken: GitHub token to use for working around rate limits.
* @param {function} fetchReleasesFn: Optional function to fetch releases.
* @return {Release} Release.
*/
async function getRelease (version, githubToken, fetchReleasesFn = fetchReleases) {
async function getRelease (version, fetchReleasesFn = fetchReleases) {
const latestVersionLabel = 'latest';
const versionsRange = semver.validRange(version, { prerelease: true, loose: true });
@ -82,7 +71,7 @@ async function getRelease (version, githubToken, fetchReleasesFn = fetchReleases
throw new Error('Input version cannot be used, see semver: https://semver.org/spec/v2.0.0.html');
}
const releases = await fetchReleasesFn(githubToken);
const releases = await fetchReleasesFn();
if (releases === null || releases.length === 0) {
throw new Error('No tofu releases found, please contact OpenTofu');

View file

@ -48,7 +48,7 @@ async function downloadAndExtractCLI (url) {
if (os.platform().startsWith('win')) {
core.debug(`OpenTofu CLI Download Path is ${pathToCLIZip}`);
const fixedPathToCLIZip = `${pathToCLIZip}.zip`;
await io.mv(pathToCLIZip, fixedPathToCLIZip);
io.mv(pathToCLIZip, fixedPathToCLIZip);
core.debug(`Moved download to ${fixedPathToCLIZip}`);
pathToCLI = await tc.extractZip(fixedPathToCLIZip);
} else {
@ -105,7 +105,7 @@ async function addCredentials (credentialsHostname, credentialsToken, osPlat) {
credentials "${credentialsHostname}" {
token = "${credentialsToken}"
}`.trim();
// eslint-enable
// eslint-enable
// default to OS-specific path
let credsFile = osPlat === 'win32'
@ -128,45 +128,17 @@ credentials "${credentialsHostname}" {
async function run () {
try {
// Gather GitHub Actions inputs
let version = core.getInput('tofu_version');
const versionFile = core.getInput('tofu_version_file');
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';
let githubToken = core.getInput('github_token');
if (githubToken === '' && !(process.env.FORGEJO_ACTIONS || process.env.GITEA_ACTIONS)) {
// Only default to the environment variable when running in GitHub Actions. Don't do this for other CI systems
// that may set the GITHUB_TOKEN environment variable.
githubToken = process.env.GITHUB_TOKEN;
}
// If tofu_version_file is provided, read the version from the file
if (versionFile) {
try {
core.debug(`Reading OpenTofu version from file: ${versionFile}`);
const fileVersion = await fs.readFile(versionFile, 'utf8');
const trimmedVersion = fileVersion.trim();
if (trimmedVersion) {
version = trimmedVersion;
core.debug(`Using version from file: ${version}`);
} else {
core.warning(
`Version file ${versionFile} is empty, using tofu_version input: ${version}`
);
}
} catch (error) {
core.warning(
`Failed to read version from file ${versionFile}: ${error.message}. Using tofu_version input: ${version}`
);
}
}
// 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, githubToken);
const release = await releases.getRelease(version);
const platform = mapOS(osPlatform);
const arch = mapArch(osArch);
const build = release.getBuild(platform, arch);

View file

@ -3,133 +3,184 @@ const pkg = require('../releases');
describe('getRelease', () => {
function mockFetchReleases () {
const mockReleasesMeta = [{
id: 'v1.7.0-alpha2',
files: [
'tofu_1.7.0-alpha2_386.apk',
'tofu_1.7.0-alpha2_386.deb',
'tofu_1.7.0-alpha2_386.rpm',
'tofu_1.7.0-alpha2_amd64.apk',
'tofu_1.7.0-alpha2_amd64.deb',
'tofu_1.7.0-alpha2_amd64.rpm',
'tofu_1.7.0-alpha2_arm.apk',
'tofu_1.7.0-alpha2_arm.deb',
'tofu_1.7.0-alpha2_arm.rpm',
'tofu_1.7.0-alpha2_arm64.apk',
'tofu_1.7.0-alpha2_arm64.deb',
'tofu_1.7.0-alpha2_arm64.rpm',
'tofu_1.7.0-alpha2_darwin_arm64.zip',
'tofu_1.7.0-alpha2_freebsd_386.zip',
'tofu_1.7.0-alpha2_freebsd_amd64.zip',
'tofu_1.7.0-alpha2_freebsd_arm.zip',
'tofu_1.7.0-alpha2_linux_386.zip',
'tofu_1.7.0-alpha2_linux_amd64.zip',
'tofu_1.7.0-alpha2_linux_arm.zip',
'tofu_1.7.0-alpha2_linux_arm64.zip',
'tofu_1.7.0-alpha2_openbsd_386.zip',
'tofu_1.7.0-alpha2_openbsd_amd64.zip',
'tofu_1.7.0-alpha2_SHA256SUMS',
'tofu_1.7.0-alpha2_SHA256SUMS.pem',
'tofu_1.7.0-alpha2_SHA256SUMS.sig',
'tofu_1.7.0-alpha2_solaris_amd64.zip',
'tofu_1.7.0-alpha2_windows_386.zip',
'tofu_1.7.0-alpha2_windows_amd64.zip'
]
tag_name: 'v1.6.0-alpha2',
assets: [{
name: 'tofu_1.6.0-alpha2_386.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_386.apk'
}, {
name: 'tofu_1.6.0-alpha2_386.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_386.deb'
}, {
name: 'tofu_1.6.0-alpha2_386.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_386.rpm'
}, {
name: 'tofu_1.6.0-alpha2_amd64.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_amd64.apk'
}, {
name: 'tofu_1.6.0-alpha2_amd64.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_amd64.deb'
}, {
name: 'tofu_1.6.0-alpha2_amd64.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_amd64.rpm'
}, {
name: 'tofu_1.6.0-alpha2_arm.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm.apk'
}, {
name: 'tofu_1.6.0-alpha2_arm.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm.deb'
}, {
name: 'tofu_1.6.0-alpha2_arm.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm.rpm'
}, {
name: 'tofu_1.6.0-alpha2_arm64.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm64.apk'
}, {
name: 'tofu_1.6.0-alpha2_arm64.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm64.deb'
}, {
name: 'tofu_1.6.0-alpha2_arm64.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_arm64.rpm'
}, {
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_darwin_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha2_darwin_arm64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_darwin_arm64.zip'
}, {
name: 'tofu_1.6.0-alpha2_freebsd_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_freebsd_386.zip'
}, {
name: 'tofu_1.6.0-alpha2_freebsd_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_freebsd_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha2_freebsd_arm.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_freebsd_arm.zip'
}, {
name: 'tofu_1.6.0-alpha2_linux_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_linux_386.zip'
}, {
name: 'tofu_1.6.0-alpha2_linux_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_linux_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha2_linux_arm.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_linux_arm.zip'
}, {
name: 'tofu_1.6.0-alpha2_linux_arm64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_linux_arm64.zip'
}, {
name: 'tofu_1.6.0-alpha2_openbsd_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_openbsd_386.zip'
}, {
name: 'tofu_1.6.0-alpha2_openbsd_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_openbsd_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha2_SHA256SUMS',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_SHA256SUMS'
}, {
name: 'tofu_1.6.0-alpha2_SHA256SUMS.pem',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_SHA256SUMS.pem'
}, {
name: 'tofu_1.6.0-alpha2_SHA256SUMS.sig',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_SHA256SUMS.sig'
}, {
name: 'tofu_1.6.0-alpha2_solaris_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_solaris_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha2_windows_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_windows_386.zip'
}, {
name: 'tofu_1.6.0-alpha2_windows_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha2/tofu_1.6.0-alpha2_windows_amd64.zip'
}]
}, {
id: 'v1.6.0',
files: [
'tofu_1.6.0_386.apk',
'tofu_1.6.0_386.deb',
'tofu_1.6.0_386.rpm',
'tofu_1.6.0_amd64.apk',
'tofu_1.6.0_amd64.deb',
'tofu_1.6.0_amd64.rpm',
'tofu_1.6.0_arm.apk',
'tofu_1.6.0_arm.deb',
'tofu_1.6.0_arm.rpm',
'tofu_1.6.0_arm64.apk',
'tofu_1.6.0_arm64.deb',
'tofu_1.6.0_arm64.rpm',
'tofu_1.6.0_darwin_arm64.zip',
'tofu_1.6.0_freebsd_386.zip',
'tofu_1.6.0_freebsd_amd64.zip',
'tofu_1.6.0_freebsd_arm.zip',
'tofu_1.6.0_linux_386.zip',
'tofu_1.6.0_linux_amd64.zip',
'tofu_1.6.0_linux_arm.zip',
'tofu_1.6.0_linux_arm64.zip',
'tofu_1.6.0_openbsd_386.zip',
'tofu_1.6.0_openbsd_amd64.zip',
'tofu_1.6.0_SHA256SUMS',
'tofu_1.6.0_SHA256SUMS.pem',
'tofu_1.6.0_SHA256SUMS.sig',
'tofu_1.6.0_solaris_amd64.zip',
'tofu_1.6.0_windows_386.zip',
'tofu_1.6.0_windows_amd64.zip']
}, {
id: 'v1.6.0-alpha2',
files: [
'tofu_1.6.0-alpha2_386.apk',
'tofu_1.6.0-alpha2_386.deb',
'tofu_1.6.0-alpha2_386.rpm',
'tofu_1.6.0-alpha2_amd64.apk',
'tofu_1.6.0-alpha2_amd64.deb',
'tofu_1.6.0-alpha2_amd64.rpm',
'tofu_1.6.0-alpha2_arm.apk',
'tofu_1.6.0-alpha2_arm.deb',
'tofu_1.6.0-alpha2_arm.rpm',
'tofu_1.6.0-alpha2_arm64.apk',
'tofu_1.6.0-alpha2_arm64.deb',
'tofu_1.6.0-alpha2_arm64.rpm',
'tofu_1.6.0-alpha2_darwin_arm64.zip',
'tofu_1.6.0-alpha2_freebsd_386.zip',
'tofu_1.6.0-alpha2_freebsd_amd64.zip',
'tofu_1.6.0-alpha2_freebsd_arm.zip',
'tofu_1.6.0-alpha2_linux_386.zip',
'tofu_1.6.0-alpha2_linux_amd64.zip',
'tofu_1.6.0-alpha2_linux_arm.zip',
'tofu_1.6.0-alpha2_linux_arm64.zip',
'tofu_1.6.0-alpha2_openbsd_386.zip',
'tofu_1.6.0-alpha2_openbsd_amd64.zip',
'tofu_1.6.0-alpha2_SHA256SUMS',
'tofu_1.6.0-alpha2_SHA256SUMS.pem',
'tofu_1.6.0-alpha2_SHA256SUMS.sig',
'tofu_1.6.0-alpha2_solaris_amd64.zip',
'tofu_1.6.0-alpha2_windows_386.zip',
'tofu_1.6.0-alpha2_windows_amd64.zip'
]
}, {
id: 'v1.6.0-alpha1',
files: [
'tofu_1.6.0-alpha1_386.apk',
'tofu_1.6.0-alpha1_386.deb',
'tofu_1.6.0-alpha1_386.rpm',
'tofu_1.6.0-alpha1_amd64.apk',
'tofu_1.6.0-alpha1_amd64.deb',
'tofu_1.6.0-alpha1_amd64.rpm',
'tofu_1.6.0-alpha1_arm.apk',
'tofu_1.6.0-alpha1_arm.deb',
'tofu_1.6.0-alpha1_arm.rpm',
'tofu_1.6.0-alpha1_arm64.apk',
'tofu_1.6.0-alpha1_arm64.deb',
'tofu_1.6.0-alpha1_arm64.rpm',
'tofu_1.6.0-alpha1_darwin_amd64.zip',
'tofu_1.6.0-alpha1_darwin_arm64.zip',
'tofu_1.6.0-alpha1_freebsd_386.zip',
'tofu_1.6.0-alpha1_freebsd_amd64.zip',
'tofu_1.6.0-alpha1_freebsd_arm.zip',
'tofu_1.6.0-alpha1_linux_386.zip',
'tofu_1.6.0-alpha1_linux_amd64.zip',
'tofu_1.6.0-alpha1_linux_arm.zip',
'tofu_1.6.0-alpha1_linux_arm64.zip',
'tofu_1.6.0-alpha1_openbsd_386.zip',
'tofu_1.6.0-alpha1_openbsd_amd64.zip',
'tofu_1.6.0-alpha1_SHA256SUMS',
'tofu_1.6.0-alpha1_SHA256SUMS.pem',
'tofu_1.6.0-alpha1_SHA256SUMS.sig',
'tofu_1.6.0-alpha1_solaris_amd64.zip',
'tofu_1.6.0-alpha1_windows_386.zip',
'tofu_1.6.0-alpha1_windows_amd64.zip'
]
tag_name: 'v1.6.0-alpha1',
assets: [{
name: 'tofu_1.6.0-alpha1_386.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_386.apk'
}, {
name: 'tofu_1.6.0-alpha1_386.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_386.deb'
}, {
name: 'tofu_1.6.0-alpha1_386.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_386.rpm'
}, {
name: 'tofu_1.6.0-alpha1_amd64.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_amd64.apk'
}, {
name: 'tofu_1.6.0-alpha1_amd64.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_amd64.deb'
}, {
name: 'tofu_1.6.0-alpha1_amd64.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_amd64.rpm'
}, {
name: 'tofu_1.6.0-alpha1_arm.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm.apk'
}, {
name: 'tofu_1.6.0-alpha1_arm.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm.deb'
}, {
name: 'tofu_1.6.0-alpha1_arm.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm.rpm'
}, {
name: 'tofu_1.6.0-alpha1_arm64.apk',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm64.apk'
}, {
name: 'tofu_1.6.0-alpha1_arm64.deb',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm64.deb'
}, {
name: 'tofu_1.6.0-alpha1_arm64.rpm',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_arm64.rpm'
}, {
name: 'tofu_1.6.0-alpha1_darwin_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_darwin_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha1_darwin_arm64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_darwin_arm64.zip'
}, {
name: 'tofu_1.6.0-alpha1_freebsd_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_freebsd_386.zip'
}, {
name: 'tofu_1.6.0-alpha1_freebsd_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_freebsd_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha1_freebsd_arm.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_freebsd_arm.zip'
}, {
name: 'tofu_1.6.0-alpha1_linux_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_linux_386.zip'
}, {
name: 'tofu_1.6.0-alpha1_linux_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_linux_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha1_linux_arm.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_linux_arm.zip'
}, {
name: 'tofu_1.6.0-alpha1_linux_arm64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_linux_arm64.zip'
}, {
name: 'tofu_1.6.0-alpha1_openbsd_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_openbsd_386.zip'
}, {
name: 'tofu_1.6.0-alpha1_openbsd_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_openbsd_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha1_SHA256SUMS',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_SHA256SUMS'
}, {
name: 'tofu_1.6.0-alpha1_SHA256SUMS.pem',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_SHA256SUMS.pem'
}, {
name: 'tofu_1.6.0-alpha1_SHA256SUMS.sig',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_SHA256SUMS.sig'
}, {
name: 'tofu_1.6.0-alpha1_solaris_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_solaris_amd64.zip'
}, {
name: 'tofu_1.6.0-alpha1_windows_386.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_windows_386.zip'
}, {
name: 'tofu_1.6.0-alpha1_windows_amd64.zip',
browser_download_url: 'https://github.com/opentofu/opentofu/releases/download/v1.6.0-alpha1/tofu_1.6.0-alpha1_windows_amd64.zip'
}]
}];
return mockReleasesMeta.map(el => new pkg.Release(el));
@ -137,36 +188,29 @@ describe('getRelease', () => {
it.each(
[
['latest', '1.6.0'],
['latest', '1.6.0-alpha2'],
['<1.6.0-beta', '1.6.0-alpha2'],
['>1.6.0-alpha1', '1.6.0'],
['>1.6.0-alpha1', '1.6.0-alpha2'],
['>1.6.0-alpha1 <1.6.0', '1.6.0-alpha2'],
['~1.6.0-alpha', '1.6.0'],
['<=1.6.0-alpha1', '1.6.0-alpha1'],
['>1.5.0', '1.6.0'],
['>1.5.0', '1.6.0'],
['>1.7.0-alpha', '1.7.0-alpha2']
['~1.6.0-alpha', '1.6.0-alpha2'],
['<=1.6.0-alpha1', '1.6.0-alpha1']
]
)('happy path: getRelease(\'%s\') -> \'%s\'', async (input, wantVersion) => {
const want = mockFetchReleases().find(el => el.version === wantVersion);
const gotRelease = await pkg.getRelease(input, '', mockFetchReleases);
const gotRelease = await pkg.getRelease(input, mockFetchReleases);
expect(gotRelease).toEqual(want);
});
it.each(
[
['foo', 'Input version cannot be used, see semver: https://semver.org/spec/v2.0.0.html', mockFetchReleases],
['2.0', 'No matching version found', mockFetchReleases],
['latest', 'No tofu releases found, please contact OpenTofu', async () => {
return null;
}],
['latest', 'No tofu releases found, please contact OpenTofu', async () => {
return [];
}]
['foo', 'Input version cannot be used, see semver: https://semver.org/spec/v2.0.0.html', undefined],
['2.0', 'No matching version found', undefined],
['latest', 'No tofu releases found, please contact OpenTofu', async () => { return null; }],
['latest', 'No tofu releases found, please contact OpenTofu', async () => { return []; }]
]
)('unhappy path: getRelease(\'%s\') -> throw Error(\'%s\')', async (input, wantErrorMessage, mockFetchReleasesFn) => {
try {
await pkg.getRelease(input, '', mockFetchReleasesFn);
await pkg.getRelease(input, mockFetchReleasesFn);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(wantErrorMessage);

View file

@ -1,176 +0,0 @@
// Node.js core
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
// External
const core = require('@actions/core');
// First party
const releases = require('../releases');
const setup = require('../setup-tofu');
// Mock dependencies
jest.mock('@actions/core');
jest.mock('@actions/io', () => ({
mv: jest.fn(),
cp: jest.fn(),
mkdirP: jest.fn()
}));
jest.mock('@actions/tool-cache', () => ({
downloadTool: jest.fn(),
extractZip: jest.fn()
}));
// Mock releases.js so setup-tofu.js can be tested in isolation
jest.mock('../releases');
// Set up global test fixtures
const fallbackVersion = 'latest';
let tempDir;
let tempDirPath;
let version = '1.10.5';
let versionFile;
let versionFileName = '.opentofu-version';
describe('setup-tofu', () => {
beforeAll(async () => {
// Mock dependencies
const tc = require('@actions/tool-cache');
tc.downloadTool.mockResolvedValue('/mock/download/path');
tc.extractZip.mockResolvedValue('/mock/extract/path');
const io = require('@actions/io');
io.mv.mockResolvedValue();
io.cp.mockResolvedValue();
io.mkdirP.mockResolvedValue();
const mockRelease = {
getBuild: jest.fn().mockReturnValue({ url: 'mock-url' })
};
releases.getRelease.mockResolvedValue(mockRelease);
// Write version file to temporary directory
tempDirPath = path.join(os.tmpdir(), 'setup-tofu-');
tempDir = await fs.mkdtemp(tempDirPath);
versionFile = path.join(tempDir, versionFileName);
await fs.writeFile(versionFile, `${version}\n`);
// Mock action inputs to return default values
core.getInput.mockImplementation((name) => {
const defaults = {
tofu_version: fallbackVersion,
tofu_version_file: versionFile,
cli_config_credentials_hostname: '',
cli_config_credentials_token: '',
tofu_wrapper: 'true',
github_token: ''
};
return defaults[name] || '';
});
// Mock environment variables
process.env.GITHUB_TOKEN = 'mock-github-token';
});
beforeEach(() => {
jest.clearAllMocks();
});
afterAll(async () => {
delete process.env.GITHUB_TOKEN;
});
describe('tofu_version_file functionality', () => {
it('should read version from file when tofu_version_file is provided', async () => {
jest.spyOn(fs, 'readFile');
await setup();
expect(releases.getRelease).toHaveBeenCalledWith(
version, process.env.GITHUB_TOKEN
);
expect(fs.readFile).toHaveBeenCalled();
});
it('should handle empty version file gracefully', async () => {
jest.spyOn(fs, 'readFile');
version = ' ';
versionFileName = '.opentofu-version-empty';
versionFile = path.join(tempDir, versionFileName);
await fs.writeFile(versionFile, `${version}\n`);
core.getInput.mockImplementation((name) => {
if (name === 'tofu_version_file') {
return versionFile;
}
if (name === 'tofu_version') {
return fallbackVersion;
}
return '';
});
await setup();
expect(releases.getRelease).toHaveBeenCalledWith(
fallbackVersion, process.env.GITHUB_TOKEN
);
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining(`Version file ${versionFile} is empty`)
);
expect(fs.readFile).toHaveBeenCalled();
});
it('should handle file read errors gracefully', async () => {
jest.spyOn(fs, 'readFile');
versionFileName = '.opentofu-version-file-does-not-exist';
versionFile = path.join(tempDir, versionFileName);
core.getInput.mockImplementation((name) => {
if (name === 'tofu_version_file') {
return versionFile;
}
if (name === 'tofu_version') {
return fallbackVersion;
}
return '';
});
await setup();
expect(releases.getRelease).toHaveBeenCalledWith(
fallbackVersion, process.env.GITHUB_TOKEN
);
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining(`Failed to read version from file ${versionFile}`)
);
expect(fs.readFile).toHaveBeenCalled();
});
it('should not read file when tofu_version_file is not provided', async () => {
jest.spyOn(fs, 'readFile');
core.getInput.mockImplementation((name) => {
if (name === 'tofu_version') {
return fallbackVersion;
}
return '';
});
await setup();
expect(releases.getRelease).toHaveBeenCalledWith(
fallbackVersion, process.env.GITHUB_TOKEN
);
expect(fs.readFile).not.toHaveBeenCalled();
});
});
});

3310
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "setup-opentofu",
"version": "1.0.1",
"version": "0.0.1",
"description": "Setup OpenTofu CLI for GitHub Actions",
"license": "MPL-2.0",
"publisher": "OpenTofu",
@ -19,16 +19,17 @@
"keywords": [],
"author": "",
"dependencies": {
"@actions/core": "2.0.1",
"@actions/exec": "2.0.0",
"@actions/io": "2.0.0",
"@actions/tool-cache": "2.0.2",
"semver": "7.7.3"
"@actions/core": "1.10.1",
"@actions/exec": "1.1.1",
"@actions/io": "1.1.3",
"@actions/tool-cache": "2.0.1",
"semver": "7.5.4"
},
"devDependencies": {
"@vercel/ncc": "0.38.4",
"husky": "9.1.7",
"jest": "30.2.0",
"@vercel/ncc": "0.38.0",
"husky": "8.0.3",
"jest": "29.7.0",
"nock": "13.3.3",
"semistandard": "17.0.0"
},
"semistandard": {

View file

@ -9,8 +9,7 @@
*
* @example
* // Instantiate a new listener
* // stream is used to write the data before waiting for the listener to complete
* const listener = new OutputListener(stream);
* const listener = new OutputListener();
* // Register listener against STDOUT stream
* await exec.exec('ls', ['-ltr'], {
* listeners: {
@ -21,14 +20,12 @@
* console.log(listener.contents);
*/
class OutputListener {
constructor (stream) {
constructor () {
this._buff = [];
this._stream = stream;
}
get listener () {
const listen = function listen (data) {
this._stream.write(data);
this._buff.push(data);
};
return listen.bind(this);

View file

@ -4,18 +4,14 @@
*/
const OutputListener = require('../lib/output-listener');
const { PassThrough } = require('stream');
describe('output-listener', () => {
it('receives and exposes data', () => {
const stream = new PassThrough();
const listener = new OutputListener(stream);
const listener = new OutputListener();
const listen = listener.listener;
listen(Buffer.from('foo'));
expect(stream.read()).toEqual(Buffer.from('foo'));
listen(Buffer.from('bar'));
expect(stream.read()).toEqual(Buffer.from('bar'));
listen(Buffer.from('baz'));
expect(stream.read()).toEqual(Buffer.from('baz'));
expect(listener.contents).toEqual('foobarbaz');
});
});

View file

@ -22,8 +22,8 @@ async function checkTofu () {
await checkTofu();
// Create listeners to receive output (in memory) as well
const stdout = new OutputListener(process.stdout);
const stderr = new OutputListener(process.stderr);
const stdout = new OutputListener();
const stderr = new OutputListener();
const listeners = {
stdout: stdout.listener,
stderr: stderr.listener
@ -33,10 +33,13 @@ async function checkTofu () {
const args = process.argv.slice(2);
const options = {
listeners,
ignoreReturnCode: true,
silent: true // don't print "[command...]" into stdout: https://github.com/actions/toolkit/issues/649
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);