From 409343f6e40ffec85a7c30f90e27bf42f6de3528 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 9 Dec 2024 15:25:13 +0100 Subject: [PATCH] feat: implement support for the terraform_version_file fix --- lib/setup-terraform.js | 58 +++++++++++++++++- test/setup-terraform.test.js | 111 +++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/lib/setup-terraform.js b/lib/setup-terraform.js index 87e7575..f6d937e 100644 --- a/lib/setup-terraform.js +++ b/lib/setup-terraform.js @@ -121,14 +121,70 @@ credentials "${credentialsHostname}" { await fs.writeFile(credsFile, creds); } +async function getVersionFromFileContent (versionFile) { + if (!versionFile) { + return; + } + + let versionRegExp; + const versionFileName = path.basename(versionFile); + if (versionFileName === '.tool-versions') { + versionRegExp = /^(terraform\s+)(?[^\s]+)$/m; + } else if (versionFileName) { + versionRegExp = /(?[^\s]+)/; + } else { + return; + } + + try { + const content = fs.readFileSync(versionFile).toString().trim(); + let fileContent = ''; + if (content.match(versionRegExp)?.groups?.version) { + fileContent = content.match(versionRegExp)?.groups?.version; + } + if (!fileContent) { + return; + } + core.debug(`Version from file '${fileContent}'`); + return fileContent; + } catch (error) { + if (error.code === 'ENOENT') { + return; + } + throw error; + } +} + +// get the Terraform version from the action inputs +async function getTerraformVersion (versionInput, versionFile) { + const DEFAULT_VERSION = 'latest'; + + let version = versionInput; + if (!versionInput && !versionFile) { + core.info(`Set default value for version to ${DEFAULT_VERSION}`); + version = DEFAULT_VERSION; + } + + if (!version && versionFile) { + version = await getVersionFromFileContent(versionFile); + if (!version) { + throw new Error(`No supported version was found in file ${versionFile}`); + } + } + return version; +} + async function run () { try { // Gather GitHub Actions inputs - const version = core.getInput('terraform_version'); + const versionInput = core.getInput('terraform_version', { required: false }); + const versionFile = core.getInput('terraform_version_file', { required: false }); const credentialsHostname = core.getInput('cli_config_credentials_hostname'); const credentialsToken = core.getInput('cli_config_credentials_token'); const wrapper = core.getInput('terraform_wrapper') === 'true'; + const version = await getTerraformVersion(versionInput, versionFile); + // Gather OS details const osPlatform = os.platform(); const osArch = os.arch(); diff --git a/test/setup-terraform.test.js b/test/setup-terraform.test.js index 57cda0f..14dc697 100644 --- a/test/setup-terraform.test.js +++ b/test/setup-terraform.test.js @@ -44,12 +44,14 @@ describe('Setup Terraform', () => { test('gets specific version and adds token and hostname on linux, amd64', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -86,12 +88,14 @@ describe('Setup Terraform', () => { test('gets specific version and adds token and hostname on windows, 386', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -131,12 +135,14 @@ describe('Setup Terraform', () => { test('gets latest version and adds token and hostname on linux, amd64', async () => { const version = 'latest'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -174,12 +180,14 @@ describe('Setup Terraform', () => { test('gets latest version matching specification adds token and hostname on linux, amd64', async () => { const version = '<0.10.0'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -217,12 +225,14 @@ describe('Setup Terraform', () => { test('gets latest version matching tilde range patch', async () => { const version = '~0.1.0'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -259,12 +269,14 @@ describe('Setup Terraform', () => { test('gets latest version matching tilde range minor', async () => { const version = '~0.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -301,12 +313,14 @@ describe('Setup Terraform', () => { test('gets latest version matching tilde range minor', async () => { const version = '~0'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -343,12 +357,14 @@ describe('Setup Terraform', () => { test('gets latest version matching .X range ', async () => { const version = '0.1.x'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -385,12 +401,14 @@ describe('Setup Terraform', () => { test('gets latest version matching - range ', async () => { const version = '0.1.0 - 0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -427,12 +445,14 @@ describe('Setup Terraform', () => { test('fails when metadata cannot be downloaded', async () => { const version = 'latest'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -449,12 +469,14 @@ describe('Setup Terraform', () => { test('fails when specific version cannot be found', async () => { const version = '0.9.9'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -471,12 +493,14 @@ describe('Setup Terraform', () => { test('fails when CLI for os and architecture cannot be found', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -509,12 +533,14 @@ describe('Setup Terraform', () => { test('fails when CLI cannot be downloaded', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken); @@ -547,6 +573,7 @@ describe('Setup Terraform', () => { test('installs wrapper on linux', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; const wrapperPath = path.resolve([__dirname, '..', 'wrapper', 'dist', 'index.js'].join(path.sep)); @@ -559,6 +586,7 @@ describe('Setup Terraform', () => { core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken) .mockReturnValueOnce('true'); @@ -591,6 +619,7 @@ describe('Setup Terraform', () => { test('installs wrapper on windows', async () => { const version = '0.1.1'; + const versionFile = ''; const credentialsHostname = 'app.terraform.io'; const credentialsToken = 'asdfjkl'; const wrapperPath = path.resolve([__dirname, '..', 'wrapper', 'dist', 'index.js'].join(path.sep)); @@ -603,6 +632,7 @@ describe('Setup Terraform', () => { core.getInput = jest .fn() .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) .mockReturnValueOnce(credentialsHostname) .mockReturnValueOnce(credentialsToken) .mockReturnValueOnce('true'); @@ -632,4 +662,85 @@ describe('Setup Terraform', () => { expect(ioMv).toHaveBeenCalledWith(`file${path.sep}terraform.exe`, `file${path.sep}terraform-bin.exe`); expect(ioCp).toHaveBeenCalledWith(wrapperPath, `file${path.sep}terraform`); }); + + test('gets version from .tool-versions file and adds token and hostname on linux, amd64', async () => { + const version = ''; + const versionFile = '.tool-versions'; + const credentialsHostname = 'app.terraform.io'; + const credentialsToken = 'asdfjkl'; + + core.getInput = jest + .fn() + .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) + .mockReturnValueOnce(credentialsHostname) + .mockReturnValueOnce(credentialsToken); + + tc.downloadTool = jest + .fn() + .mockReturnValueOnce('file.zip'); + tc.extractZip = jest + .fn() + .mockReturnValueOnce('file'); + + os.platform = jest + .fn() + .mockReturnValue('linux'); + + os.arch = jest + .fn() + .mockReturnValue('amd64'); + + nock('https://releases.hashicorp.com') + .get('/terraform/index.json') + .reply(200, json); + + fs.readFileSync = jest + .fn() + .mockReturnValueOnce('terraform 0.10.0'); + + const versionObj = await setup(); + expect(versionObj.version).toEqual('0.10.0'); + }); + + test('gets version from version if both (version and .tool-versions file) are set', async () => { + const version = '0.1.0'; + const versionFile = '.tool-versions'; + const credentialsHostname = 'app.terraform.io'; + const credentialsToken = 'asdfjkl'; + + core.getInput = jest + .fn() + .mockReturnValueOnce(version) + .mockReturnValueOnce(versionFile) + .mockReturnValueOnce(credentialsHostname) + .mockReturnValueOnce(credentialsToken); + + tc.downloadTool = jest + .fn() + .mockReturnValueOnce('file.zip'); + + tc.extractZip = jest + .fn() + .mockReturnValueOnce('file'); + + os.platform = jest + .fn() + .mockReturnValue('linux'); + + os.arch = jest + .fn() + .mockReturnValue('amd64'); + + nock('https://releases.hashicorp.com') + .get('/terraform/index.json') + .reply(200, json); + + fs.readFileSync = jest + .fn() + .mockReturnValueOnce('terraform 0.10.0'); + + const versionObj = await setup(); + expect(versionObj.version).toEqual('0.1.0'); + }); });