Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(manager/terraform): support registry aliases for docker images and oci helm charts #22022

Merged
merged 5 commits into from May 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/usage/configuration-options.md
Expand Up @@ -3136,6 +3136,7 @@ This feature works with the following managers:
- [`kubernetes`](/modules/manager/kubernetes)
- [`ansible`](/modules/manager/ansible)
- [`droneci`](/modules/manager/droneci)
- [`terraform`](/modules/manager/terraform)

## registryUrls

Expand Down
2 changes: 1 addition & 1 deletion lib/modules/manager/dockerfile/extract.ts
Expand Up @@ -182,7 +182,7 @@ export function getDep(
...getDep(`${value}/${groups.depName}`),
replaceString: currentFrom,
};
dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep)!;
dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep);
return dep;
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/modules/manager/terraform/__fixtures__/docker.tf
Expand Up @@ -15,9 +15,9 @@ resource "docker_image" "ignore_variable" {

# docker_container resources
# https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/container
resource "docker_container" "foo" {
name = "foo"
image = "nginx:1.7.8"
resource "docker_container" "proxy" {
viceice marked this conversation as resolved.
Show resolved Hide resolved
name = "proxy"
image = "hub.proxy.test/bitnami/nginx:1.24.0"
}

resource "docker_container" "invalid" {
Expand Down
8 changes: 8 additions & 0 deletions lib/modules/manager/terraform/__fixtures__/helm.tf
Expand Up @@ -53,3 +53,11 @@ resource "helm_release" "karpenter_oci_repo" {
chart = "karpenter"
version = "v0.22.1"
}

## chart in OCI registry
resource "helm_release" "proxy_oci_repo" {
name = "kube-prometheus"
repository = "oci://hub.proxy.test/bitnamicharts"
chart = "kube-prometheus"
version = "8.9.1"
}
5 changes: 3 additions & 2 deletions lib/modules/manager/terraform/base.ts
@@ -1,7 +1,7 @@
import is from '@sindresorhus/is';
import { regEx } from '../../../util/regex';
import { TerraformProviderDatasource } from '../../datasource/terraform-provider';
import type { PackageDependency } from '../types';
import type { ExtractConfig, PackageDependency } from '../types';
import type { TerraformDefinitionFile } from './hcl/types';
import type { ProviderLock } from './lockfile/types';
import { getLockedVersion, massageProviderLookupName } from './util';
Expand All @@ -20,7 +20,8 @@ export abstract class DependencyExtractor {
*/
abstract extract(
hclRoot: TerraformDefinitionFile,
locks: ProviderLock[]
locks: ProviderLock[],
config: ExtractConfig
): PackageDependency[];
}

Expand Down
83 changes: 47 additions & 36 deletions lib/modules/manager/terraform/extract.spec.ts
Expand Up @@ -401,44 +401,46 @@ describe('modules/manager/terraform/extract', () => {
});

it('extracts docker resources', async () => {
const res = await extractPackageFile(docker, 'docker.tf', {});
const res = await extractPackageFile(docker, 'docker.tf', {
registryAliases: { 'hub.proxy.test': 'index.docker.io' },
});
expect(res?.deps).toHaveLength(6);
expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(3);
expect(res?.deps).toIncludeAllPartialMembers([
expect(res?.deps).toMatchObject([
viceice marked this conversation as resolved.
Show resolved Hide resolved
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentValue: '1.7.8',
datasource: 'docker',
depName: 'nginx',
depType: 'docker_image',
replaceString: 'nginx:1.7.8',
replaceString: '${data.docker_registry_image.ubuntu.name}',
skipReason: 'contains-variable',
},
{
depType: 'docker_image',
skipReason: 'invalid-dependency-specification',
},
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
datasource: 'docker',
depType: 'docker_image',
replaceString: '${data.docker_registry_image.ubuntu.name}',
skipReason: 'contains-variable',
},
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentValue: '1.7.8',
datasource: 'docker',
depName: 'nginx',
depType: 'docker_container',
depType: 'docker_image',
replaceString: 'nginx:1.7.8',
},
{
depType: 'docker_container',
skipReason: 'invalid-dependency-specification',
},
{
autoReplaceStringTemplate:
'hub.proxy.test/bitnami/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentValue: '1.24.0',
datasource: 'docker',
depName: 'index.docker.io/bitnami/nginx',
depType: 'docker_container',
replaceString: 'hub.proxy.test/bitnami/nginx:1.24.0',
},
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
Expand Down Expand Up @@ -574,29 +576,12 @@ describe('modules/manager/terraform/extract', () => {
});

it('extract helm releases', async () => {
const res = await extractPackageFile(helm, 'helm.tf', {});
expect(res?.deps).toHaveLength(8);
const res = await extractPackageFile(helm, 'helm.tf', {
registryAliases: { 'hub.proxy.test': 'index.docker.io' },
});
expect(res?.deps).toHaveLength(9);
expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(2);
expect(res?.deps).toIncludeAllPartialMembers([
{
currentValue: '1.0.1',
datasource: 'helm',
depName: 'redis',
depType: 'helm_release',
registryUrls: ['https://charts.helm.sh/stable'],
},
{
datasource: 'helm',
depName: 'redis',
depType: 'helm_release',
registryUrls: ['https://charts.helm.sh/stable'],
},
{
datasource: 'helm',
depName: './charts/example',
depType: 'helm_release',
skipReason: 'local-chart',
},
expect(res?.deps).toMatchObject([
{
currentValue: '4.0.1',
datasource: 'helm',
Expand Down Expand Up @@ -630,6 +615,32 @@ describe('modules/manager/terraform/extract', () => {
depType: 'helm_release',
packageName: 'public.ecr.aws/karpenter/karpenter',
},
{
datasource: 'helm',
depName: './charts/example',
depType: 'helm_release',
skipReason: 'local-chart',
},
{
currentValue: '8.9.1',
datasource: 'docker',
depName: 'kube-prometheus',
depType: 'helm_release',
packageName: 'index.docker.io/bitnamicharts/kube-prometheus',
},
{
currentValue: '1.0.1',
datasource: 'helm',
depName: 'redis',
depType: 'helm_release',
registryUrls: ['https://charts.helm.sh/stable'],
},
{
datasource: 'helm',
depName: 'redis',
depType: 'helm_release',
registryUrls: ['https://charts.helm.sh/stable'],
},
]);
});

Expand Down
2 changes: 1 addition & 1 deletion lib/modules/manager/terraform/extract.ts
Expand Up @@ -46,7 +46,7 @@ export async function extractPackageFile(
const locks = await extractLocksForPackageFile(fileName);

for (const extractor of passedExtractors) {
const deps = extractor.extract(hclMap, locks);
const deps = extractor.extract(hclMap, locks, config);
dependencies.push(...deps);
}

Expand Down
Expand Up @@ -4,7 +4,7 @@ describe('modules/manager/terraform/extractors/resources/generic-docker-image-re
const extractor = new GenericDockerImageRefExtractor();

it('return empty array if no resource is found', () => {
const res = extractor.extract({});
const res = extractor.extract({}, [], {});
expect(res).toBeArrayOfSize(0);
});
});
@@ -1,16 +1,21 @@
import is from '@sindresorhus/is';
import { getDep } from '../../../dockerfile/extract';
import type { PackageDependency } from '../../../types';
import type { ExtractConfig, PackageDependency } from '../../../types';
import { DependencyExtractor } from '../../base';
import type { TerraformDefinitionFile } from '../../hcl/types';
import type { ProviderLock } from '../../lockfile/types';
import { generic_image_resource } from './utils';

export class GenericDockerImageRefExtractor extends DependencyExtractor {
getCheckList(): string[] {
return generic_image_resource.map((value) => `"${value.type}"`);
}

extract(hclMap: TerraformDefinitionFile): PackageDependency[] {
extract(
hclMap: TerraformDefinitionFile,
_locks: ProviderLock[],
config: ExtractConfig
): PackageDependency[] {
const resourceTypMap = hclMap.resource;
if (is.nullOrUndefined(resourceTypMap)) {
return [];
Expand All @@ -28,7 +33,9 @@ export class GenericDockerImageRefExtractor extends DependencyExtractor {

// loop over instances of a resource type
for (const instance of Object.values(resourceInstancesMap).flat()) {
dependencies.push(...this.walkPath({ depType: type }, instance, path));
dependencies.push(
...this.walkPath({ depType: type }, instance, path, config)
);
}
}
return dependencies;
Expand All @@ -45,7 +52,8 @@ export class GenericDockerImageRefExtractor extends DependencyExtractor {
private walkPath(
abstractDep: PackageDependency,
parentElement: unknown,
leftPath: string[]
leftPath: string[],
config: ExtractConfig
): PackageDependency[] {
const dependencies: PackageDependency[] = [];
// if there are no path elements left, we have reached the end of the path
Expand All @@ -59,7 +67,7 @@ export class GenericDockerImageRefExtractor extends DependencyExtractor {
},
];
}
const test = getDep(parentElement);
const test = getDep(parentElement, true, config.registryAliases);
const dep: PackageDependency = {
...abstractDep,
...test,
Expand Down Expand Up @@ -87,11 +95,11 @@ export class GenericDockerImageRefExtractor extends DependencyExtractor {
if (is.array(element)) {
for (const arrayElement of element) {
dependencies.push(
...this.walkPath(abstractDep, arrayElement, leftPath.slice(1))
...this.walkPath(abstractDep, arrayElement, leftPath.slice(1), config)
);
}
return dependencies;
}
return this.walkPath(abstractDep, element, leftPath.slice(1));
return this.walkPath(abstractDep, element, leftPath.slice(1), config);
}
}
Expand Up @@ -4,7 +4,7 @@ describe('modules/manager/terraform/extractors/resources/helm-release', () => {
const extractor = new HelmReleaseExtractor();

it('return empty array if no resource is found', () => {
const res = extractor.extract({});
const res = extractor.extract({}, [], {});
expect(res).toBeArrayOfSize(0);
});
});
40 changes: 30 additions & 10 deletions lib/modules/manager/terraform/extractors/resources/helm-release.ts
@@ -1,20 +1,25 @@
import is from '@sindresorhus/is';
import { logger } from '../../../../../logger';
import { joinUrlParts } from '../../../../../util/url';
import { DockerDatasource } from '../../../../datasource/docker';
import { HelmDatasource } from '../../../../datasource/helm';
import { getDep } from '../../../dockerfile/extract';
import { isOCIRegistry } from '../../../helmv3/utils';
import type { PackageDependency } from '../../../types';
import type { ExtractConfig, PackageDependency } from '../../../types';
import { DependencyExtractor } from '../../base';
import type { TerraformDefinitionFile } from '../../hcl/types';
import type { ProviderLock } from '../../lockfile/types';
import { checkIfStringIsPath } from '../../util';

export class HelmReleaseExtractor extends DependencyExtractor {
getCheckList(): string[] {
return [`"helm_release"`];
}

override extract(hclMap: TerraformDefinitionFile): PackageDependency[] {
override extract(
hclMap: TerraformDefinitionFile,
_locks: ProviderLock[],
config: ExtractConfig
): PackageDependency[] {
const dependencies = [];

const helmReleases = hclMap?.resource?.helm_release;
Expand Down Expand Up @@ -46,19 +51,20 @@ export class HelmReleaseExtractor extends DependencyExtractor {
} 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;
this.processOci(dep.depName, config, dep);
viceice marked this conversation as resolved.
Show resolved Hide resolved
} else if (checkIfStringIsPath(helmRelease.chart)) {
dep.skipReason = 'local-chart';
} else if (is.nonEmptyString(helmRelease.repository)) {
if (isOCIRegistry(helmRelease.repository)) {
{
// For oci repos, we remove the oci://, join the chart name and use the docker datasource
dep.packageName = joinUrlParts(
// For oci charts, we remove the oci:// and use the docker datasource
this.processOci(
viceice marked this conversation as resolved.
Show resolved Hide resolved
joinUrlParts(
helmRelease.repository.replace('oci://', ''),
helmRelease.chart
);
dep.datasource = DockerDatasource.id;
}
),
config,
dep
);
} else {
dep.registryUrls = [helmRelease.repository];
}
Expand All @@ -67,4 +73,18 @@ export class HelmReleaseExtractor extends DependencyExtractor {

return dependencies;
}

private processOci(
viceice marked this conversation as resolved.
Show resolved Hide resolved
depName: string,
viceice marked this conversation as resolved.
Show resolved Hide resolved
config: ExtractConfig,
dep: PackageDependency
): void {
const { depName: packageName, datasource } = getDep(
depName,
false,
config.registryAliases
);
dep.packageName = packageName;
dep.datasource = datasource;
}
}