diff --git a/lib/datasource/terraform-provider/__snapshots__/index.spec.ts.snap b/lib/datasource/terraform-provider/__snapshots__/index.spec.ts.snap index cc8000d12f97d7c9eac7692a51f9e006ba5e2edd..80bcd42a8b6d499abcd96a23d5a8849f47c3ee8d 100644 --- a/lib/datasource/terraform-provider/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/terraform-provider/__snapshots__/index.spec.ts.snap @@ -14,6 +14,7 @@ Object { "version": "2.0.0", }, ], + "sourceUrl": "https://github.com/terraform-providers/terraform-provider-google-beta", "versions": Object {}, } `; @@ -284,6 +285,237 @@ Array [ ] `; +exports[`datasource/terraform getReleases processes real data from lookupName 1`] = ` +Object { + "homepage": "https://registry.company.com/providers/hashicorp/azurerm", + "name": "hashicorp/azurerm", + "releases": Array [ + Object { + "version": "0.1.0", + }, + Object { + "version": "0.1.1", + }, + Object { + "version": "0.1.2", + }, + Object { + "version": "0.1.3", + }, + Object { + "version": "0.1.4", + }, + Object { + "version": "0.1.5", + }, + Object { + "version": "0.1.6", + }, + Object { + "version": "0.1.7", + }, + Object { + "version": "0.2.0", + }, + Object { + "version": "0.2.1", + }, + Object { + "version": "0.2.2", + }, + Object { + "version": "0.3.0", + }, + Object { + "version": "0.3.1", + }, + Object { + "version": "0.3.2", + }, + Object { + "version": "0.3.3", + }, + Object { + "version": "1.0.0", + }, + Object { + "version": "1.0.1", + }, + Object { + "version": "1.1.0", + }, + Object { + "version": "1.1.1", + }, + Object { + "version": "1.1.2", + }, + Object { + "version": "1.2.0", + }, + Object { + "version": "1.3.0", + }, + Object { + "version": "1.3.1", + }, + Object { + "version": "1.3.2", + }, + Object { + "version": "1.3.3", + }, + Object { + "version": "1.4.0", + }, + Object { + "version": "1.5.0", + }, + Object { + "version": "1.6.0", + }, + Object { + "version": "1.7.0", + }, + Object { + "version": "1.8.0", + }, + Object { + "version": "1.9.0", + }, + Object { + "version": "1.10.0", + }, + Object { + "version": "1.11.0", + }, + Object { + "version": "1.12.0", + }, + Object { + "version": "1.13.0", + }, + Object { + "version": "1.14.0", + }, + Object { + "version": "1.15.0", + }, + Object { + "version": "1.16.0", + }, + Object { + "version": "1.17.0", + }, + Object { + "version": "1.18.0", + }, + Object { + "version": "1.19.0", + }, + Object { + "version": "1.20.0", + }, + Object { + "version": "1.21.0", + }, + Object { + "version": "1.22.0", + }, + Object { + "version": "1.22.1", + }, + Object { + "version": "1.23.0", + }, + Object { + "version": "1.24.0", + }, + Object { + "version": "1.25.0", + }, + Object { + "version": "1.26.0", + }, + Object { + "version": "1.27.0", + }, + Object { + "version": "1.27.1", + }, + Object { + "version": "1.28.0", + }, + Object { + "version": "1.29.0", + }, + Object { + "version": "1.30.0", + }, + Object { + "version": "1.30.1", + }, + Object { + "version": "1.31.0", + }, + Object { + "version": "1.32.0", + }, + Object { + "version": "1.32.1", + }, + Object { + "version": "1.33.0", + }, + Object { + "version": "1.33.1", + }, + Object { + "version": "1.34.0", + }, + Object { + "version": "1.35.0", + }, + Object { + "version": "1.36.0", + }, + Object { + "version": "1.36.1", + }, + Object { + "version": "1.37.0", + }, + ], + "sourceUrl": "https://github.com/terraform-providers/terraform-provider-azurerm", + "versions": Object {}, +} +`; + +exports[`datasource/terraform getReleases processes real data from lookupName 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/.well-known/terraform.json", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v1/providers/hashicorp/azurerm", + }, +] +`; + exports[`datasource/terraform getReleases returns null for 404 1`] = ` Array [ Object { diff --git a/lib/datasource/terraform-provider/index.spec.ts b/lib/datasource/terraform-provider/index.spec.ts index 53c750c492035acd2eb953a22b8b6cc743810552..16ee16d21044473acad601c9d599eb773bd0c1b8 100644 --- a/lib/datasource/terraform-provider/index.spec.ts +++ b/lib/datasource/terraform-provider/index.spec.ts @@ -90,6 +90,24 @@ describe('datasource/terraform', () => { expect(res).not.toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('processes real data from lookupName', async () => { + httpMock + .scope('https://registry.company.com') + .get('/v1/providers/hashicorp/azurerm') + .reply(200, JSON.parse(consulData)) + .get('/.well-known/terraform.json') + .reply(200, serviceDiscoveryResult); + const res = await getPkgReleases({ + datasource, + depName: 'azure', + lookupName: 'hashicorp/azurerm', + registryUrls: ['https://registry.company.com'], + }); + expect(res).toMatchSnapshot(); + expect(res).not.toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('processes data with alternative backend', async () => { httpMock .scope(primaryUrl) diff --git a/lib/datasource/terraform-provider/index.ts b/lib/datasource/terraform-provider/index.ts index f97a3608482243073595e19508dba492dc5c8777..0016d9b8c0acb4d36de6a10fbc591ec42a5e0755 100644 --- a/lib/datasource/terraform-provider/index.ts +++ b/lib/datasource/terraform-provider/index.ts @@ -59,6 +59,7 @@ async function queryRegistry( return dep; } +// TODO: add long term cache async function queryReleaseBackend( lookupName: string, registryURL: string, @@ -72,6 +73,7 @@ async function queryReleaseBackend( name: repository, versions: {}, releases: null, + sourceUrl: `https://github.com/terraform-providers/${backendLookUpName}`, }; dep.releases = Object.keys(res[backendLookUpName].versions).map( (version) => ({ @@ -91,7 +93,9 @@ export async function getReleases({ lookupName, registryUrl, }: GetReleasesConfig): Promise<ReleaseResult | null> { - const repository = `hashicorp/${lookupName}`; + const repository = lookupName.includes('/') + ? lookupName + : `hashicorp/${lookupName}`; const cacheNamespace = 'terraform-provider'; const pkgUrl = `${registryUrl}/${repository}`; diff --git a/lib/manager/terraform/__fixtures__/1.tf b/lib/manager/terraform/__fixtures__/1.tf index db6f8e68aab6b7a5d51c041fcb95dfaad14058bd..26296ff400eae233a5eec811fa10b9b73c789fe7 100644 --- a/lib/manager/terraform/__fixtures__/1.tf +++ b/lib/manager/terraform/__fixtures__/1.tf @@ -168,11 +168,32 @@ terraform { terraform { required_providers { - aws = ">= 2.5.0" azurerm = ">= 2.0.0" } } +terraform { + required_providers { + docker = { + source = "terraform-providers/docker" + version = "2.7.2" + } + invalid = { + source = "//hashicorp/helm" + version = "1.2.4" + } + helm = { + source = "hashicorp/helm" + version = "1.2.4" + } + kubernetes = { + source = "terraform.example.com/hashicorp/kubernetes" + version = ">= 1.0" + } + } + required_version = ">= 0.13" +} + # docker_image resources # https://www.terraform.io/docs/providers/docker/r/image.html diff --git a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap index b4aac1bbb757e566e0c452d84599dedb74e13e4a..157c984b05368e614af083ab4c44e5aeb8fa7c0a 100644 --- a/lib/manager/terraform/__snapshots__/extract.spec.ts.snap +++ b/lib/manager/terraform/__snapshots__/extract.spec.ts.snap @@ -284,18 +284,51 @@ Object { "depType": "terraform", }, Object { - "currentValue": ">= 2.5.0", + "currentValue": ">= 2.0.0", "datasource": "terraform-provider", - "depName": "aws", - "depNameShort": "aws", + "depName": "azurerm", + "depNameShort": "azurerm", "depType": "terraform", }, Object { - "currentValue": ">= 2.0.0", + "currentValue": "2.7.2", "datasource": "terraform-provider", - "depName": "azurerm", - "depNameShort": "azurerm", + "depName": "docker", + "depNameShort": "docker", + "depType": "terraform", + "registryUrls": Array [ + "https://releases.hashicorp.com", + ], + }, + Object { + "currentValue": "1.2.4", + "datasource": "terraform-provider", + "depName": "invalid", + "depNameShort": "invalid", "depType": "terraform", + "skipReason": "unsupported-url", + }, + Object { + "currentValue": "1.2.4", + "datasource": "terraform-provider", + "depName": "helm", + "depNameShort": "helm", + "depType": "terraform", + "lookupName": "hashicorp/helm", + "registryUrls": Array [ + "https://registry.terraform.io", + ], + }, + Object { + "currentValue": ">= 1.0", + "datasource": "terraform-provider", + "depName": "kubernetes", + "depNameShort": "kubernetes", + "depType": "terraform", + "lookupName": "hashicorp/kubernetes", + "registryUrls": Array [ + "https://terraform.example.com", + ], }, Object { "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", diff --git a/lib/manager/terraform/extract.spec.ts b/lib/manager/terraform/extract.spec.ts index 6cd7d5285d26d5e02bebb5b72f1a358a923805cc..e2cb312032da77b52dcc66492122d0fb29a856ba 100644 --- a/lib/manager/terraform/extract.spec.ts +++ b/lib/manager/terraform/extract.spec.ts @@ -16,8 +16,8 @@ describe('lib/manager/terraform/extract', () => { it('extracts', () => { const res = extractPackageFile(tf1); expect(res).toMatchSnapshot(); - expect(res.deps).toHaveLength(38); - expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(11); + expect(res.deps).toHaveLength(41); + expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(12); }); it('returns null if only local deps', () => { expect(extractPackageFile(tf2)).toBeNull(); diff --git a/lib/manager/terraform/providers.ts b/lib/manager/terraform/providers.ts index db0dd9839f2334252f76f03b8a1d8d2f1f28362a..256e4dad91bd73e3b14a09826de48b544c5ed10c 100644 --- a/lib/manager/terraform/providers.ts +++ b/lib/manager/terraform/providers.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import * as datasourceTerraformProvider from '../../datasource/terraform-provider'; import { SkipReason } from '../../types'; import { isValid } from '../../versioning/hashicorp'; @@ -8,6 +9,8 @@ import { keyValueExtractionRegex, } from './util'; +export const sourceExtractionRegex = /^(?:(?<hostname>[^/]+)\/)?(?<namespace>[^/]+)\/(?<type>[^/]+)/; + export function extractTerraformProvider( startingLine: number, lines: string[], @@ -48,5 +51,24 @@ export function analyzeTerraformProvider(dep: PackageDependency): void { if (!isValid(dep.currentValue)) { dep.skipReason = SkipReason.UnsupportedVersion; } + + if (is.nonEmptyString(dep.managerData.source)) { + const source = sourceExtractionRegex.exec(dep.managerData.source); + if (source) { + // buildin providers https://github.com/terraform-providers + if (source.groups.namespace === 'terraform-providers') { + dep.registryUrls = [`https://releases.hashicorp.com`]; + } else { + dep.lookupName = `${source.groups.namespace}/${source.groups.type}`; + if (source.groups.hostname) { + dep.registryUrls = [`https://${source.groups.hostname}`]; + } else { + dep.registryUrls = [`https://registry.terraform.io`]; + } + } + } else { + dep.skipReason = SkipReason.UnsupportedUrl; + } + } /* eslint-enable no-param-reassign */ } diff --git a/lib/manager/terraform/required_providers.ts b/lib/manager/terraform/required_providers.ts index ef422e9fe5b27584f5d55587b39c0c5b08e692ad..147b8ebd861727f71c5497afd4fd143ca66355c8 100644 --- a/lib/manager/terraform/required_providers.ts +++ b/lib/manager/terraform/required_providers.ts @@ -5,6 +5,40 @@ import { keyValueExtractionRegex, } from './util'; +export const providerBlockExtractionRegex = /^\s*(?<key>[^\s]+)\s+=\s+{/; + +function extractBlock( + lineNum: number, + lines: string[], + dep: PackageDependency +): number { + let lineNumber = lineNum; + let line: string; + do { + lineNumber += 1; + line = lines[lineNumber]; + const kvMatch = keyValueExtractionRegex.exec(line); + if (kvMatch) { + /* eslint-disable no-param-reassign */ + switch (kvMatch.groups.key) { + case 'source': + dep.managerData.source = kvMatch.groups.value; + break; + + case 'version': + dep.currentValue = kvMatch.groups.value; + break; + + /* istanbul ignore next */ + default: + break; + } + /* eslint-enable no-param-reassign */ + } + } while (line.trim() !== '}'); + return lineNumber; +} + export function extractTerraformRequiredProviders( startingLine: number, lines: string[] @@ -26,6 +60,14 @@ export function extractTerraformRequiredProviders( dep.currentValue = kvMatch.groups.value; dep.managerData.moduleName = kvMatch.groups.key; deps.push(dep); + } else { + const nameMatch = providerBlockExtractionRegex.exec(line); + + if (nameMatch?.groups) { + dep.managerData.moduleName = nameMatch.groups.key; + lineNumber = extractBlock(lineNumber, lines, dep); + deps.push(dep); + } } } while (line.trim() !== '}'); return { lineNumber, dependencies: deps };