From b43bbbc1b5bf2b4f7cade6ca76c6e2bbf61d2b33 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 1 Aug 2024 16:27:54 -0600 Subject: [PATCH 01/29] feat: open tofu flag --- packages/cli/lib/util/getSettings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/lib/util/getSettings.js b/packages/cli/lib/util/getSettings.js index a20ec4a..7e5a030 100644 --- a/packages/cli/lib/util/getSettings.js +++ b/packages/cli/lib/util/getSettings.js @@ -8,7 +8,8 @@ let settings export const defaultSettings = { disableErrors: false, disableAWSWarnings: false, - disableSettingPrompts: false + disableSettingPrompts: false, + useOpenTofu: false } /** From 7a1015c00f4889133fb06e7b1eb956627c47d4f4 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 1 Aug 2024 16:50:19 -0600 Subject: [PATCH 02/29] feat: update install command and alter dir obj to hold opentofu dir --- packages/cli/lib/commands/install.js | 10 +++++++++- packages/cli/lib/util/getDirectoriesObj.js | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index f9e7326..3a59883 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -10,6 +10,7 @@ import getErrorMessage from '../util/errorChecker.js' import getTerraformVersion from '../util/tfVersion.js' import getLatest from '../util/getLatest.js' import { logger } from '../util/logger.js' +import { getSettings } from '../util/getSettings.js' async function install (versionNum) { try { @@ -55,7 +56,14 @@ export default install export async function installFromWeb (versionNum, printMessage = true) { const zipPath = TfvmFS.getPath(TfvmFS.tfVersionsDir, `v${versionNum}.zip`) const newVersionDir = TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + versionNum) - const url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${TfvmFS.architecture}.zip` + + const settingsObj = await getSettings() + + if (settingsObj.useOpenTofu) { + const url = `https://github.com/opentofu/opentofu/releases/download/${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` + } else { + const url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${TfvmFS.architecture}.zip` + } await download(url, zipPath, versionNum) await fs.mkdir(newVersionDir) await unzipFile(zipPath, newVersionDir) diff --git a/packages/cli/lib/util/getDirectoriesObj.js b/packages/cli/lib/util/getDirectoriesObj.js index 97ee010..aea06fd 100644 --- a/packages/cli/lib/util/getDirectoriesObj.js +++ b/packages/cli/lib/util/getDirectoriesObj.js @@ -5,6 +5,7 @@ const settingsFileName = 'settings.json' const logFolderName = 'logs' const tfVersionsFolderName = 'versions' const tfvmAppDataFolderName = 'tfvm' +const otfvmAppDataFolderName = 'otfvm' const dirSeparator = '\\' /** @@ -13,13 +14,15 @@ const dirSeparator = '\\' export class TfvmFS { static appDataDir = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share') static tfvmDir = this.appDataDir.concat(dirSeparator + tfvmAppDataFolderName) // where tfvms own files are in AppData + static otfvmDir = this.appDataDir.concat(dirSeparator + otfvmAppDataFolderName) // where open tofu version manager (still tfvm) are in AppData static tfVersionsDir = this.tfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored static logsDir = this.tfvmDir.concat(dirSeparator + logFolderName) // where tfvm logs are stored (in appdata)= static terraformDir = this.appDataDir.concat(dirSeparator + 'terraform') // where the path is looking for terraform.exe to be found + static openTofuDir = this.appDataDir.concat(dirSeparator + 'OpenTofu') // where the path is looking for OpenTofu to be found static settingsDir = this.tfvmDir.concat(dirSeparator + settingsFileName) // where the tfvm settings file can be located static architecture = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? 'windows_amd64' : 'windows_386' static bitWidth = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? '64' : '32' - + static getDirectoriesObj () { return { appDataDir: this.appDataDir, @@ -27,7 +30,8 @@ export class TfvmFS { logsDir: this.logsDir, tfVersionsDir: this.tfVersionsDir, terraformDir: this.terraformDir, - settingsDir: this.settingsDir + settingsDir: this.settingsDir, + otfvmDir: this.otfvmDir } } @@ -37,6 +41,7 @@ export class TfvmFS { */ static async createTfAppDataDir () { if (!fs.existsSync(this.terraformDir)) fs.mkdirSync(TfvmFS.terraformDir) + if (!fs.existsSync(this.openTofuDir)) fs.mkdirSync(TfvmFS.openTofuDir) } /** @@ -48,6 +53,9 @@ export class TfvmFS { if ((await fsp.readdir(this.terraformDir)).includes('terraform.exe')) { await fsp.unlink(this.terraformDir + dirSeparator + 'terraform.exe') } + if ((await fsp.readdir(this.openTofuDir)).includes('tofu.exe')) { + await fsp.unlink(this.openTofuDir + dirSeparator + 'tofu.exe') + } } /** From 1eb21558b3535bb8251c079f4bc90f17b6b08741 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 1 Aug 2024 16:53:43 -0600 Subject: [PATCH 03/29] fix: url set up --- packages/cli/lib/commands/install.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 3a59883..d3a9388 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -58,11 +58,11 @@ export async function installFromWeb (versionNum, printMessage = true) { const newVersionDir = TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + versionNum) const settingsObj = await getSettings() - + let url if (settingsObj.useOpenTofu) { - const url = `https://github.com/opentofu/opentofu/releases/download/${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` + url = `https://github.com/opentofu/opentofu/releases/download/${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` } else { - const url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${TfvmFS.architecture}.zip` + url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${TfvmFS.architecture}.zip` } await download(url, zipPath, versionNum) await fs.mkdir(newVersionDir) From de7522deda074e3e54169988eee7be798ad6c8c3 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 1 Aug 2024 17:06:21 -0600 Subject: [PATCH 04/29] chore: open tofu versions dir and zip path --- packages/cli/lib/commands/install.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index d3a9388..78848fa 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -54,14 +54,19 @@ async function install (versionNum) { export default install export async function installFromWeb (versionNum, printMessage = true) { - const zipPath = TfvmFS.getPath(TfvmFS.tfVersionsDir, `v${versionNum}.zip`) - const newVersionDir = TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + versionNum) - const settingsObj = await getSettings() + let url + let zipPath + let newVersionDir + if (settingsObj.useOpenTofu) { + zipPath = TfvmFS.getPath(TfvmFS.openTofuVersionsDir, `v${versionNum}.zip`) + newVersionDir = TfvmFS.getPath(TfvmFS.openTofuVersionsDir, 'v' + versionNum) url = `https://github.com/opentofu/opentofu/releases/download/${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` } else { + zipPath = TfvmFS.getPath(TfvmFS.tfVersionsDir, `v${versionNum}.zip`) + newVersionDir = TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + versionNum) url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${TfvmFS.architecture}.zip` } await download(url, zipPath, versionNum) From 2dc155a3db5d84be266a14493a2dbc81f5f927d8 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 10:09:31 -0600 Subject: [PATCH 05/29] chore: spelling and syntax fix --- packages/cli/lib/commands/install.js | 2 +- packages/cli/lib/index.js | 5 +++-- packages/cli/lib/util/getDirectoriesObj.js | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 78848fa..2ced714 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -10,7 +10,7 @@ import getErrorMessage from '../util/errorChecker.js' import getTerraformVersion from '../util/tfVersion.js' import getLatest from '../util/getLatest.js' import { logger } from '../util/logger.js' -import { getSettings } from '../util/getSettings.js' +import getSettings from '../util/getSettings.js' async function install (versionNum) { try { diff --git a/packages/cli/lib/index.js b/packages/cli/lib/index.js index c8dc669..67c75b8 100755 --- a/packages/cli/lib/index.js +++ b/packages/cli/lib/index.js @@ -65,10 +65,11 @@ program .action(config) .addHelpText('after', '\nAll settings are either true or false (default is false), and set like this:\n' + chalk.cyan('\n tfvm config =\n\n') + - 'Herer are all the available settings:\n' + + 'Here are all the available settings:\n' + 'disableErrors - Disables some recurrent warning messages\n' + 'disableAWSWarnings - Disables warnings about needing old AWS authentication with tf versions older than 0.14.6\n' + - 'disableSettingsPrompts - Disables prompts to turn off warnings by enabling these settings') + 'disableSettingsPrompts - Disables prompts to turn off warnings by enabling these settings\n' + + 'useOpenTofu - Uses OpenTofu instead of Terraform') program .command('install ') diff --git a/packages/cli/lib/util/getDirectoriesObj.js b/packages/cli/lib/util/getDirectoriesObj.js index aea06fd..ac148e2 100644 --- a/packages/cli/lib/util/getDirectoriesObj.js +++ b/packages/cli/lib/util/getDirectoriesObj.js @@ -16,9 +16,10 @@ export class TfvmFS { static tfvmDir = this.appDataDir.concat(dirSeparator + tfvmAppDataFolderName) // where tfvms own files are in AppData static otfvmDir = this.appDataDir.concat(dirSeparator + otfvmAppDataFolderName) // where open tofu version manager (still tfvm) are in AppData static tfVersionsDir = this.tfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored + static openTofuVersionsDir = this.otfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored static logsDir = this.tfvmDir.concat(dirSeparator + logFolderName) // where tfvm logs are stored (in appdata)= static terraformDir = this.appDataDir.concat(dirSeparator + 'terraform') // where the path is looking for terraform.exe to be found - static openTofuDir = this.appDataDir.concat(dirSeparator + 'OpenTofu') // where the path is looking for OpenTofu to be found + static openTofuDir = this.appDataDir.concat(dirSeparator + 'opentofu') // where the path is looking for OpenTofu to be found static settingsDir = this.tfvmDir.concat(dirSeparator + settingsFileName) // where the tfvm settings file can be located static architecture = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? 'windows_amd64' : 'windows_386' static bitWidth = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? '64' : '32' From ff52957373a5eed47889bc7f3cd6a3063af6251a Mon Sep 17 00:00:00 2001 From: tab518 Date: Fri, 2 Aug 2024 10:16:02 -0600 Subject: [PATCH 06/29] feat: get latest function supports opentofu --- packages/cli/lib/util/getLatest.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/cli/lib/util/getLatest.js b/packages/cli/lib/util/getLatest.js index 186bc94..3e92ebe 100644 --- a/packages/cli/lib/util/getLatest.js +++ b/packages/cli/lib/util/getLatest.js @@ -1,12 +1,23 @@ import axios from 'axios' -import { logger } from './logger.js' +import {logger} from './logger.js' +import getSettings from "./getSettings.js"; export default async function () { + const settings = await getSettings() try { - const response = await axios.get('https://checkpoint-api.hashicorp.com/v1/check/terraform') - return response.data.current_version + if (settings.useOpenTofu){ + const response = await axios.get('https://api.github.com/repos/opentofu/opentofu/releases/latest') + return response.data.name.replace('v', '') + } else { + const response = await axios.get('https://checkpoint-api.hashicorp.com/v1/check/terraform') + return response.data.current_version + } } catch (e) { - logger.fatal(e, 'Error attempting to fetch latest terraform version with Checkpoint Hashicorp API:') + if (settings.useOpenTofu){ + logger.fatal(e, 'Error attempting to fetch latest opentofu version with GitHub API:') + } else { + logger.fatal(e, 'Error attempting to fetch latest terraform version with Checkpoint Hashicorp API:') + } return null } } From e6af177f740d69fc13e7dbedb6a36827267fbd1c Mon Sep 17 00:00:00 2001 From: tab518 Date: Fri, 2 Aug 2024 11:03:57 -0600 Subject: [PATCH 07/29] chore: improve some logging --- packages/cli/lib/util/constants.js | 1 + packages/cli/lib/util/download.js | 8 +++++++- packages/cli/lib/util/getInstalledVersions.js | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/cli/lib/util/constants.js b/packages/cli/lib/util/constants.js index 3c584a7..fbe0d2c 100644 --- a/packages/cli/lib/util/constants.js +++ b/packages/cli/lib/util/constants.js @@ -2,3 +2,4 @@ export const versionRegEx = /^v[0-9]+.{1}[0-9]+.{1}[0-9]+/ // Uses positive-lookbehind to only match versions preceded by 'Terraform ' but without extracting 'Terraform ' export const tfCurrVersionRegEx = /(?<=Terraform )v[0-9]+.{1}[0-9]+.{1}[0-9]+/gm +export const openTofuCurrVersionRegEx = /(?<=OpenTofu )v[0-9]+.{1}[0-9]+.{1}[0-9]+/gm diff --git a/packages/cli/lib/util/download.js b/packages/cli/lib/util/download.js index c034659..7866e20 100644 --- a/packages/cli/lib/util/download.js +++ b/packages/cli/lib/util/download.js @@ -3,9 +3,11 @@ import * as http from 'http' import chalk from 'chalk' import fs from 'node:fs' import { logger } from './logger.js' +import getSettings from "./getSettings.js"; async function download (url, filePath, version) { const proto = !url.charAt(4).localeCompare('s') ? https : http + const settings = await getSettings() return new Promise((resolve, reject) => { const file = fs.createWriteStream(filePath) @@ -14,7 +16,11 @@ async function download (url, filePath, version) { const request = proto.get(url, response => { if (response.statusCode !== 200) { fs.unlink(filePath, () => { - console.log(chalk.red.bold(`Terraform ${version} is not yet released or available.`)) + if (settings.useOpenTofu){ + console.log(chalk.red.bold(`OpenTofu ${version} is not yet released or available.`)) + } else { + console.log(chalk.red.bold(`Terraform ${version} is not yet released or available.`)) + } }) return } diff --git a/packages/cli/lib/util/getInstalledVersions.js b/packages/cli/lib/util/getInstalledVersions.js index 2533ad1..a1a3ac1 100644 --- a/packages/cli/lib/util/getInstalledVersions.js +++ b/packages/cli/lib/util/getInstalledVersions.js @@ -2,6 +2,7 @@ import fs from 'node:fs/promises' import { versionRegEx } from './constants.js' import { TfvmFS } from './getDirectoriesObj.js' import { logger } from './logger.js' +import getSettings from "./getSettings.js"; let installedVersions @@ -10,6 +11,7 @@ let installedVersions * @returns {Promise} */ async function getInstalledVersions () { + const settings = await getSettings() // return the list of installed versions if that is already cached if (!installedVersions) { const tfList = [] @@ -23,7 +25,11 @@ async function getInstalledVersions () { }) installedVersions = tfList } else { - logger.debug(`Unable to find installed versions of terraform with directoriesObj=${JSON.stringify(TfvmFS.getDirectoriesObj())} and files=${JSON.stringify(files)}`) + if (settings.useOpenTofu){ + logger.debug(`Unable to find installed versions of OpenTofu with directoriesObj=${JSON.stringify(TfvmFS.getDirectoriesObj())} and files=${JSON.stringify(files)}`) + } else { + logger.debug(`Unable to find installed versions of Terraform with directoriesObj=${JSON.stringify(TfvmFS.getDirectoriesObj())} and files=${JSON.stringify(files)}`) + } return [] } } From 46bd0629b2491352531f2392e1fb5610e4a94274 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 12:25:48 -0600 Subject: [PATCH 08/29] fix: install from web and download for redirection --- package-lock.json | 8 ++- packages/cli/lib/commands/install.js | 6 +- packages/cli/lib/commands/use.js | 17 +++-- .../cli/lib/scripts/addToPathOpenTofu.ps1 | 14 ++++ packages/cli/lib/util/download.js | 12 ++-- packages/cli/lib/util/getDirectoriesObj.js | 2 +- packages/cli/lib/util/getInstalledVersions.js | 17 +++-- packages/cli/lib/util/logger.js | 1 + packages/cli/lib/util/tfVersion.js | 15 +++- packages/cli/lib/util/verifySetup.js | 68 +++++++++++++++---- packages/cli/package.json | 1 + 11 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 packages/cli/lib/scripts/addToPathOpenTofu.ps1 diff --git a/package-lock.json b/package-lock.json index c6deb66..277b09b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4091,15 +4091,16 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -10728,6 +10729,7 @@ "commander": "^9.4.0", "compare-versions": "^6.0.0", "enquirer": "^2.3.6", + "follow-redirects": "^1.15.6", "node-stream-zip": "^1.15.0", "pino": "^8.14.1", "pino-pretty": "^10.0.0" diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 2ced714..9485c95 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -61,9 +61,9 @@ export async function installFromWeb (versionNum, printMessage = true) { let newVersionDir if (settingsObj.useOpenTofu) { - zipPath = TfvmFS.getPath(TfvmFS.openTofuVersionsDir, `v${versionNum}.zip`) - newVersionDir = TfvmFS.getPath(TfvmFS.openTofuVersionsDir, 'v' + versionNum) - url = `https://github.com/opentofu/opentofu/releases/download/${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` + zipPath = TfvmFS.getPath(TfvmFS.otfVersionsDir, `v${versionNum}.zip`) + newVersionDir = TfvmFS.getPath(TfvmFS.otfVersionsDir, 'v' + versionNum) + url = `https://github.com/opentofu/opentofu/releases/download/v${versionNum}/tofu_${versionNum}_${TfvmFS.architecture}.zip` } else { zipPath = TfvmFS.getPath(TfvmFS.tfVersionsDir, `v${versionNum}.zip`) newVersionDir = TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + versionNum) diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index b29a02a..eab7645 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -67,17 +67,24 @@ export async function installNewVersion (version) { * @returns {Promise} */ export async function switchVersionTo (version) { + const settings = await getSettings() if (version[0] === 'v') version = version.substring(1) await TfvmFS.createTfAppDataDir() await TfvmFS.deleteCurrentTfExe() - await fs.copyFile( - TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + version, 'terraform.exe'), // source file - TfvmFS.getPath(TfvmFS.terraformDir, 'terraform.exe') // destination file - ) + if (settings.useOpenTofu) { + await fs.copyFile( + TfvmFS.getPath(TfvmFS.otfVersionsDir, 'v' + version, 'tofu.exe'), // source file + TfvmFS.getPath(TfvmFS.openTofuDir, 'tofu.exe') // destination file + ) + } else { + await fs.copyFile( + TfvmFS.getPath(TfvmFS.tfVersionsDir, 'v' + version, 'terraform.exe'), // source file + TfvmFS.getPath(TfvmFS.terraformDir, 'terraform.exe') // destination file + ) + } console.log(chalk.cyan.bold(`Now using terraform v${version} (${TfvmFS.bitWidth}-bit)`)) - const settings = await getSettings() if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { console.log(chalk.yellow.bold('Warning: This tf version is not compatible with the newest ' + 'AWS CLI authentication methods (e.g. aws sso login). Use short-term credentials instead.')) diff --git a/packages/cli/lib/scripts/addToPathOpenTofu.ps1 b/packages/cli/lib/scripts/addToPathOpenTofu.ps1 new file mode 100644 index 0000000..7227193 --- /dev/null +++ b/packages/cli/lib/scripts/addToPathOpenTofu.ps1 @@ -0,0 +1,14 @@ +# This script attempts to add opentofu to the system and local paths. +# It will not add it if it already exists. +# It will not add it to the system path if the shell it is being run from does not have admin rights + +$userName = $env:UserName +$path2addOpenTofu = ";C:\Users\$userName\AppData\Roaming\opentofu" +$userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); + +# attempts to add to local path +If (!$userPath.contains($path2addOpenTofu)) { + $userPath += $path2addOpenTofu + $userPath = $userPath -join ';' + [Environment]::SetEnvironmentVariable('Path', $userPath, 'User'); +} diff --git a/packages/cli/lib/util/download.js b/packages/cli/lib/util/download.js index 7866e20..2b96840 100644 --- a/packages/cli/lib/util/download.js +++ b/packages/cli/lib/util/download.js @@ -1,9 +1,9 @@ -import * as https from 'https' -import * as http from 'http' import chalk from 'chalk' import fs from 'node:fs' import { logger } from './logger.js' -import getSettings from "./getSettings.js"; +import getSettings from './getSettings.js' +import followRedirects from 'follow-redirects' +const { http, https } = followRedirects async function download (url, filePath, version) { const proto = !url.charAt(4).localeCompare('s') ? https : http @@ -16,11 +16,7 @@ async function download (url, filePath, version) { const request = proto.get(url, response => { if (response.statusCode !== 200) { fs.unlink(filePath, () => { - if (settings.useOpenTofu){ - console.log(chalk.red.bold(`OpenTofu ${version} is not yet released or available.`)) - } else { - console.log(chalk.red.bold(`Terraform ${version} is not yet released or available.`)) - } + console.log(chalk.red.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} ${version} is not yet released or available.`)) }) return } diff --git a/packages/cli/lib/util/getDirectoriesObj.js b/packages/cli/lib/util/getDirectoriesObj.js index ac148e2..9ba695a 100644 --- a/packages/cli/lib/util/getDirectoriesObj.js +++ b/packages/cli/lib/util/getDirectoriesObj.js @@ -16,7 +16,7 @@ export class TfvmFS { static tfvmDir = this.appDataDir.concat(dirSeparator + tfvmAppDataFolderName) // where tfvms own files are in AppData static otfvmDir = this.appDataDir.concat(dirSeparator + otfvmAppDataFolderName) // where open tofu version manager (still tfvm) are in AppData static tfVersionsDir = this.tfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored - static openTofuVersionsDir = this.otfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored + static otfVersionsDir = this.otfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored static logsDir = this.tfvmDir.concat(dirSeparator + logFolderName) // where tfvm logs are stored (in appdata)= static terraformDir = this.appDataDir.concat(dirSeparator + 'terraform') // where the path is looking for terraform.exe to be found static openTofuDir = this.appDataDir.concat(dirSeparator + 'opentofu') // where the path is looking for OpenTofu to be found diff --git a/packages/cli/lib/util/getInstalledVersions.js b/packages/cli/lib/util/getInstalledVersions.js index a1a3ac1..0610785 100644 --- a/packages/cli/lib/util/getInstalledVersions.js +++ b/packages/cli/lib/util/getInstalledVersions.js @@ -2,7 +2,7 @@ import fs from 'node:fs/promises' import { versionRegEx } from './constants.js' import { TfvmFS } from './getDirectoriesObj.js' import { logger } from './logger.js' -import getSettings from "./getSettings.js"; +import getSettings from './getSettings.js' let installedVersions @@ -14,18 +14,23 @@ async function getInstalledVersions () { const settings = await getSettings() // return the list of installed versions if that is already cached if (!installedVersions) { - const tfList = [] + let versionsList = [] + let files + if (settings.useOpenTofu) { + files = await fs.readdir(TfvmFS.otfVersionsDir) + } else { + files = await fs.readdir(TfvmFS.tfVersionsDir) + } - const files = await fs.readdir(TfvmFS.tfVersionsDir) if (files && files.length) { files.forEach(file => { if (versionRegEx.test(file)) { - tfList.push(file) + versionsList.push(file) } }) - installedVersions = tfList + installedVersions = versionsList } else { - if (settings.useOpenTofu){ + if (settings.useOpenTofu) { logger.debug(`Unable to find installed versions of OpenTofu with directoriesObj=${JSON.stringify(TfvmFS.getDirectoriesObj())} and files=${JSON.stringify(files)}`) } else { logger.debug(`Unable to find installed versions of Terraform with directoriesObj=${JSON.stringify(TfvmFS.getDirectoriesObj())} and files=${JSON.stringify(files)}`) diff --git a/packages/cli/lib/util/logger.js b/packages/cli/lib/util/logger.js index 7e7edbb..33735dc 100644 --- a/packages/cli/lib/util/logger.js +++ b/packages/cli/lib/util/logger.js @@ -8,6 +8,7 @@ const date = new Date().toISOString().split('T')[0] // before creating a log file, make sure the logs folder exists (and its parent tfvm folder) if (!fs.existsSync(TfvmFS.tfvmDir)) fs.mkdirSync(TfvmFS.tfvmDir) +if (!fs.existsSync(TfvmFS.otfvmDir)) fs.mkdirSync(TfvmFS.otfvmDir) if (!fs.existsSync(TfvmFS.logsDir)) fs.mkdirSync(TfvmFS.logsDir) // store logs in AppData so that they are maintained when switching node versions diff --git a/packages/cli/lib/util/tfVersion.js b/packages/cli/lib/util/tfVersion.js index 144f584..e25cd6c 100644 --- a/packages/cli/lib/util/tfVersion.js +++ b/packages/cli/lib/util/tfVersion.js @@ -1,17 +1,28 @@ import runShell from './runShell.js' import { logger } from './logger.js' import { tfCurrVersionRegEx } from './constants.js' +import getSettings from './getSettings.js' let currentTfVersion +let currentOtfVersion /** * Returns the current terraform version, if there is one. Returns null if there is no current version * @returns {Promise} */ async function getTerraformVersion () { + const settings = await getSettings() // cache current tf version during a single execution of tfvm - if (currentTfVersion) return currentTfVersion - const response = (await runShell('terraform -v')) + if (currentTfVersion && !settings.useOpenTofu) return currentTfVersion + if (currentOtfVersion && settings.useOpenTofu) return currentOtfVersion + + let response + if (settings.useOpenTofu) { + response = (await runShell('tofu -v')) + } else { + response = (await runShell('terraform -v')) + } + if (response == null) { logger.error('Error getting terraform version') return null diff --git a/packages/cli/lib/util/verifySetup.js b/packages/cli/lib/util/verifySetup.js index 25f8aa6..86abdb4 100644 --- a/packages/cli/lib/util/verifySetup.js +++ b/packages/cli/lib/util/verifySetup.js @@ -12,9 +12,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url)) async function verifySetup () { // STEP 1: Check that the appdata/roaming/tfvm folder exists if (!fs.existsSync(TfvmFS.tfVersionsDir)) fs.mkdirSync(TfvmFS.tfVersionsDir) + if (!fs.existsSync(TfvmFS.otfVersionsDir)) fs.mkdirSync(TfvmFS.otfVersionsDir) // STEP 2: Check that the path is set const tfPaths = [] + const otfPaths = [] const PATH = await runShell('echo %path%') logger.trace(`PATH in verifySetup(): ${PATH}`) if (PATH == null) { @@ -23,28 +25,70 @@ async function verifySetup () { } // do we want to have an error here? const pathVars = PATH.split(';') let pathVarDoesntExist = true + let pathVarDoesntExistOpenTofu = true for (const variable of pathVars) { if (variable.replace(/[\r\n]/gm, '') === TfvmFS.terraformDir) pathVarDoesntExist = false if (variable.toLowerCase().includes('terraform') && variable.replace(/[\r\n]/gm, '') !== TfvmFS.terraformDir) { // strip newlines tfPaths.push(variable) } + if (variable.replace(/[\r\n]/gm, '') === TfvmFS.openTofuDir) pathVarDoesntExistOpenTofu = false + if (variable.toLowerCase().includes('opentofu') && variable.replace(/[\r\n]/gm, '') !== TfvmFS.openTofuDir) { // strip newlines + otfPaths.push(variable) + } } - if (pathVarDoesntExist) { + if (pathVarDoesntExist || pathVarDoesntExistOpenTofu) { // add to local paths logger.warn(`Couldn't find tfvm in path where this is the path: ${PATH}`) - logger.debug('Attempting to run addToPath.ps1...') - if (await runShell(resolve(__dirname, './../scripts/addToPath.ps1'), { shell: 'powershell.exe' }) == null) { - console.log(chalk.red.bold('tfvm script failed to run. Please run the following command in a powershell window:\n')) - console.log(chalk.blue.bold('Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser')) - } else { - logger.debug('Successfully ran addToPath.ps1, added to path.') - console.log(chalk.red.bold('We couldn\'t find the right path variable for terraform, so we just added it.\n' + - 'Please restart your terminal, or open a new one, for terraform to work correctly.\n')) + logger.debug(`Attempting to run ${pathVarDoesntExist ? 'addToPath.ps1' : 'addToPath.ps1'}...`) + if (pathVarDoesntExist) { + if (await runShell(resolve(__dirname, './../scripts/addToPath.ps1'), { shell: 'powershell.exe' }) == null) { + console.log(chalk.red.bold('tfvm script failed to run. Please run the following command in a powershell window:\n')) + console.log(chalk.blue.bold('Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser')) + } else { + logger.debug('Successfully ran addToPath.ps1, added to path.') + console.log(chalk.red.bold('We couldn\'t find the right path variable for terraform, so we just added it.\n' + + 'Please restart your terminal, or open a new one, for terraform to work correctly.\n')) + } + if (await runShell(resolve(__dirname, './../scripts/addToPathOpenTofu.ps1'), { shell: 'powershell.exe' }) == null) { + console.log(chalk.red.bold('tfvm script failed to run. Please run the following command in a powershell window:\n')) + console.log(chalk.blue.bold('Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser')) + } else { + logger.debug('Successfully ran addToPathOpenTofu.ps1, added to path.') + console.log(chalk.red.bold('We couldn\'t find the right path variable for opentofu, so we just added it.\n' + + 'Please restart your terminal, or open a new one, for opentofu to work correctly.\n')) + } + return false } - return false } const settings = await getSettings() + // OpenTofu path check if (settings.disableErrors === 'false') { + if (otfPaths.length === 1) { + if (otfPaths[0] !== TfvmFS.openTofuDir) { + logger.error(`Extra opentofu path in PATH: ${otfPaths[0]}.`) + console.log(chalk.red.bold(`It appears you have ${otfPaths[0]} in your Path system environmental variables.`)) + console.log(chalk.red.bold('This may stop tfvm from working correctly, so please remove this from the path.\n' + + 'If you make changes to the path, make sure to restart your terminal.')) + if (!settings.disableSettingPrompts) { + console.log(chalk.cyan.bold('To disable this error run \'tfvm config disableErrors=true\'')) + } + return false + } + } else if (otfPaths.length > 1) { + console.log(chalk.red.bold('Your Path environmental variable includes the following terraform paths:')) + for (const badPath of otfPaths) { + logger.warn(`It appears you have ${badPath} in your environmental variables, which may be bad.`) + console.log(chalk.red.bold(badPath)) + } + console.log(chalk.red.bold('This may stop tfvm from working correctly, so please remove these from the path.\n' + + 'If you make changes to the path, make sure to restart your terminal.')) + if (!settings.disableSettingPrompts) { + console.log(chalk.cyan.bold('To disable this error run \'tfvm config disableErrors=true\'')) + } + logger.trace('verifySetup exited unsuccessfully') + return false + } + // Terraform path check if (tfPaths.length === 1) { if (tfPaths[0] !== TfvmFS.terraformDir) { logger.error(`Extra terraform path in PATH: ${tfPaths[0]}.`) @@ -67,11 +111,11 @@ async function verifySetup () { if (!settings.disableSettingPrompts) { console.log(chalk.cyan.bold('To disable this error run \'tfvm config disableErrors=true\'')) } - logger.trace('verifySetup excited unsuccessfully') + logger.trace('verifySetup exited unsuccessfully') return false } } - logger.trace('verifySetup excited successfully') + logger.trace('verifySetup exited successfully') return true } diff --git a/packages/cli/package.json b/packages/cli/package.json index e044477..4ba819a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,6 +38,7 @@ "commander": "^9.4.0", "compare-versions": "^6.0.0", "enquirer": "^2.3.6", + "follow-redirects": "^1.15.6", "node-stream-zip": "^1.15.0", "pino": "^8.14.1", "pino-pretty": "^10.0.0" From ccf77518e6ee1e69ba7a0db880d9b721fbfc3f47 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 13:44:47 -0600 Subject: [PATCH 09/29] feat: support current with OpenTofu and update README --- packages/cli/README.md | 1 + packages/cli/lib/commands/current.js | 10 ++++++---- packages/cli/lib/commands/uninstall.js | 14 ++++++++++---- packages/cli/lib/commands/use.js | 7 ++++--- packages/cli/lib/util/tfVersion.js | 8 ++++++-- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 1121cbc..937d9b0 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -66,6 +66,7 @@ Run `tfvm` in any command line, followed by one of these commands: - `tfvm config disableErrors=true` - disables configuration warnings. - `tfvm config disableAWSWarnings=true` - disables AWS warnings that appear when using older terraform versions. - `tfvm config disableSettingPrompts=true` - disables prompts that show how to hide some error messages. + - `tfvm config useOpenTofu=true` - uses the open source version of Terraform, OpenTofu (experimental flag) - `help`: prints usage information. Run `tfvm help ` to see information about the other tfvm commands. ## FAQ diff --git a/packages/cli/lib/commands/current.js b/packages/cli/lib/commands/current.js index 1cbbae3..b6cf491 100644 --- a/packages/cli/lib/commands/current.js +++ b/packages/cli/lib/commands/current.js @@ -4,18 +4,20 @@ import getTerraformVersion from '../util/tfVersion.js' import getErrorMessage from '../util/errorChecker.js' import { logger } from '../util/logger.js' import { TfvmFS } from '../util/getDirectoriesObj.js' +import getSettings from '../util/getSettings.js' async function current () { try { + const settings = await getSettings() const currentTFVersion = await getTerraformVersion() if (currentTFVersion !== null) { - console.log(chalk.white.bold('Current Terraform version:\n' + + console.log(chalk.white.bold(`Current ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} version:\n` + currentTFVersion + ` (Currently using ${TfvmFS.bitWidth}-bit executable)`)) } else { - console.log(chalk.cyan.bold('It appears there is no terraform version running on your computer, or ' + + console.log(chalk.cyan.bold(`It appears there is no ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version running on your computer, or ` + 'there was an error extracting the version.\n')) - console.log(chalk.green.bold('Run tfvm use to set your terraform version, ' + - 'or `terraform -v` to manually check the current version.')) + console.log(chalk.green.bold(`Run tfvm use to set your ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version, ` + + `or ${settings.useOpenTofu ? 'tofu' : 'terraform'} -v to manually check the current version.`)) } } catch (error) { logger.fatal(error, 'Fatal error when running "current" command: ') diff --git a/packages/cli/lib/commands/uninstall.js b/packages/cli/lib/commands/uninstall.js index 58668be..f100c87 100644 --- a/packages/cli/lib/commands/uninstall.js +++ b/packages/cli/lib/commands/uninstall.js @@ -5,6 +5,7 @@ import getInstalledVersions from '../util/getInstalledVersions.js' import { TfvmFS } from '../util/getDirectoriesObj.js' import getErrorMessage from '../util/errorChecker.js' import { logger } from '../util/logger.js' +import getSettings from '../util/getSettings.js' async function uninstall (uninstallVersion) { try { @@ -12,13 +13,18 @@ async function uninstall (uninstallVersion) { if (!versionRegEx.test(uninstallVersion)) { console.log(chalk.red.bold('Invalid version syntax')) } else { + const settings = await getSettings() const installedVersions = await getInstalledVersions() if (!installedVersions.includes(uninstallVersion)) { - console.log(chalk.white.bold(`terraform ${uninstallVersion} is not installed. Type "tfvm list" to see what is installed.`)) + console.log(chalk.white.bold(`${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion} is not installed. Type "tfvm list" to see what is installed.`)) } else { - console.log(chalk.white.bold(`Uninstalling terraform ${uninstallVersion}...`)) - await TfvmFS.deleteDirectory(TfvmFS.tfVersionsDir, uninstallVersion) - console.log(chalk.cyan.bold(`Successfully uninstalled terraform ${uninstallVersion}`)) + console.log(chalk.white.bold(`Uninstalling ${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion}...`)) + if (settings.useOpenTofu) { + await TfvmFS.deleteDirectory(TfvmFS.otfVersionsDir, uninstallVersion) + } else { + await TfvmFS.deleteDirectory(TfvmFS.tfVersionsDir, uninstallVersion) + } + console.log(chalk.cyan.bold(`Successfully uninstalled ${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion}`)) } } } catch (error) { diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index eab7645..ade8e7e 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -47,13 +47,14 @@ export async function useVersion (version) { * @returns {Promise} true if the user opted to install the version, false if they did not */ export async function installNewVersion (version) { - console.log(chalk.white.bold(`Terraform v${version} is not installed. Would you like to install it?`)) + const settings = getSettings() + console.log(chalk.white.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) const installToggle = new enquirer.Toggle({ disabled: 'Yes', enabled: 'No' }) if (await installToggle.run()) { - console.log(chalk.white.bold(`No action taken. Use 'tfvm install ${version}' to install terraform v${version}`)) + console.log(chalk.white.bold(`No action taken. Use 'tfvm install ${version}' to install ${settings.useOpenTofu ? 'opentofu' : 'terraform'} v${version}`)) return false } else { await installFromWeb(version, false) @@ -84,7 +85,7 @@ export async function switchVersionTo (version) { TfvmFS.getPath(TfvmFS.terraformDir, 'terraform.exe') // destination file ) } - console.log(chalk.cyan.bold(`Now using terraform v${version} (${TfvmFS.bitWidth}-bit)`)) + console.log(chalk.cyan.bold(`Now using ${settings.useOpenTofu ? 'opentofu' : 'terraform'} v${version} (${TfvmFS.bitWidth}-bit)`)) if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { console.log(chalk.yellow.bold('Warning: This tf version is not compatible with the newest ' + 'AWS CLI authentication methods (e.g. aws sso login). Use short-term credentials instead.')) diff --git a/packages/cli/lib/util/tfVersion.js b/packages/cli/lib/util/tfVersion.js index e25cd6c..c36c62e 100644 --- a/packages/cli/lib/util/tfVersion.js +++ b/packages/cli/lib/util/tfVersion.js @@ -1,6 +1,6 @@ import runShell from './runShell.js' import { logger } from './logger.js' -import { tfCurrVersionRegEx } from './constants.js' +import { tfCurrVersionRegEx, openTofuCurrVersionRegEx } from './constants.js' import getSettings from './getSettings.js' let currentTfVersion @@ -27,7 +27,11 @@ async function getTerraformVersion () { logger.error('Error getting terraform version') return null } - const versionExtractionResult = Array.from(response.matchAll(tfCurrVersionRegEx)) + + let versionExtractionResult + if (settings.useOpenTofu) versionExtractionResult = Array.from(response.matchAll(openTofuCurrVersionRegEx)) + else versionExtractionResult = Array.from(response.matchAll(tfCurrVersionRegEx)) + if (versionExtractionResult.length === 0) { logger.error('Error extracting terraform version where this is the response from `terraform -v`:\n' + response) return null From 135f7f0cf8fe1316d2fb1977a414d09e695884dc Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 14:22:30 -0600 Subject: [PATCH 10/29] fix: regex bug --- packages/cli/lib/commands/install.js | 14 ++++++++++---- packages/cli/lib/util/constants.js | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 9485c95..8f86293 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -14,12 +14,18 @@ import getSettings from '../util/getSettings.js' async function install (versionNum) { try { + const settings = getSettings() const installVersion = 'v' + versionNum if (!versionRegEx.test(installVersion) && versionNum !== 'latest') { logger.warn(`invalid version attempted to install with version ${installVersion}`) console.log(chalk.red.bold('Invalid version syntax.')) - console.log(chalk.white.bold('Version should be formatted as \'vX.X.X\'\nGet a list of all current ' + + if (settings.useOpenTofu) { + console.log(chalk.white.bold('Version should be formatted as \'vX.X.X\'\nGet a list of all current ' + + 'opentofu versions here: https://github.com/opentofu/opentofu/releases')) + } else { + console.log(chalk.white.bold('Version should be formatted as \'vX.X.X\'\nGet a list of all current ' + 'terraform versions here: https://releases.hashicorp.com/terraform/')) + } } else if (versionNum === 'latest') { const installedVersions = await getInstalledVersions() const latest = await getLatest() @@ -27,11 +33,11 @@ async function install (versionNum) { if (latest) { const versionLatest = 'v' + latest if (installedVersions.includes(versionLatest) && currentVersion !== versionLatest) { - console.log(chalk.bold.cyan(`The latest terraform version is ${latest} and is ` + + console.log(chalk.bold.cyan(`The latest ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version is ${latest} and is ` + `already installed on your computer. Run 'tfvm use ${latest}' to use.`)) } else if (installedVersions.includes(versionLatest) && currentVersion === versionLatest) { const currentVersion = await getTerraformVersion() - console.log(chalk.bold.cyan(`The latest terraform version is ${currentVersion} and ` + + console.log(chalk.bold.cyan(`The latest ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version is ${currentVersion} and ` + 'is already installed and in use on your computer.')) } else { await installFromWeb(latest) @@ -40,7 +46,7 @@ async function install (versionNum) { } else { const installedVersions = await getInstalledVersions() if (installedVersions.includes(installVersion)) { - console.log(chalk.white.bold(`Terraform version ${installVersion} is already installed.`)) + console.log(chalk.white.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} version ${installVersion} is already installed.`)) } else { await installFromWeb(versionNum) } diff --git a/packages/cli/lib/util/constants.js b/packages/cli/lib/util/constants.js index fbe0d2c..d937407 100644 --- a/packages/cli/lib/util/constants.js +++ b/packages/cli/lib/util/constants.js @@ -1,4 +1,4 @@ -export const versionRegEx = /^v[0-9]+.{1}[0-9]+.{1}[0-9]+/ +export const versionRegEx = /^v[0-9]+\.[0-9]+\.[0-9]+/ // Uses positive-lookbehind to only match versions preceded by 'Terraform ' but without extracting 'Terraform ' export const tfCurrVersionRegEx = /(?<=Terraform )v[0-9]+.{1}[0-9]+.{1}[0-9]+/gm From 77a8e1eefd2617765988f92f9cceb2fb19b37bcc Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 14:36:33 -0600 Subject: [PATCH 11/29] fix: await for settings --- packages/cli/lib/commands/install.js | 2 +- packages/cli/lib/commands/use.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 8f86293..2040504 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -14,7 +14,7 @@ import getSettings from '../util/getSettings.js' async function install (versionNum) { try { - const settings = getSettings() + const settings = await getSettings() const installVersion = 'v' + versionNum if (!versionRegEx.test(installVersion) && versionNum !== 'latest') { logger.warn(`invalid version attempted to install with version ${installVersion}`) diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index ade8e7e..7a04414 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -47,7 +47,7 @@ export async function useVersion (version) { * @returns {Promise} true if the user opted to install the version, false if they did not */ export async function installNewVersion (version) { - const settings = getSettings() + const settings = await getSettings() console.log(chalk.white.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) const installToggle = new enquirer.Toggle({ disabled: 'Yes', From 4157565005ed6722453b9f76099edc02f94c25f7 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 14:45:21 -0600 Subject: [PATCH 12/29] chore: get rid of extra line --- packages/cli/lib/commands/list.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/lib/commands/list.js b/packages/cli/lib/commands/list.js index 0127ffb..352cbf6 100644 --- a/packages/cli/lib/commands/list.js +++ b/packages/cli/lib/commands/list.js @@ -14,7 +14,6 @@ async function list () { if (tfList.length > 0) { const currentTFVersion = await getTerraformVersion() - console.log('\n') tfList.sort(compareVersions).reverse() for (const versionDir of tfList) { if (versionDir === currentTFVersion) { From de1f8baf1bb18a46e14becd25ce4c8fe6a9b034d Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 2 Aug 2024 16:50:43 -0600 Subject: [PATCH 13/29] fix: lint and delete file --- packages/cli/lib/util/getDirectoriesObj.js | 93 ---------------------- packages/cli/lib/util/getLatest.js | 8 +- packages/cli/lib/util/verifySetup.js | 3 +- 3 files changed, 5 insertions(+), 99 deletions(-) delete mode 100644 packages/cli/lib/util/getDirectoriesObj.js diff --git a/packages/cli/lib/util/getDirectoriesObj.js b/packages/cli/lib/util/getDirectoriesObj.js deleted file mode 100644 index 9ba695a..0000000 --- a/packages/cli/lib/util/getDirectoriesObj.js +++ /dev/null @@ -1,93 +0,0 @@ -import fsp from 'node:fs/promises' -import fs from 'node:fs' - -const settingsFileName = 'settings.json' -const logFolderName = 'logs' -const tfVersionsFolderName = 'versions' -const tfvmAppDataFolderName = 'tfvm' -const otfvmAppDataFolderName = 'otfvm' -const dirSeparator = '\\' - -/** - * TFVM File System Class - */ -export class TfvmFS { - static appDataDir = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share') - static tfvmDir = this.appDataDir.concat(dirSeparator + tfvmAppDataFolderName) // where tfvms own files are in AppData - static otfvmDir = this.appDataDir.concat(dirSeparator + otfvmAppDataFolderName) // where open tofu version manager (still tfvm) are in AppData - static tfVersionsDir = this.tfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored - static otfVersionsDir = this.otfvmDir.concat(dirSeparator + tfVersionsFolderName) // where all the versions of terraform are stored - static logsDir = this.tfvmDir.concat(dirSeparator + logFolderName) // where tfvm logs are stored (in appdata)= - static terraformDir = this.appDataDir.concat(dirSeparator + 'terraform') // where the path is looking for terraform.exe to be found - static openTofuDir = this.appDataDir.concat(dirSeparator + 'opentofu') // where the path is looking for OpenTofu to be found - static settingsDir = this.tfvmDir.concat(dirSeparator + settingsFileName) // where the tfvm settings file can be located - static architecture = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? 'windows_amd64' : 'windows_386' - static bitWidth = process.env.PROCESSOR_ARCHITECTURE === 'AMD64' ? '64' : '32' - - static getDirectoriesObj () { - return { - appDataDir: this.appDataDir, - tfvmDir: this.tfvmDir, - logsDir: this.logsDir, - tfVersionsDir: this.tfVersionsDir, - terraformDir: this.terraformDir, - settingsDir: this.settingsDir, - otfvmDir: this.otfvmDir - } - } - - /** - * Creates appdata/roaming/terraform of it doesn't already exist - * @returns {Promise} - */ - static async createTfAppDataDir () { - if (!fs.existsSync(this.terraformDir)) fs.mkdirSync(TfvmFS.terraformDir) - if (!fs.existsSync(this.openTofuDir)) fs.mkdirSync(TfvmFS.openTofuDir) - } - - /** - * Deletes the terraform exe so that a new one can be copied in - * @returns {Promise} - */ - static async deleteCurrentTfExe () { - // if appdata/roaming/terraform/terraform.exe exists, delete it - if ((await fsp.readdir(this.terraformDir)).includes('terraform.exe')) { - await fsp.unlink(this.terraformDir + dirSeparator + 'terraform.exe') - } - if ((await fsp.readdir(this.openTofuDir)).includes('tofu.exe')) { - await fsp.unlink(this.openTofuDir + dirSeparator + 'tofu.exe') - } - } - - /** - * Creates a 'path' by joining the arguments together with the system-specific dirSeparator - * @param {...string} items - * @returns {string} - */ - static getPath = (...items) => items.join(dirSeparator) - - /** - * removes the file name from a given path - * @param {string} path - * @returns {unknown} - */ - static getFileNameFromPath = (path) => path.split(dirSeparator).pop() - - /** - * Delete a directory (and all containing files) from a parent directory - * @param {string} baseDirectory path of the parent directory - * @param {string} removeDir path of the directory to remove (relative to the baseDirectory) - * @returns {Promise} - */ - static async deleteDirectory (baseDirectory, removeDir) { - const baseDirFiles = await fsp.readdir(baseDirectory) - if (baseDirFiles.includes(removeDir)) { - const fullPath = baseDirectory + dirSeparator + removeDir - const files = await fsp.readdir(fullPath) - for (const file of files) { - await fsp.unlink(fullPath + dirSeparator + file) - } - await fsp.rmdir(fullPath) - } - } -} diff --git a/packages/cli/lib/util/getLatest.js b/packages/cli/lib/util/getLatest.js index 3e92ebe..c167f80 100644 --- a/packages/cli/lib/util/getLatest.js +++ b/packages/cli/lib/util/getLatest.js @@ -1,11 +1,11 @@ import axios from 'axios' -import {logger} from './logger.js' -import getSettings from "./getSettings.js"; +import { logger } from './logger.js' +import getSettings from './getSettings.js' export default async function () { const settings = await getSettings() try { - if (settings.useOpenTofu){ + if (settings.useOpenTofu) { const response = await axios.get('https://api.github.com/repos/opentofu/opentofu/releases/latest') return response.data.name.replace('v', '') } else { @@ -13,7 +13,7 @@ export default async function () { return response.data.current_version } } catch (e) { - if (settings.useOpenTofu){ + if (settings.useOpenTofu) { logger.fatal(e, 'Error attempting to fetch latest opentofu version with GitHub API:') } else { logger.fatal(e, 'Error attempting to fetch latest terraform version with Checkpoint Hashicorp API:') diff --git a/packages/cli/lib/util/verifySetup.js b/packages/cli/lib/util/verifySetup.js index 8eebf44..74b030f 100644 --- a/packages/cli/lib/util/verifySetup.js +++ b/packages/cli/lib/util/verifySetup.js @@ -4,7 +4,6 @@ import getSettings from './getSettings.js' import runShell from './runShell.js' import { logger } from './logger.js' import { getOS } from './tfvmOS.js' -import TfvmFS from './getDirectoriesObj.js' async function verifySetup () { const os = getOS() @@ -54,7 +53,7 @@ async function verifySetup () { 'Please restart your terminal, or open a new one, for terraform to work correctly.\n')) } } else { - if (await runShell(resolve(__dirname, './../scripts/addToPathOpenTofu.ps1'), { shell: 'powershell.exe' }) == null) { + if (await runShell(...(__dirname, './../scripts/addToPathOpenTofu.ps1'), { shell: 'powershell.exe' }) == null) { console.log(chalk.red.bold('tfvm script failed to run. Please run the following command in a powershell window:\n')) console.log(chalk.blue.bold('Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser')) } else { From 184eb42baa2fabc5c383669b82baaf8f25b15609 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Mon, 5 Aug 2024 12:28:48 -0600 Subject: [PATCH 14/29] fix: scripts for opentofu and working with beta branch changes --- packages/cli/lib/commands/install.js | 1 - .../cli/lib/scripts/addToPathLinuxOpenTofu.sh | 25 ++++++++++++++++ .../cli/lib/scripts/addToPathMacOpenTofu.sh | 25 ++++++++++++++++ ...nTofu.ps1 => addToPathWindowsOpenTofu.ps1} | 0 packages/cli/lib/util/tfvmOS.js | 25 +++++++++++++--- packages/cli/lib/util/verifySetup.js | 29 +++++++++---------- 6 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 packages/cli/lib/scripts/addToPathLinuxOpenTofu.sh create mode 100644 packages/cli/lib/scripts/addToPathMacOpenTofu.sh rename packages/cli/lib/scripts/{addToPathOpenTofu.ps1 => addToPathWindowsOpenTofu.ps1} (100%) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index f2f8c6f..667b34e 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -11,7 +11,6 @@ import { logger } from '../util/logger.js' import getSettings from '../util/getSettings.js' import { getOS, Mac } from '../util/tfvmOS.js' import { compare } from 'compare-versions' -import { TfvmFS } from '../util/getDirectoryObj.js' const os = getOS() const LAST_TF_VERSION_WITHOUT_ARM = '1.0.1' diff --git a/packages/cli/lib/scripts/addToPathLinuxOpenTofu.sh b/packages/cli/lib/scripts/addToPathLinuxOpenTofu.sh new file mode 100644 index 0000000..a91ef7e --- /dev/null +++ b/packages/cli/lib/scripts/addToPathLinuxOpenTofu.sh @@ -0,0 +1,25 @@ + +#!/bin/bash + +# Set the path to add +path2add="$HOME/.local/share/opentofu" + +# Get the user's PATH +userPath=$(echo $PATH) + +# Check if the path already contains the path to add +if [[ ! "$userPath" == *"$path2add"* ]]; then + # Add the path to the user's PATH + export PATH="$PATH:$path2add" + # Update .bashrc to persist the changes (also creates .bashrc if it doesn't already exist) + echo "export PATH=\"\$PATH:$path2add\"" >> ~/.bashrc + echo "OpenTofu path added to user PATH in .bashrc." + # Check if .zshrc exists + if [ -f "$HOME/.zshrc" ]; then + # Add the path to .zshrc + echo "export PATH=\"\$PATH:$path2add\"" >> ~/.zshrc + echo "OpenTofu path added to user PATH in .zshrc." + fi +else + echo "OpenTofu path already exists in user PATH." +fi diff --git a/packages/cli/lib/scripts/addToPathMacOpenTofu.sh b/packages/cli/lib/scripts/addToPathMacOpenTofu.sh new file mode 100644 index 0000000..0a5e9ed --- /dev/null +++ b/packages/cli/lib/scripts/addToPathMacOpenTofu.sh @@ -0,0 +1,25 @@ + +#!/bin/bash + +# Set the path to add +path2add="$HOME/Library/Application Support/opentofu" + +# Get the user's PATH +userPath=$(echo $PATH) + +# Check if the path already contains the path to add +if [[ ! "$userPath" == *"$path2add"* ]]; then + # Update .zshrc to persist the changes (also creates .zshrc if it doesn't already exist) + echo "export PATH=\"\$PATH:$path2add\"" >> ~/.zshrc + echo "OpenTofu path added to user PATH in .zshrc." + + + # Check if .bashrc exists (if the user happens to be using a non-zsh terminal, we want to support that) + if [ -f "$HOME/.bashrc" ]; then + # Add the path to .bashrc + echo "export PATH=\"\$PATH:$path2add\"" >> ~/.bashrc + echo "OpenTofu path added to user PATH in .bashrc." + fi +else + echo "OpenTofu path already exists in user PATH." +fi diff --git a/packages/cli/lib/scripts/addToPathOpenTofu.ps1 b/packages/cli/lib/scripts/addToPathWindowsOpenTofu.ps1 similarity index 100% rename from packages/cli/lib/scripts/addToPathOpenTofu.ps1 rename to packages/cli/lib/scripts/addToPathWindowsOpenTofu.ps1 diff --git a/packages/cli/lib/util/tfvmOS.js b/packages/cli/lib/util/tfvmOS.js index b99efde..a3951fb 100644 --- a/packages/cli/lib/util/tfvmOS.js +++ b/packages/cli/lib/util/tfvmOS.js @@ -68,7 +68,8 @@ export class TfvmOS { /** * Returns arguments for the runShell() function and prepares the script for being run, if necessary */ - getAddToPathShellArgs () { throw new Error('Not implemented in parent class') } + getAddToPathShellArgsTerraform () { throw new Error('Not implemented in parent class') } + getAddToPathShellArgsOpenTofu () { throw new Error('Not implemented in parent class') } getArchitecture () { throw new Error('Not implemented in parent class') } getBitWidth () { throw new Error('Not implemented in parent class') } getPathCommand () { throw new Error('Not implemented in parent class') } @@ -116,12 +117,18 @@ export class Mac extends TfvmOS { return ':' } - async getAddToPathShellArgs () { + async getAddToPathShellArgsTerraform () { const scriptPath = resolve(__dirname, './../scripts/addToPathMac.sh') await fsp.chmod(scriptPath, EXECUTE_PERM_CODE) return [scriptPath, {}] } + async getAddToPathShellArgsOpenTofu () { + const scriptPath = resolve(__dirname, './../scripts/addToPathMacOpenTofu.sh') + await fsp.chmod(scriptPath, EXECUTE_PERM_CODE) + return [scriptPath, {}] + } + getAppDataDir () { return process.env.HOME + '/Library/Application Support' } @@ -171,10 +178,14 @@ export class Windows extends TfvmOS { return ';' } - async getAddToPathShellArgs () { + async getAddToPathShellArgsTerraform () { return [resolve(__dirname, './../scripts/addToPathWindows.ps1'), { shell: 'powershell.exe' }] } + async getAddToPathShellArgsOpenTofu () { + return [resolve(__dirname, './../scripts/addToPathWindowsOpenTofu.ps1'), { shell: 'powershell.exe' }] + } + getAppDataDir () { return process.env.APPDATA } @@ -226,12 +237,18 @@ export class Linux extends TfvmOS { throw new Error('Bash script failed to add terraform directory to the path') } - async getAddToPathShellArgs () { + async getAddToPathShellArgsTerraform () { const scriptPath = resolve(__dirname, './../scripts/addToPathLinux.sh') await fsp.chmod(scriptPath, EXECUTE_PERM_CODE) return [scriptPath, {}] } + async getAddToPathShellArgsOpenTofu () { + const scriptPath = resolve(__dirname, './../scripts/addToPathLinuxOpenTofu.sh') + await fsp.chmod(scriptPath, EXECUTE_PERM_CODE) + return [scriptPath, {}] + } + getArchitecture () { // 'arm' or 'arm64', 'amd64', '386' const arches = { diff --git a/packages/cli/lib/util/verifySetup.js b/packages/cli/lib/util/verifySetup.js index 74b030f..96284c7 100644 --- a/packages/cli/lib/util/verifySetup.js +++ b/packages/cli/lib/util/verifySetup.js @@ -42,25 +42,22 @@ async function verifySetup () { if (pathVarDoesntExist || pathVarDoesntExistOpenTofu) { // add to local paths logger.warn(`Couldn't find tfvm in path where this is the path: ${PATH}`) - logger.debug(`Attempting to run ${pathVarDoesntExist ? 'addToPath.ps1' : 'addToPath.ps1'}...`) + logger.debug('Attempting to run addToPath script...') + let successfulAddToPath = false if (pathVarDoesntExist) { - logger.debug('Attempting to run addToPath script...') - if (await runShell(...(await os.getAddToPathShellArgs())) == null) { + if ((await runShell(...(await os.getAddToPathShellArgsTerraform())) == null)) { os.handleAddPathError() - } else { - logger.debug('Successfully ran addToPath script, added to path.') - console.log(chalk.red.bold('We couldn\'t find the right path variable for terraform, so we just added it.\n' + + } else successfulAddToPath = true + } else if (pathVarDoesntExistOpenTofu) { + if ((await runShell(...(await os.getAddToPathShellArgsOpenTofu())) == null)) { + os.handleAddPathError() + } else successfulAddToPath = true + } + + if (successfulAddToPath) { + logger.debug('Successfully ran addToPath script, added to path.') + console.log(chalk.red.bold('We couldn\'t find the right path variable for terraform, so we just added it.\n' + 'Please restart your terminal, or open a new one, for terraform to work correctly.\n')) - } - } else { - if (await runShell(...(__dirname, './../scripts/addToPathOpenTofu.ps1'), { shell: 'powershell.exe' }) == null) { - console.log(chalk.red.bold('tfvm script failed to run. Please run the following command in a powershell window:\n')) - console.log(chalk.blue.bold('Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser')) - } else { - logger.debug('Successfully ran addToPathOpenTofu.ps1, added to path.') - console.log(chalk.red.bold('We couldn\'t find the right path variable for opentofu, so we just added it.\n' + - 'Please restart your terminal, or open a new one, for opentofu to work correctly.\n')) - } } return false } From 8f37a7bdf1a816e700a374418c56fe5fc18442f2 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Mon, 5 Aug 2024 12:47:16 -0600 Subject: [PATCH 15/29] fix: install bug --- packages/cli/lib/commands/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 667b34e..277a131 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -73,7 +73,7 @@ export async function installFromWeb (versionNum, printMessage = true) { if (settingsObj.useOpenTofu) { zipPath = os.getPath(os.getOtfVersionsDir(), `v${versionNum}.zip`) newVersionDir = os.getPath(os.getOtfVersionsDir(), 'v' + versionNum) - url = `https://github.com/opentofu/opentofu/releases/download/v${versionNum}/tofu_${versionNum}_${arch}.zip` + url = `https://github.com/opentofu/opentofu/releases/download/v${versionNum}/tofu_${versionNum}_${os.getOSName()}_${arch}.zip` } else { zipPath = os.getPath(os.getTfVersionsDir(), `v${versionNum}.zip`) newVersionDir = os.getPath(os.getTfVersionsDir(), 'v' + versionNum) From a04f279fc36a98cbc434854339240ad0508635c3 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Mon, 5 Aug 2024 14:10:40 -0600 Subject: [PATCH 16/29] fix: semver stuff and other bugs --- package-lock.json | 15 ++++++++++++++- packages/cli/lib/commands/install.js | 3 ++- packages/cli/lib/commands/use.js | 18 +++++++++--------- packages/cli/lib/util/getInstalledVersions.js | 10 ++++++++-- packages/cli/package.json | 3 ++- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1eb44e0..38cdeb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10588,7 +10588,8 @@ "follow-redirects": "^1.15.6", "node-stream-zip": "^1.15.0", "pino": "^8.15.1", - "pino-pretty": "^10.0.0" + "pino-pretty": "^10.0.0", + "semver": "^7.6.3" }, "bin": { "tfvm": "lib/index.js" @@ -10604,6 +10605,18 @@ "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } + }, + "packages/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } } } } diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 277a131..35055ce 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -11,6 +11,7 @@ import { logger } from '../util/logger.js' import getSettings from '../util/getSettings.js' import { getOS, Mac } from '../util/tfvmOS.js' import { compare } from 'compare-versions' +import * as semver from 'semver' const os = getOS() const LAST_TF_VERSION_WITHOUT_ARM = '1.0.1' @@ -70,7 +71,7 @@ export async function installFromWeb (versionNum, printMessage = true) { let newVersionDir let arch = os.getArchitecture() - if (settingsObj.useOpenTofu) { + if (settingsObj.useOpenTofu && semver.gte(versionNum, '1.6.0')) { zipPath = os.getPath(os.getOtfVersionsDir(), `v${versionNum}.zip`) newVersionDir = os.getPath(os.getOtfVersionsDir(), 'v' + versionNum) url = `https://github.com/opentofu/opentofu/releases/download/v${versionNum}/tofu_${versionNum}_${os.getOSName()}_${arch}.zip` diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index 1b33339..ed23d81 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -10,6 +10,7 @@ import getSettings from '../util/getSettings.js' import requiresOldAWSAuth from '../util/requiresOldAWSAuth.js' import { logger } from '../util/logger.js' import { getOS } from '../util/tfvmOS.js' +import * as semver from "semver"; const os = getOS() @@ -34,7 +35,7 @@ export async function useVersion (version) { if (!versionRegEx.test(versionWithV)) { console.log(chalk.red.bold('Invalid version syntax')) } else { - const installedVersions = await getInstalledVersions() + const installedVersions = await getInstalledVersions(version) if (!installedVersions.includes(versionWithV)) { const successfullyInstalled = await installNewVersion(version) if (!successfullyInstalled) return @@ -50,7 +51,7 @@ export async function useVersion (version) { */ export async function installNewVersion (version) { const settings = await getSettings() - console.log(chalk.white.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) + console.log(chalk.white.bold(`${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) const installToggle = new enquirer.Toggle({ disabled: 'Yes', enabled: 'No' @@ -73,23 +74,22 @@ export async function switchVersionTo (version) { const settings = await getSettings() if (version[0] === 'v') version = version.substring(1) - await TfvmFS.createTfAppDataDir() - await TfvmFS.deleteCurrentTfExe() - await TfvmFS.createOtfAppDataDir() - await TfvmFS.deleteCurrentOtfExe() - - if (settings.useOpenTofu) { + if (settings.useOpenTofu && semver.gte(version, '1.6.0')) { + await TfvmFS.createOtfAppDataDir() + await TfvmFS.deleteCurrentOtfExe() await fs.copyFile( os.getPath(os.getOtfVersionsDir(), 'v' + version, os.getOtfExecutableName()), // source file os.getPath(os.getOpenTofuDir(), os.getOtfExecutableName()) // destination file ) } else { + await TfvmFS.createTfAppDataDir() + await TfvmFS.deleteCurrentTfExe() await fs.copyFile( os.getPath(os.getTfVersionsDir(), 'v' + version, os.getTFExecutableName()), // source file os.getPath(os.getTerraformDir(), os.getTFExecutableName()) // destination file ) } - console.log(chalk.cyan.bold(`Now using ${settings.useOpenTofu ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) + console.log(chalk.cyan.bold(`Now using ${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { console.log(chalk.yellow.bold('Warning: This tf version is not compatible with the newest ' + 'AWS CLI authentication methods (e.g. aws sso login). Use short-term credentials instead.')) diff --git a/packages/cli/lib/util/getInstalledVersions.js b/packages/cli/lib/util/getInstalledVersions.js index 649cf4f..67ec982 100644 --- a/packages/cli/lib/util/getInstalledVersions.js +++ b/packages/cli/lib/util/getInstalledVersions.js @@ -3,6 +3,7 @@ import { versionRegEx } from './constants.js' import { logger } from './logger.js' import getSettings from './getSettings.js' import { getOS } from './tfvmOS.js' +import * as semver from 'semver' const os = getOS() let installedVersions @@ -11,13 +12,18 @@ let installedVersions * Returns a list of installed tf versions. * @returns {Promise} */ -async function getInstalledVersions () { +async function getInstalledVersions (version = '') { const settings = await getSettings() // return the list of installed versions if that is already cached if (!installedVersions) { const versionsList = [] let files - if (settings.useOpenTofu) { + + let semverCheck = true + if (version !== '') { + semverCheck = semver.gte(version, '1.6.0') + } + if (settings.useOpenTofu && semverCheck) { files = await fs.readdir(os.getOtfVersionsDir()) } else { files = await fs.readdir(os.getTfVersionsDir()) diff --git a/packages/cli/package.json b/packages/cli/package.json index a7c5c25..bc4b0ec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,7 +41,8 @@ "follow-redirects": "^1.15.6", "node-stream-zip": "^1.15.0", "pino": "^8.15.1", - "pino-pretty": "^10.0.0" + "pino-pretty": "^10.0.0", + "semver": "^7.6.3" }, "publishConfig": { "access": "public" From 6d042f08252291d3fc2c5ea98857b69d0cb94a57 Mon Sep 17 00:00:00 2001 From: tab518 Date: Tue, 6 Aug 2024 15:28:44 -0600 Subject: [PATCH 17/29] feat: detect works with opentofu --- packages/cli/lib/commands/detect.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/cli/lib/commands/detect.js b/packages/cli/lib/commands/detect.js index 0b78af6..e785d26 100644 --- a/packages/cli/lib/commands/detect.js +++ b/packages/cli/lib/commands/detect.js @@ -13,8 +13,10 @@ import getTerraformVersion from '../util/tfVersion.js' import { installNewVersion, switchVersionTo, useVersion } from './use.js' import { logger } from '../util/logger.js' import { TfvmFS } from '../util/TfvmFS.js' +import getSettings from "../util/getSettings.js"; async function detect () { + const settings = await getSettings() // set of objects that contain the constraints and the file name const tfVersionConstraintSet = new Set() try { @@ -26,10 +28,10 @@ async function detect () { await satisfyConstraints(tfVersionConstraintSet) } else { // todo let the user select from list of frequently used versions instead of this disappointing message - console.log(chalk.white.bold('No terraform files containing any version constraints are found in this directory.')) + console.log(chalk.white.bold(`No ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} files containing any version constraints are found in this directory.`)) } } catch (error) { - logger.fatal(error, 'Fatal error when running "detect" command with these local terraform ' + + logger.fatal(error, `Fatal error when running "detect" command with these local ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} ` + `constraints: ${Array.from(tfVersionConstraintSet).map(c => JSON.stringify(c)).join('; ')}: `) getErrorMessage(error) } @@ -48,9 +50,10 @@ async function satisfyConstraints (tfVersionConstraintSet) { ? tfVersionConstraints // if the current version is null, then all the constraints are unmet : getUnmetConstraints(tfVersionConstraints, currentTfVersion) if (unmetVersionConstraints.length === 0) { + const settings = await getSettings() // exit quickly if the current tf version satisfies all required constraints - console.log(chalk.cyan.bold(`Your current terraform version (${currentTfVersion}) already ` + - 'satisfies the requirements of your local terraform files.')) + console.log(chalk.cyan.bold(`Your current ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} version (${currentTfVersion}) already ` + + `satisfies the requirements of your local ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} files.`)) } else if (unmetVersionConstraints.length === 1 && tfVersionConstraints.length === 1) { await satisfySingleConstraint(unmetVersionConstraints[0]) } else if (unmetVersionConstraints.length >= 1) { @@ -68,7 +71,8 @@ async function satisfyMultipleConstraints (tfVersionConstraints) { // if all the constraints are single versions, give them a dropdown list to select from await chooseAndUseVersionFrom(tfVersionConstraints) } else { - console.log(chalk.white.bold('There are multiple terraform version constraints in this directory:')) + const settings = await getSettings() + console.log(chalk.white.bold(`There are multiple ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} version constraints in this directory:`)) tfVersionConstraints.forEach(c => console.log(chalk.white.bold(` - ${c.displayVersion} (${c.fileName})`))) @@ -186,13 +190,14 @@ async function findLocalVersionConstraints (constraintSet) { const fileName = TfvmFS.getFileNameFromPath(filePath) const fileHclAsJson = parser.parse(content) if (fileHclAsJson.required_core) { + const settings = await getSettings() fileHclAsJson.required_core.forEach(version => { // terraform supports '!=' and `~>' in semver but the 'compare-versions' package does not for (const badOperator of ['!=']) { if (version.includes(badOperator)) { logger.error(`Failed to parse version ${version} because of '${badOperator}'.`) console.log(chalk.red.bold(`Ignoring constraint from ${fileName} ` + - `because tfvm doesn't support parsing versions with '${badOperator}' in the terraform required_version.`)) + `because tfvm doesn't support parsing versions with '${badOperator}' in the ${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} required_version.`)) return // functional equivalent of 'continue' in forEach } } From 21a97ea8cb6d9f26ba28f599add745e678622878 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Tue, 6 Aug 2024 15:32:03 -0600 Subject: [PATCH 18/29] fix: lint --- packages/cli/lib/commands/use.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index ed23d81..7b10786 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -10,7 +10,7 @@ import getSettings from '../util/getSettings.js' import requiresOldAWSAuth from '../util/requiresOldAWSAuth.js' import { logger } from '../util/logger.js' import { getOS } from '../util/tfvmOS.js' -import * as semver from "semver"; +import * as semver from 'semver' const os = getOS() From 848790cc5778dff81d25920bdf2d8afe846a53d3 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Tue, 6 Aug 2024 15:38:46 -0600 Subject: [PATCH 19/29] fix: more lint --- packages/cli/lib/commands/detect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/lib/commands/detect.js b/packages/cli/lib/commands/detect.js index e785d26..060bb03 100644 --- a/packages/cli/lib/commands/detect.js +++ b/packages/cli/lib/commands/detect.js @@ -13,7 +13,7 @@ import getTerraformVersion from '../util/tfVersion.js' import { installNewVersion, switchVersionTo, useVersion } from './use.js' import { logger } from '../util/logger.js' import { TfvmFS } from '../util/TfvmFS.js' -import getSettings from "../util/getSettings.js"; +import getSettings from '../util/getSettings.js' async function detect () { const settings = await getSettings() From 4a0169deb9259f413a7599ad1f6e2b010003f4a7 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 8 Aug 2024 10:40:23 -0600 Subject: [PATCH 20/29] feat: type on list feature --- packages/cli/lib/commands/list.js | 28 +++++++++++++++++-- packages/cli/lib/util/download.js | 3 +- packages/cli/lib/util/getInstalledVersions.js | 6 ++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/list.js b/packages/cli/lib/commands/list.js index f3e04b6..46fd991 100644 --- a/packages/cli/lib/commands/list.js +++ b/packages/cli/lib/commands/list.js @@ -5,6 +5,8 @@ import getInstalledVersions from '../util/getInstalledVersions.js' import getErrorMessage from '../util/errorChecker.js' import { logger } from '../util/logger.js' import { getOS } from '../util/tfvmOS.js' +import * as semver from 'semver' +import getSettings from '../util/getSettings.js' const os = getOS() async function list () { @@ -16,14 +18,36 @@ async function list () { const currentTFVersion = await getTerraformVersion() tfList.sort(compareVersions).reverse() for (const versionDir of tfList) { + const settings = await getSettings() + const version = versionDir.substring(1, versionDir.length) + + let type = '' + if (settings.useOpenTofu) { + // logic to get the correct spacing + const parsed = semver.parse(version) + type += (parsed.minor.toString().length === 1 && parsed.patch.toString().length === 1 ? ' ' : ' ') + if (semver.gte(version, '1.6.0')) { + type += '[OpenTofu]' + } else if (semver.lt(version, '1.6.0')) { + type += '[Terraform]' + } + } + if (versionDir === currentTFVersion) { let printVersion = ' * ' - printVersion = printVersion + versionDir.substring(1, versionDir.length) + printVersion += version + if (settings.useOpenTofu) { + printVersion += type + } printVersion = printVersion + ` (Currently using ${os.getBitWidth()}-bit executable)` printList.push(printVersion) } else { let printVersion = ' ' - printVersion = printVersion + versionDir.substring(1, versionDir.length) + printVersion += version + + if (settings.useOpenTofu) { + printVersion += type + } printList.push(printVersion) } } diff --git a/packages/cli/lib/util/download.js b/packages/cli/lib/util/download.js index ac236ca..c682cd9 100644 --- a/packages/cli/lib/util/download.js +++ b/packages/cli/lib/util/download.js @@ -2,6 +2,7 @@ import chalk from 'chalk' import getSettings from './getSettings.js' import axios from 'axios' import fs from 'node:fs/promises' +import * as semver from 'semver' const download = async (url, filePath, version) => { try { @@ -10,7 +11,7 @@ const download = async (url, filePath, version) => { await fs.writeFile(filePath, fileData) } catch (err) { const settings = await getSettings() - console.log(chalk.red.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} ${version} is not yet released or available.`)) + console.log(chalk.red.bold(`${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'OpenTofu' : 'Terraform'} ${version} is not yet released or available.`)) throw new Error() } } diff --git a/packages/cli/lib/util/getInstalledVersions.js b/packages/cli/lib/util/getInstalledVersions.js index 67ec982..bffe6f0 100644 --- a/packages/cli/lib/util/getInstalledVersions.js +++ b/packages/cli/lib/util/getInstalledVersions.js @@ -25,6 +25,12 @@ async function getInstalledVersions (version = '') { } if (settings.useOpenTofu && semverCheck) { files = await fs.readdir(os.getOtfVersionsDir()) + const terraformFiles = await fs.readdir(os.getTfVersionsDir()) + terraformFiles.forEach(file => { + if (semver.lt(file, '1.6.0')) { + files.push(file) + } + }) } else { files = await fs.readdir(os.getTfVersionsDir()) } From fc8fbc4513a76d52d292bc7fe8f5be795b802767 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 8 Aug 2024 11:41:25 -0600 Subject: [PATCH 21/29] clean: openTofuCheck --- packages/cli/lib/commands/install.js | 32 +++++++++++++++----------- packages/cli/lib/commands/uninstall.js | 12 ++++++---- packages/cli/lib/commands/use.js | 1 + 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 35055ce..649f69d 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -15,11 +15,14 @@ import * as semver from 'semver' const os = getOS() const LAST_TF_VERSION_WITHOUT_ARM = '1.0.1' +const LOWEST_OTF_VERSION = '1.6.0' async function install (versionNum) { try { const settings = await getSettings() const installVersion = 'v' + versionNum + const openTofuCheck = settings.useOpenTofu && semver.gte(versionNum, LOWEST_OTF_VERSION) + if (!versionRegEx.test(installVersion) && versionNum !== 'latest') { logger.warn(`invalid version attempted to install with version ${installVersion}`) console.log(chalk.red.bold('Invalid version syntax.')) @@ -37,11 +40,11 @@ async function install (versionNum) { if (latest) { const versionLatest = 'v' + latest if (installedVersions.includes(versionLatest) && currentVersion !== versionLatest) { - console.log(chalk.bold.cyan(`The latest ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version is ${latest} and is ` + + console.log(chalk.bold.cyan(`The latest ${openTofuCheck ? 'opentofu' : 'terraform'} version is ${latest} and is ` + `already installed on your computer. Run 'tfvm use ${latest}' to use.`)) } else if (installedVersions.includes(versionLatest) && currentVersion === versionLatest) { const currentVersion = await getTerraformVersion() - console.log(chalk.bold.cyan(`The latest ${settings.useOpenTofu ? 'opentofu' : 'terraform'} version is ${currentVersion} and ` + + console.log(chalk.bold.cyan(`The latest ${openTofuCheck ? 'opentofu' : 'terraform'} version is ${currentVersion} and ` + 'is already installed and in use on your computer.')) } else { await installFromWeb(latest) @@ -50,7 +53,7 @@ async function install (versionNum) { } else { const installedVersions = await getInstalledVersions() if (installedVersions.includes(installVersion)) { - console.log(chalk.white.bold(`${settings.useOpenTofu ? 'OpenTofu' : 'Terraform'} version ${installVersion} is already installed.`)) + console.log(chalk.white.bold(`${openTofuCheck ? 'OpenTofu' : 'Terraform'} version ${installVersion} is already installed.`)) } else { await installFromWeb(versionNum) } @@ -64,14 +67,23 @@ async function install (versionNum) { export default install export async function installFromWeb (versionNum, printMessage = true) { - const settingsObj = await getSettings() - + const settings = await getSettings() + const openTofuCheck = settings.useOpenTofu && semver.gte(versionNum, LOWEST_OTF_VERSION) let url let zipPath let newVersionDir let arch = os.getArchitecture() - if (settingsObj.useOpenTofu && semver.gte(versionNum, '1.6.0')) { + // Only newer terraform versions include a release for ARM (Apple Silicon) hardware, but their chips *can* + // run the amd64 ones, it just isn't ideal. If the user requests to download a terraform version that doesn't + // have an arm release (and they are on an Arm Mac), then just download the amd64 one instead. + if (os instanceof Mac && arch === 'arm64' && compare(versionNum, LAST_TF_VERSION_WITHOUT_ARM, '<=')) { + arch = 'amd64' + console.log(chalk.bold.yellow(`Warning: There is no available ARM release of Terraform for version ${versionNum}. + Installing the amd64 version instead (should run without issue via Rosetta)...`)) + } + + if (openTofuCheck) { zipPath = os.getPath(os.getOtfVersionsDir(), `v${versionNum}.zip`) newVersionDir = os.getPath(os.getOtfVersionsDir(), 'v' + versionNum) url = `https://github.com/opentofu/opentofu/releases/download/v${versionNum}/tofu_${versionNum}_${os.getOSName()}_${arch}.zip` @@ -81,14 +93,6 @@ export async function installFromWeb (versionNum, printMessage = true) { url = `https://releases.hashicorp.com/terraform/${versionNum}/terraform_${versionNum}_${os.getOSName()}_${arch}.zip` } - // Only newer terraform versions include a release for ARM (Apple Silicon) hardware, but their chips *can* - // run the amd64 ones, it just isn't ideal. If the user requests to download a terraform version that doesn't - // have an arm release (and they are on an Arm Mac), then just download the amd64 one instead. - if (os instanceof Mac && arch === 'arm64' && compare(versionNum, LAST_TF_VERSION_WITHOUT_ARM, '<=')) { - arch = 'amd64' - console.log(chalk.bold.yellow(`Warning: There is no available ARM release of Terraform for version ${versionNum}. - Installing the amd64 version instead (should run without issue via Rosetta)...`)) - } await download(url, zipPath, versionNum) await fs.mkdir(newVersionDir) await unzipFile(zipPath, newVersionDir) diff --git a/packages/cli/lib/commands/uninstall.js b/packages/cli/lib/commands/uninstall.js index 725b3dc..fd800c5 100644 --- a/packages/cli/lib/commands/uninstall.js +++ b/packages/cli/lib/commands/uninstall.js @@ -6,6 +6,7 @@ import { logger } from '../util/logger.js' import getSettings from '../util/getSettings.js' import { getOS } from '../util/tfvmOS.js' import { TfvmFS } from '../util/TfvmFS.js' +import * as semver from 'semver' const os = getOS() async function uninstall (uninstallVersion) { @@ -16,16 +17,19 @@ async function uninstall (uninstallVersion) { } else { const settings = await getSettings() const installedVersions = await getInstalledVersions() + const semverCheck = semver.gte(uninstallVersion, '1.6.0') + const openTofuCheck = settings.useOpenTofu && semverCheck + if (!installedVersions.includes(uninstallVersion)) { - console.log(chalk.white.bold(`${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion} is not installed. Type "tfvm list" to see what is installed.`)) + console.log(chalk.white.bold(`${openTofuCheck ? 'opentofu' : 'terraform'} ${uninstallVersion} is not installed. Type "tfvm list" to see what is installed.`)) } else { - console.log(chalk.white.bold(`Uninstalling ${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion}...`)) - if (settings.useOpenTofu) { + console.log(chalk.white.bold(`Uninstalling ${openTofuCheck ? 'opentofu' : 'terraform'} ${uninstallVersion}...`)) + if (openTofuCheck) { await TfvmFS.deleteDirectory(os.getOtfVersionsDir(), uninstallVersion) } else { await TfvmFS.deleteDirectory(os.getTfVersionsDir(), uninstallVersion) } - console.log(chalk.cyan.bold(`Successfully uninstalled ${settings.useOpenTofu ? 'opentofu' : 'terraform'} ${uninstallVersion}`)) + console.log(chalk.cyan.bold(`Successfully uninstalled ${openTofuCheck ? 'opentofu' : 'terraform'} ${uninstallVersion}`)) } } } catch (error) { diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index 7b10786..825dcf6 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -90,6 +90,7 @@ export async function switchVersionTo (version) { ) } console.log(chalk.cyan.bold(`Now using ${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) + if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { console.log(chalk.yellow.bold('Warning: This tf version is not compatible with the newest ' + 'AWS CLI authentication methods (e.g. aws sso login). Use short-term credentials instead.')) From 6ff29a61a624a249b7bb775e838dadd4fd066433 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 8 Aug 2024 13:12:57 -0600 Subject: [PATCH 22/29] feat: note for downloading versions less than 1.6.0 --- packages/cli/lib/commands/install.js | 4 ++++ packages/cli/lib/commands/use.js | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index 649f69d..eb91bb2 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -69,6 +69,10 @@ export default install export async function installFromWeb (versionNum, printMessage = true) { const settings = await getSettings() const openTofuCheck = settings.useOpenTofu && semver.gte(versionNum, LOWEST_OTF_VERSION) + const openTofuCheckLessThan = settings.useOpenTofu && semver.lte(versionNum, LOWEST_OTF_VERSION) + if (openTofuCheckLessThan) { + console.log(chalk.magenta.bold('Note: With the useOpenTofu flag, versions below 1.6.0 will be downloaded as Terraform since OpenTofu only has versions beginning at version 1.6.0')) + } let url let zipPath let newVersionDir diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index 825dcf6..81f8256 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -13,6 +13,8 @@ import { getOS } from '../util/tfvmOS.js' import * as semver from 'semver' const os = getOS() +const LOWEST_OTF_VERSION = '1.6.0' +let openTofuCheck = false async function use (version) { try { @@ -36,6 +38,8 @@ export async function useVersion (version) { console.log(chalk.red.bold('Invalid version syntax')) } else { const installedVersions = await getInstalledVersions(version) + const settings = getSettings() + openTofuCheck = settings.useOpenTofu && semver.gte(version, LOWEST_OTF_VERSION) if (!installedVersions.includes(versionWithV)) { const successfullyInstalled = await installNewVersion(version) if (!successfullyInstalled) return @@ -51,7 +55,8 @@ export async function useVersion (version) { */ export async function installNewVersion (version) { const settings = await getSettings() - console.log(chalk.white.bold(`${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) + const openTofuCheck = settings.useOpenTofu && semver.gte(version, LOWEST_OTF_VERSION) + console.log(chalk.white.bold(`${openTofuCheck ? 'OpenTofu' : 'Terraform'} v${version} is not installed. Would you like to install it?`)) const installToggle = new enquirer.Toggle({ disabled: 'Yes', enabled: 'No' @@ -74,7 +79,7 @@ export async function switchVersionTo (version) { const settings = await getSettings() if (version[0] === 'v') version = version.substring(1) - if (settings.useOpenTofu && semver.gte(version, '1.6.0')) { + if (openTofuCheck) { await TfvmFS.createOtfAppDataDir() await TfvmFS.deleteCurrentOtfExe() await fs.copyFile( @@ -89,7 +94,7 @@ export async function switchVersionTo (version) { os.getPath(os.getTerraformDir(), os.getTFExecutableName()) // destination file ) } - console.log(chalk.cyan.bold(`Now using ${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) + console.log(chalk.cyan.bold(`Now using ${openTofuCheck ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { console.log(chalk.yellow.bold('Warning: This tf version is not compatible with the newest ' + From bb90e1232f03678b435497277356196dc965fdd1 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 8 Aug 2024 13:26:57 -0600 Subject: [PATCH 23/29] chore: update README --- packages/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index c01986a..49b6676 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -71,7 +71,7 @@ Run `tfvm` in any command line, followed by one of these commands: - `tfvm config disableErrors=true` - disables configuration warnings. - `tfvm config disableAWSWarnings=true` - disables AWS warnings that appear when using older terraform versions. - `tfvm config disableSettingPrompts=true` - disables prompts that show how to hide some error messages. - - `tfvm config useOpenTofu=true` - uses the open source version of Terraform, OpenTofu (experimental flag) + - `tfvm config useOpenTofu=true` - uses the open source version of Terraform, OpenTofu (experimental flag). This flag will also delete your terraform executable so you can only perfom tofu actions. When you switch back to `useOpenTofu=false`, the tofu executable will be deleted. This is so you don't perform any accidental commands in the wrong type of IAC. - `help`: prints usage information. Run `tfvm help ` to see information about the other tfvm commands. ## FAQ From b4994c849d5ee2c0dda38bdcfb40658734954b1e Mon Sep 17 00:00:00 2001 From: chlohilt Date: Thu, 8 Aug 2024 14:04:27 -0600 Subject: [PATCH 24/29] feat: delete executable when useOpenTofu flag is set --- packages/cli/lib/commands/config.js | 6 ++++++ packages/cli/lib/commands/use.js | 2 +- packages/cli/lib/util/deleteExecutable.js | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 packages/cli/lib/util/deleteExecutable.js diff --git a/packages/cli/lib/commands/config.js b/packages/cli/lib/commands/config.js index db07adc..bbb4122 100644 --- a/packages/cli/lib/commands/config.js +++ b/packages/cli/lib/commands/config.js @@ -4,6 +4,7 @@ import getSettings, { defaultSettings } from '../util/getSettings.js' import getErrorMessage from '../util/errorChecker.js' import { logger } from '../util/logger.js' import { getOS } from '../util/tfvmOS.js' +import deleteExecutable from '../util/deleteExecutable.js' const os = getOS() @@ -20,10 +21,15 @@ async function config (setting) { // we need to store logical true or false, not the string 'true' or 'false'. This converts to a boolean: settingsObj[settingKey] = value === 'true' await fs.writeFile(os.getSettingsDir(), JSON.stringify(settingsObj), 'utf8') + + if (settingKey === 'useOpenTofu') { + await deleteExecutable(settingsObj[settingKey], os) + } } else { console.log(chalk.red.bold(`Invalid input for ${settingKey} setting. ` + `Use either 'tfvm config ${settingKey}=true' or 'tfvm config ${settingKey}=false'`)) } + console.log(chalk.cyan.bold(`Successfully set ${setting}`)) } else { logger.warn(`Invalid setting change attempt with setting=${setting}`) diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index 81f8256..4fe468e 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -38,7 +38,7 @@ export async function useVersion (version) { console.log(chalk.red.bold('Invalid version syntax')) } else { const installedVersions = await getInstalledVersions(version) - const settings = getSettings() + const settings = await getSettings() openTofuCheck = settings.useOpenTofu && semver.gte(version, LOWEST_OTF_VERSION) if (!installedVersions.includes(versionWithV)) { const successfullyInstalled = await installNewVersion(version) diff --git a/packages/cli/lib/util/deleteExecutable.js b/packages/cli/lib/util/deleteExecutable.js new file mode 100644 index 0000000..d103c2d --- /dev/null +++ b/packages/cli/lib/util/deleteExecutable.js @@ -0,0 +1,20 @@ +import getErrorMessage from './errorChecker.js' +import { logger } from './logger.js' +import { TfvmFS } from './TfvmFS.js' + +async function deleteExecutable (useOpenTofu, os) { + try { + if (useOpenTofu === true) { + await TfvmFS.deleteCurrentTfExe() + logger.info('Successfully deleted Terraform executable') + } else { + await TfvmFS.deleteCurrentOtfExe() + logger.info('Successfully deleted Open Tofu executable') + } + } catch (error) { + logger.fatal(error, `Fatal error when deleting executable when useOpenTofu=${useOpenTofu}`) + getErrorMessage(error) + } +} + +export default deleteExecutable From 7e5013786eb88e15baa4e37d4553f349a126c086 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 9 Aug 2024 10:28:21 -0600 Subject: [PATCH 25/29] wip: switch types message --- packages/cli/README.md | 1 + packages/cli/lib/commands/config.js | 2 +- packages/cli/lib/commands/install.js | 7 +- packages/cli/lib/commands/use.js | 8 ++ packages/cli/lib/index.js | 161 +++++++++++----------- packages/cli/lib/util/TfvmFS.js | 10 +- packages/cli/lib/util/deleteExecutable.js | 18 ++- packages/cli/lib/util/getSettings.js | 3 +- 8 files changed, 119 insertions(+), 91 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 49b6676..2a3a189 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -72,6 +72,7 @@ Run `tfvm` in any command line, followed by one of these commands: - `tfvm config disableAWSWarnings=true` - disables AWS warnings that appear when using older terraform versions. - `tfvm config disableSettingPrompts=true` - disables prompts that show how to hide some error messages. - `tfvm config useOpenTofu=true` - uses the open source version of Terraform, OpenTofu (experimental flag). This flag will also delete your terraform executable so you can only perfom tofu actions. When you switch back to `useOpenTofu=false`, the tofu executable will be deleted. This is so you don't perform any accidental commands in the wrong type of IAC. + - `tfvm config disableTofuWarnings=true` - disables warnings related to using Tofu (deleting executables, using Tofu instead of Terraform, etc.) - `help`: prints usage information. Run `tfvm help ` to see information about the other tfvm commands. ## FAQ diff --git a/packages/cli/lib/commands/config.js b/packages/cli/lib/commands/config.js index bbb4122..a08ad51 100644 --- a/packages/cli/lib/commands/config.js +++ b/packages/cli/lib/commands/config.js @@ -23,7 +23,7 @@ async function config (setting) { await fs.writeFile(os.getSettingsDir(), JSON.stringify(settingsObj), 'utf8') if (settingKey === 'useOpenTofu') { - await deleteExecutable(settingsObj[settingKey], os) + await deleteExecutable(settingsObj[settingKey]) } } else { console.log(chalk.red.bold(`Invalid input for ${settingKey} setting. ` + diff --git a/packages/cli/lib/commands/install.js b/packages/cli/lib/commands/install.js index eb91bb2..60bc278 100644 --- a/packages/cli/lib/commands/install.js +++ b/packages/cli/lib/commands/install.js @@ -1,4 +1,5 @@ import chalk from 'chalk' +import deleteExecutable from '../util/deleteExecutable.js' import fs from 'node:fs/promises' import { versionRegEx } from '../util/constants.js' import getInstalledVersions from '../util/getInstalledVersions.js' @@ -69,9 +70,11 @@ export default install export async function installFromWeb (versionNum, printMessage = true) { const settings = await getSettings() const openTofuCheck = settings.useOpenTofu && semver.gte(versionNum, LOWEST_OTF_VERSION) - const openTofuCheckLessThan = settings.useOpenTofu && semver.lte(versionNum, LOWEST_OTF_VERSION) + const openTofuCheckLessThan = settings.useOpenTofu && semver.lt(versionNum, LOWEST_OTF_VERSION) if (openTofuCheckLessThan) { - console.log(chalk.magenta.bold('Note: With the useOpenTofu flag, versions below 1.6.0 will be downloaded as Terraform since OpenTofu only has versions beginning at version 1.6.0')) + await deleteExecutable(false) + } else if (openTofuCheck) { + await deleteExecutable(true) } let url let zipPath diff --git a/packages/cli/lib/commands/use.js b/packages/cli/lib/commands/use.js index 4fe468e..47624db 100644 --- a/packages/cli/lib/commands/use.js +++ b/packages/cli/lib/commands/use.js @@ -1,4 +1,5 @@ import chalk from 'chalk' +import deleteExecutable from '../util/deleteExecutable.js' import fs from 'node:fs/promises' import enquirer from 'enquirer' import { versionRegEx } from '../util/constants.js' @@ -40,6 +41,12 @@ export async function useVersion (version) { const installedVersions = await getInstalledVersions(version) const settings = await getSettings() openTofuCheck = settings.useOpenTofu && semver.gte(version, LOWEST_OTF_VERSION) + // delete tofu executable if using a version lower than 1.6.0 + if (settings.useOpenTofu && semver.lt(version, LOWEST_OTF_VERSION)) { + await deleteExecutable(false) + } else if (openTofuCheck) { + await deleteExecutable(true) + } if (!installedVersions.includes(versionWithV)) { const successfullyInstalled = await installNewVersion(version) if (!successfullyInstalled) return @@ -94,6 +101,7 @@ export async function switchVersionTo (version) { os.getPath(os.getTerraformDir(), os.getTFExecutableName()) // destination file ) } + console.log(chalk.cyan.bold(`Now using ${openTofuCheck ? 'opentofu' : 'terraform'} v${version} (${os.getBitWidth()}-bit)`)) if (requiresOldAWSAuth(version) && !settings.disableAWSWarnings) { diff --git a/packages/cli/lib/index.js b/packages/cli/lib/index.js index 67c75b8..75b7728 100755 --- a/packages/cli/lib/index.js +++ b/packages/cli/lib/index.js @@ -1,83 +1,84 @@ #! /usr/bin/env node - -import { Command } from 'commander' -import list from './commands/list.js' -import current from './commands/current.js' -import uninstall from './commands/uninstall.js' -import use from './commands/use.js' -import install from './commands/install.js' -import getTFVMVersion from './util/getTFVMVersion.js' -import config from './commands/config.js' -import detect from './commands/detect.js' -import { logger } from './util/logger.js' -import verifySetup from './util/verifySetup.js' -import chalk from 'chalk' - -const program = new Command() - -program - .option('-l, --log-level ', 'specify log level (default "info")') - .hook('preAction', async (thisCommand) => { - const logLevel = thisCommand.opts().logLevel - logger.level = logLevel || process.env.LOG_LEVEL || 'info' - logger.debug(`Beginning execution of command "${thisCommand.args.join(' ')}":`) - logger.trace(`Raw Args: ${JSON.stringify(thisCommand.rawArgs.join(' '))}`) - if (!await verifySetup()) { - logger.fatal('failed verifySetup()') - process.exit(-1) - } - }) - .hook('postAction', (thisCommand) => { - logger.debug(`Execution of "${thisCommand.args.join(' ')}" command finished.\n\n\n`) - }) - -program - .command('detect', { isDefault: true }) - .description('Switch to version specified by local .tf files') - .action(detect) - -program - .command('uninstall ') - .description('Uninstall a version of terraform') - .action(uninstall) - -program - .command('list') - .alias('ls') - .description('List all downloaded version of terraform') - .action(list) - -program - .command('current') - .description('Display current version of terraform. Does the same thing as `terraform -v`') - .action(current) - -program - .command('use ') - .alias('u') - .description('Use a version of terraform') - .action(use) - -program - .command('config') - .argument('[setting=boolean]') - .description('Change a tfvm setting') - .action(config) - .addHelpText('after', '\nAll settings are either true or false (default is false), and set like this:\n' + - chalk.cyan('\n tfvm config =\n\n') + - 'Here are all the available settings:\n' + - 'disableErrors - Disables some recurrent warning messages\n' + - 'disableAWSWarnings - Disables warnings about needing old AWS authentication with tf versions older than 0.14.6\n' + + +import { Command } from 'commander' +import list from './commands/list.js' +import current from './commands/current.js' +import uninstall from './commands/uninstall.js' +import use from './commands/use.js' +import install from './commands/install.js' +import getTFVMVersion from './util/getTFVMVersion.js' +import config from './commands/config.js' +import detect from './commands/detect.js' +import { logger } from './util/logger.js' +import verifySetup from './util/verifySetup.js' +import chalk from 'chalk' + +const program = new Command() + +program + .option('-l, --log-level ', 'specify log level (default "info")') + .hook('preAction', async (thisCommand) => { + const logLevel = thisCommand.opts().logLevel + logger.level = logLevel || process.env.LOG_LEVEL || 'info' + logger.debug(`Beginning execution of command "${thisCommand.args.join(' ')}":`) + logger.trace(`Raw Args: ${JSON.stringify(thisCommand.rawArgs.join(' '))}`) + if (!await verifySetup()) { + logger.fatal('failed verifySetup()') + process.exit(-1) + } + }) + .hook('postAction', (thisCommand) => { + logger.debug(`Execution of "${thisCommand.args.join(' ')}" command finished.\n\n\n`) + }) + +program + .command('detect', { isDefault: true }) + .description('Switch to version specified by local .tf files') + .action(detect) + +program + .command('uninstall ') + .description('Uninstall a version of terraform') + .action(uninstall) + +program + .command('list') + .alias('ls') + .description('List all downloaded version of terraform') + .action(list) + +program + .command('current') + .description('Display current version of terraform. Does the same thing as `terraform -v`') + .action(current) + +program + .command('use ') + .alias('u') + .description('Use a version of terraform') + .action(use) + +program + .command('config') + .argument('[setting=boolean]') + .description('Change a tfvm setting') + .action(config) + .addHelpText('after', '\nAll settings are either true or false (default is false), and set like this:\n' + + chalk.cyan('\n tfvm config =\n\n') + + 'Here are all the available settings:\n' + + 'disableErrors - Disables some recurrent warning messages\n' + + 'disableAWSWarnings - Disables warnings about needing old AWS authentication with tf versions older than 0.14.6\n' + 'disableSettingsPrompts - Disables prompts to turn off warnings by enabling these settings\n' + - 'useOpenTofu - Uses OpenTofu instead of Terraform') - -program - .command('install ') - .alias('i') - .description('Install a version of terraform') - .action(install) - .addHelpText('after', '\nGet a list of all current terraform versions here: ' + chalk.blue.bold('https://releases.hashicorp.com/terraform/')) - -program.version(await getTFVMVersion(), '-v, --version', 'Output the current version of tfvm') + 'useOpenTofu - Uses OpenTofu instead of Terraform\n' + + 'disableTofuWarnings - Disables warnings related to using Tofu (deleting executables, using Tofu instead of Terraform, etc.)' ) -program.parse() +program + .command('install ') + .alias('i') + .description('Install a version of terraform') + .action(install) + .addHelpText('after', '\nGet a list of all current terraform versions here: ' + chalk.blue.bold('https://releases.hashicorp.com/terraform/')) + +program.version(await getTFVMVersion(), '-v, --version', 'Output the current version of tfvm') + +program.parse() diff --git a/packages/cli/lib/util/TfvmFS.js b/packages/cli/lib/util/TfvmFS.js index f8789d7..5034f57 100644 --- a/packages/cli/lib/util/TfvmFS.js +++ b/packages/cli/lib/util/TfvmFS.js @@ -26,23 +26,29 @@ export class TfvmFS { /** * Deletes the terraform exe so that a new one can be copied in - * @returns {Promise} + * @returns {Promise} */ static async deleteCurrentTfExe () { // if appdata/roaming/terraform/terraform.exe exists, delete it if ((await fsp.readdir(os.getTerraformDir())).includes(os.getTFExecutableName())) { await fsp.unlink(os.getTerraformDir() + path.sep + os.getTFExecutableName()) + return true + } else { + return false } } /** * Deletes the opentofu exe so that a new one can be copied in - * @returns {Promise} + * @returns {Promise} */ static async deleteCurrentOtfExe () { // if appdata/roaming/opentofu/tofu.exe exists, delete it if ((await fsp.readdir(os.getOpenTofuDir())).includes(os.getOtfExecutableName())) { await fsp.unlink(os.getOpenTofuDir() + path.sep + os.getOtfExecutableName()) + return true + } else { + return false } } diff --git a/packages/cli/lib/util/deleteExecutable.js b/packages/cli/lib/util/deleteExecutable.js index d103c2d..619cad5 100644 --- a/packages/cli/lib/util/deleteExecutable.js +++ b/packages/cli/lib/util/deleteExecutable.js @@ -1,15 +1,23 @@ import getErrorMessage from './errorChecker.js' +import chalk from 'chalk' import { logger } from './logger.js' import { TfvmFS } from './TfvmFS.js' +import getSettings from './getSettings.js' -async function deleteExecutable (useOpenTofu, os) { +async function deleteExecutable (useOpenTofu) { try { + let successfulDeletion = false if (useOpenTofu === true) { - await TfvmFS.deleteCurrentTfExe() - logger.info('Successfully deleted Terraform executable') + successfulDeletion = await TfvmFS.deleteCurrentTfExe() } else { - await TfvmFS.deleteCurrentOtfExe() - logger.info('Successfully deleted Open Tofu executable') + successfulDeletion = await TfvmFS.deleteCurrentOtfExe() + } + + logger.info(`Successfully deleted ${useOpenTofu ? 'Terraform' : 'OpenTofu'} executable`) + const settings = await getSettings() + if (!settings.disableTofuWarnings && successfulDeletion) { + console.log(chalk.magenta.bold(`Switching to ${useOpenTofu ? 'OpenTofu' : 'Terraform'}. Make sure to use the ${useOpenTofu ? 'tofu' : 'terraform'} command instead of ${useOpenTofu ? 'terraform' : 'tofu'}.`)) + return true } } catch (error) { logger.fatal(error, `Fatal error when deleting executable when useOpenTofu=${useOpenTofu}`) diff --git a/packages/cli/lib/util/getSettings.js b/packages/cli/lib/util/getSettings.js index a2da71b..1917588 100644 --- a/packages/cli/lib/util/getSettings.js +++ b/packages/cli/lib/util/getSettings.js @@ -10,7 +10,8 @@ export const defaultSettings = { disableErrors: false, disableAWSWarnings: false, disableSettingPrompts: false, - useOpenTofu: false + useOpenTofu: false, + disableTofuWarnings: false } /** From 93bc9c1a60040974ac686ef83d1521396be7a8fd Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 9 Aug 2024 10:47:09 -0600 Subject: [PATCH 26/29] fix: annoying carriage return chars --- packages/cli/lib/index.js | 159 +++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/packages/cli/lib/index.js b/packages/cli/lib/index.js index 75b7728..fe55964 100755 --- a/packages/cli/lib/index.js +++ b/packages/cli/lib/index.js @@ -1,84 +1,83 @@ #! /usr/bin/env node - -import { Command } from 'commander' -import list from './commands/list.js' -import current from './commands/current.js' -import uninstall from './commands/uninstall.js' -import use from './commands/use.js' -import install from './commands/install.js' -import getTFVMVersion from './util/getTFVMVersion.js' -import config from './commands/config.js' -import detect from './commands/detect.js' -import { logger } from './util/logger.js' -import verifySetup from './util/verifySetup.js' -import chalk from 'chalk' - -const program = new Command() - -program - .option('-l, --log-level ', 'specify log level (default "info")') - .hook('preAction', async (thisCommand) => { - const logLevel = thisCommand.opts().logLevel - logger.level = logLevel || process.env.LOG_LEVEL || 'info' - logger.debug(`Beginning execution of command "${thisCommand.args.join(' ')}":`) - logger.trace(`Raw Args: ${JSON.stringify(thisCommand.rawArgs.join(' '))}`) - if (!await verifySetup()) { - logger.fatal('failed verifySetup()') - process.exit(-1) - } - }) - .hook('postAction', (thisCommand) => { - logger.debug(`Execution of "${thisCommand.args.join(' ')}" command finished.\n\n\n`) - }) - -program - .command('detect', { isDefault: true }) - .description('Switch to version specified by local .tf files') - .action(detect) - -program - .command('uninstall ') - .description('Uninstall a version of terraform') - .action(uninstall) - -program - .command('list') - .alias('ls') - .description('List all downloaded version of terraform') - .action(list) - -program - .command('current') - .description('Display current version of terraform. Does the same thing as `terraform -v`') - .action(current) - -program - .command('use ') - .alias('u') - .description('Use a version of terraform') - .action(use) - -program - .command('config') - .argument('[setting=boolean]') - .description('Change a tfvm setting') - .action(config) - .addHelpText('after', '\nAll settings are either true or false (default is false), and set like this:\n' + - chalk.cyan('\n tfvm config =\n\n') + - 'Here are all the available settings:\n' + - 'disableErrors - Disables some recurrent warning messages\n' + - 'disableAWSWarnings - Disables warnings about needing old AWS authentication with tf versions older than 0.14.6\n' + +import { Command } from 'commander' +import list from './commands/list.js' +import current from './commands/current.js' +import uninstall from './commands/uninstall.js' +import use from './commands/use.js' +import install from './commands/install.js' +import getTFVMVersion from './util/getTFVMVersion.js' +import config from './commands/config.js' +import detect from './commands/detect.js' +import { logger } from './util/logger.js' +import verifySetup from './util/verifySetup.js' +import chalk from 'chalk' + +const program = new Command() + +program + .option('-l, --log-level ', 'specify log level (default "info")') + .hook('preAction', async (thisCommand) => { + const logLevel = thisCommand.opts().logLevel + logger.level = logLevel || process.env.LOG_LEVEL || 'info' + logger.debug(`Beginning execution of command "${thisCommand.args.join(' ')}":`) + logger.trace(`Raw Args: ${JSON.stringify(thisCommand.rawArgs.join(' '))}`) + if (!await verifySetup()) { + logger.fatal('failed verifySetup()') + process.exit(-1) + } + }) + .hook('postAction', (thisCommand) => { + logger.debug(`Execution of "${thisCommand.args.join(' ')}" command finished.\n\n\n`) + }) + +program + .command('detect', { isDefault: true }) + .description('Switch to version specified by local .tf files') + .action(detect) + +program + .command('uninstall ') + .description('Uninstall a version of terraform') + .action(uninstall) + +program + .command('list') + .alias('ls') + .description('List all downloaded version of terraform') + .action(list) + +program + .command('current') + .description('Display current version of terraform. Does the same thing as `terraform -v`') + .action(current) + +program + .command('use ') + .alias('u') + .description('Use a version of terraform') + .action(use) + +program + .command('config') + .argument('[setting=boolean]') + .description('Change a tfvm setting') + .action(config) + .addHelpText('after', '\nAll settings are either true or false (default is false), and set like this:\n' + + chalk.cyan('\n tfvm config =\n\n') + + 'Here are all the available settings:\n' + + 'disableErrors - Disables some recurrent warning messages\n' + + 'disableAWSWarnings - Disables warnings about needing old AWS authentication with tf versions older than 0.14.6\n' + 'disableSettingsPrompts - Disables prompts to turn off warnings by enabling these settings\n' + 'useOpenTofu - Uses OpenTofu instead of Terraform\n' + - 'disableTofuWarnings - Disables warnings related to using Tofu (deleting executables, using Tofu instead of Terraform, etc.)' ) + 'disableTofuWarnings - Disables warnings related to using Tofu (deleting executables, using Tofu instead of Terraform, etc.)') + +program + .command('install ') + .alias('i') + .description('Install a version of terraform') + .action(install) + .addHelpText('after', '\nGet a list of all current terraform versions here: ' + chalk.blue.bold('https://releases.hashicorp.com/terraform/')) + +program.version(await getTFVMVersion(), '-v, --version', 'Output the current version of tfvm') -program - .command('install ') - .alias('i') - .description('Install a version of terraform') - .action(install) - .addHelpText('after', '\nGet a list of all current terraform versions here: ' + chalk.blue.bold('https://releases.hashicorp.com/terraform/')) - -program.version(await getTFVMVersion(), '-v, --version', 'Output the current version of tfvm') - -program.parse() +program.parse() From 28ac6c35c2fa428258bce904bded28e683542fc2 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 9 Aug 2024 11:12:48 -0600 Subject: [PATCH 27/29] chore: clean up --- packages/cli/lib/commands/list.js | 5 +++-- packages/cli/lib/commands/uninstall.js | 3 ++- packages/cli/lib/util/download.js | 4 +++- packages/cli/lib/util/getInstalledVersions.js | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/cli/lib/commands/list.js b/packages/cli/lib/commands/list.js index 46fd991..d36994a 100644 --- a/packages/cli/lib/commands/list.js +++ b/packages/cli/lib/commands/list.js @@ -9,6 +9,7 @@ import * as semver from 'semver' import getSettings from '../util/getSettings.js' const os = getOS() +const LOWEST_OTF_VERSION = '1.6.0' async function list () { try { const printList = [] @@ -26,9 +27,9 @@ async function list () { // logic to get the correct spacing const parsed = semver.parse(version) type += (parsed.minor.toString().length === 1 && parsed.patch.toString().length === 1 ? ' ' : ' ') - if (semver.gte(version, '1.6.0')) { + if (semver.gte(version, LOWEST_OTF_VERSION)) { type += '[OpenTofu]' - } else if (semver.lt(version, '1.6.0')) { + } else if (semver.lt(version, LOWEST_OTF_VERSION)) { type += '[Terraform]' } } diff --git a/packages/cli/lib/commands/uninstall.js b/packages/cli/lib/commands/uninstall.js index fd800c5..42d37c2 100644 --- a/packages/cli/lib/commands/uninstall.js +++ b/packages/cli/lib/commands/uninstall.js @@ -9,6 +9,7 @@ import { TfvmFS } from '../util/TfvmFS.js' import * as semver from 'semver' const os = getOS() +const LOWEST_OTF_VERSION = '1.6.0' async function uninstall (uninstallVersion) { try { uninstallVersion = 'v' + uninstallVersion @@ -17,7 +18,7 @@ async function uninstall (uninstallVersion) { } else { const settings = await getSettings() const installedVersions = await getInstalledVersions() - const semverCheck = semver.gte(uninstallVersion, '1.6.0') + const semverCheck = semver.gte(uninstallVersion, LOWEST_OTF_VERSION) const openTofuCheck = settings.useOpenTofu && semverCheck if (!installedVersions.includes(uninstallVersion)) { diff --git a/packages/cli/lib/util/download.js b/packages/cli/lib/util/download.js index c682cd9..ebb14ce 100644 --- a/packages/cli/lib/util/download.js +++ b/packages/cli/lib/util/download.js @@ -4,6 +4,8 @@ import axios from 'axios' import fs from 'node:fs/promises' import * as semver from 'semver' +const LOWEST_OTF_VERSION = '1.6.0' + const download = async (url, filePath, version) => { try { const response = await axios.get(url, { responseType: 'arraybuffer' }) @@ -11,7 +13,7 @@ const download = async (url, filePath, version) => { await fs.writeFile(filePath, fileData) } catch (err) { const settings = await getSettings() - console.log(chalk.red.bold(`${settings.useOpenTofu && semver.gte(version, '1.6.0') ? 'OpenTofu' : 'Terraform'} ${version} is not yet released or available.`)) + console.log(chalk.red.bold(`${settings.useOpenTofu && semver.gte(version, LOWEST_OTF_VERSION) ? 'OpenTofu' : 'Terraform'} ${version} is not yet released or available.`)) throw new Error() } } diff --git a/packages/cli/lib/util/getInstalledVersions.js b/packages/cli/lib/util/getInstalledVersions.js index bffe6f0..245e876 100644 --- a/packages/cli/lib/util/getInstalledVersions.js +++ b/packages/cli/lib/util/getInstalledVersions.js @@ -7,6 +7,7 @@ import * as semver from 'semver' const os = getOS() let installedVersions +const LOWEST_OTF_VERSION = '1.6.0' /** * Returns a list of installed tf versions. @@ -21,13 +22,13 @@ async function getInstalledVersions (version = '') { let semverCheck = true if (version !== '') { - semverCheck = semver.gte(version, '1.6.0') + semverCheck = semver.gte(version, LOWEST_OTF_VERSION) } if (settings.useOpenTofu && semverCheck) { files = await fs.readdir(os.getOtfVersionsDir()) const terraformFiles = await fs.readdir(os.getTfVersionsDir()) terraformFiles.forEach(file => { - if (semver.lt(file, '1.6.0')) { + if (semver.lt(file, LOWEST_OTF_VERSION)) { files.push(file) } }) From 96b9388c642a54b5f9bcb51e5d58119b9520ff83 Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 9 Aug 2024 11:48:05 -0600 Subject: [PATCH 28/29] fix: current version starred --- packages/cli/lib/util/tfVersion.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/lib/util/tfVersion.js b/packages/cli/lib/util/tfVersion.js index c36c62e..88ae01e 100644 --- a/packages/cli/lib/util/tfVersion.js +++ b/packages/cli/lib/util/tfVersion.js @@ -2,9 +2,11 @@ import runShell from './runShell.js' import { logger } from './logger.js' import { tfCurrVersionRegEx, openTofuCurrVersionRegEx } from './constants.js' import getSettings from './getSettings.js' +import * as semver from 'semver' let currentTfVersion let currentOtfVersion +const LOWEST_OTF_VERSION = '1.6.0' /** * Returns the current terraform version, if there is one. Returns null if there is no current version @@ -19,6 +21,9 @@ async function getTerraformVersion () { let response if (settings.useOpenTofu) { response = (await runShell('tofu -v')) + if (response === null) { + response = (await runShell('terraform -v')) + } } else { response = (await runShell('terraform -v')) } @@ -29,8 +34,8 @@ async function getTerraformVersion () { } let versionExtractionResult - if (settings.useOpenTofu) versionExtractionResult = Array.from(response.matchAll(openTofuCurrVersionRegEx)) - else versionExtractionResult = Array.from(response.matchAll(tfCurrVersionRegEx)) + if (response.includes('Terraform')) versionExtractionResult = Array.from(response.matchAll(tfCurrVersionRegEx)) + else versionExtractionResult = Array.from(response.matchAll(openTofuCurrVersionRegEx)) if (versionExtractionResult.length === 0) { logger.error('Error extracting terraform version where this is the response from `terraform -v`:\n' + response) From 710523e3c699972927130c69a9e8797864f0910e Mon Sep 17 00:00:00 2001 From: chlohilt Date: Fri, 9 Aug 2024 11:54:47 -0600 Subject: [PATCH 29/29] fix: lint --- packages/cli/lib/util/tfVersion.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cli/lib/util/tfVersion.js b/packages/cli/lib/util/tfVersion.js index 88ae01e..94b71c9 100644 --- a/packages/cli/lib/util/tfVersion.js +++ b/packages/cli/lib/util/tfVersion.js @@ -2,11 +2,9 @@ import runShell from './runShell.js' import { logger } from './logger.js' import { tfCurrVersionRegEx, openTofuCurrVersionRegEx } from './constants.js' import getSettings from './getSettings.js' -import * as semver from 'semver' let currentTfVersion let currentOtfVersion -const LOWEST_OTF_VERSION = '1.6.0' /** * Returns the current terraform version, if there is one. Returns null if there is no current version