diff --git a/lib/manager/api.ts b/lib/manager/api.ts index 71970dc081fce35bfdd63635d9924fe711cc77b2..1731c2500f3fab6391e314382d1a019e3e035331 100644 --- a/lib/manager/api.ts +++ b/lib/manager/api.ts @@ -29,6 +29,7 @@ import * as gradleWrapper from './gradle-wrapper'; import * as helmRequirements from './helm-requirements'; import * as helmValues from './helm-values'; import * as helmfile from './helmfile'; +import * as helmsman from './helmsman'; import * as helmv3 from './helmv3'; import * as homebrew from './homebrew'; import * as html from './html'; @@ -97,6 +98,7 @@ api.set('gradle-wrapper', gradleWrapper); api.set('helm-requirements', helmRequirements); api.set('helm-values', helmValues); api.set('helmfile', helmfile); +api.set('helmsman', helmsman); api.set('helmv3', helmv3); api.set('homebrew', homebrew); api.set('html', html); diff --git a/lib/manager/helmsman/__fixtures__/empty.yaml b/lib/manager/helmsman/__fixtures__/empty.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d25f01ccddad1d080030cb811e6a7f204d1eb5fd --- /dev/null +++ b/lib/manager/helmsman/__fixtures__/empty.yaml @@ -0,0 +1,6 @@ +namespaces: + +helmRepos: + test: +apps: + test: diff --git a/lib/manager/helmsman/__fixtures__/validHelmsfile.yaml b/lib/manager/helmsman/__fixtures__/validHelmsfile.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6bb8d4ee90bb97f118e9b57278c08589cec196a8 --- /dev/null +++ b/lib/manager/helmsman/__fixtures__/validHelmsfile.yaml @@ -0,0 +1,78 @@ +namespaces: + redis-operator: + strimzi: + monitoring: + +helmRepos: + ot-helm: "https://ot-container-kit.github.io/helm-charts/" + strimzi: "https://strimzi.io/charts/" + open-telemetry: "https://open-telemetry.github.io/opentelemetry-helm-charts" + grafana: "https://grafana.github.io/helm-charts" + prometheus-community: https://prometheus-community.github.io/helm-charts + +apps: +# valid apps + kube-prometheus: + enabled: true + namespace: monitoring + chart: prometheus-community/kube-prometheus-stack + version: 19.0.3 + valuesFiles: + - ./kube-prometheus/values.yaml + priority: -90 + loki: + enabled: true + namespace: monitoring + chart: grafana/loki + version: 2.6.0 + priority: -85 + tempo: + enabled: true + namespace: monitoring + chart: grafana/tempo + version: 0.7.7 + priority: -80 + otlp-collector: + enabled: true + namespace: monitoring + chart: open-telemetry/opentelemetry-collector + version: 0.6.0 + valuesFile: ./otlp-collector/values.yaml + priority: -75 + strimzi-operator: + enabled: true + namespace: strimzi + chart: strimzi/strimzi-kafka-operator + version: 0.25.0 + +# missing version + strimzi-operator-missing-version: + enabled: true + namespace: strimzi + chart: strimzi/strimzi-kafka-operator + +# malformed chart + loki-no-registry-ref: + enabled: true + namespace: monitoring + chart: /loki + version: 2.6.0 + tempo-no-registry-ref: + enabled: true + namespace: monitoring + chart: tempo + version: 0.7.7 + kube-prometheus-no-lookup-name: + enabled: true + namespace: monitoring + chart: prometheus-community/ + version: 19.0.3 + valuesFiles: + - ./kube-prometheus/values.yaml + priority: -90 + otlp-collector-no-chart: + enabled: true + namespace: monitoring + version: 0.6.0 + valuesFile: ./otlp-collector/values.yaml + priority: -75 diff --git a/lib/manager/helmsman/__snapshots__/extract.spec.ts.snap b/lib/manager/helmsman/__snapshots__/extract.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..240088271e92c0e42089cb16f88d718dd49bdd4d --- /dev/null +++ b/lib/manager/helmsman/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/helmsman/extract extractPackageFile() extract deps 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "19.0.3", + "depName": "kube-prometheus", + "lookupName": "kube-prometheus-stack", + "registryUrls": Array [ + "https://prometheus-community.github.io/helm-charts", + ], + }, + Object { + "currentValue": "2.6.0", + "depName": "loki", + "lookupName": "loki", + "registryUrls": Array [ + "https://grafana.github.io/helm-charts", + ], + }, + Object { + "currentValue": "0.7.7", + "depName": "tempo", + "lookupName": "tempo", + "registryUrls": Array [ + "https://grafana.github.io/helm-charts", + ], + }, + Object { + "currentValue": "0.6.0", + "depName": "otlp-collector", + "lookupName": "opentelemetry-collector", + "registryUrls": Array [ + "https://open-telemetry.github.io/opentelemetry-helm-charts", + ], + }, + Object { + "currentValue": "0.25.0", + "depName": "strimzi-operator", + "lookupName": "strimzi-kafka-operator", + "registryUrls": Array [ + "https://strimzi.io/charts/", + ], + }, + Object { + "depName": "strimzi-operator-missing-version", + "skipReason": "no-version", + }, + Object { + "currentValue": "2.6.0", + "depName": "loki-no-registry-ref", + "lookupName": "loki", + "skipReason": "no-repository", + }, + Object { + "currentValue": "0.7.7", + "depName": "tempo-no-registry-ref", + "skipReason": "invalid-url", + }, + Object { + "currentValue": "19.0.3", + "depName": "kube-prometheus-no-lookup-name", + "skipReason": "invalid-name", + }, + Object { + "currentValue": "0.6.0", + "depName": "otlp-collector-no-chart", + "skipReason": "invalid-url", + }, + ], +} +`; diff --git a/lib/manager/helmsman/extract.spec.ts b/lib/manager/helmsman/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b5b042cf0f7db441bbc4730fbd62cda8955d42e --- /dev/null +++ b/lib/manager/helmsman/extract.spec.ts @@ -0,0 +1,32 @@ +import { loadFixture } from '../../../test/util'; +import { extractPackageFile } from '.'; + +const multiDepFile = loadFixture('validHelmsfile.yaml'); +const otherYamlFile = loadFixture('empty.yaml'); + +describe('manager/helmsman/extract', () => { + describe('extractPackageFile()', () => { + it('returns null if empty', () => { + const content = ``; + const fileName = 'desired_state.yaml'; + const result = extractPackageFile(content, fileName, {}); + expect(result).toBeNull(); + }); + + it('returns null if extracting non helmsman yaml file', () => { + const content = otherYamlFile; + const fileName = 'requirements.yaml'; + const result = extractPackageFile(content, fileName, {}); + expect(result).toBeNull(); + }); + + it('extract deps', () => { + const fileName = 'helmsman.yaml'; + const result = extractPackageFile(multiDepFile, fileName, {}); + expect(result).not.toBeNull(); + expect(result.deps).toHaveLength(10); + expect(result.deps.filter((value) => value.skipReason)).toHaveLength(5); + expect(result).toMatchSnapshot(); + }); + }); +}); diff --git a/lib/manager/helmsman/extract.ts b/lib/manager/helmsman/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..e81dc2a638e4b6d154ed14a16ea16046d265f4aa --- /dev/null +++ b/lib/manager/helmsman/extract.ts @@ -0,0 +1,80 @@ +import is from '@sindresorhus/is'; +import { load } from 'js-yaml'; +import { logger } from '../../logger'; +import { SkipReason } from '../../types'; +import { regEx } from '../../util/regex'; +import type { ExtractConfig, PackageDependency, PackageFile } from '../types'; +import type { HelmsmanDocument } from './types'; + +const chartRegex = regEx('^(?<registryRef>[^/]*)/(?<lookupName>[^/]*)$'); + +function createDep(key: string, doc: HelmsmanDocument): PackageDependency { + const dep: PackageDependency = { + depName: key, + }; + const anApp = doc.apps[key]; + if (!anApp) { + return null; + } + + if (!anApp.version) { + dep.skipReason = SkipReason.NoVersion; + return dep; + } + dep.currentValue = anApp.version; + + const regexResult = chartRegex.exec(anApp.chart); + if (!regexResult) { + dep.skipReason = SkipReason.InvalidUrl; + return dep; + } + + if (!is.nonEmptyString(regexResult.groups.lookupName)) { + dep.skipReason = SkipReason.InvalidName; + return dep; + } + dep.lookupName = regexResult.groups.lookupName; + + const registryUrl = doc.helmRepos[regexResult.groups.registryRef]; + if (!is.nonEmptyString(registryUrl)) { + dep.skipReason = SkipReason.NoRepository; + return dep; + } + dep.registryUrls = [registryUrl]; + + return dep; +} + +export function extractPackageFile( + content: string, + fileName: string, + config: ExtractConfig +): PackageFile | null { + try { + // TODO: fix me (#9610) + const doc = load(content, { + json: true, + }) as HelmsmanDocument; + if (!(doc?.helmRepos && doc.apps)) { + logger.debug({ fileName }, 'Missing helmRepos and/or apps keys'); + return null; + } + + const deps = Object.keys(doc.apps) + .map((key) => createDep(key, doc)) + .filter(Boolean); // filter null values + + if (deps.length === 0) { + return null; + } + + return { deps }; + } catch (err) /* istanbul ignore next */ { + if (err.stack?.startsWith('YAMLException:')) { + logger.debug({ err }, 'YAML exception extracting'); + } else { + logger.warn({ err }, 'Error extracting'); + } + return null; + } +} diff --git a/lib/manager/helmsman/index.ts b/lib/manager/helmsman/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1012c71468dbc48b337475d9c06e8a2591ee13b4 --- /dev/null +++ b/lib/manager/helmsman/index.ts @@ -0,0 +1,5 @@ +export { extractPackageFile } from './extract'; + +export const defaultConfig = { + fileMatch: [], +}; diff --git a/lib/manager/helmsman/readme.md b/lib/manager/helmsman/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..cd1a853a0345b97cbde0716d46ed10910aea2572 --- /dev/null +++ b/lib/manager/helmsman/readme.md @@ -0,0 +1,21 @@ +The `helmsman` manager is currently limited and does not support the full feature set of [Helmsman](https://github.com/Praqma/helmsman), read about the limitations below. + +### Non-configured fileMatch + +By default the `helmsman` manager has an empty array for its `fileMatch` configuration option, because there is no convention for file naming in practice. +This means that `helmsman` won't search for any files, and you won't get any updates from the manager. + +To enable the `helmsman` manager, provide a valid `fileMatch` yourself, for example: + +```json +{ + "helmsman": { + "fileMatch": ["(^|/)desired_state\\.yaml$"] + } +} +``` + +### File format + +Currently, state files must be in the `.yaml` format. +The `.toml` format is not supported. diff --git a/lib/manager/helmsman/types.ts b/lib/manager/helmsman/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d11c6cca6d7405b16128f42961716d677fe5bbb --- /dev/null +++ b/lib/manager/helmsman/types.ts @@ -0,0 +1,9 @@ +export interface HelmsmanDocument { + helmRepos: Record<string, string>; + apps: Record<string, HelmsmanApp>; +} + +export interface HelmsmanApp { + version?: string; + chart?: string; +}