From a4205761c8ccb6a177b08f15e8647e27d35d62ae Mon Sep 17 00:00:00 2001 From: Johannes Feichtner <Churro@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:51:11 +0200 Subject: [PATCH] feat(terraform): kubernetes image resources support (#16029) * add tests * add support for kubernetes image resources * Update lib/modules/manager/terraform/extract.ts Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com> * added unknown resource * added Kubernetes to Readme * missed one spot in the Readme * Update lib/modules/manager/terraform/readme.md Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com> * switch to toMatchObject() in tests Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com> Co-authored-by: Rhys Arkins <rhys@arkins.net> Co-authored-by: Michael Kriese <michael.kriese@visualon.de> --- .../manager/terraform/__fixtures__/docker.tf | 6 +- .../terraform/__fixtures__/kubernetes.tf | 255 ++++++++++++++++++ lib/modules/manager/terraform/common.ts | 54 ++-- lib/modules/manager/terraform/extract.spec.ts | 85 ++++++ lib/modules/manager/terraform/extract.ts | 4 +- lib/modules/manager/terraform/readme.md | 42 ++- lib/modules/manager/terraform/resources.ts | 43 ++- lib/modules/manager/terraform/types.ts | 7 +- 8 files changed, 427 insertions(+), 69 deletions(-) create mode 100644 lib/modules/manager/terraform/__fixtures__/kubernetes.tf diff --git a/lib/modules/manager/terraform/__fixtures__/docker.tf b/lib/modules/manager/terraform/__fixtures__/docker.tf index 9111419102..de04d0cd91 100644 --- a/lib/modules/manager/terraform/__fixtures__/docker.tf +++ b/lib/modules/manager/terraform/__fixtures__/docker.tf @@ -1,5 +1,5 @@ # docker_image resources -# https://www.terraform.io/docs/providers/docker/r/image.html +# https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/image resource "docker_image" "nginx" { name = "nginx:1.7.8" } @@ -14,7 +14,7 @@ resource "docker_image" "ignore_variable" { # docker_container resources -# https://www.terraform.io/docs/providers/docker/r/container.html +# https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/container resource "docker_container" "foo" { name = "foo" image = "nginx:1.7.8" @@ -26,7 +26,7 @@ resource "docker_container" "invalid" { # docker_service resources -# https://www.terraform.io/docs/providers/docker/r/service.html +# https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/service resource "docker_service" "foo" { name = "foo-service" diff --git a/lib/modules/manager/terraform/__fixtures__/kubernetes.tf b/lib/modules/manager/terraform/__fixtures__/kubernetes.tf new file mode 100644 index 0000000000..bd8e5886b4 --- /dev/null +++ b/lib/modules/manager/terraform/__fixtures__/kubernetes.tf @@ -0,0 +1,255 @@ +resource "kubernetes_cron_job_v1" "demo" { + metadata {} + spec { + job_template { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "kaniko" + image = "gcr.io/kaniko-project/executor:v1.7.0@sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52" + } + } + } + } + } + schedule = "" + } +} + +resource "kubernetes_cron_job" "demo" { + metadata {} + spec { + job_template { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "kaniko" + image = "gcr.io/kaniko-project/executor:v1.8.0@sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52" + } + } + } + } + } + schedule = "" + } +} + +resource "kubernetes_daemon_set_v1" "example" { + metadata {} + spec { + template { + metadata {} + spec { + container { + image = "nginx:1.21.1" + name = "example1" + } + } + } + } +} + +resource "kubernetes_daemonset" "example" { + metadata {} + spec { + template { + metadata {} + spec { + container { + image = "nginx:1.21.2" + name = "example2" + } + } + } + } +} + +resource "kubernetes_deployment" "example" { + metadata {} + spec { + template { + metadata {} + spec { + container { + image = "nginx:1.21.3" + name = "example3" + } + } + } + } +} + +resource "kubernetes_deployment_v1" "example" { + metadata {} + spec { + template { + metadata {} + spec { + container { + image = "nginx:1.21.4" + name = "example4" + } + } + } + } +} + +resource "kubernetes_job" "demo" { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "example5" + image = "nginx:1.21.5" + } + } + } + } +} + +resource "kubernetes_job" "demo_invalid" { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "example5-invalid" + } + } + } + } +} + +resource "kubernetes_job_invalid" "demo_invalid2" { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "example5" + image = "nginx:1.21.6" + } + } + } + } +} + +resource "kubernetes_job_v1" "demo" { + metadata {} + spec { + template { + metadata {} + spec { + container { + name = "example6" + image = "nginx:1.21.6" + } + } + } + } +} + +resource "kubernetes_pod" "test" { + metadata {} + spec { + container { + image = "nginx:1.21.7" + name = "example7" + } + } +} + +resource "kubernetes_pod_v1" "test" { + metadata {} + spec { + container { + image = "nginx:1.21.8" + name = "example8" + } + } +} + +resource "kubernetes_replication_controller" "example" { + metadata {} + spec { + selector = {} + template { + metadata {} + spec { + container { + image = "nginx:1.21.9" + name = "example9" + } + } + } + } +} + +resource "kubernetes_replication_controller_v1" "example" { + metadata {} + spec { + selector = {} + template { + metadata {} + spec { + container { + image = "nginx:1.21.10" + name = "example10" + } + } + } + } +} + +resource "kubernetes_stateful_set" "prometheus" { + metadata {} + spec { + template { + metadata {} + spec { + init_container { + name = "example11" + image = "nginx:1.21.11" + } + container { + name = "prometheus-server1" + image = "prom/prometheus:v2.2.1" + } + } + } + service_name = "" + selector {} + } +} + +resource "kubernetes_stateful_set_v1" "prometheus" { + metadata {} + spec { + template { + metadata {} + spec { + init_container { + name = "example12" + image = "nginx:1.21.12" + } + + container { + name = "prometheus-server2" + image = "prom/prometheus:v2.2.2" + } + } + } + service_name = "" + selector {} + } +} diff --git a/lib/modules/manager/terraform/common.ts b/lib/modules/manager/terraform/common.ts index 8e168d7d02..8e123299a0 100644 --- a/lib/modules/manager/terraform/common.ts +++ b/lib/modules/manager/terraform/common.ts @@ -11,27 +11,33 @@ export enum TerraformDependencyTypes { terraform_version = 'terraform_version', } -// eslint-disable-next-line typescript-enum/no-enum -export enum TerraformResourceTypes { - unknown = 'unknown', - /** - * https://www.terraform.io/docs/providers/docker/r/container.html - */ - docker_container = 'docker_container', - /** - * https://www.terraform.io/docs/providers/docker/r/image.html - */ - docker_image = 'docker_image', - /** - * https://www.terraform.io/docs/providers/docker/r/service.html - */ - docker_service = 'docker_service', - /** - * https://www.terraform.io/docs/providers/helm/r/release.html - */ - helm_release = 'helm_release', - /** - * https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace - */ - tfe_workspace = 'tfe_workspace', -} +export const TerraformResourceTypes: Record<string, string[]> = { + unknown: ['unknown'], + generic_image_resource: [ + // Docker provider: https://registry.terraform.io/providers/kreuzwerker/docker + 'docker_container', + 'docker_service', + // Kubernetes provider: https://registry.terraform.io/providers/hashicorp/kubernetes + 'kubernetes_cron_job', + 'kubernetes_cron_job_v1', + 'kubernetes_daemon_set', + 'kubernetes_daemon_set_v1', + 'kubernetes_daemonset', + 'kubernetes_deployment', + 'kubernetes_deployment_v1', + 'kubernetes_job', + 'kubernetes_job_v1', + 'kubernetes_pod', + 'kubernetes_pod_v1', + 'kubernetes_replication_controller', + 'kubernetes_replication_controller_v1', + 'kubernetes_stateful_set', + 'kubernetes_stateful_set_v1', + ], + // https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/image + docker_image: ['docker_image'], + // https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release + helm_release: ['helm_release'], + // https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace + tfe_workspace: ['tfe_workspace'], +}; diff --git a/lib/modules/manager/terraform/extract.spec.ts b/lib/modules/manager/terraform/extract.spec.ts index d1d82d8556..e876b5bc0a 100644 --- a/lib/modules/manager/terraform/extract.spec.ts +++ b/lib/modules/manager/terraform/extract.spec.ts @@ -10,6 +10,7 @@ const bitbucketModules = Fixtures.get('bitbucketModules.tf'); const azureDevOpsModules = Fixtures.get('azureDevOpsModules.tf'); const providers = Fixtures.get('providers.tf'); const docker = Fixtures.get('docker.tf'); +const kubernetes = Fixtures.get('kubernetes.tf'); const tf2 = `module "relative" { source = "../fe" @@ -104,6 +105,90 @@ describe('modules/manager/terraform/extract', () => { expect(res).toMatchSnapshot(); }); + it('extracts kubernetes resources', async () => { + const res = await extractPackageFile(kubernetes, 'kubernetes.tf', {}); + expect(res.deps).toHaveLength(16); + expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(2); + expect(res.deps).toMatchObject([ + { + depName: 'gcr.io/kaniko-project/executor', + currentValue: 'v1.7.0', + currentDigest: + 'sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52', + depType: 'kubernetes_cron_job_v1', + }, + { + depName: 'gcr.io/kaniko-project/executor', + currentValue: 'v1.8.0', + currentDigest: + 'sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52', + depType: 'kubernetes_cron_job', + }, + { + depName: 'nginx', + currentValue: '1.21.1', + depType: 'kubernetes_daemon_set_v1', + }, + { + depName: 'nginx', + currentValue: '1.21.2', + depType: 'kubernetes_daemonset', + }, + { + depName: 'nginx', + currentValue: '1.21.3', + depType: 'kubernetes_deployment', + }, + { + depName: 'nginx', + currentValue: '1.21.4', + depType: 'kubernetes_deployment_v1', + }, + { + depName: 'nginx', + currentValue: '1.21.5', + depType: 'kubernetes_job', + }, + { skipReason: 'invalid-dependency-specification' }, + { skipReason: 'invalid-value' }, + { + depName: 'nginx', + currentValue: '1.21.6', + depType: 'kubernetes_job_v1', + }, + { + depName: 'nginx', + currentValue: '1.21.7', + depType: 'kubernetes_pod', + }, + { + depName: 'nginx', + currentValue: '1.21.8', + depType: 'kubernetes_pod_v1', + }, + { + depName: 'nginx', + currentValue: '1.21.9', + depType: 'kubernetes_replication_controller', + }, + { + depName: 'nginx', + currentValue: '1.21.10', + depType: 'kubernetes_replication_controller_v1', + }, + { + depName: 'prom/prometheus', + currentValue: 'v2.2.1', + depType: 'kubernetes_stateful_set', + }, + { + depName: 'prom/prometheus', + currentValue: 'v2.2.2', + depType: 'kubernetes_stateful_set_v1', + }, + ]); + }); + it('returns null if only local deps', async () => { expect(await extractPackageFile(tf2, '2.tf', {})).toBeNull(); }); diff --git a/lib/modules/manager/terraform/extract.ts b/lib/modules/manager/terraform/extract.ts index 1f2aae7a14..73df9d7b29 100644 --- a/lib/modules/manager/terraform/extract.ts +++ b/lib/modules/manager/terraform/extract.ts @@ -34,11 +34,13 @@ const dependencyBlockExtractionRegex = regEx( const contentCheckList = [ 'module "', 'provider "', + '"docker_', + '"kubernetes_', 'required_providers ', ' "helm_release" ', ' "docker_image" ', 'required_version', - 'terraform_version', // part of tfe_workspace + 'terraform_version', // part of tfe_workspace ]; export async function extractPackageFile( diff --git a/lib/modules/manager/terraform/readme.md b/lib/modules/manager/terraform/readme.md index d158f3e080..405ada1aec 100644 --- a/lib/modules/manager/terraform/readme.md +++ b/lib/modules/manager/terraform/readme.md @@ -1,4 +1,4 @@ -Currently, Terraform supports renovating the following dependencies, where sub points represent hosting options of the dependencies: +Currently, Terraform supports renovating the following dependencies, where sub-points represent hosting options of the dependencies: - modules - GitTags @@ -13,6 +13,8 @@ Currently, Terraform supports renovating the following dependencies, where sub p - chart repository ( Public and Private ) - docker\_\* - Docker registry ( Public and Private ) +- kubernetes\_\* + - Docker registry ( Public and Private ) - [tfe_workspace](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace) ( `terraform_version` argument ) Terraform range constraints are supported: @@ -23,18 +25,32 @@ Terraform range constraints are supported: - `~> 1.2`: any non-beta version >= 1.2.0 and < 2.0.0, e.g. 1.X.Y - `>= 1.0.0, <= 2.0.0`: any version between 1.0.0 and 2.0.0 inclusive -For fine-grained control, e.g. to turn off only parts of this manager, you can use the following `depTypes`: +For fine-grained control, e.g., to turn off only parts of this manager, you can use the following `depTypes`: -| resource | depType | Notes | -| --------------------------- | :-----------------: | :------------------------------------------------------------------------: | -| Terraform provider | `provider` | | -| required Terraform provider | `required_provider` | | -| required Terraform version | `required_version` | This handles the `required_version` in terraform blocks | -| TFE workspace | `tfe_workspace` | This handles the `terraform_version` argument in `tfe_workspace` resources | -| Terraform module | `module` | | -| Helm release | `helm_release` | | -| Docker container | `docker_container` | | -| Docker image | `docker_image` | | -| Docker service | `docker_service` | | +| resource | depType | Notes | +| ------------------------------------ | :------------------------------------: | :------------------------------------------------------------------------: | +| Terraform provider | `provider` | | +| required Terraform provider | `required_provider` | | +| required Terraform version | `required_version` | This handles the `required_version` in terraform blocks | +| TFE workspace | `tfe_workspace` | This handles the `terraform_version` argument in `tfe_workspace` resources | +| Terraform module | `module` | | +| Helm release | `helm_release` | | +| Docker container | `docker_container` | | +| Docker image | `docker_image` | | +| Docker service | `docker_service` | | +| Kubernetes CronJob | `kubernetes_cron_job` | | +| Kubernetes CronJob v1 | `kubernetes_cron_job_v1` | | +| Kubernetes DaemonSet | `kubernetes_daemon_set` | | +| Kubernetes DaemonSet v1 | `kubernetes_daemon_set_v1` | | +| Kubernetes Deployment | `kubernetes_deployment` | | +| Kubernetes Deployment v1 | `kubernetes_deployment_v1` | | +| Kubernetes Job | `kubernetes_job` | | +| Kubernetes Job v1 | `kubernetes_job_v1` | | +| Kubernetes Pod | `kubernetes_pod` | | +| Kubernetes Pod v1 | `kubernetes_pod_v1` | | +| Kubernetes Replication Controller | `kubernetes_replication_controller` | | +| Kubernetes Replication Controller v1 | `kubernetes_replication_controller_v1` | | +| Kubernetes StatefulSet | `kubernetes_stateful_set` | | +| Kubernetes StatefulSet v1 | `kubernetes_stateful_set_v1` | | If you need to change the versioning format, read the [versioning](https://docs.renovatebot.com/modules/versioning/) documentation to learn more. diff --git a/lib/modules/manager/terraform/resources.ts b/lib/modules/manager/terraform/resources.ts index 587b52aac0..9811c8f104 100644 --- a/lib/modules/manager/terraform/resources.ts +++ b/lib/modules/manager/terraform/resources.ts @@ -37,10 +37,18 @@ export function extractTerraformResource( const typeMatch = resourceTypeExtractionRegex.exec(line); - // Sets the resourceType, e.g. "helm_release" 'resource "helm_release" "test_release"' - managerData.resourceType = - TerraformResourceTypes[typeMatch?.groups?.type as TerraformResourceTypes] ?? - TerraformResourceTypes.unknown; + // Sets the resourceType, e.g., 'resource "helm_release" "test_release"' -> helm_release + const resourceType = typeMatch?.groups?.type; + + const isKnownType = + resourceType && + Object.keys(TerraformResourceTypes).some((key) => { + return TerraformResourceTypes[key].includes(resourceType); + }); + + managerData.resourceType = isKnownType + ? resourceType + : TerraformResourceTypes.unknown[0]; /** * Iterates over all lines of the resource to extract the relevant key value pairs, @@ -96,21 +104,19 @@ export function extractTerraformResource( export function analyseTerraformResource( dep: PackageDependency<ResourceManagerData> ): void { - // istanbul ignore if: should tested? - if (!dep.managerData) { - return; - } - switch (dep.managerData.resourceType) { - case TerraformResourceTypes.docker_container: + switch (dep.managerData?.resourceType) { + case TerraformResourceTypes.generic_image_resource.find( + (key) => key === dep.managerData?.resourceType + ): if (dep.managerData.image) { applyDockerDependency(dep, dep.managerData.image); - dep.depType = 'docker_container'; + dep.depType = dep.managerData.resourceType; } else { dep.skipReason = 'invalid-dependency-specification'; } break; - case TerraformResourceTypes.docker_image: + case TerraformResourceTypes.docker_image[0]: if (dep.managerData.name) { applyDockerDependency(dep, dep.managerData.name); dep.depType = 'docker_image'; @@ -119,16 +125,7 @@ export function analyseTerraformResource( } break; - case TerraformResourceTypes.docker_service: - if (dep.managerData.image) { - applyDockerDependency(dep, dep.managerData.image); - dep.depType = 'docker_service'; - } else { - dep.skipReason = 'invalid-dependency-specification'; - } - break; - - case TerraformResourceTypes.helm_release: + case TerraformResourceTypes.helm_release[0]: if (!dep.managerData.chart) { dep.skipReason = 'invalid-name'; } else if (checkIfStringIsPath(dep.managerData.chart)) { @@ -141,7 +138,7 @@ export function analyseTerraformResource( dep.datasource = HelmDatasource.id; break; - case TerraformResourceTypes.tfe_workspace: + case TerraformResourceTypes.tfe_workspace[0]: if (dep.currentValue) { analyseTerraformVersion(dep); dep.depType = 'tfe_workspace'; diff --git a/lib/modules/manager/terraform/types.ts b/lib/modules/manager/terraform/types.ts index 584ac9f08d..bfe592b1c5 100644 --- a/lib/modules/manager/terraform/types.ts +++ b/lib/modules/manager/terraform/types.ts @@ -1,8 +1,5 @@ import type { PackageDependency } from '../types'; -import type { - TerraformDependencyTypes, - TerraformResourceTypes, -} from './common'; +import type { TerraformDependencyTypes } from './common'; export interface ExtractionResult { lineNumber: number; @@ -17,7 +14,7 @@ export interface TerraformManagerData { } export interface ResourceManagerData extends TerraformManagerData { - resourceType?: TerraformResourceTypes; + resourceType?: string; chart?: string; image?: string; name?: string; -- GitLab