Skip to content

Commit

Permalink
feat(helm-values): Support for bumpVersion (#8240)
Browse files Browse the repository at this point in the history
  • Loading branch information
chgl authored Feb 28, 2021
1 parent b0026d9 commit 662a60a
Show file tree
Hide file tree
Showing 12 changed files with 410 additions and 43 deletions.
2 changes: 1 addition & 1 deletion docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ This is an advance field and it's recommend you seek a config review before appl

## bumpVersion

Currently this setting supports `helmv3`, `npm` and `sbt` only, so raise a feature request if you have a use for it with other package managers.
Currently this setting supports `helmv3`, `helm-values`, `npm` and `sbt` only, so raise a feature request if you have a use for it with other package managers.
Its purpose is if you want Renovate to update the `version` field within your file's `package.json` any time it updates dependencies within.
Usually this is for automatic release purposes, so that you don't need to add another step after Renovate before you can release a new version.

Expand Down
10 changes: 9 additions & 1 deletion lib/manager/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ export interface UpdateDependencyConfig<T = Record<string, any>> {

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 ManagerApi {
Expand All @@ -237,7 +244,8 @@ export interface ManagerApi {
bumpPackageVersion?(
content: string,
currentValue: string,
bumpVersion: ReleaseType | string
bumpVersion: ReleaseType | string,
packageFile?: string
): Result<BumpPackageVersionResult>;

extractAllPackageFiles?(
Expand Down
15 changes: 15 additions & 0 deletions lib/manager/helm-values/__snapshots__/update.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`lib/manager/helm-values/update .bumpPackageVersion() increments 1`] = `
"apiVersion: v2
name: test
version: 0.0.3
"
`;

exports[`lib/manager/helm-values/update .bumpPackageVersion() updates 1`] = `
"apiVersion: v2
name: test
version: 0.1.0
"
`;
60 changes: 50 additions & 10 deletions lib/manager/helm-values/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFileSync } from 'fs';
import { fs } from '../../../test/util';
import { extractPackageFile } from './extract';

const helmDefaultChartInitValues = readFileSync(
Expand All @@ -15,27 +16,66 @@ describe('lib/manager/helm-values/extract', () => {
describe('extractPackageFile()', () => {
beforeEach(() => {
jest.resetAllMocks();
fs.readLocalFile = jest.fn();
});
it('returns null for invalid yaml file content', () => {
const result = extractPackageFile('nothing here: [');
it('returns null for invalid yaml file content', async () => {
const result = await extractPackageFile('nothing here: [');
expect(result).toBeNull();
});
it('returns null for empty yaml file content', () => {
const result = extractPackageFile('');
it('returns null for empty yaml file content', async () => {
const result = await extractPackageFile('');
expect(result).toBeNull();
});
it('returns null for no file content', () => {
const result = extractPackageFile(null);
it('returns null for no file content', async () => {
const result = await extractPackageFile(null);
expect(result).toBeNull();
});
it('extracts from values.yaml correctly with same structure as "helm create"', () => {
const result = extractPackageFile(helmDefaultChartInitValues);
it('extracts from values.yaml correctly with same structure as "helm create"', async () => {
const result = await extractPackageFile(helmDefaultChartInitValues);
expect(result).toMatchSnapshot();
});
it('extracts from complex values file correctly"', () => {
const result = extractPackageFile(helmMultiAndNestedImageValues);
it('extracts from complex values file correctly"', async () => {
const result = await extractPackageFile(helmMultiAndNestedImageValues);
expect(result).toMatchSnapshot();
expect(result.deps).toHaveLength(4);
});
it('returns the package file version from the sibling Chart.yaml"', async () => {
fs.readLocalFile.mockResolvedValueOnce(`
apiVersion: v2
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: example
version: 0.1.0
`);
const result = await extractPackageFile(
helmMultiAndNestedImageValues,
'values.yaml'
);
expect(result.packageFileVersion).toBe('0.1.0');
});
it('does not fail if the sibling Chart.yaml is invalid', async () => {
fs.readLocalFile.mockResolvedValueOnce(`
invalidYaml: [
`);
const result = await extractPackageFile(
helmMultiAndNestedImageValues,
'values.yaml'
);
expect(result).not.toBeNull();
expect(result.packageFileVersion).toBeUndefined();
});
it('does not fail if the sibling Chart.yaml does not contain the required fields', async () => {
fs.readLocalFile.mockResolvedValueOnce(`
apiVersion: v2
name: test
version-is: missing
`);
const result = await extractPackageFile(
helmMultiAndNestedImageValues,
'values.yaml'
);
expect(result).not.toBeNull();
expect(result.packageFileVersion).toBeUndefined();
});
});
});
22 changes: 20 additions & 2 deletions lib/manager/helm-values/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getDep } from '../dockerfile/extract';

import {
HelmDockerImageDependency,
getParsedSiblingChartYaml,
matchesHelmValuesDockerHeuristic,
} from './util';

Expand Down Expand Up @@ -53,7 +54,10 @@ function findDependencies(
return packageDependencies;
}

export function extractPackageFile(content: string): PackageFile {
export async function extractPackageFile(
content: string,
fileName?: string
): Promise<PackageFile> {
let parsedContent: Record<string, unknown> | HelmDockerImageDependency;
try {
// a parser that allows extracting line numbers would be preferable, with
Expand All @@ -68,7 +72,21 @@ export function extractPackageFile(content: string): PackageFile {
const deps = findDependencies(parsedContent, []);
if (deps.length) {
logger.debug({ deps }, 'Found dependencies in helm-values');
return { deps };

// 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(fileName);
const packageFileVersion = siblingChart?.version;
if (packageFileVersion) {
return {
deps,
packageFileVersion,
};
}
return {
deps,
};
}
} catch (err) /* istanbul ignore next */ {
logger.error({ err }, 'Error parsing helm-values parsed content');
Expand Down
1 change: 1 addition & 0 deletions lib/manager/helm-values/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { extractPackageFile } from './extract';
export { bumpPackageVersion } from './update';

export const defaultConfig = {
commitMessageTopic: 'helm values {{depName}}',
Expand Down
85 changes: 85 additions & 0 deletions lib/manager/helm-values/update.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import yaml from 'js-yaml';
import { fs } from '../../../test/util';
import * as helmValuesUpdater from './update';

describe('lib/manager/helm-values/update', () => {
describe('.bumpPackageVersion()', () => {
const chartContent = yaml.safeDump({
apiVersion: 'v2',
name: 'test',
version: '0.0.2',
});
const helmValuesContent = yaml.safeDump({
image: {
registry: 'docker.io',
repository: 'docker/whalesay',
tag: '1.0.0',
},
});
beforeEach(() => {
jest.resetAllMocks();
fs.readLocalFile = jest.fn();
fs.readLocalFile.mockResolvedValueOnce(chartContent);
});
it('increments', async () => {
const {
bumpedContent,
bumpedFiles,
} = await helmValuesUpdater.bumpPackageVersion(
helmValuesContent,
'0.0.2',
'patch',
'values.yaml'
);
expect(bumpedContent).toEqual(helmValuesContent);
expect(bumpedFiles).toHaveLength(1);
const bumpedFile = bumpedFiles[0];
expect(bumpedFile.fileName).toEqual('Chart.yaml');
expect(bumpedFile.newContent).toMatchSnapshot();
});
it('no ops', async () => {
const {
bumpedContent,
bumpedFiles,
} = await helmValuesUpdater.bumpPackageVersion(
helmValuesContent,
'0.0.1',
'patch',
'values.yaml'
);
expect(bumpedContent).toEqual(helmValuesContent);
expect(bumpedFiles).toHaveLength(1);
const bumpedFile = bumpedFiles[0];
expect(bumpedFile.newContent).toEqual(chartContent);
});
it('updates', async () => {
const {
bumpedContent,
bumpedFiles,
} = await helmValuesUpdater.bumpPackageVersion(
helmValuesContent,
'0.0.1',
'minor',
'values.yaml'
);
expect(bumpedContent).toEqual(helmValuesContent);
expect(bumpedFiles).toHaveLength(1);
const bumpedFile = bumpedFiles[0];
expect(bumpedFile.fileName).toEqual('Chart.yaml');
expect(bumpedFile.newContent).toMatchSnapshot();
});
it('returns content if bumping errors', async () => {
const {
bumpedContent,
bumpedFiles,
} = await helmValuesUpdater.bumpPackageVersion(
helmValuesContent,
'0.0.2',
true as any,
'values.yaml'
);
expect(bumpedContent).toEqual(helmValuesContent);
expect(bumpedFiles).toBeUndefined();
});
});
});
51 changes: 51 additions & 0 deletions lib/manager/helm-values/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ReleaseType, inc } from 'semver';
import { logger } from '../../logger';
import { getSiblingFileName } from '../../util/fs';
import { BumpPackageVersionResult } from '../common';
import { getSiblingChartYamlContent } from './util';

export async function bumpPackageVersion(
content: string,
currentValue: string,
bumpVersion: ReleaseType | string,
packageFile: string
): Promise<BumpPackageVersionResult> {
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 as ReleaseType);
if (!newChartVersion) {
throw new Error('semver inc failed');
}
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,
};
}
}
52 changes: 52 additions & 0 deletions lib/manager/helm-values/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import yaml from 'js-yaml';
import { logger } from '../../logger';
import { getSiblingFileName, readLocalFile } from '../../util/fs';
import { hasKey } from '../../util/object';

export type HelmDockerImageDependency = {
Expand Down Expand Up @@ -35,3 +38,52 @@ export function matchesHelmValuesDockerHeuristic(
hasKey('tag', data)
);
}

/**
* 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<string> {
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<any> {
try {
const chartContents = await getSiblingChartYamlContent(fileName);
if (!chartContents) {
logger.debug({ fileName }, 'Failed to find helm Chart.yaml');
return null;
}
// TODO: fix me
const chart = yaml.safeLoad(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;
}
}
Loading

0 comments on commit 662a60a

Please sign in to comment.