diff --git a/lib/modules/manager/helm-values/extract.ts b/lib/modules/manager/helm-values/extract.ts index 61437fdbb79571..7e707aab22327b 100644 --- a/lib/modules/manager/helm-values/extract.ts +++ b/lib/modules/manager/helm-values/extract.ts @@ -5,6 +5,7 @@ import { getDep } from '../dockerfile/extract'; import type { PackageDependency, PackageFileContent } from '../types'; import type { HelmDockerImageDependency } from './types'; import { + getParsedSiblingChartYaml, matchesHelmValuesDockerHeuristic, matchesHelmValuesInlineImage, } from './util'; @@ -57,10 +58,10 @@ function findDependencies( return packageDependencies; } -export function extractPackageFile( +export async function extractPackageFile( content: string, - packageFile?: string, -): PackageFileContent | null { + packageFile: string, +): Promise { let parsedContent: Record[] | HelmDockerImageDependency[]; try { // a parser that allows extracting line numbers would be preferable, with @@ -79,6 +80,17 @@ export function extractPackageFile( } if (deps.length) { + // in Helm, the current package version is the version of the chart. + // This fetches this version by reading it from the Chart.yaml + // found in the same folder as the currently processed values file. + const siblingChart = await getParsedSiblingChartYaml(packageFile); + const packageFileVersion = siblingChart?.version; + if (packageFileVersion) { + return { + deps, + packageFileVersion, + }; + } return { deps }; } } catch (err) /* istanbul ignore next */ { diff --git a/lib/modules/manager/helm-values/index.ts b/lib/modules/manager/helm-values/index.ts index 6d96f591c1b390..61290829cab14b 100644 --- a/lib/modules/manager/helm-values/index.ts +++ b/lib/modules/manager/helm-values/index.ts @@ -1,6 +1,7 @@ import type { Category } from '../../../constants'; import { DockerDatasource } from '../../datasource/docker'; export { extractPackageFile } from './extract'; +export { bumpPackageVersion } from './update'; export const defaultConfig = { commitMessageTopic: 'helm values {{depName}}', diff --git a/lib/modules/manager/helm-values/update.ts b/lib/modules/manager/helm-values/update.ts new file mode 100644 index 00000000000000..493abda86e10f0 --- /dev/null +++ b/lib/modules/manager/helm-values/update.ts @@ -0,0 +1,56 @@ +import { ReleaseType, inc } from 'semver'; +import { logger } from '../../../logger'; +import { getSiblingFileName } from '../../../util/fs'; +import type { BumpPackageVersionResult } from '../types'; +import { getSiblingChartYamlContent } from './util'; + +export async function bumpPackageVersion( + content: string, + currentValue: string, + bumpVersion: ReleaseType, + packageFile: string, +): Promise { + logger.debug( + { bumpVersion, currentValue }, + 'Checking if we should bump Chart.yaml version', + ); + const chartFileName = getSiblingFileName(packageFile, 'Chart.yaml'); + const chartYamlContent = await getSiblingChartYamlContent(packageFile); + try { + const newChartVersion = inc(currentValue, bumpVersion); + if (!newChartVersion) { + throw new Error('semver inc failed'); + } + if (chartYamlContent === null) { + throw new Error( + 'Cannot bump chart version because Chart.yaml could not be read.', + ); + } + logger.debug({ newChartVersion }); + const bumpedContent = chartYamlContent?.replace( + /^(version:\s*).*$/m, + `$1${newChartVersion}`, + ); + if (bumpedContent === chartYamlContent) { + logger.debug('Version was already bumped'); + } else { + logger.debug('Bumped Chart.yaml version'); + } + return { + bumpedContent: content, + bumpedFiles: [{ fileName: chartFileName, newContent: bumpedContent }], + }; + } catch (err) { + logger.warn( + { + chartYamlContent, + currentValue, + bumpVersion, + }, + 'Failed to bumpVersion', + ); + return { + bumpedContent: content, + }; + } +} diff --git a/lib/modules/manager/helm-values/util.ts b/lib/modules/manager/helm-values/util.ts index 34b6c2dabc86ab..c5c253b783288e 100644 --- a/lib/modules/manager/helm-values/util.ts +++ b/lib/modules/manager/helm-values/util.ts @@ -1,3 +1,6 @@ +import yaml from 'js-yaml'; +import { logger } from '../../../logger'; +import { getSiblingFileName, readLocalFile } from '../../../util/fs'; import { hasKey } from '../../../util/object'; import { regEx } from '../../../util/regex'; import type { HelmDockerImageDependency } from './types'; @@ -41,3 +44,51 @@ export function matchesHelmValuesInlineImage( ): data is string { return !!(parentKeyRe.test(parentKey) && data && typeof data === 'string'); } + +/** + * This function looks for a Chart.yaml in the same directory as @param fileName and + * returns its raw contents. + * + * @param fileName + */ +export async function getSiblingChartYamlContent( + fileName: string, +): Promise { + try { + const chartFileName = getSiblingFileName(fileName, 'Chart.yaml'); + return await readLocalFile(chartFileName, 'utf8'); + } catch (err) { + logger.debug({ fileName }, 'Failed to read helm Chart.yaml'); + return null; + } +} + +/** + * This function looks for a Chart.yaml in the same directory as @param fileName and + * if it looks like a valid Helm Chart.yaml, it is parsed and returned as an object. + * + * @param fileName + */ +export async function getParsedSiblingChartYaml( + fileName: string, +): Promise { + try { + const chartContents = await getSiblingChartYamlContent(fileName); + if (!chartContents) { + logger.debug({ fileName }, 'Failed to find helm Chart.yaml'); + return null; + } + const chart = yaml.load(chartContents, { json: true }) as any; + if (!(chart?.apiVersion && chart.name && chart.version)) { + logger.debug( + { fileName }, + 'Failed to find required fields in Chart.yaml', + ); + return null; + } + return chart; + } catch (err) { + logger.debug({ fileName }, 'Failed to parse helm Chart.yaml'); + return null; + } +} diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts index 8435b4e9b9d467..ee6cccebe25522 100644 --- a/lib/modules/manager/types.ts +++ b/lib/modules/manager/types.ts @@ -199,6 +199,12 @@ export interface UpdateDependencyConfig> { export interface BumpPackageVersionResult { bumpedContent: string | null; + // describes files that was changed instead of or in addition to the packageFile + bumpedFiles?: BumpedPackageFile[]; +} +export interface BumpedPackageFile { + fileName: string; + newContent: string; } export interface UpdateLockedConfig { @@ -235,6 +241,7 @@ export interface ManagerApi extends ModuleApi { content: string, currentValue: string, bumpVersion: ReleaseType, + packageFile?: string, ): Result; detectGlobalConfig?(): Result; diff --git a/lib/workers/repository/update/branch/get-updated.ts b/lib/workers/repository/update/branch/get-updated.ts index 648f107c25b213..a4fb441be3e2d4 100644 --- a/lib/workers/repository/update/branch/get-updated.ts +++ b/lib/workers/repository/update/branch/get-updated.ts @@ -6,6 +6,7 @@ import { get } from '../../../../modules/manager'; import type { ArtifactError, PackageDependency, + BumpedPackageFile, } from '../../../../modules/manager/types'; import { getFile } from '../../../../util/git'; import type { FileAddition, FileChange } from '../../../../util/git/types'; @@ -20,6 +21,21 @@ export interface PackageFilesResult { updatedArtifacts: FileChange[]; } +async function getFileContent( + updatedFileContents: Record, + filePath: string, + config: BranchConfig, +): Promise { + let fileContent: string | null = updatedFileContents[filePath]; + if (!fileContent) { + fileContent = await getFile( + filePath, + config.reuseExistingBranch ? config.branchName : config.baseBranch, + ); + } + return fileContent; +} + export async function getUpdatedPackageFiles( config: BranchConfig, ): Promise { @@ -46,23 +62,19 @@ export async function getUpdatedPackageFiles( packageFileUpdatedDeps[packageFile] = packageFileUpdatedDeps[packageFile] || []; packageFileUpdatedDeps[packageFile].push({ ...upgrade }); - let packageFileContent: string | null = updatedFileContents[packageFile]; - if (!packageFileContent) { - packageFileContent = await getFile( - packageFile, - reuseExistingBranch ? config.branchName : config.baseBranch, - ); - } + const packageFileContent = await getFileContent( + updatedFileContents, + packageFile, + config, + ); let lockFileContent: string | null = null; const lockFile = upgrade.lockFile ?? upgrade.lockFiles?.[0] ?? ''; if (lockFile) { - lockFileContent = updatedFileContents[lockFile]; - if (!lockFileContent) { - lockFileContent = await getFile( - lockFile, - reuseExistingBranch ? config.branchName : config.baseBranch, - ); - } + lockFileContent = await getFileContent( + updatedFileContents, + lockFile, + config, + ); } // istanbul ignore if if ( @@ -174,17 +186,20 @@ export async function getUpdatedPackageFiles( ); firstUpdate = false; if (res) { + let bumpedPackageFiles: BumpedPackageFile[] = []; if ( bumpPackageVersion && upgrade.bumpVersion && upgrade.packageFileVersion ) { - const { bumpedContent } = await bumpPackageVersion( + const bumpResult = await bumpPackageVersion( res, upgrade.packageFileVersion, upgrade.bumpVersion, + packageFile, ); - res = bumpedContent; + res = bumpResult.bumpedContent; + bumpedPackageFiles = bumpResult.bumpedFiles ?? []; } if (res === packageFileContent) { logger.debug({ packageFile, depName }, 'No content changed'); @@ -192,6 +207,18 @@ export async function getUpdatedPackageFiles( logger.debug({ packageFile, depName }, 'Contents updated'); updatedFileContents[packageFile] = res!; delete nonUpdatedFileContents[packageFile]; + // indicates that the version was bumped in one or more files in + // addition to or instead of the packageFile + if (bumpedPackageFiles) { + for (const bumpedPackageFile of bumpedPackageFiles) { + logger.debug( + { bumpedPackageFile, depName }, + 'Updating bumpedPackageFile content', + ); + updatedFileContents[bumpedPackageFile.fileName] = + bumpedPackageFile.newContent; + } + } } continue; } else if (reuseExistingBranch) { @@ -217,6 +244,7 @@ export async function getUpdatedPackageFiles( newContent, upgrade.packageFileVersion, upgrade.bumpVersion, + packageFile, ); newContent = bumpedContent; }