mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 14:36:25 +00:00
feat(manager): support kustomize (#5484)
This commit is contained in:
parent
706562f80d
commit
fe482f91c5
13 changed files with 522 additions and 0 deletions
17
lib/manager/kustomize/__fixtures__/gitImages.yaml
Normal file
17
lib/manager/kustomize/__fixtures__/gitImages.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
|
||||
images:
|
||||
- name: node
|
||||
newTag: v0.1.0
|
||||
- newTag: v0.0.1
|
||||
name: group/instance
|
||||
- name: quay.io/test/repo
|
||||
newTag: v0.0.2
|
||||
- name: gitlab.com/org/suborg/image
|
||||
newTag: v0.0.3
|
10
lib/manager/kustomize/__fixtures__/gitSshBase.yaml
Normal file
10
lib/manager/kustomize/__fixtures__/gitSshBase.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
bases:
|
||||
- git@github.com:moredhel/remote-kustomize.git?ref=v0.0.1
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
10
lib/manager/kustomize/__fixtures__/gitSubdir.yaml
Normal file
10
lib/manager/kustomize/__fixtures__/gitSubdir.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
bases:
|
||||
- git@github.com:kubernetes-sigs/kustomize.git//examples/helloWorld?ref=v2.0.0
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
5
lib/manager/kustomize/__fixtures__/kustomizeEmpty.yaml
Normal file
5
lib/manager/kustomize/__fixtures__/kustomizeEmpty.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
10
lib/manager/kustomize/__fixtures__/kustomizeHttp.yaml
Normal file
10
lib/manager/kustomize/__fixtures__/kustomizeHttp.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
bases:
|
||||
- github.com/user/repo//deploy?ref=v0.0.1
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
12
lib/manager/kustomize/__fixtures__/kustomizeWithLocal.yaml
Normal file
12
lib/manager/kustomize/__fixtures__/kustomizeWithLocal.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
bases:
|
||||
- service-1
|
||||
- https://moredhel/remote-kustomize.git?ref=v0.0.1
|
||||
- https://moredhel/remote-kustomize.git//deploy?ref=v0.0.1
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: testing-namespace
|
||||
|
||||
images:
|
||||
- name: moredhel/renovate-test-1
|
||||
newTag: v0.0.1
|
10
lib/manager/kustomize/__fixtures__/service.yaml
Normal file
10
lib/manager/kustomize/__fixtures__/service.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: sample-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
name: http
|
80
lib/manager/kustomize/__snapshots__/extract.spec.ts.snap
Normal file
80
lib/manager/kustomize/__snapshots__/extract.spec.ts.snap
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`manager/kustomize/extract extractPackageFile() extracts http dependency 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"currentValue": "v0.0.1",
|
||||
"datasource": "git-tags",
|
||||
"depName": "github.com/user/repo//deploy",
|
||||
"lookupName": "github.com/user/repo",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`manager/kustomize/extract extractPackageFile() extracts multiple image lines 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"currentValue": "v0.0.1",
|
||||
"datasource": "git-tags",
|
||||
"depName": "https://moredhel/remote-kustomize.git",
|
||||
"lookupName": "https://moredhel/remote-kustomize.git",
|
||||
},
|
||||
Object {
|
||||
"currentValue": "v0.0.1",
|
||||
"datasource": "git-tags",
|
||||
"depName": "https://moredhel/remote-kustomize.git//deploy",
|
||||
"lookupName": "https://moredhel/remote-kustomize.git//deploy",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`manager/kustomize/extract extractPackageFile() extracts ssh dependency 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"currentValue": "v0.0.1",
|
||||
"datasource": "git-tags",
|
||||
"depName": "git@github.com:moredhel/remote-kustomize.git",
|
||||
"lookupName": "git@github.com:moredhel/remote-kustomize.git",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`manager/kustomize/extract extractPackageFile() extracts ssh dependency with a subdir 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"currentValue": "v2.0.0",
|
||||
"datasource": "git-tags",
|
||||
"depName": "git@github.com:kubernetes-sigs/kustomize.git//examples/helloWorld",
|
||||
"lookupName": "git@github.com:kubernetes-sigs/kustomize.git",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`manager/kustomize/extract extractPackageFile() should extract out image versions 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"currentValue": "v0.1.0",
|
||||
"datasource": "docker",
|
||||
"depName": "node",
|
||||
"lookupName": "node",
|
||||
},
|
||||
Object {
|
||||
"currentValue": "v0.0.1",
|
||||
"datasource": "docker",
|
||||
"depName": "group/instance",
|
||||
"lookupName": "group/instance",
|
||||
},
|
||||
Object {
|
||||
"currentValue": "v0.0.2",
|
||||
"datasource": "docker",
|
||||
"depName": "quay.io/test/repo",
|
||||
"lookupName": "quay.io/test/repo",
|
||||
},
|
||||
Object {
|
||||
"currentValue": "v0.0.3",
|
||||
"datasource": "docker",
|
||||
"depName": "gitlab.com/org/suborg/image",
|
||||
"lookupName": "gitlab.com/org/suborg/image",
|
||||
},
|
||||
]
|
||||
`;
|
213
lib/manager/kustomize/extract.spec.ts
Normal file
213
lib/manager/kustomize/extract.spec.ts
Normal file
|
@ -0,0 +1,213 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import {
|
||||
extractBase,
|
||||
extractImage,
|
||||
parseKustomize,
|
||||
extractPackageFile,
|
||||
} from './extract';
|
||||
import * as datasourceDocker from '../../datasource/docker';
|
||||
import * as datasourceGitTags from '../../datasource/git-tags';
|
||||
|
||||
const kustomizeGitSSHBase = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/gitSshBase.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const kustomizeEmpty = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/kustomizeEmpty.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const kustomizeGitSSHSubdir = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/gitSubdir.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const kustomizeHTTP = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/kustomizeHttp.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const kustomizeWithLocal = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/kustomizeWithLocal.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const nonKustomize = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/service.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const gitImages = readFileSync(
|
||||
'lib/manager/kustomize/__fixtures__/gitImages.yaml',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
describe('manager/kustomize/extract', () => {
|
||||
it('should successfully parse a valid kustomize file', () => {
|
||||
const file = parseKustomize(kustomizeGitSSHBase);
|
||||
expect(file).not.toBeNull();
|
||||
});
|
||||
it('return null on an invalid file', () => {
|
||||
const file = parseKustomize('');
|
||||
expect(file).toBeNull();
|
||||
});
|
||||
describe('extractBase', () => {
|
||||
it('should return null for a local base ', () => {
|
||||
const res = extractBase('./service-1');
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
it('should extract out the version of an http base', () => {
|
||||
const base = 'https://github.com/user/test-repo.git';
|
||||
const version = 'v1.0.0';
|
||||
const sample = {
|
||||
currentValue: version,
|
||||
datasource: datasourceGitTags.id,
|
||||
depName: base,
|
||||
lookupName: base,
|
||||
};
|
||||
|
||||
const pkg = extractBase(`${base}?ref=${version}`);
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should extract out the version of a git base', () => {
|
||||
const base = 'git@github.com:user/repo.git';
|
||||
const version = 'v1.0.0';
|
||||
const sample = {
|
||||
currentValue: version,
|
||||
datasource: datasourceGitTags.id,
|
||||
depName: base,
|
||||
lookupName: base,
|
||||
};
|
||||
|
||||
const pkg = extractBase(`${base}?ref=${version}`);
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should extract out the version of a git base with subdir', () => {
|
||||
const base = 'git@github.com:user/repo.git';
|
||||
const version = 'v1.0.0';
|
||||
const sample = {
|
||||
currentValue: version,
|
||||
datasource: datasourceGitTags.id,
|
||||
depName: `${base}//subdir`,
|
||||
lookupName: base,
|
||||
};
|
||||
|
||||
const pkg = extractBase(`${sample.depName}?ref=${version}`);
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
});
|
||||
describe('image extraction', () => {
|
||||
it('should return null on a null input', () => {
|
||||
const pkg = extractImage({
|
||||
name: null,
|
||||
newTag: null,
|
||||
});
|
||||
expect(pkg).toEqual(null);
|
||||
});
|
||||
it('should correctly extract a default image', () => {
|
||||
const sample = {
|
||||
currentValue: 'v1.0.0',
|
||||
datasource: datasourceDocker.id,
|
||||
depName: 'node',
|
||||
lookupName: 'node',
|
||||
};
|
||||
const pkg = extractImage({
|
||||
name: sample.lookupName,
|
||||
newTag: sample.currentValue,
|
||||
});
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should correctly extract an image in a repo', () => {
|
||||
const sample = {
|
||||
currentValue: 'v1.0.0',
|
||||
datasource: datasourceDocker.id,
|
||||
depName: 'test/node',
|
||||
lookupName: 'test/node',
|
||||
};
|
||||
const pkg = extractImage({
|
||||
name: sample.lookupName,
|
||||
newTag: sample.currentValue,
|
||||
});
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should correctly extract from a different registry', () => {
|
||||
const sample = {
|
||||
currentValue: 'v1.0.0',
|
||||
datasource: datasourceDocker.id,
|
||||
depName: 'quay.io/repo/image',
|
||||
lookupName: 'quay.io/repo/image',
|
||||
};
|
||||
const pkg = extractImage({
|
||||
name: sample.lookupName,
|
||||
newTag: sample.currentValue,
|
||||
});
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should correctly extract from a different port', () => {
|
||||
const sample = {
|
||||
currentValue: 'v1.0.0',
|
||||
datasource: datasourceDocker.id,
|
||||
depName: 'localhost:5000/repo/image',
|
||||
lookupName: 'localhost:5000/repo/image',
|
||||
};
|
||||
const pkg = extractImage({
|
||||
name: sample.lookupName,
|
||||
newTag: sample.currentValue,
|
||||
});
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
it('should correctly extract from a multi-depth registry', () => {
|
||||
const sample = {
|
||||
currentValue: 'v1.0.0',
|
||||
datasource: datasourceDocker.id,
|
||||
depName: 'localhost:5000/repo/image/service',
|
||||
lookupName: 'localhost:5000/repo/image/service',
|
||||
};
|
||||
const pkg = extractImage({
|
||||
name: sample.lookupName,
|
||||
newTag: sample.currentValue,
|
||||
});
|
||||
expect(pkg).toEqual(sample);
|
||||
});
|
||||
});
|
||||
describe('extractPackageFile()', () => {
|
||||
it('returns null for non kustomize kubernetes files', () => {
|
||||
expect(extractPackageFile(nonKustomize)).toBeNull();
|
||||
});
|
||||
it('extracts multiple image lines', () => {
|
||||
const res = extractPackageFile(kustomizeWithLocal);
|
||||
expect(res.deps).toMatchSnapshot();
|
||||
expect(res.deps).toHaveLength(2);
|
||||
});
|
||||
it('extracts ssh dependency', () => {
|
||||
const res = extractPackageFile(kustomizeGitSSHBase);
|
||||
expect(res.deps).toMatchSnapshot();
|
||||
expect(res.deps).toHaveLength(1);
|
||||
});
|
||||
it('extracts ssh dependency with a subdir', () => {
|
||||
const res = extractPackageFile(kustomizeGitSSHSubdir);
|
||||
expect(res.deps).toMatchSnapshot();
|
||||
expect(res.deps).toHaveLength(1);
|
||||
});
|
||||
it('extracts http dependency', () => {
|
||||
const res = extractPackageFile(kustomizeHTTP);
|
||||
expect(res.deps).toMatchSnapshot();
|
||||
expect(res.deps).toHaveLength(1);
|
||||
expect(res.deps[0].currentValue).toEqual('v0.0.1');
|
||||
});
|
||||
it('should extract out image versions', () => {
|
||||
const res = extractPackageFile(gitImages);
|
||||
expect(res.deps).toMatchSnapshot();
|
||||
expect(res.deps).toHaveLength(4);
|
||||
expect(res.deps[0].currentValue).toEqual('v0.1.0');
|
||||
expect(res.deps[1].currentValue).toEqual('v0.0.1');
|
||||
});
|
||||
it('ignores non-Kubernetes empty files', () => {
|
||||
expect(extractPackageFile('')).toBeNull();
|
||||
});
|
||||
it('does nothing with kustomize empty kustomize files', () => {
|
||||
expect(extractPackageFile(kustomizeEmpty)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
112
lib/manager/kustomize/extract.ts
Normal file
112
lib/manager/kustomize/extract.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { safeLoad } from 'js-yaml';
|
||||
import { PackageFile, PackageDependency } from '../common';
|
||||
import { logger } from '../../logger';
|
||||
import * as datasourceDocker from '../../datasource/docker';
|
||||
import * as datasourceGitTags from '../../datasource/git-tags';
|
||||
|
||||
interface Image {
|
||||
name: string;
|
||||
newTag: string;
|
||||
}
|
||||
|
||||
interface Kustomize {
|
||||
kind: string;
|
||||
bases: string[];
|
||||
images: Image[];
|
||||
}
|
||||
|
||||
// extract the version from the url
|
||||
const versionMatch = /(?<basename>.*)\?ref=(?<version>.*)\s*$/;
|
||||
|
||||
// extract the url from the base of a url with a subdir
|
||||
const extractUrl = /^(?<url>.*)(?:\/\/.*)$/;
|
||||
|
||||
export function extractBase(base: string): PackageDependency | null {
|
||||
const basenameVersion = versionMatch.exec(base);
|
||||
if (basenameVersion) {
|
||||
const currentValue = basenameVersion.groups.version;
|
||||
const root = basenameVersion.groups.basename;
|
||||
|
||||
const urlResult = extractUrl.exec(root);
|
||||
let url = root;
|
||||
// if a match, then there was a subdir, update
|
||||
if (urlResult && !url.startsWith('http')) {
|
||||
url = urlResult.groups.url;
|
||||
}
|
||||
|
||||
return {
|
||||
datasource: datasourceGitTags.id,
|
||||
depName: root,
|
||||
lookupName: url,
|
||||
currentValue,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractImage(image: Image): PackageDependency | null {
|
||||
if (image && image.name && image.newTag) {
|
||||
return {
|
||||
datasource: datasourceDocker.id,
|
||||
depName: image.name,
|
||||
lookupName: image.name,
|
||||
currentValue: image.newTag,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseKustomize(content: string): Kustomize | null {
|
||||
let pkg = null;
|
||||
try {
|
||||
pkg = safeLoad(content);
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!pkg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pkg.kind !== 'Kustomization') {
|
||||
return null;
|
||||
}
|
||||
|
||||
pkg.bases = pkg.bases || [];
|
||||
pkg.images = pkg.images || [];
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
export function extractPackageFile(content: string): PackageFile | null {
|
||||
logger.trace('kustomize.extractPackageFile()');
|
||||
const deps: PackageDependency[] = [];
|
||||
|
||||
const pkg = parseKustomize(content);
|
||||
if (!pkg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// grab the remote bases
|
||||
for (const base of pkg.bases) {
|
||||
const dep = extractBase(base);
|
||||
if (dep) {
|
||||
deps.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
// grab the image tags
|
||||
for (const image of pkg.images) {
|
||||
const dep = extractImage(image);
|
||||
if (dep) {
|
||||
deps.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
if (!deps.length) {
|
||||
return null;
|
||||
}
|
||||
return { deps };
|
||||
}
|
7
lib/manager/kustomize/index.ts
Normal file
7
lib/manager/kustomize/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export { extractPackageFile } from './extract';
|
||||
|
||||
export const autoReplace = true;
|
||||
|
||||
export const defaultConfig = {
|
||||
fileMatch: ['(^|/)kustomization\\.yaml'],
|
||||
};
|
28
lib/manager/kustomize/readme.md
Normal file
28
lib/manager/kustomize/readme.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
This package will manage two parts of the `kustomization.yaml` file:
|
||||
|
||||
1. [remote bases](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/remoteBuild.md)
|
||||
2. [image tags](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/image.md)
|
||||
|
||||
**How It Works**
|
||||
|
||||
1. Renovate will search each repository for any `kustomization.yaml` files.
|
||||
2. Existing dependencies will be extracted from remote bases & image tags
|
||||
3. Renovate will resolve the dependency's source repository and check for semver tags if found.
|
||||
4. If an update was found, Renovate will update `kustomization.yaml`
|
||||
|
||||
**Limitations**
|
||||
|
||||
- Currently this hasn't been tested using https to fetch the repos
|
||||
- the image tags are limited to the following formats:
|
||||
|
||||
```
|
||||
- name: image/name
|
||||
newTag: v0.0.1
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
- newTag: v0.0.1
|
||||
name: image/name
|
||||
```
|
Loading…
Reference in a new issue