diff --git a/action.yml b/action.yml index 00d25a20..dc2a0bce 100644 --- a/action.yml +++ b/action.yml @@ -3,8 +3,11 @@ description: 'Install a specific version of kubectl binary. Acceptable values ar inputs: version: description: 'Version of kubectl' - required: true + required: false default: 'latest' + version-file: + description: 'Path to a .tool-versions (asdf/mise) file to read the kubectl version from. Takes precedence over the version input.' + required: false outputs: kubectl-path: description: 'Path to the cached kubectl binary' diff --git a/src/helpers.ts b/src/helpers.ts index 1774c719..c37c2188 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -54,3 +54,18 @@ export function getExecutableExtension(): string { } return '' } + +export function parseToolVersionsFile(filePath: string): string { + const content = fs.readFileSync(filePath, 'utf8').toString() + for (const line of content.split('\n')) { + const trimmed = line.trim() + if (trimmed.startsWith('#') || trimmed === '') continue + const [tool, version] = trimmed.split(/\s+/) + if (tool === 'kubectl' && version) { + return version + } + } + throw new Error( + `Could not find a kubectl entry in tool-versions file: ${filePath}` + ) +} diff --git a/src/run.test.ts b/src/run.test.ts index 40db1053..df01301b 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -24,7 +24,8 @@ const { getkubectlDownloadURL, getKubectlArch, getExecutableExtension, - getLatestPatchVersion + getLatestPatchVersion, + parseToolVersionsFile } = await import('./helpers.js') describe('Testing all functions in run file.', () => { @@ -226,7 +227,10 @@ describe('Testing all functions in run file.', () => { expect(result).toBe('v1.27.15') }) test('run() - download specified version and set output', async () => { - vi.mocked(core.getInput).mockReturnValue('v1.15.5') + vi.mocked(core.getInput).mockImplementation((name) => { + if (name === 'version-file') return '' + return 'v1.15.5' + }) vi.mocked(toolCache.find).mockReturnValue('pathToCachedTool') vi.mocked(os.type).mockReturnValue('Windows_NT') vi.mocked(fs.chmodSync).mockImplementation() @@ -234,6 +238,7 @@ describe('Testing all functions in run file.', () => { vi.spyOn(console, 'log').mockImplementation() vi.mocked(core.setOutput).mockImplementation() expect(await run.run()).toBeUndefined() + expect(core.getInput).toHaveBeenCalledWith('version-file') expect(core.getInput).toHaveBeenCalledWith('version', {required: true}) expect(core.addPath).toHaveBeenCalledWith('pathToCachedTool') expect(core.setOutput).toHaveBeenCalledWith( @@ -242,7 +247,10 @@ describe('Testing all functions in run file.', () => { ) }) test('run() - get latest version, download it and set output', async () => { - vi.mocked(core.getInput).mockReturnValue('latest') + vi.mocked(core.getInput).mockImplementation((name) => { + if (name === 'version-file') return '' + return 'latest' + }) vi.mocked(toolCache.downloadTool).mockResolvedValue('pathToTool') vi.mocked(fs.readFileSync).mockReturnValue('v1.20.4') vi.mocked(toolCache.find).mockReturnValue('pathToCachedTool') @@ -255,6 +263,7 @@ describe('Testing all functions in run file.', () => { expect(toolCache.downloadTool).toHaveBeenCalledWith( 'https://dl.k8s.io/release/stable.txt' ) + expect(core.getInput).toHaveBeenCalledWith('version-file') expect(core.getInput).toHaveBeenCalledWith('version', {required: true}) expect(core.addPath).toHaveBeenCalledWith('pathToCachedTool') expect(core.setOutput).toHaveBeenCalledWith( @@ -262,4 +271,43 @@ describe('Testing all functions in run file.', () => { path.join('pathToCachedTool', 'kubectl.exe') ) }) + test('parseToolVersionsFile() - return kubectl version from .tool-versions file', () => { + vi.mocked(fs.readFileSync).mockReturnValue( + 'kubectl 1.27.15\nnode 20.0.0\n' + ) + expect(parseToolVersionsFile('.tool-versions')).toBe('1.27.15') + expect(fs.readFileSync).toHaveBeenCalledWith('.tool-versions', 'utf8') + }) + test('parseToolVersionsFile() - ignore comments and blank lines', () => { + vi.mocked(fs.readFileSync).mockReturnValue( + '# comment\n\nkubectl 1.27.15\n' + ) + expect(parseToolVersionsFile('.tool-versions')).toBe('1.27.15') + }) + test('parseToolVersionsFile() - throw error when kubectl entry is not found', () => { + vi.mocked(fs.readFileSync).mockReturnValue('node 20.0.0\npython 3.11.0\n') + expect(() => parseToolVersionsFile('.tool-versions')).toThrow( + 'Could not find a kubectl entry in tool-versions file: .tool-versions' + ) + }) + test('run() - use version-file to determine kubectl version', async () => { + vi.mocked(core.getInput).mockImplementation((name) => { + if (name === 'version-file') return '.tool-versions' + return '' + }) + vi.mocked(fs.readFileSync).mockReturnValue('kubectl 1.27.15\n') + vi.mocked(toolCache.find).mockReturnValue('pathToCachedTool') + vi.mocked(os.type).mockReturnValue('Linux') + vi.mocked(fs.chmodSync).mockImplementation() + vi.mocked(core.addPath).mockImplementation() + vi.mocked(core.setOutput).mockImplementation() + expect(await run.run()).toBeUndefined() + expect(core.getInput).toHaveBeenCalledWith('version-file') + expect(fs.readFileSync).toHaveBeenCalledWith('.tool-versions', 'utf8') + expect(core.addPath).toHaveBeenCalledWith('pathToCachedTool') + expect(core.setOutput).toHaveBeenCalledWith( + 'kubectl-path', + path.join('pathToCachedTool', 'kubectl') + ) + }) }) diff --git a/src/run.ts b/src/run.ts index e6ff023c..70c2c187 100644 --- a/src/run.ts +++ b/src/run.ts @@ -7,7 +7,8 @@ import { getkubectlDownloadURL, getKubectlArch, getExecutableExtension, - getLatestPatchVersion + getLatestPatchVersion, + parseToolVersionsFile } from './helpers.js' const kubectlToolName = 'kubectl' @@ -15,11 +16,19 @@ const stableKubectlVersion = 'v1.15.0' const stableVersionUrl = 'https://dl.k8s.io/release/stable.txt' export async function run() { - let version = core.getInput('version', {required: true}) - if (version.toLocaleLowerCase() === 'latest') { - version = await getStableKubectlVersion() + const versionFile = core.getInput('version-file') + let version: string + + if (versionFile) { + const rawVersion = parseToolVersionsFile(versionFile) + version = await resolveKubectlVersion(rawVersion) } else { - version = await resolveKubectlVersion(version) + version = core.getInput('version', {required: true}) + if (version.toLocaleLowerCase() === 'latest') { + version = await getStableKubectlVersion() + } else { + version = await resolveKubectlVersion(version) + } } const cachedPath = await downloadKubectl(version)