mirror of
https://github.com/Azure/setup-helm.git
synced 2026-06-28 17:40:45 +00:00
Add version-file input to read the Helm version from a .tool-versions file (#281)
* feat: add version-file input to read helm version from .tool-versions * feat: validate semver shape of helm version from .tool-versions
This commit is contained in:
parent
95ecf4967d
commit
017211e1b1
4 changed files with 221 additions and 5 deletions
19
README.md
19
README.md
|
|
@ -13,8 +13,25 @@ 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, an explicitly requested `version` takes precedence and `version-file` is ignored (a warning is emitted). Because `version` defaults to `latest`, `version-file` is only ignored when you set `version` to a specific value other than `latest`; if `version` is left at its default, the version from `version-file` is used.
|
||||
|
||||
> [!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.
|
||||
> If something goes wrong with fetching the latest version the action will use the hardcoded default version (currently v3.18.4). If you rely on a certain version higher than the default, you should explicitly use that version instead of latest.
|
||||
|
||||
The cached helm binary path is prepended to the PATH environment variable as well as stored in the helm-path output variable.
|
||||
Refer to the action metadata file for details about all the inputs https://github.com/Azure/setup-helm/blob/master/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
|
||||
|
|
|
|||
137
src/run.test.ts
137
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,140 @@ 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('getVersionFromToolVersionsFile() - throw when the helm version is not semver-shaped', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true)
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('helm latest')
|
||||
|
||||
expect(() =>
|
||||
run.getVersionFromToolVersionsFile('.tool-versions')
|
||||
).toThrow(
|
||||
"The helm version 'latest' in '.tool-versions' is not a valid semantic version"
|
||||
)
|
||||
})
|
||||
|
||||
test('isSemVerShaped() - accept semver-shaped versions with or without a v prefix', () => {
|
||||
expect(run.isSemVerShaped('3.14.0')).toBe(true)
|
||||
expect(run.isSemVerShaped('v3.14.0')).toBe(true)
|
||||
expect(run.isSemVerShaped('3.14.0-rc.1')).toBe(true)
|
||||
})
|
||||
|
||||
test('isSemVerShaped() - reject values that are not semver-shaped', () => {
|
||||
expect(run.isSemVerShaped('latest')).toBe(false)
|
||||
expect(run.isSemVerShaped('3.14')).toBe(false)
|
||||
expect(run.isSemVerShaped('abc')).toBe(false)
|
||||
})
|
||||
|
||||
// Stubs the download chain so run() resolves to a cached helm binary,
|
||||
// letting these tests focus on version-vs-version-file resolution.
|
||||
const stubDownloadChain = () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux')
|
||||
vi.mocked(os.arch).mockReturnValue('x64')
|
||||
vi.mocked(toolCache.find).mockReturnValue('pathToCachedDir')
|
||||
vi.mocked(fs.chmodSync).mockImplementation(() => {})
|
||||
vi.mocked(fs.readdirSync).mockReturnValue([
|
||||
'helm' as unknown as fs.Dirent<NonSharedBuffer>
|
||||
])
|
||||
vi.mocked(fs.statSync).mockReturnValue({
|
||||
isDirectory: () => false
|
||||
} as fs.Stats)
|
||||
}
|
||||
|
||||
const inputs = (version: string, versionFile: string) =>
|
||||
vi.mocked(core.getInput).mockImplementation((name: string) => {
|
||||
if (name === 'version') return version
|
||||
if (name === 'version-file') return versionFile
|
||||
if (name === 'downloadBaseURL') return downloadBaseURL
|
||||
return ''
|
||||
})
|
||||
|
||||
test('run() - resolve the version from version-file when version is not set', async () => {
|
||||
stubDownloadChain()
|
||||
inputs('', '.tool-versions')
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true)
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('helm 3.14.0')
|
||||
|
||||
await run.run()
|
||||
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.14.0')
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
'helm-path',
|
||||
path.join('pathToCachedDir', 'helm')
|
||||
)
|
||||
})
|
||||
|
||||
test('run() - resolve the version from version-file when version is left at the latest default', async () => {
|
||||
stubDownloadChain()
|
||||
inputs('latest', '.tool-versions')
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true)
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('helm 3.14.0')
|
||||
|
||||
await run.run()
|
||||
|
||||
expect(core.warning).not.toHaveBeenCalled()
|
||||
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.14.0')
|
||||
})
|
||||
|
||||
test('run() - warn and prefer version over version-file when both are set', async () => {
|
||||
stubDownloadChain()
|
||||
inputs('3.5.0', '.tool-versions')
|
||||
|
||||
await run.run()
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
`Both 'version' and 'version-file' inputs are specified, only 'version' will be used.`
|
||||
)
|
||||
expect(fs.readFileSync).not.toHaveBeenCalled()
|
||||
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.5.0')
|
||||
})
|
||||
|
||||
test('walkSync() - return path to the all files matching fileToFind in dir', () => {
|
||||
vi.mocked(fs.readdirSync).mockImplementation((file, _?) => {
|
||||
if (file == 'mainFolder')
|
||||
|
|
|
|||
65
src/run.ts
65
src/run.ts
|
|
@ -13,11 +13,27 @@ 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')
|
||||
version = getValidVersion(version)
|
||||
core.info(`Normalized Helm version to '${version}'`)
|
||||
}
|
||||
if (version.toLocaleLowerCase() === 'latest') {
|
||||
version = await getLatestHelmVersion()
|
||||
|
|
@ -46,6 +62,51 @@ export function getValidVersion(version: string): string {
|
|||
return 'v' + version
|
||||
}
|
||||
|
||||
// Matches a semantic version (major.minor.patch) with an optional leading 'v'
|
||||
// and optional pre-release / build-metadata suffixes, e.g. '3.14.0', 'v3.14.0',
|
||||
// '3.14.0-rc.1'.
|
||||
const semVerShape = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
|
||||
|
||||
// Returns true when version looks like a semantic version
|
||||
export function isSemVerShaped(version: string): boolean {
|
||||
return semVerShape.test(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}'`)
|
||||
}
|
||||
if (!isSemVerShaped(version)) {
|
||||
throw new Error(
|
||||
`The helm version '${version}' in '${filePath}' is not a valid semantic version`
|
||||
)
|
||||
}
|
||||
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<string> {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue