diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index cc7ff295fc3eac9a55e136b278ea9dabb86aa9dd..e450e35e3de13f213bb161d9dc11a042721a9664 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -21,6 +21,7 @@ import * as depsEdn from './deps-edn'; import * as dockerCompose from './docker-compose'; import * as dockerfile from './dockerfile'; import * as droneci from './droneci'; +import * as fleet from './fleet'; import * as flux from './flux'; import * as fvm from './fvm'; import * as gitSubmodules from './git-submodules'; @@ -97,6 +98,7 @@ api.set('deps-edn', depsEdn); api.set('docker-compose', dockerCompose); api.set('dockerfile', dockerfile); api.set('droneci', droneci); +api.set('fleet', fleet); api.set('flux', flux); api.set('fvm', fvm); api.set('git-submodules', gitSubmodules); diff --git a/lib/modules/manager/fleet/__fixtures__/invalid_fleet.yaml b/lib/modules/manager/fleet/__fixtures__/invalid_fleet.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ed4329b7dec71e253740a1c4f9b31de64439de62 --- /dev/null +++ b/lib/modules/manager/fleet/__fixtures__/invalid_fleet.yaml @@ -0,0 +1,26 @@ +defaultNamespace: cert-manager +helm: + chart: cert-manager + repo: https://charts.jetstack.io + releaseName: cert-manager + values: + installCRDs: true +--- +defaultNamespace: logging-system +helm: + chart: logging-operator + releaseName: logging-operator + version: 3.17.7 + values: +--- +defaultNamespace: monitoring +helm: + repo: https://prometheus-community.github.io/helm-charts + releaseName: kube-prometheus + version: 35.4.2 +--- +defaultNamespace: monitoring +helm: + chart: './charts/example' + releaseName: anExample + version: 35.4.2 diff --git a/lib/modules/manager/fleet/__fixtures__/invalid_gitrepo.yaml b/lib/modules/manager/fleet/__fixtures__/invalid_gitrepo.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ed6cad34635ab4044aa870360f54cf47d8e11dd3 --- /dev/null +++ b/lib/modules/manager/fleet/__fixtures__/invalid_gitrepo.yaml @@ -0,0 +1,19 @@ +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: renovate + namespace: fleet-local +spec: + revision: 32.89.2 + paths: + - simple +--- +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: rancher + namespace: fleet-local +spec: + repo: https://github.com/rancher/rancher + paths: + - simple diff --git a/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml b/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml new file mode 100644 index 0000000000000000000000000000000000000000..88126be690b2ed20253e3dc5f47f63eecdc20968 --- /dev/null +++ b/lib/modules/manager/fleet/__fixtures__/valid_fleet.yaml @@ -0,0 +1,17 @@ +defaultNamespace: cert-manager +helm: + chart: cert-manager + repo: https://charts.jetstack.io + releaseName: cert-manager + version: v1.8.0 + values: + installCRDs: true +--- +defaultNamespace: logging-system +helm: + chart: logging-operator + repo: "https://kubernetes-charts.banzaicloud.com" + releaseName: logging-operator + version: 3.17.7 + values: + diff --git a/lib/modules/manager/fleet/__fixtures__/valid_gitrepo.yaml b/lib/modules/manager/fleet/__fixtures__/valid_gitrepo.yaml new file mode 100644 index 0000000000000000000000000000000000000000..418eb075e9c833e36613e9f831848a9cb0d59141 --- /dev/null +++ b/lib/modules/manager/fleet/__fixtures__/valid_gitrepo.yaml @@ -0,0 +1,21 @@ +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: my-repo + namespace: fleet-local +spec: + repo: https://github.com/rancher/fleet-examples + revision: v0.3.0 + paths: + - simple +--- +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: renovate + namespace: fleet-local +spec: + repo: https://github.com/renovatebot/renovate + revision: 32.89.2 + paths: + - simple diff --git a/lib/modules/manager/fleet/extract.spec.ts b/lib/modules/manager/fleet/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f040fbda96dc9ae76f67f2997ab4447fe7498c0 --- /dev/null +++ b/lib/modules/manager/fleet/extract.spec.ts @@ -0,0 +1,152 @@ +import { Fixtures } from '../../../../test/fixtures'; +import { extractPackageFile } from '.'; + +const validFleetYaml = Fixtures.get('valid_fleet.yaml'); +const inValidFleetYaml = Fixtures.get('invalid_fleet.yaml'); + +const validGitRepoYaml = Fixtures.get('valid_gitrepo.yaml'); +const invalidGitRepoYaml = Fixtures.get('invalid_gitrepo.yaml'); + +const configMapYaml = Fixtures.get('configmap.yaml', '../kubernetes'); + +describe('modules/manager/fleet/extract', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('extractPackageFile()', () => { + it('should return null if empty content', () => { + const result = extractPackageFile('', 'fleet.yaml'); + + expect(result).toBeNull(); + }); + + it('should return null if a unknown manifest is supplied', () => { + const result = extractPackageFile(configMapYaml, 'fleet.yaml'); + + expect(result).toBeNull(); + }); + + describe('fleet.yaml', () => { + it('should return null if content is a malformed YAML', () => { + const result = extractPackageFile( + `apiVersion: v1 +kind: Fleet +< `, + 'fleet.yaml' + ); + + expect(result).toBeNull(); + }); + + it('should parse valid configuration', () => { + const result = extractPackageFile(validFleetYaml, 'fleet.yaml'); + + expect(result).not.toBeNull(); + expect(result?.deps).toMatchObject([ + { + currentValue: 'v1.8.0', + datasource: 'helm', + depName: 'cert-manager', + registryUrls: ['https://charts.jetstack.io'], + depType: 'fleet', + }, + { + currentValue: '3.17.7', + datasource: 'helm', + depName: 'logging-operator', + registryUrls: ['https://kubernetes-charts.banzaicloud.com'], + depType: 'fleet', + }, + ]); + }); + + it('should parse parse invalid configurations', () => { + const result = extractPackageFile(inValidFleetYaml, 'fleet.yaml'); + + expect(result).not.toBeNull(); + expect(result?.deps).toMatchObject([ + { + skipReason: 'no-version', + datasource: 'helm', + depName: 'cert-manager', + registryUrls: ['https://charts.jetstack.io'], + depType: 'fleet', + }, + { + datasource: 'helm', + depName: 'logging-operator', + skipReason: 'no-repository', + depType: 'fleet', + }, + { + datasource: 'helm', + skipReason: 'missing-depname', + depType: 'fleet', + }, + { + datasource: 'helm', + depName: './charts/example', + skipReason: 'local-chart', + depType: 'fleet', + }, + ]); + }); + }); + + describe('GitRepo', () => { + it('should return null if content is a malformed YAML', () => { + const result = extractPackageFile( + `apiVersion: v1 + kind: GitRepo + < `, + 'test.yaml' + ); + + expect(result).toBeNull(); + }); + + it('should parse valid configuration', () => { + const result = extractPackageFile(validGitRepoYaml, 'test.yaml'); + + expect(result).not.toBeNull(); + expect(result?.deps).toMatchObject([ + { + currentValue: 'v0.3.0', + datasource: 'git-tags', + depName: 'https://github.com/rancher/fleet-examples', + depType: 'git_repo', + sourceUrl: 'https://github.com/rancher/fleet-examples', + }, + { + currentValue: '32.89.2', + datasource: 'git-tags', + depName: 'https://github.com/renovatebot/renovate', + depType: 'git_repo', + sourceUrl: 'https://github.com/renovatebot/renovate', + }, + ]); + }); + + it('should parse invalid configuration', () => { + const result = extractPackageFile(invalidGitRepoYaml, 'test.yaml'); + + expect(result).not.toBeNull(); + expect(result?.deps).toMatchObject([ + { + datasource: 'git-tags', + depType: 'git_repo', + skipReason: 'missing-depname', + }, + { + datasource: 'git-tags', + depName: 'https://github.com/rancher/rancher', + depType: 'git_repo', + skipReason: 'no-version', + sourceUrl: 'https://github.com/rancher/rancher', + }, + ]); + }); + }); + }); +}); diff --git a/lib/modules/manager/fleet/extract.ts b/lib/modules/manager/fleet/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..2716749ae60cd4a1a7488e33fe00ef6ce479b412 --- /dev/null +++ b/lib/modules/manager/fleet/extract.ts @@ -0,0 +1,114 @@ +import is from '@sindresorhus/is'; +import { loadAll } from 'js-yaml'; +import { logger } from '../../../logger'; +import { regEx } from '../../../util/regex'; +import { GitTagsDatasource } from '../../datasource/git-tags'; +import { HelmDatasource } from '../../datasource/helm'; +import { checkIfStringIsPath } from '../terraform/util'; +import type { PackageDependency, PackageFile } from '../types'; +import type { FleetFile, FleetFileHelm, GitRepo } from './types'; + +function extractGitRepo(doc: GitRepo): PackageDependency { + const dep: PackageDependency = { + depType: 'git_repo', + datasource: GitTagsDatasource.id, + }; + + const repo = doc.spec?.repo; + if (!repo) { + return { + ...dep, + skipReason: 'missing-depname', + }; + } + dep.sourceUrl = repo; + dep.depName = repo; + + const currentValue = doc.spec.revision; + if (!currentValue) { + return { + ...dep, + skipReason: 'no-version', + }; + } + + return { + ...dep, + currentValue, + }; +} + +function extractFleetFile(doc: FleetFileHelm): PackageDependency { + const dep: PackageDependency = { + depType: 'fleet', + datasource: HelmDatasource.id, + }; + + if (!doc.chart) { + return { + ...dep, + skipReason: 'missing-depname', + }; + } + dep.depName = doc.chart; + + if (!doc.repo) { + if (checkIfStringIsPath(doc.chart)) { + return { + ...dep, + skipReason: 'local-chart', + }; + } + return { + ...dep, + skipReason: 'no-repository', + }; + } + dep.registryUrls = [doc.repo]; + + const currentValue = doc.version; + if (!doc.version) { + return { + ...dep, + skipReason: 'no-version', + }; + } + + return { + ...dep, + currentValue, + }; +} + +export function extractPackageFile( + content: string, + packageFile: string +): PackageFile | null { + if (!content) { + return null; + } + const deps: PackageDependency[] = []; + + try { + if (regEx('fleet.ya?ml').test(packageFile)) { + // TODO: fix me (#9610) + const docs = loadAll(content, null, { json: true }) as FleetFile[]; + const fleetDeps = docs + .filter((doc) => is.truthy(doc?.helm)) + .flatMap((doc) => extractFleetFile(doc.helm)); + + deps.push(...fleetDeps); + } else { + // TODO: fix me (#9610) + const docs = loadAll(content, null, { json: true }) as GitRepo[]; + const gitRepoDeps = docs + .filter((doc) => doc.kind === 'GitRepo') // ensure only GitRepo manifests are processed + .flatMap((doc) => extractGitRepo(doc)); + deps.push(...gitRepoDeps); + } + } catch (err) { + logger.error({ error: err, packageFile }, 'Failed to parse fleet YAML'); + } + + return deps.length ? { deps } : null; +} diff --git a/lib/modules/manager/fleet/index.ts b/lib/modules/manager/fleet/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..997a8501ac0598e08bec72a34314a4b9a9490c80 --- /dev/null +++ b/lib/modules/manager/fleet/index.ts @@ -0,0 +1,10 @@ +import { GitTagsDatasource } from '../../datasource/git-tags'; +import { HelmDatasource } from '../../datasource/helm'; + +export { extractPackageFile } from './extract'; + +export const defaultConfig = { + fileMatch: ['(^|/)fleet.ya?ml'], +}; + +export const supportedDatasources = [GitTagsDatasource.id, HelmDatasource.id]; diff --git a/lib/modules/manager/fleet/readme.md b/lib/modules/manager/fleet/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..606e240030a83e1ea329622dee33b54899ec67d1 --- /dev/null +++ b/lib/modules/manager/fleet/readme.md @@ -0,0 +1,10 @@ +Can upgrade bundle definitions and GitRepo YAML manifests of Rancher Fleet. + +By default, only bundles with Helm references will be upgraded. +To enable GitRepo updates you have to extend your [`fileMatch`](https://docs.renovatebot.com/configuration-options/#filematch) configuration. + +```json +{ + "fileMatch": ["'(^|/)fleet.ya?ml", "myGitRepoManifests\\.yaml"] +} +``` diff --git a/lib/modules/manager/fleet/types.ts b/lib/modules/manager/fleet/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..01451edd8e36559360c762fe3a8e23ae67f82864 --- /dev/null +++ b/lib/modules/manager/fleet/types.ts @@ -0,0 +1,29 @@ +/** + Represent a GitRepo Kubernetes manifest of Fleet. + @link https://fleet.rancher.io/gitrepo-add/#create-gitrepo-instance + */ +export interface GitRepo { + metadata: { + name: string; + }; + kind: string; + spec: { + repo: string; + revision?: string; + }; +} + +/** + Represent a Bundle configuration of Fleet, which is located in `fleet.yaml` files. + @link https://fleet.rancher.io/gitrepo-structure/#fleetyaml + */ +export interface FleetFile { + helm: FleetFileHelm; +} + +export interface FleetFileHelm { + chart: string; + repo?: string; + version: string; + releaseName: string; +}