diff --git a/lib/modules/manager/terraform/base.ts b/lib/modules/manager/terraform/base.ts index 52a5bea3ba4a15..274052535b94aa 100644 --- a/lib/modules/manager/terraform/base.ts +++ b/lib/modules/manager/terraform/base.ts @@ -2,6 +2,7 @@ import is from '@sindresorhus/is'; import { regEx } from '../../../util/regex'; import { TerraformProviderDatasource } from '../../datasource/terraform-provider'; import type { PackageDependency } from '../types'; +import type { TerraformDefinitionFile } from './hcl/types'; import type { ProviderLock } from './lockfile/types'; import { getLockedVersion, massageProviderLookupName } from './util'; @@ -17,7 +18,10 @@ export abstract class DependencyExtractor { * @param hclRoot HCL parsing artifact. * @param locks currently existing locks */ - abstract extract(hclRoot: any, locks: ProviderLock[]): PackageDependency[]; + abstract extract( + hclRoot: TerraformDefinitionFile, + locks: ProviderLock[] + ): PackageDependency[]; } export abstract class TerraformProviderExtractor extends DependencyExtractor { diff --git a/lib/modules/manager/terraform/extractors.ts b/lib/modules/manager/terraform/extractors.ts index 1a16410a47af9b..5d36732eb50f8f 100644 --- a/lib/modules/manager/terraform/extractors.ts +++ b/lib/modules/manager/terraform/extractors.ts @@ -1,7 +1,7 @@ import type { DependencyExtractor } from './base'; import { ModuleExtractor } from './extractors/others/modules'; import { ProvidersExtractor } from './extractors/others/providers'; -import { GenericDockerImageRef } from './extractors/resources/generic-docker-image-ref'; +import { GenericDockerImageRefExtractor } from './extractors/resources/generic-docker-image-ref'; import { HelmReleaseExtractor } from './extractors/resources/helm-release'; import { TerraformWorkspaceExtractor } from './extractors/resources/terraform-workspace'; import { RequiredProviderExtractor } from './extractors/terraform-block/required-provider'; @@ -9,7 +9,7 @@ import { TerraformVersionExtractor } from './extractors/terraform-block/terrafor export const resourceExtractors: DependencyExtractor[] = [ new HelmReleaseExtractor(), - new GenericDockerImageRef(), + new GenericDockerImageRefExtractor(), new TerraformWorkspaceExtractor(), new RequiredProviderExtractor(), new TerraformVersionExtractor(), diff --git a/lib/modules/manager/terraform/extractors/others/modules.ts b/lib/modules/manager/terraform/extractors/others/modules.ts index e12fe3253b06b3..114bc2c3f7440f 100644 --- a/lib/modules/manager/terraform/extractors/others/modules.ts +++ b/lib/modules/manager/terraform/extractors/others/modules.ts @@ -7,6 +7,7 @@ import { GithubTagsDatasource } from '../../../../datasource/github-tags'; import { TerraformModuleDatasource } from '../../../../datasource/terraform-module'; import type { PackageDependency } from '../../../types'; import { DependencyExtractor } from '../../base'; +import type { TerraformDefinitionFile } from '../../hcl/types'; export const githubRefMatchRegex = regEx( /github\.com([/:])(?[^/]+\/[a-z0-9-_.]+).*\?ref=(?.*)$/i @@ -27,25 +28,21 @@ export class ModuleExtractor extends DependencyExtractor { return ['module']; } - extract(hclRoot: any): PackageDependency[] { + extract(hclRoot: TerraformDefinitionFile): PackageDependency[] { const modules = hclRoot.module; if (is.nullOrUndefined(modules)) { return []; } const dependencies = []; - for (const moduleKeys of Object.keys(modules)) { - const module = modules[moduleKeys]; - for (const moduleElement of module) { - const dep = { - currentValue: moduleElement.version, - managerData: { - source: moduleElement.source, - }, - }; - const massagedDep = this.analyseTerraformModule(dep); - dependencies.push(massagedDep); - } + for (const moduleElement of Object.values(modules).flat()) { + const dep = { + currentValue: moduleElement.version, + managerData: { + source: moduleElement.source, + }, + }; + dependencies.push(this.analyseTerraformModule(dep)); } return dependencies; } diff --git a/lib/modules/manager/terraform/extractors/others/providers.ts b/lib/modules/manager/terraform/extractors/others/providers.ts index e930a54c16d422..53b17c2443fd01 100644 --- a/lib/modules/manager/terraform/extractors/others/providers.ts +++ b/lib/modules/manager/terraform/extractors/others/providers.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import type { PackageDependency } from '../../../types'; import { TerraformProviderExtractor } from '../../base'; +import type { TerraformDefinitionFile } from '../../hcl/types'; import type { ProviderLock } from '../../lockfile/types'; export class ProvidersExtractor extends TerraformProviderExtractor { @@ -8,7 +9,10 @@ export class ProvidersExtractor extends TerraformProviderExtractor { return ['provider']; } - extract(hclRoot: any, locks: ProviderLock[]): PackageDependency[] { + extract( + hclRoot: TerraformDefinitionFile, + locks: ProviderLock[] + ): PackageDependency[] { const providerTypes = hclRoot?.provider; if (is.nullOrUndefined(providerTypes)) { return []; @@ -22,7 +26,6 @@ export class ProvidersExtractor extends TerraformProviderExtractor { currentValue: providerTypeElement.version, managerData: { moduleName: providerTypeName, - source: providerTypeElement.source, }, }, locks, diff --git a/lib/modules/manager/terraform/extractors/resources/generic-docker-image.spec.ts b/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.spec.ts similarity index 56% rename from lib/modules/manager/terraform/extractors/resources/generic-docker-image.spec.ts rename to lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.spec.ts index b6f4314926fc3e..dc8e78c5e1f8a8 100644 --- a/lib/modules/manager/terraform/extractors/resources/generic-docker-image.spec.ts +++ b/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.spec.ts @@ -1,7 +1,7 @@ -import { GenericDockerImageRef } from './generic-docker-image-ref'; +import { GenericDockerImageRefExtractor } from './generic-docker-image-ref'; -describe('modules/manager/terraform/extractors/resources/generic-docker-image', () => { - const extractor = new GenericDockerImageRef(); +describe('modules/manager/terraform/extractors/resources/generic-docker-image-ref', () => { + const extractor = new GenericDockerImageRefExtractor(); it('return empty array if no resource is found', () => { const res = extractor.extract({}); diff --git a/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.ts b/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.ts index 43d53b21e3adb0..eb3f4c97d92628 100644 --- a/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.ts +++ b/lib/modules/manager/terraform/extractors/resources/generic-docker-image-ref.ts @@ -2,14 +2,15 @@ import is from '@sindresorhus/is'; import { getDep } from '../../../dockerfile/extract'; import type { PackageDependency } from '../../../types'; import { DependencyExtractor } from '../../base'; +import type { TerraformDefinitionFile } from '../../hcl/types'; import { generic_image_resource } from './utils'; -export class GenericDockerImageRef extends DependencyExtractor { +export class GenericDockerImageRefExtractor extends DependencyExtractor { getCheckList(): string[] { return generic_image_resource.map((value) => `"${value.type}"`); } - extract(hclMap: any): PackageDependency[] { + extract(hclMap: TerraformDefinitionFile): PackageDependency[] { const resourceTypMap = hclMap.resource; if (is.nullOrUndefined(resourceTypMap)) { return []; @@ -21,18 +22,13 @@ export class GenericDockerImageRef extends DependencyExtractor { const { type, path } = image_resource_def; const resourceInstancesMap = resourceTypMap[type]; // is there a resource with current looked at type ( `image_resource_def` ) - if (is.nullOrUndefined(resourceInstancesMap)) { + if (!is.nonEmptyObject(resourceInstancesMap)) { continue; } // loop over instances of a resource type - for (const instanceName of Object.keys(resourceInstancesMap)) { - const instanceList = resourceInstancesMap[instanceName]; - for (const instance of instanceList) { - dependencies.push( - ...this.walkPath({ depType: type }, instance, path) - ); - } + for (const instance of Object.values(resourceInstancesMap).flat()) { + dependencies.push(...this.walkPath({ depType: type }, instance, path)); } } return dependencies; @@ -48,14 +44,14 @@ export class GenericDockerImageRef extends DependencyExtractor { */ private walkPath( abstractDep: PackageDependency, - parentElement: any, + parentElement: unknown, leftPath: string[] ): PackageDependency[] { const dependencies: PackageDependency[] = []; // if there are no path elements left, we have reached the end of the path if (leftPath.length === 0) { // istanbul ignore if - if (is.nullOrUndefined(parentElement)) { + if (!is.nonEmptyString(parentElement)) { return [ { ...abstractDep, @@ -75,7 +71,9 @@ export class GenericDockerImageRef extends DependencyExtractor { const pathElement = leftPath[0]; // get sub element - const element = parentElement[pathElement]; + const element = is.nonEmptyObject(parentElement) + ? parentElement[pathElement] + : null; if (is.nullOrUndefined(element)) { return leftPath.length === 1 // if this is the last element assume a false defined dependency ? [ diff --git a/lib/modules/manager/terraform/extractors/resources/helm-release.ts b/lib/modules/manager/terraform/extractors/resources/helm-release.ts index 83d156881e9ba4..62e3e81d5d1edb 100644 --- a/lib/modules/manager/terraform/extractors/resources/helm-release.ts +++ b/lib/modules/manager/terraform/extractors/resources/helm-release.ts @@ -4,6 +4,7 @@ import { HelmDatasource } from '../../../../datasource/helm'; import { isOCIRegistry } from '../../../helmv3/utils'; import type { PackageDependency } from '../../../types'; import { DependencyExtractor } from '../../base'; +import type { TerraformDefinitionFile } from '../../hcl/types'; import { checkIfStringIsPath } from '../../util'; export class HelmReleaseExtractor extends DependencyExtractor { @@ -11,36 +12,34 @@ export class HelmReleaseExtractor extends DependencyExtractor { return [`"helm_release"`]; } - override extract(hclMap: any): PackageDependency[] { + override extract(hclMap: TerraformDefinitionFile): PackageDependency[] { const dependencies = []; const helmReleases = hclMap?.resource?.helm_release; if (is.nullOrUndefined(helmReleases)) { return []; } - for (const helmReleaseName of Object.keys(helmReleases)) { - for (const helmRelease of helmReleases[helmReleaseName]) { - const dep: PackageDependency = { - currentValue: helmRelease.version, - depType: 'helm_release', - depName: helmRelease.chart, - datasource: HelmDatasource.id, - }; - if (!is.nullOrUndefined(helmRelease.repository)) { - dep.registryUrls = [helmRelease.repository]; - } - if (!helmRelease.chart) { - dep.skipReason = 'invalid-name'; - } else if (isOCIRegistry(helmRelease.chart)) { - // For oci charts, we remove the oci:// and use the docker datasource - dep.depName = helmRelease.chart.replace('oci://', ''); - dep.datasource = DockerDatasource.id; - } else if (checkIfStringIsPath(helmRelease.chart)) { - dep.skipReason = 'local-chart'; - } - - dependencies.push(dep); + for (const helmRelease of Object.values(helmReleases).flat()) { + const dep: PackageDependency = { + currentValue: helmRelease.version, + depType: 'helm_release', + depName: helmRelease.chart, + datasource: HelmDatasource.id, + }; + if (is.nonEmptyString(helmRelease.repository)) { + dep.registryUrls = [helmRelease.repository]; + } + if (!helmRelease.chart) { + dep.skipReason = 'invalid-name'; + } else if (isOCIRegistry(helmRelease.chart)) { + // For oci charts, we remove the oci:// and use the docker datasource + dep.depName = helmRelease.chart.replace('oci://', ''); + dep.datasource = DockerDatasource.id; + } else if (checkIfStringIsPath(helmRelease.chart)) { + dep.skipReason = 'local-chart'; } + + dependencies.push(dep); } return dependencies; diff --git a/lib/modules/manager/terraform/extractors/resources/terraform-workspace.ts b/lib/modules/manager/terraform/extractors/resources/terraform-workspace.ts index 906678ceb3a8fc..d85582a9fc1b89 100644 --- a/lib/modules/manager/terraform/extractors/resources/terraform-workspace.ts +++ b/lib/modules/manager/terraform/extractors/resources/terraform-workspace.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import type { PackageDependency } from '../../../types'; +import type { TerraformDefinitionFile } from '../../hcl/types'; import { TerraformVersionExtractor } from '../terraform-block/terraform-version'; export class TerraformWorkspaceExtractor extends TerraformVersionExtractor { @@ -7,7 +8,7 @@ export class TerraformWorkspaceExtractor extends TerraformVersionExtractor { return [`"tfe_workspace"`]; } - override extract(hclMap: any): PackageDependency[] { + override extract(hclMap: TerraformDefinitionFile): PackageDependency[] { const dependencies = []; const workspaces = hclMap?.resource?.tfe_workspace; @@ -15,20 +16,18 @@ export class TerraformWorkspaceExtractor extends TerraformVersionExtractor { return []; } - for (const workspaceName of Object.keys(workspaces)) { - for (const workspace of workspaces[workspaceName]) { - const dep: PackageDependency = this.analyseTerraformVersion({ - currentValue: workspace.terraform_version, - }); + for (const workspace of Object.values(workspaces).flat()) { + const dep: PackageDependency = this.analyseTerraformVersion({ + currentValue: workspace.terraform_version, + }); - if (is.nullOrUndefined(workspace.terraform_version)) { - dep.skipReason = 'no-version'; - } - dependencies.push({ - ...dep, - depType: 'tfe_workspace', - }); + if (is.nullOrUndefined(workspace.terraform_version)) { + dep.skipReason = 'no-version'; } + dependencies.push({ + ...dep, + depType: 'tfe_workspace', + }); } return dependencies; } diff --git a/lib/modules/manager/terraform/extractors/terraform-block/required-provider.ts b/lib/modules/manager/terraform/extractors/terraform-block/required-provider.ts index 6d198e04b26a0b..79c447f3830a89 100644 --- a/lib/modules/manager/terraform/extractors/terraform-block/required-provider.ts +++ b/lib/modules/manager/terraform/extractors/terraform-block/required-provider.ts @@ -1,6 +1,10 @@ import is from '@sindresorhus/is'; import type { PackageDependency } from '../../../types'; import { TerraformProviderExtractor } from '../../base'; +import type { + TerraformDefinitionFile, + TerraformRequiredProvider, +} from '../../hcl/types'; import type { ProviderLock } from '../../lockfile/types'; export class RequiredProviderExtractor extends TerraformProviderExtractor { @@ -8,7 +12,10 @@ export class RequiredProviderExtractor extends TerraformProviderExtractor { return ['required_providers']; } - extract(hclRoot: any, locks: ProviderLock[]): PackageDependency[] { + extract( + hclRoot: TerraformDefinitionFile, + locks: ProviderLock[] + ): PackageDependency[] { const terraformBlocks = hclRoot?.terraform; if (is.nullOrUndefined(terraformBlocks)) { return []; @@ -21,35 +28,31 @@ export class RequiredProviderExtractor extends TerraformProviderExtractor { continue; } - for (const requiredProvidersMap of requiredProviders) { - for (const requiredProviderName of Object.keys(requiredProvidersMap)) { - const value = requiredProvidersMap[requiredProviderName]; - - // name = version declaration method - let dep: PackageDependency; - if (typeof value === 'string') { - dep = { - currentValue: value, - managerData: { - moduleName: requiredProviderName, - }, - }; - } + const entries: [string, TerraformRequiredProvider | string][] = + requiredProviders.flatMap(Object.entries); + for (const [requiredProviderName, value] of entries) { + // name = version declaration method + let dep: PackageDependency; + if (is.string(value)) { + dep = { + currentValue: value, + managerData: { + moduleName: requiredProviderName, + }, + }; + } else { // block declaration aws = { source = 'aws', version = '2.0.0' } - dep ??= { + dep = { currentValue: value['version'], managerData: { moduleName: requiredProviderName, source: value['source'], }, }; - const massagedDep = this.analyzeTerraformProvider( - dep, - locks, - 'required_provider' - ); - dependencies.push(massagedDep); } + dependencies.push( + this.analyzeTerraformProvider(dep, locks, 'required_provider') + ); } } return dependencies; diff --git a/lib/modules/manager/terraform/extractors/terraform-block/terraform-version.ts b/lib/modules/manager/terraform/extractors/terraform-block/terraform-version.ts index c8a090bc020226..b2a3f1526b384b 100644 --- a/lib/modules/manager/terraform/extractors/terraform-block/terraform-version.ts +++ b/lib/modules/manager/terraform/extractors/terraform-block/terraform-version.ts @@ -2,13 +2,14 @@ import is from '@sindresorhus/is'; import { GithubReleasesDatasource } from '../../../../datasource/github-releases'; import type { PackageDependency } from '../../../types'; import { DependencyExtractor } from '../../base'; +import type { TerraformDefinitionFile } from '../../hcl/types'; export class TerraformVersionExtractor extends DependencyExtractor { getCheckList(): string[] { return ['required_version']; } - extract(hclRoot: any): PackageDependency[] { + extract(hclRoot: TerraformDefinitionFile): PackageDependency[] { const terraformBlocks = hclRoot?.terraform; if (is.nullOrUndefined(terraformBlocks)) { return []; diff --git a/lib/modules/manager/terraform/hcl/index.spec.ts b/lib/modules/manager/terraform/hcl/index.spec.ts index aada37398a30b2..bd9be234275687 100644 --- a/lib/modules/manager/terraform/hcl/index.spec.ts +++ b/lib/modules/manager/terraform/hcl/index.spec.ts @@ -8,9 +8,10 @@ const lockedVersion = Fixtures.get('lockedVersion.tf'); describe('modules/manager/terraform/hcl/index', () => { describe('parseHCL()', () => { - it('should return flat modules', async () => { - const res = await parseHCL(modulesTF); - expect(Object.keys(res.module)).toBeArrayOfSize(6); + it('should return flat modules', () => { + const res = parseHCL(modulesTF); + expect(res?.module).toBeDefined(); + expect(Object.keys(res!.module!)).toBeArrayOfSize(6); expect(res).toMatchObject({ module: { bar: [ @@ -49,8 +50,8 @@ describe('modules/manager/terraform/hcl/index', () => { }); }); - it('should return nested terraform block', async () => { - const res = await parseHCL(lockedVersion); + it('should return nested terraform block', () => { + const res = parseHCL(lockedVersion); expect(res).toMatchObject({ terraform: [ { @@ -66,8 +67,8 @@ describe('modules/manager/terraform/hcl/index', () => { }); }); - it('should return resource blocks', async () => { - const res = await parseHCL(resourcesTF); + it('should return resource blocks', () => { + const res = parseHCL(resourcesTF); expect(res).toMatchObject({ resource: { docker_container: { @@ -97,8 +98,8 @@ describe('modules/manager/terraform/hcl/index', () => { }); describe('parseJSON', () => { - it('should parse json', async () => { - const res = await parseJSON(resourcesTFJSON); + it('should parse json', () => { + const res = parseJSON(resourcesTFJSON); expect(res).toMatchObject({ resource: { aws_instance: { diff --git a/lib/modules/manager/terraform/hcl/index.ts b/lib/modules/manager/terraform/hcl/index.ts index 576d8de1c55a18..61803c995dda4d 100644 --- a/lib/modules/manager/terraform/hcl/index.ts +++ b/lib/modules/manager/terraform/hcl/index.ts @@ -1,6 +1,8 @@ import * as hcl_parser from 'hcl2-parser'; -export function parseHCL(content: string): any { +import type { TerraformDefinitionFile } from './types'; + +export function parseHCL(content: string): TerraformDefinitionFile | null { try { return hcl_parser.parseToObject(content)[0]; } catch (err) /* istanbul ignore next */ { @@ -8,6 +10,6 @@ export function parseHCL(content: string): any { } } -export function parseJSON(content: string): any { +export function parseJSON(content: string): TerraformDefinitionFile | null { return JSON.parse(content); } diff --git a/lib/modules/manager/terraform/hcl/types.ts b/lib/modules/manager/terraform/hcl/types.ts new file mode 100644 index 00000000000000..b9ba9b1468d01d --- /dev/null +++ b/lib/modules/manager/terraform/hcl/types.ts @@ -0,0 +1,46 @@ +export interface TerraformDefinitionFile { + terraform?: TerraformBlock[]; + module?: Record; + resource?: TerraformResources; + provider?: Record; +} + +export interface TerraformBlock { + required_providers?: TerraformRequiredProviderBlock[]; + required_version?: string; +} + +export interface TerraformRequiredProviderBlock { + [s: string]: TerraformRequiredProvider | string; +} + +export interface TerraformRequiredProvider { + source?: string; + version?: string; +} + +export interface TerraformModule { + source?: string; + version?: string; +} + +export interface TerraformResources { + helm_release?: Record; + tfe_workspace?: Record; + [s: string]: unknown; // generic docker resources +} + +export interface TerraformProvider { + alias?: string; + version?: string; +} + +export interface TerraformHelmRelease { + version?: string; + repository?: string; + chart?: string; +} + +export interface TerraformWorkspace { + terraform_version?: string; +}