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

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;