diff --git a/README.md b/README.md index 0b271877..c5a5a34d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,23 @@ Acceptable values are latest or any semantic version string like v3.5.0 Use this id: install ``` +Alternatively, the version can be read from a [`.tool-versions`](https://asdf-vm.com/manage/configuration.html) file (the format used by [asdf](https://asdf-vm.com/) and [mise](https://mise.jdx.dev/)) via the `version-file` input: + +```yaml +- uses: azure/setup-helm@v5.0.0 + with: + version-file: .tool-versions + id: install +``` + +The action reads the version declared for the `helm` tool, for example: + +``` +helm 3.18.4 +``` + +If both `version` and `version-file` are set, `version` takes precedence. + > [!NOTE] > If something goes wrong with fetching the latest version the action will use the hardcoded default version (currently v3.18.3). If you rely on a certain version higher than the default, you should explicitly use that version instead of latest. diff --git a/action.yml b/action.yml index 39d8caec..572af494 100644 --- a/action.yml +++ b/action.yml @@ -3,8 +3,11 @@ description: 'Install a specific version of helm binary. Acceptable values are l inputs: version: description: 'Version of helm' - required: true + required: false default: 'latest' + version-file: + description: 'Path to a .tool-versions file to read the helm version from' + required: false token: description: GitHub token. Used to be required to fetch the latest version required: false diff --git a/src/run.test.ts b/src/run.test.ts index 01f2fd00..073c747d 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -19,7 +19,8 @@ vi.mock('fs', async (importOriginal) => { readdirSync: vi.fn(), statSync: vi.fn(), chmodSync: vi.fn(), - readFileSync: vi.fn() + readFileSync: vi.fn(), + existsSync: vi.fn() } }) @@ -161,6 +162,53 @@ describe('run.ts', () => { expect(run.getValidVersion('3.8.0')).toBe('v3.8.0') }) + test('parseToolVersions() - return the helm version from .tool-versions content', () => { + const content = ['nodejs 20.11.0', 'helm 3.14.0', 'terraform 1.7.0'].join( + '\n' + ) + expect(run.parseToolVersions(content)).toBe('3.14.0') + }) + + test('parseToolVersions() - ignore comments and blank lines', () => { + const content = ['# tools', '', ' helm 3.15.2 ', ''].join('\n') + expect(run.parseToolVersions(content)).toBe('3.15.2') + }) + + test('parseToolVersions() - return the first version when several are listed', () => { + expect(run.parseToolVersions('helm 3.14.0 3.13.0')).toBe('3.14.0') + }) + + test('parseToolVersions() - return empty string when helm is not declared', () => { + expect(run.parseToolVersions('nodejs 20.11.0')).toBe('') + }) + + test('getVersionFromToolVersionsFile() - read the helm version from a file', () => { + vi.mocked(fs.existsSync).mockReturnValue(true) + vi.mocked(fs.readFileSync).mockReturnValue('helm 3.14.0') + + expect(run.getVersionFromToolVersionsFile('.tool-versions')).toBe( + '3.14.0' + ) + expect(fs.readFileSync).toHaveBeenCalledWith('.tool-versions', 'utf8') + }) + + test('getVersionFromToolVersionsFile() - throw when the file does not exist', () => { + vi.mocked(fs.existsSync).mockReturnValue(false) + + expect(() => + run.getVersionFromToolVersionsFile('missing.tool-versions') + ).toThrow("The version-file 'missing.tool-versions' does not exist") + }) + + test('getVersionFromToolVersionsFile() - throw when no helm version is present', () => { + vi.mocked(fs.existsSync).mockReturnValue(true) + vi.mocked(fs.readFileSync).mockReturnValue('nodejs 20.11.0') + + expect(() => + run.getVersionFromToolVersionsFile('.tool-versions') + ).toThrow("No helm version found in '.tool-versions'") + }) + test('walkSync() - return path to the all files matching fileToFind in dir', () => { vi.mocked(fs.readdirSync).mockImplementation((file, _?) => { if (file == 'mainFolder') diff --git a/src/run.ts b/src/run.ts index 9554797b..6d9d3ec8 100644 --- a/src/run.ts +++ b/src/run.ts @@ -13,7 +13,23 @@ const helmToolName = 'helm' export const stableHelmVersion = 'v3.18.4' export async function run() { - let version = core.getInput('version', {required: true}) + let version = core.getInput('version') + const versionFile = core.getInput('version-file') + + if (versionFile) { + if (version && version !== 'latest') { + core.warning( + `Both 'version' and 'version-file' inputs are specified, only 'version' will be used.` + ) + } else { + version = getVersionFromToolVersionsFile(versionFile) + core.info(`Resolved Helm version '${version}' from '${versionFile}'`) + } + } + + if (!version) { + version = 'latest' + } if (version !== 'latest' && version[0] !== 'v') { core.info('Getting latest Helm version') @@ -46,6 +62,36 @@ export function getValidVersion(version: string): string { return 'v' + version } +// Reads a .tool-versions file and returns the helm version declared in it +export function getVersionFromToolVersionsFile(filePath: string): string { + if (!fs.existsSync(filePath)) { + throw new Error(`The version-file '${filePath}' does not exist`) + } + const content = fs.readFileSync(filePath, 'utf8') + const version = parseToolVersions(content) + if (!version) { + throw new Error(`No helm version found in '${filePath}'`) + } + return version +} + +// Parses .tool-versions content (asdf/mise format) and returns the first +// helm version, or an empty string when none is declared. Lines look like +// `helm 3.14.0`; comments (#) and blank lines are ignored. +export function parseToolVersions(content: string): string { + for (const line of content.split(/\r?\n/)) { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#')) { + continue + } + const [tool, version] = trimmed.split(/\s+/) + if (tool === helmToolName && version) { + return version + } + } + return '' +} + // Gets the latest helm version or returns a default stable if getting latest fails export async function getLatestHelmVersion(): Promise { try {