From f3d920c4546a96a416c57a1c48776b1445ea0695 Mon Sep 17 00:00:00 2001 From: Sebastian Poxhofer Date: Thu, 2 Jul 2020 19:20:30 +0200 Subject: [PATCH] feat(terraform-provider): Implement terraform helm chart update (#6631) Co-authored-by: Michael Kriese --- lib/manager/terraform/__fixtures__/helm.tf | 40 +++++++++++ .../__snapshots__/extract.spec.ts.snap | 68 +++++++++++++++++++ lib/manager/terraform/extract.spec.ts | 7 ++ lib/manager/terraform/extract.ts | 20 +++++- lib/manager/terraform/resources.ts | 58 ++++++++++++++++ lib/manager/terraform/util.ts | 10 +++ 6 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 lib/manager/terraform/__fixtures__/helm.tf create mode 100644 lib/manager/terraform/resources.ts diff --git a/lib/manager/terraform/__fixtures__/helm.tf b/lib/manager/terraform/__fixtures__/helm.tf new file mode 100644 index 00000000000000..01d0dd84f7c28e --- /dev/null +++ b/lib/manager/terraform/__fixtures__/helm.tf @@ -0,0 +1,40 @@ +# legit use cases +## complete example +resource "helm_release" "example" { + name = "my-redis-release" + repository = "https://kubernetes-charts.storage.googleapis.com" + chart = "redis" + version = "1.0.1" +} + +## example without version, this will default to latest in Terraform +resource "helm_release" "example" { + name = "my-redis-release" + repository = "https://kubernetes-charts.storage.googleapis.com" + chart = "redis" +} + +## local chart +resource "helm_release" "local" { + name = "my-local-chart" + chart = "./charts/example" +} + +## malformed examples +resource "helm_release" "example" { + name = "my-redis-release" + repository = "https://kubernetes-charts.storage.googleapis.com" + version = "4.0.1" +} + +resource "helm_release" "example" { + repository = "https://kubernetes-charts.storage.googleapis.com" + chart = "redis" + version = "5.0.1" +} + +resource "helm_release" "example" { + name = "my-redis-release" + chart = "redis" + version = "6.0.1" +} diff --git a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap index e61646a6faa8bd..2d3476bb075324 100644 --- a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap @@ -1,5 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`lib/manager/terraform/extract extractPackageFile() extract helm releases 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "1.0.1", + "datasource": "helm", + "depName": "redis", + "depNameShort": "redis", + "depType": "helm", + "registryUrls": Array [ + "https://kubernetes-charts.storage.googleapis.com", + ], + }, + Object { + "datasource": "helm", + "depName": "redis", + "depNameShort": "redis", + "depType": "helm", + "registryUrls": Array [ + "https://kubernetes-charts.storage.googleapis.com", + ], + "skipReason": "unsupported-version", + }, + Object { + "datasource": "helm", + "depName": "./charts/example", + "depNameShort": "./charts/example", + "depType": "helm", + "registryUrls": Array [ + undefined, + ], + "skipReason": "local-chart", + }, + Object { + "currentValue": "4.0.1", + "datasource": "helm", + "depName": undefined, + "depNameShort": undefined, + "depType": "helm", + "registryUrls": Array [ + "https://kubernetes-charts.storage.googleapis.com", + ], + "skipReason": "invalid-name", + }, + Object { + "currentValue": "5.0.1", + "datasource": "helm", + "depName": "redis", + "depNameShort": "redis", + "depType": "helm", + "registryUrls": Array [ + "https://kubernetes-charts.storage.googleapis.com", + ], + }, + Object { + "currentValue": "6.0.1", + "datasource": "helm", + "depName": "redis", + "depNameShort": "redis", + "depType": "helm", + "registryUrls": Array [ + undefined, + ], + }, + ], +} +`; + exports[`lib/manager/terraform/extract extractPackageFile() extracts 1`] = ` Object { "deps": Array [ diff --git a/lib/manager/terraform/extract.spec.ts b/lib/manager/terraform/extract.spec.ts index 5e3fba602b7475..23019135fb0247 100644 --- a/lib/manager/terraform/extract.spec.ts +++ b/lib/manager/terraform/extract.spec.ts @@ -6,6 +6,7 @@ const tf2 = `module "relative" { source = "../../modules/fe" } `; +const helm = readFileSync('lib/manager/terraform/__fixtures__/helm.tf', 'utf8'); describe('lib/manager/terraform/extract', () => { describe('extractPackageFile()', () => { @@ -21,5 +22,11 @@ describe('lib/manager/terraform/extract', () => { it('returns null if only local deps', () => { expect(extractPackageFile(tf2)).toBeNull(); }); + it('extract helm releases', () => { + const res = extractPackageFile(helm); + expect(res).toMatchSnapshot(); + expect(res.deps).toHaveLength(6); + expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(3); + }); }); }); diff --git a/lib/manager/terraform/extract.ts b/lib/manager/terraform/extract.ts index 48e469d250d24d..93d206da9bdc1d 100644 --- a/lib/manager/terraform/extract.ts +++ b/lib/manager/terraform/extract.ts @@ -6,14 +6,23 @@ import { extractTerraformProvider, } from './providers'; import { extractTerraformRequiredProviders } from './required_providers'; +import { + analyseTerraformResource, + extractTerraformResource, +} from './resources'; import { TerraformDependencyTypes, checkFileContainsDependency, getTerraformDependencyType, } from './util'; -const dependencyBlockExtractionRegex = /^\s*(?module|provider|required_providers)\s+("(?[^"]+)"\s+)?{\s*$/; -const contentCheckList = ['module "', 'provider "', 'required_providers ']; +const dependencyBlockExtractionRegex = /^\s*(?[a-z_]+)\s+("(?[^"]+)"\s+)?("(?[^"]+)"\s+)?{\s*$/; +const contentCheckList = [ + 'module "', + 'provider "', + 'required_providers ', + ' "helm_release" ', +]; export function extractPackageFile(content: string): PackageFile | null { logger.trace({ content }, 'terraform.extractPackageFile()'); @@ -55,6 +64,10 @@ export function extractPackageFile(content: string): PackageFile | null { ); break; } + case TerraformDependencyTypes.resource: { + result = extractTerraformResource(lineNumber, lines); + break; + } /* istanbul ignore next */ default: logger.warn( @@ -81,6 +94,9 @@ export function extractPackageFile(content: string): PackageFile | null { case TerraformDependencyTypes.module: analyseTerraformModule(dep); break; + case TerraformDependencyTypes.resource: + analyseTerraformResource(dep); + break; /* istanbul ignore next */ default: } diff --git a/lib/manager/terraform/resources.ts b/lib/manager/terraform/resources.ts new file mode 100644 index 00000000000000..793b4ced500054 --- /dev/null +++ b/lib/manager/terraform/resources.ts @@ -0,0 +1,58 @@ +import * as datasourceHelm from '../../datasource/helm'; +import { SkipReason } from '../../types'; +import { isValid } from '../../versioning/hashicorp'; +import { PackageDependency } from '../common'; +import { + ExtractionResult, + TerraformDependencyTypes, + checkIfStringIsPath, + keyValueExtractionRegex, +} from './util'; + +export function extractTerraformResource( + startingLine: number, + lines: string[] +): ExtractionResult { + let lineNumber = startingLine; + let line; + const deps: PackageDependency[] = []; + const dep: PackageDependency = { + managerData: { + terraformDependencyType: TerraformDependencyTypes.resource, + }, + }; + + do { + lineNumber += 1; + line = lines[lineNumber]; + const kvMatch = keyValueExtractionRegex.exec(line); + if (kvMatch) { + if (kvMatch.groups.key === 'version') { + dep.currentValue = kvMatch.groups.value; + } else if (kvMatch.groups.key === 'chart') { + dep.managerData.chart = kvMatch.groups.value; + } else if (kvMatch.groups.key === 'repository') { + dep.managerData.repository = kvMatch.groups.value; + } + } + } while (line.trim() !== '}'); + deps.push(dep); + return { lineNumber, dependencies: deps }; +} + +export function analyseTerraformResource(dep: PackageDependency): void { + /* eslint-disable no-param-reassign */ + if (dep.managerData.chart == null) { + dep.skipReason = SkipReason.InvalidName; + } else if (checkIfStringIsPath(dep.managerData.chart)) { + dep.skipReason = SkipReason.LocalChart; + } else if (!isValid(dep.currentValue)) { + dep.skipReason = SkipReason.UnsupportedVersion; + } + dep.depType = 'helm'; + dep.registryUrls = [dep.managerData.repository]; + dep.depName = dep.managerData.chart; + dep.depNameShort = dep.managerData.chart; + dep.datasource = datasourceHelm.id; + /* eslint-enable no-param-reassign */ +} diff --git a/lib/manager/terraform/util.ts b/lib/manager/terraform/util.ts index 996fbae14269ff..c2fd55855b351b 100644 --- a/lib/manager/terraform/util.ts +++ b/lib/manager/terraform/util.ts @@ -12,6 +12,7 @@ export enum TerraformDependencyTypes { module = 'module', provider = 'provider', required_providers = 'required_providers', + resource = 'resource', } export function getTerraformDependencyType( @@ -27,6 +28,9 @@ export function getTerraformDependencyType( case 'required_providers': { return TerraformDependencyTypes.required_providers; } + case 'resource': { + return TerraformDependencyTypes.resource; + } default: { return TerraformDependencyTypes.unknown; } @@ -41,3 +45,9 @@ export function checkFileContainsDependency( return content.includes(check); }); } + +const pathStringRegex = /(.|..)?(\/[^/])+/; +export function checkIfStringIsPath(path: string): boolean { + const match = pathStringRegex.exec(path); + return !!match; +}