diff --git a/.github/RELEASE_DRAFTER.yml b/.github/RELEASE_DRAFTER.yml index 1ae725f..c6af0a0 100644 --- a/.github/RELEASE_DRAFTER.yml +++ b/.github/RELEASE_DRAFTER.yml @@ -7,11 +7,25 @@ categories: - 'enhancement' - title: 'Bug Fixes' labels: - - 'fix' - - 'bugfix' - 'bug' - title: 'Documentation' label: 'documentation' + - title: 'CI' + label: 'ci' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'feature' + - 'enhancement' + patch: + labels: + - 'ci' + - 'bug' + - 'documentation' + default: patch change-template: '- $TITLE, by @$AUTHOR (#$NUMBER)' template: | # What's changed diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7c0b1ec..bbbd33f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,5 +1,8 @@ name: pre-commit +permissions: + contents: read + on: workflow_dispatch: pull_request: @@ -8,36 +11,42 @@ on: - master env: + PYTHON_VERSION: "3.10" TERRAFORM_DOCS_VERSION: "v0.16.0" - TFLINT_VERSION: "v0.36.2" + TFLINT_VERSION: "v0.40.1" jobs: pre-commit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - - name: Install additional pre-commit hooks - shell: bash - run: | - echo "########### Install Checkov ####################" - pip install checkov - - echo "########### Install Terraform-docs #############" - wget https://github.com/terraform-docs/terraform-docs/releases/download/${{ env.TERRAFORM_DOCS_VERSION }}/terraform-docs-${{ env.TERRAFORM_DOCS_VERSION }}-linux-amd64.tar.gz - tar xvzf terraform-docs-${{ env.TERRAFORM_DOCS_VERSION }}-linux-amd64.tar.gz - mkdir -p ~/terraform-docs/bin/ - install terraform-docs ~/terraform-docs/bin/ - echo '~/terraform-docs/bin/' >> $GITHUB_PATH - - echo "########### Install Terraform-linters ##########" - wget https://github.com/terraform-linters/tflint/releases/download/${{ env.TFLINT_VERSION }}/tflint_linux_amd64.zip - unzip tflint_linux_amd64.zip - mkdir -p ~/tflint/bin/ - echo '~/tflint/bin/' >> $GITHUB_PATH - install tflint ~/tflint/bin/ - ~/tflint/bin/tflint --init - - - name: Run pre-commit - uses: pre-commit/action@v2.0.3 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + + - name: Install Python dependencies + run: pip install -r requirements.txt + + - name: Install terraform-docs + uses: jaxxstorm/action-install-gh-release@v1.9.0 + with: + repo: terraform-docs/terraform-docs + tag: ${{ env.TERRAFORM_DOCS_VERSION }} + cache: enable + + - name: TFLint cache + uses: actions/cache@v3 + with: + path: ~/.tflint.d/plugins + key: ${{ runner.os }}-tflint-${{ hashFiles('.tflint.hcl') }} + + - name: Install TFLint + uses: terraform-linters/setup-tflint@v2 + with: + tflint_version: ${{ env.TFLINT_VERSION }} + + - name: Run pre-commit + uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 7cae11f..9528a7a 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -15,7 +15,7 @@ on: jobs: update_release_draft: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: release-drafter/release-drafter@v5 with: diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 2d3f60e..2b9e3e7 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -1,5 +1,8 @@ name: Terraform validate +permissions: + contents: read + on: workflow_dispatch: pull_request: @@ -10,11 +13,11 @@ on: jobs: versionExtract: name: Extract min/max Terraform versions - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Extract Terraform min/max versions id: minMax @@ -26,7 +29,7 @@ jobs: maxVersion: ${{ steps.minMax.outputs.maxVersion }} terraform-validate: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: versionExtract strategy: matrix: @@ -35,7 +38,7 @@ jobs: - ${{ needs.versionExtract.outputs.maxVersion }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ matrix.tf_ver }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b105c2c..5ce50e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: check-merge-conflict @@ -10,23 +10,18 @@ repos: - id: end-of-file-fixer - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.71.0 + rev: v1.75.0 hooks: - - id: terraform_fmt - - id: terraform_tflint - - id: terraform_validate - - id: terraform_checkov - - id: terraform_docs - args: - - '--args=--hide providers --sort-by required' - - - repo: https://github.com/pecigonzalo/pre-commit-terraform-vars - rev: v1.0.0 - hooks: - - id: terraform-vars + - id: terraform_fmt + - id: terraform_tflint + - id: terraform_validate + - id: terraform_checkov + - id: terraform_docs + args: + - '--args=--config=.terraform-docs.yml' - repo: https://github.com/Yelp/detect-secrets - rev: v1.2.0 + rev: v1.3.0 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline'] diff --git a/.secrets.baseline b/.secrets.baseline index 62fe8c6..eaf7047 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,20 +1,18 @@ { - "custom_plugin_paths": [], - "exclude": { - "files": null, - "lines": null - }, - "generated_at": "2020-09-21T15:31:24Z", + "version": "1.3.0", "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, { "name": "AWSKeyDetector" }, { - "name": "ArtifactoryDetector" + "name": "AzureStorageKeyDetector" }, { - "base64_limit": 4.5, - "name": "Base64HighEntropyString" + "name": "Base64HighEntropyString", + "limit": 4.5 }, { "name": "BasicAuthDetector" @@ -23,8 +21,11 @@ "name": "CloudantDetector" }, { - "hex_limit": 3, - "name": "HexHighEntropyString" + "name": "GitHubTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 }, { "name": "IbmCloudIamDetector" @@ -36,21 +37,30 @@ "name": "JwtTokenDetector" }, { - "keyword_exclude": null, - "name": "KeywordDetector" + "name": "KeywordDetector", + "keyword_exclude": "" }, { "name": "MailchimpDetector" }, + { + "name": "NpmDetector" + }, { "name": "PrivateKeyDetector" }, + { + "name": "SendGridDetector" + }, { "name": "SlackDetector" }, { "name": "SoftlayerDetector" }, + { + "name": "SquareOAuthDetector" + }, { "name": "StripeDetector" }, @@ -58,10 +68,46 @@ "name": "TwilioKeyDetector" } ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], "results": {}, - "version": "0.14.3", - "word_list": { - "file": null, - "hash": null - } + "generated_at": "2022-07-28T10:50:47Z" } diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..cb998c2 --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,15 @@ +formatter: markdown table + +output: + mode: inject + template: |- + + {{ .Content }} + + +sections: + hide: + - providers + +sort: + by: required diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..372282e --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,11 @@ +plugin "terraform" { + enabled = true + version = "0.1.1" + source = "github.com/terraform-linters/tflint-ruleset-terraform" + preset = "recommended" +} +plugin "aws" { + enabled = true + version = "0.17.0" + source = "github.com/terraform-linters/tflint-ruleset-aws" +} diff --git a/README.md b/README.md index c689e86..4e2332a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# AWS EKS cluster autoscaler Terraform module +# AWS EKS Cluster Autoscaler Terraform module -[![labyrinth labs logo](ll-logo.png)](https://lablabs.io/) +[](https://lablabs.io/) -We help companies build, run, deploy and scale software and infrastructure by embracing the right technologies and principles. Check out our website at https://lablabs.io/ +We help companies build, run, deploy and scale software and infrastructure by embracing the right technologies and principles. Check out our website at --- @@ -20,17 +20,19 @@ Check out other [terraform kubernetes addons](https://github.com/orgs/lablabs/re ## Deployment methods ### Helm -Deploy helm chart by helm (default method, set `enabled = true`) +Deploy Helm chart via Helm resource (default method, set `enabled = true`) -### Argo kubernetes -Deploy helm chart as argo application by kubernetes manifest (set `enabled = true` and `argo_enabled = true`) +### Argo Kubernetes +Deploy Helm chart as ArgoCD Application via Kubernetes manifest resource (set `enabled = true` and `argo_enabled = true`) -### Argo helm -When deploying with ArgoCD application, Kubernetes terraform provider requires access to Kubernetes cluster API during plan time. This introduces potential issue when you want to deploy the cluster with this addon at the same time, during the same Terraform run. +> **Warning** +> +> When deploying with ArgoCD application, Kubernetes terraform provider requires access to Kubernetes cluster API during plan time. This introduces potential issue when you want to deploy the cluster with this addon at the same time, during the same Terraform run. +> +> To overcome this issue, the module deploys the ArgoCD application object using the Helm provider, which does not require API access during plan. If you want to deploy the application using this workaround, you can set the `argo_helm_enabled` variable to `true`. -To overcome this issue, the module deploys the ArgoCD application object using the Helm provider, which does not require API access during plan. If you want to deploy the application using this workaround, you can set the `argo_helm_enabled` variable to `true`. - -Create helm release resource and deploy it as argo application (set `enabled = true`, `argo_enabled = true` and `argo_helm_enabled = true`) +### Argo Helm +Deploy Helm chart as ArgoCD Application via Helm resource (set `enabled = true`, `argo_enabled = true` and `argo_helm_enabled = true`) ## AWS IAM resources @@ -52,7 +54,7 @@ See [Basic example](examples/basic/README.md) for further information. | [terraform](#requirement\_terraform) | >= 1.0 | | [aws](#requirement\_aws) | >= 4.19.0 | | [helm](#requirement\_helm) | >= 2.6.0 | -| [kubernetes](#requirement\_kubernetes) | >= 2.11.0 | +| [kubernetes](#requirement\_kubernetes) | >= 2.16.0 | | [utils](#requirement\_utils) | >= 0.17.0 | ## Modules @@ -69,7 +71,11 @@ No modules. | [aws_iam_role_policy_attachment.this_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [helm_release.argo_application](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [helm_release.this](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [kubernetes_job.helm_argo_application_wait](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/job) | resource | | [kubernetes_manifest.this](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | +| [kubernetes_role.helm_argo_application_wait](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role) | resource | +| [kubernetes_role_binding.helm_argo_application_wait](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/role_binding) | resource | +| [kubernetes_service_account.helm_argo_application_wait](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account) | resource | | [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.this_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.this_irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -89,16 +95,18 @@ No modules. | [argo\_enabled](#input\_argo\_enabled) | If set to true, the module will be deployed as ArgoCD application, otherwise it will be deployed as a Helm release | `bool` | `false` | no | | [argo\_helm\_enabled](#input\_argo\_helm\_enabled) | If set to true, the ArgoCD Application manifest will be deployed using Kubernetes provider as a Helm release. Otherwise it'll be deployed as a Kubernetes manifest. See Readme for more info | `bool` | `false` | no | | [argo\_helm\_values](#input\_argo\_helm\_values) | Value overrides to use when deploying argo application object with helm | `string` | `""` | no | -| [argo\_info](#input\_argo\_info) | ArgoCD info manifest parameter | `list` |
[
{
"name": "terraform",
"value": "true"
}
]
| no | -| [argo\_kubernetes\_manifest\_computed\_fields](#input\_argo\_kubernetes\_manifest\_computed\_fields) | List of paths of fields to be handled as "computed". The user-configured value for the field will be overridden by any different value returned by the API after apply. | `list(string)` |
[
"metadata.labels",
"metadata.annotations"
]
| no | +| [argo\_helm\_wait\_backoff\_limit](#input\_argo\_helm\_wait\_backoff\_limit) | Backoff limit for ArgoCD Application Helm release wait job | `number` | `6` | no | +| [argo\_helm\_wait\_timeout](#input\_argo\_helm\_wait\_timeout) | Timeout for ArgoCD Application Helm release wait job | `string` | `"10m"` | no | +| [argo\_info](#input\_argo\_info) | ArgoCD info manifest parameter |
list(object({
name = string
value = string
}))
|
[
{
"name": "terraform",
"value": "true"
}
]
| no | +| [argo\_kubernetes\_manifest\_computed\_fields](#input\_argo\_kubernetes\_manifest\_computed\_fields) | List of paths of fields to be handled as "computed". The user-configured value for the field will be overridden by any different value returned by the API after apply. | `list(string)` |
[
"metadata.labels",
"metadata.annotations",
"metadata.finalizers"
]
| no | | [argo\_kubernetes\_manifest\_field\_manager\_force\_conflicts](#input\_argo\_kubernetes\_manifest\_field\_manager\_force\_conflicts) | Forcibly override any field manager conflicts when applying the kubernetes manifest resource | `bool` | `false` | no | | [argo\_kubernetes\_manifest\_field\_manager\_name](#input\_argo\_kubernetes\_manifest\_field\_manager\_name) | The name of the field manager to use when applying the kubernetes manifest resource. Defaults to Terraform | `string` | `"Terraform"` | no | | [argo\_kubernetes\_manifest\_wait\_fields](#input\_argo\_kubernetes\_manifest\_wait\_fields) | A map of fields and a corresponding regular expression with a pattern to wait for. The provider will wait until the field matches the regular expression. Use * for any value. | `map(string)` | `{}` | no | -| [argo\_metadata](#input\_argo\_metadata) | ArgoCD Application metadata configuration. Override or create additional metadata parameters | `map` |
{
"finalizers": [
"resources-finalizer.argocd.argoproj.io"
]
}
| no | +| [argo\_metadata](#input\_argo\_metadata) | ArgoCD Application metadata configuration. Override or create additional metadata parameters | `any` |
{
"finalizers": [
"resources-finalizer.argocd.argoproj.io"
]
}
| no | | [argo\_namespace](#input\_argo\_namespace) | Namespace to deploy ArgoCD application CRD to | `string` | `"argo"` | no | | [argo\_project](#input\_argo\_project) | ArgoCD Application project | `string` | `"default"` | no | -| [argo\_spec](#input\_argo\_spec) | ArgoCD Application spec configuration. Override or create additional spec parameters | `map` | `{}` | no | -| [argo\_sync\_policy](#input\_argo\_sync\_policy) | ArgoCD syncPolicy manifest parameter | `map` | `{}` | no | +| [argo\_spec](#input\_argo\_spec) | ArgoCD Application spec configuration. Override or create additional spec parameters | `any` | `{}` | no | +| [argo\_sync\_policy](#input\_argo\_sync\_policy) | ArgoCD syncPolicy manifest parameter | `any` | `{}` | no | | [enabled](#input\_enabled) | Variable indicating whether deployment is enabled | `bool` | `true` | no | | [helm\_atomic](#input\_helm\_atomic) | If set, installation process purges chart on fail. The wait flag will be set automatically if atomic is used | `bool` | `false` | no | | [helm\_chart\_name](#input\_helm\_chart\_name) | Helm chart name to be installed | `string` | `"cluster-autoscaler"` | no | @@ -135,17 +143,17 @@ No modules. | [helm\_wait\_for\_jobs](#input\_helm\_wait\_for\_jobs) | If wait is enabled, will wait until all helm Jobs have been completed before marking the release as successful. It will wait for as long as timeout | `bool` | `false` | no | | [irsa\_additional\_policies](#input\_irsa\_additional\_policies) | Map of the additional policies to be attached to default role. Where key is arbitrary id and value is policy arn. | `map(string)` | `{}` | no | | [irsa\_assume\_role\_arn](#input\_irsa\_assume\_role\_arn) | Assume role arn. Assume role must be enabled. | `string` | `""` | no | -| [irsa\_assume\_role\_enabled](#input\_irsa\_assume\_role\_enabled) | Whether IRSA is allowed to assume role defined by assume\_role\_arn. | `bool` | `false` | no | +| [irsa\_assume\_role\_enabled](#input\_irsa\_assume\_role\_enabled) | Whether IRSA is allowed to assume role defined by irsa\_assume\_role\_arn. | `bool` | `false` | no | | [irsa\_policy\_enabled](#input\_irsa\_policy\_enabled) | Whether to create opinionated policy to allow operations on specified zones in `policy_allowed_zone_ids`. | `bool` | `true` | no | | [irsa\_role\_create](#input\_irsa\_role\_create) | Whether to create IRSA role and annotate service account | `bool` | `true` | no | -| [irsa\_role\_name\_prefix](#input\_irsa\_role\_name\_prefix) | The IRSA role name prefix for vector | `string` | `"cluster-autoscaler-irsa"` | no | +| [irsa\_role\_name\_prefix](#input\_irsa\_role\_name\_prefix) | The IRSA role name prefix for <$addon-name> | `string` | `"cluster-autoscaler-irsa"` | no | | [irsa\_tags](#input\_irsa\_tags) | IRSA resources tags | `map(string)` | `{}` | no | | [namespace](#input\_namespace) | The K8s namespace in which the node-problem-detector service account has been created | `string` | `"cluster-autoscaler"` | no | | [rbac\_create](#input\_rbac\_create) | Whether to create and use RBAC resources | `bool` | `true` | no | | [service\_account\_create](#input\_service\_account\_create) | Whether to create Service Account | `bool` | `true` | no | -| [service\_account\_name](#input\_service\_account\_name) | The k8s cluster-autoscaler service account name | `string` | `"cluster-autoscaler"` | no | -| [settings](#input\_settings) | Additional helm sets which will be passed to the Helm chart values, see https://hub.helm.sh/charts/stable/cluster-autoscaler | `map(any)` | `{}` | no | -| [values](#input\_values) | Additional yaml encoded values which will be passed to the Helm chart, see https://hub.helm.sh/charts/stable/cluster-autoscaler | `string` | `""` | no | +| [service\_account\_name](#input\_service\_account\_name) | The k8s <$addon-name> service account name | `string` | `"cluster-autoscaler"` | no | +| [settings](#input\_settings) | Additional helm sets which will be passed to the Helm chart values, see https://hub.helm.sh/charts/stable/<$addon-name> | `map(any)` | `{}` | no | +| [values](#input\_values) | Additional yaml encoded values which will be passed to the Helm chart, see https://hub.helm.sh/charts/stable/<$addon-name> | `string` | `""` | no | ## Outputs @@ -153,7 +161,7 @@ No modules. |------|-------------| | [helm\_release\_application\_metadata](#output\_helm\_release\_application\_metadata) | Argo application helm release attributes | | [helm\_release\_metadata](#output\_helm\_release\_metadata) | Helm release attributes | -| [iam\_role\_attributes](#output\_iam\_role\_attributes) | Vector IAM role atributes | +| [iam\_role\_attributes](#output\_iam\_role\_attributes) | Cluster Autoscaler IAM role attributes | | [kubernetes\_application\_attributes](#output\_kubernetes\_application\_attributes) | Argo kubernetes manifest attributes | diff --git a/argo-helm.tf b/argo-helm.tf new file mode 100644 index 0000000..7ce67a0 --- /dev/null +++ b/argo-helm.tf @@ -0,0 +1,149 @@ +locals { + helm_argo_application_enabled = var.enabled && var.argo_enabled && var.argo_helm_enabled + helm_argo_application_wait_enabled = local.helm_argo_application_enabled && length(keys(var.argo_kubernetes_manifest_wait_fields)) > 0 + helm_argo_application_values = [ + one(data.utils_deep_merge_yaml.argo_helm_values[*].output), + var.argo_helm_values + ] +} + +data "utils_deep_merge_yaml" "argo_helm_values" { + count = local.helm_argo_application_enabled ? 1 : 0 + + input = compact([ + yamlencode({ + "apiVersion" : var.argo_apiversion + }), + yamlencode({ + "spec" : local.argo_application_values + }), + yamlencode({ + "spec" : var.argo_spec + }), + yamlencode( + local.argo_application_metadata + ) + ]) +} + +resource "helm_release" "argo_application" { + count = local.helm_argo_application_enabled ? 1 : 0 + + chart = "${path.module}/helm/argocd-application" + name = var.helm_release_name + namespace = var.argo_namespace + + values = local.helm_argo_application_values +} + +resource "kubernetes_role" "helm_argo_application_wait" { + count = local.helm_argo_application_wait_enabled ? 1 : 0 + + metadata { + name = "${var.helm_release_name}-argo-application-wait" + namespace = var.argo_namespace + labels = local.argo_application_metadata.labels + annotations = local.argo_application_metadata.annotations + } + + rule { + api_groups = ["argoproj.io"] + resources = ["applications"] + verbs = ["get", "list", "watch"] + } +} + +resource "kubernetes_role_binding" "helm_argo_application_wait" { + count = local.helm_argo_application_wait_enabled ? 1 : 0 + + metadata { + name = "${var.helm_release_name}-argo-application-wait" + namespace = var.argo_namespace + labels = local.argo_application_metadata.labels + annotations = local.argo_application_metadata.annotations + } + + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = one(kubernetes_role.helm_argo_application_wait[*].metadata[0].name) + } + + subject { + kind = "ServiceAccount" + name = one(kubernetes_service_account.helm_argo_application_wait[*].metadata[0].name) + namespace = one(kubernetes_service_account.helm_argo_application_wait[*].metadata[0].namespace) + } +} + +resource "kubernetes_service_account" "helm_argo_application_wait" { + count = local.helm_argo_application_wait_enabled ? 1 : 0 + + metadata { + name = "${var.helm_release_name}-argo-application-wait" + namespace = var.argo_namespace + labels = local.argo_application_metadata.labels + annotations = local.argo_application_metadata.annotations + } +} + +resource "kubernetes_job" "helm_argo_application_wait" { + count = local.helm_argo_application_wait_enabled ? 1 : 0 + + metadata { + generate_name = "${var.helm_release_name}-argo-application-wait" + namespace = var.argo_namespace + labels = local.argo_application_metadata.labels + annotations = local.argo_application_metadata.annotations + } + + spec { + template { + metadata { + labels = local.argo_application_metadata.labels + annotations = local.argo_application_metadata.annotations + } + + spec { + service_account_name = one(kubernetes_service_account.helm_argo_application_wait[*].metadata[0].name) + + dynamic "container" { + for_each = var.argo_kubernetes_manifest_wait_fields + + content { + name = "${lower(replace(container.key, ".", "-"))}-${md5(jsonencode(local.helm_argo_application_values))}" # md5 suffix is a workaround for https://github.com/hashicorp/terraform-provider-kubernetes/issues/1325 + image = "bitnami/kubectl:latest" + command = ["/bin/bash", "-ecx"] + # Waits for ArgoCD Application to be "Healthy", see https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#wait + # i.e. kubectl wait --for=jsonpath='{.status.sync.status}'=Healthy application.argoproj.io <$addon-name> + args = [ + <<-EOT + kubectl wait \ + --namespace ${var.argo_namespace} \ + --for=jsonpath='{.${container.key}}'=${container.value} \ + --timeout=${var.argo_helm_wait_timeout} \ + application.argoproj.io ${var.helm_release_name} + EOT + ] + } + } + + # ArgoCD Application status fields might not be available immediately after creation + restart_policy = "OnFailure" + } + } + + backoff_limit = var.argo_helm_wait_backoff_limit + } + + wait_for_completion = true + + timeouts { + create = var.argo_helm_wait_timeout + update = var.argo_helm_wait_timeout + } + + depends_on = [ + helm_release.argo_application + ] +} diff --git a/argo.tf b/argo.tf index a952837..0ce0334 100644 --- a/argo.tf +++ b/argo.tf @@ -25,39 +25,6 @@ locals { } } -data "utils_deep_merge_yaml" "argo_helm_values" { - count = var.enabled && var.argo_enabled && var.argo_helm_enabled ? 1 : 0 - input = compact([ - yamlencode({ - "apiVersion" : var.argo_apiversion - }), - yamlencode({ - "spec" : local.argo_application_values - }), - yamlencode({ - "spec" : var.argo_spec - }), - yamlencode( - local.argo_application_metadata - ) - ]) -} - - -resource "helm_release" "argo_application" { - count = var.enabled && var.argo_enabled && var.argo_helm_enabled ? 1 : 0 - - chart = "${path.module}/helm/argocd-application" - name = var.helm_release_name - namespace = var.argo_namespace - - values = [ - data.utils_deep_merge_yaml.argo_helm_values[0].output, - var.argo_helm_values - ] -} - - resource "kubernetes_manifest" "this" { count = var.enabled && var.argo_enabled && !var.argo_helm_enabled ? 1 : 0 manifest = { diff --git a/examples/basic/README.md b/examples/basic/README.md index 918d18a..49f360a 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -5,18 +5,24 @@ The code in this example shows how to use the module with basic configuration an ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.19.0 | +| [helm](#requirement\_helm) | >= 2.6.0 | +| [kubernetes](#requirement\_kubernetes) | >= 2.16.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [cluster-autoscaler\_argo\_helm](#module\_cluster-autoscaler\_argo\_helm) | ../../ | n/a | -| [cluster-autoscaler\_argo\_manifests](#module\_cluster-autoscaler\_argo\_manifests) | ../../ | n/a | -| [cluster-autoscaler\_helm](#module\_cluster-autoscaler\_helm) | ../../ | n/a | -| [eks\_cluster](#module\_eks\_cluster) | cloudposse/eks-cluster/aws | 0.43.2 | -| [eks\_node\_group](#module\_eks\_node\_group) | cloudposse/eks-node-group/aws | 0.25.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | 3.6.0 | +| [addon\_installation\_argo\_helm](#module\_addon\_installation\_argo\_helm) | ../../ | n/a | +| [addon\_installation\_argo\_kubernetes](#module\_addon\_installation\_argo\_kubernetes) | ../../ | n/a | +| [addon\_installation\_disabled](#module\_addon\_installation\_disabled) | ../../ | n/a | +| [addon\_installation\_helm](#module\_addon\_installation\_helm) | ../../ | n/a | +| [eks\_cluster](#module\_eks\_cluster) | cloudposse/eks-cluster/aws | 2.3.0 | +| [eks\_node\_group](#module\_eks\_node\_group) | cloudposse/eks-node-group/aws | 2.4.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | 4.0.0 | ## Resources diff --git a/examples/basic/base.tf b/examples/basic/base.tf new file mode 100644 index 0000000..25ad652 --- /dev/null +++ b/examples/basic/base.tf @@ -0,0 +1,33 @@ +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "4.0.0" + + name = "<$addon-name>-vpc" + cidr = "10.0.0.0/16" + azs = ["eu-central-1a", "eu-central-1b"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] + enable_nat_gateway = true +} + +module "eks_cluster" { + source = "cloudposse/eks-cluster/aws" + version = "2.3.0" + + region = "eu-central-1" + subnet_ids = module.vpc.public_subnets + vpc_id = module.vpc.vpc_id + name = "basic-example" +} + +module "eks_node_group" { + source = "cloudposse/eks-node-group/aws" + version = "2.4.0" + + cluster_name = module.eks_cluster.eks_cluster_id + instance_types = ["t3.medium"] + subnet_ids = module.vpc.public_subnets + min_size = 1 + desired_size = 1 + max_size = 2 + depends_on = [module.eks_cluster.kubernetes_config_map_id] +} diff --git a/examples/basic/main.tf b/examples/basic/main.tf index 64c925d..0a2c3c4 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -1,38 +1,14 @@ -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "3.6.0" - - name = "cluster-autoscaler-vpc" - cidr = "10.0.0.0/16" - azs = ["eu-central-1a", "eu-central-1b"] - public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] - enable_nat_gateway = true -} - -module "eks_cluster" { - source = "cloudposse/eks-cluster/aws" - version = "0.43.2" - - region = "eu-central-1" - subnet_ids = module.vpc.public_subnets - vpc_id = module.vpc.vpc_id - name = "cluster-autoscaler" -} +module "addon_installation_disabled" { + source = "../../" -module "eks_node_group" { - source = "cloudposse/eks-node-group/aws" - version = "0.25.0" + enabled = false - cluster_name = "cluster-autoscaler" - instance_types = ["t3.medium"] - subnet_ids = module.vpc.public_subnets - min_size = 1 - desired_size = 1 - max_size = 2 - depends_on = [module.eks_cluster.kubernetes_config_map_id] + cluster_name = module.eks_cluster.eks_cluster_id + cluster_identity_oidc_issuer = module.eks_cluster.eks_cluster_identity_oidc_issuer + cluster_identity_oidc_issuer_arn = module.eks_cluster.eks_cluster_identity_oidc_issuer_arn } -module "cluster-autoscaler_helm" { +module "addon_installation_helm" { source = "../../" enabled = true @@ -44,18 +20,12 @@ module "cluster-autoscaler_helm" { cluster_identity_oidc_issuer_arn = module.eks_cluster.eks_cluster_identity_oidc_issuer_arn values = yamlencode({ - "image" : { - "tag" : "v1.21.2" - } + # insert sample values here }) - - argo_sync_policy = { - "automated" : {} - "syncOptions" = ["CreateNamespace=true"] - } } -module "cluster-autoscaler_argo_manifests" { +# Please, see README.md and Argo Kubernetes deployment method for implications of using Kubernetes installation method +module "addon_installation_argo_kubernetes" { source = "../../" enabled = true @@ -67,9 +37,7 @@ module "cluster-autoscaler_argo_manifests" { cluster_identity_oidc_issuer_arn = module.eks_cluster.eks_cluster_identity_oidc_issuer_arn values = yamlencode({ - "image" : { - "tag" : "v1.21.2" - } + # insert sample values here }) argo_sync_policy = { @@ -78,8 +46,7 @@ module "cluster-autoscaler_argo_manifests" { } } - -module "cluster-autoscaler_argo_helm" { +module "addon_installation_argo_helm" { source = "../../" enabled = true @@ -90,12 +57,6 @@ module "cluster-autoscaler_argo_helm" { cluster_identity_oidc_issuer = module.eks_cluster.eks_cluster_identity_oidc_issuer cluster_identity_oidc_issuer_arn = module.eks_cluster.eks_cluster_identity_oidc_issuer_arn - values = yamlencode({ - "image" : { - "tag" : "v1.21.2" - } - }) - argo_sync_policy = { "automated" : {} "syncOptions" = ["CreateNamespace=true"] diff --git a/examples/basic/providers.tf b/examples/basic/providers.tf index e8369d1..f18aaaa 100644 --- a/examples/basic/providers.tf +++ b/examples/basic/providers.tf @@ -10,10 +10,16 @@ data "aws_eks_cluster_auth" "this" { name = module.eks_cluster.eks_cluster_id } +provider "kubernetes" { + host = data.aws_eks_cluster.this.endpoint + token = data.aws_eks_cluster_auth.this.token + cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data) +} + provider "helm" { kubernetes { host = data.aws_eks_cluster.this.endpoint token = data.aws_eks_cluster_auth.this.token - cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority.0.data) + cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data) } } diff --git a/examples/basic/versions.tf b/examples/basic/versions.tf new file mode 100644 index 0000000..b19337f --- /dev/null +++ b/examples/basic/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.19.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.16.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.6.0" + } + } +} diff --git a/iam.tf b/iam.tf index 7d0b8be..71fcd26 100644 --- a/iam.tf +++ b/iam.tf @@ -20,13 +20,13 @@ data "aws_iam_policy_document" "this" { "eks:DescribeNodegroup" ] + #checkov:skip=CKV_AWS_111: Ensure IAM policies does not allow write access without constraints resources = [ "*", ] effect = "Allow" } - } data "aws_iam_policy_document" "this_assume" { @@ -87,7 +87,7 @@ resource "aws_iam_role" "this" { } resource "aws_iam_role_policy_attachment" "this" { - count = local.irsa_role_create ? 1 : 0 + count = local.irsa_role_create && var.irsa_policy_enabled ? 1 : 0 role = aws_iam_role.this[0].name policy_arn = aws_iam_policy.this[0].arn } diff --git a/ll-logo.png b/ll-logo.png deleted file mode 100644 index 8aa2c6a..0000000 Binary files a/ll-logo.png and /dev/null differ diff --git a/outputs.tf b/outputs.tf index 39b56a3..20a3d23 100644 --- a/outputs.tf +++ b/outputs.tf @@ -10,10 +10,10 @@ output "helm_release_application_metadata" { output "kubernetes_application_attributes" { description = "Argo kubernetes manifest attributes" - value = try(kubernetes_manifest.this, {}) + value = try(kubernetes_manifest.this[0], {}) } output "iam_role_attributes" { - description = "Vector IAM role atributes" + description = "Cluster Autoscaler IAM role attributes" value = try(aws_iam_role.this[0], {}) } diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..41a7bea --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +checkov==2.2.158 diff --git a/variables.tf b/variables.tf index e74b3a7..d84fa4d 100644 --- a/variables.tf +++ b/variables.tf @@ -36,6 +36,7 @@ variable "helm_release_name" { default = "cluster-autoscaler" description = "Helm release name" } + variable "helm_repo_url" { type = string default = "https://kubernetes.github.io/autoscaler" @@ -54,6 +55,20 @@ variable "namespace" { description = "The K8s namespace in which the node-problem-detector service account has been created" } +variable "settings" { + type = map(any) + default = {} + description = "Additional helm sets which will be passed to the Helm chart values, see https://hub.helm.sh/charts/stable/<$addon-name>" +} + +variable "values" { + type = string + default = "" + description = "Additional yaml encoded values which will be passed to the Helm chart, see https://hub.helm.sh/charts/stable/<$addon-name>" +} + +# ================ IRSA variables (optional) ================ + variable "rbac_create" { type = bool default = true @@ -66,6 +81,12 @@ variable "service_account_create" { description = "Whether to create Service Account" } +variable "service_account_name" { + type = string + default = "cluster-autoscaler" + description = "The k8s <$addon-name> service account name" +} + variable "irsa_role_create" { type = bool default = true @@ -81,10 +102,11 @@ variable "irsa_policy_enabled" { variable "irsa_assume_role_enabled" { type = bool default = false - description = "Whether IRSA is allowed to assume role defined by assume_role_arn." + description = "Whether IRSA is allowed to assume role defined by irsa_assume_role_arn." } variable "irsa_assume_role_arn" { + type = string default = "" description = "Assume role arn. Assume role must be enabled." } @@ -98,37 +120,16 @@ variable "irsa_additional_policies" { variable "irsa_role_name_prefix" { type = string default = "cluster-autoscaler-irsa" - description = "The IRSA role name prefix for vector" + description = "The IRSA role name prefix for <$addon-name>" } -variable "service_account_name" { - default = "cluster-autoscaler" - description = "The k8s cluster-autoscaler service account name" -} - -variable "settings" { - type = map(any) - default = {} - description = "Additional helm sets which will be passed to the Helm chart values, see https://hub.helm.sh/charts/stable/cluster-autoscaler" -} - -variable "helm_set_sensitive" { - type = map(any) +variable "irsa_tags" { + type = map(string) default = {} - description = "Value block with custom sensitive values to be merged with the values yaml that won't be exposed in the plan's diff" + description = "IRSA resources tags" } -variable "helm_postrender" { - type = map(any) - default = {} - description = "Value block with a path to a binary file to run after helm renders the manifest which can alter the manifest contents" -} - -variable "values" { - type = string - default = "" - description = "Additional yaml encoded values which will be passed to the Helm chart, see https://hub.helm.sh/charts/stable/cluster-autoscaler" -} +# ================ argo variables (required) ================ variable "argo_namespace" { type = string @@ -148,6 +149,18 @@ variable "argo_helm_enabled" { description = "If set to true, the ArgoCD Application manifest will be deployed using Kubernetes provider as a Helm release. Otherwise it'll be deployed as a Kubernetes manifest. See Readme for more info" } +variable "argo_helm_wait_timeout" { + type = string + default = "10m" + description = "Timeout for ArgoCD Application Helm release wait job" +} + +variable "argo_helm_wait_backoff_limit" { + type = number + default = 6 + description = "Backoff limit for ArgoCD Application Helm release wait job" +} + variable "argo_destination_server" { type = string default = "https://kubernetes.default.svc" @@ -161,6 +174,10 @@ variable "argo_project" { } variable "argo_info" { + type = list(object({ + name = string + value = string + })) default = [{ "name" = "terraform" "value" = "true" @@ -169,10 +186,67 @@ variable "argo_info" { } variable "argo_sync_policy" { + type = any description = "ArgoCD syncPolicy manifest parameter" default = {} } +variable "argo_metadata" { + type = any + default = { + "finalizers" : [ + "resources-finalizer.argocd.argoproj.io" + ] + } + description = "ArgoCD Application metadata configuration. Override or create additional metadata parameters" +} + +variable "argo_apiversion" { + type = string + default = "argoproj.io/v1alpha1" + description = "ArgoCD Appliction apiVersion" +} + +variable "argo_spec" { + type = any + default = {} + description = "ArgoCD Application spec configuration. Override or create additional spec parameters" +} + +variable "argo_helm_values" { + type = string + default = "" + description = "Value overrides to use when deploying argo application object with helm" +} + +# ================ argo kubernetes manifest variables (required) ================ + +variable "argo_kubernetes_manifest_computed_fields" { + type = list(string) + default = ["metadata.labels", "metadata.annotations", "metadata.finalizers"] + description = "List of paths of fields to be handled as \"computed\". The user-configured value for the field will be overridden by any different value returned by the API after apply." +} + +variable "argo_kubernetes_manifest_field_manager_name" { + type = string + default = "Terraform" + description = "The name of the field manager to use when applying the kubernetes manifest resource. Defaults to Terraform" +} + +variable "argo_kubernetes_manifest_field_manager_force_conflicts" { + type = bool + default = false + description = "Forcibly override any field manager conflicts when applying the kubernetes manifest resource" +} + +variable "argo_kubernetes_manifest_wait_fields" { + type = map(string) + default = {} + description = "A map of fields and a corresponding regular expression with a pattern to wait for. The provider will wait until the field matches the regular expression. Use * for any value." +} + +# ================ helm release variables (required) ================ + variable "helm_repo_key_file" { type = string default = "" @@ -329,56 +403,14 @@ variable "helm_lint" { description = "Run the helm chart linter during the plan" } -variable "argo_metadata" { - default = { - "finalizers" : [ - "resources-finalizer.argocd.argoproj.io" - ] - } - description = "ArgoCD Application metadata configuration. Override or create additional metadata parameters" -} - -variable "argo_apiversion" { - default = "argoproj.io/v1alpha1" - description = "ArgoCD Appliction apiVersion" -} - -variable "argo_spec" { - default = {} - description = "ArgoCD Application spec configuration. Override or create additional spec parameters" -} - -variable "argo_helm_values" { - type = string - default = "" - description = "Value overrides to use when deploying argo application object with helm" -} - -variable "argo_kubernetes_manifest_computed_fields" { - type = list(string) - default = ["metadata.labels", "metadata.annotations"] - description = "List of paths of fields to be handled as \"computed\". The user-configured value for the field will be overridden by any different value returned by the API after apply." -} - -variable "argo_kubernetes_manifest_field_manager_name" { - default = "Terraform" - description = "The name of the field manager to use when applying the kubernetes manifest resource. Defaults to Terraform" -} - -variable "argo_kubernetes_manifest_field_manager_force_conflicts" { - type = bool - default = false - description = "Forcibly override any field manager conflicts when applying the kubernetes manifest resource" -} - -variable "argo_kubernetes_manifest_wait_fields" { - type = map(string) +variable "helm_set_sensitive" { + type = map(any) default = {} - description = "A map of fields and a corresponding regular expression with a pattern to wait for. The provider will wait until the field matches the regular expression. Use * for any value." + description = "Value block with custom sensitive values to be merged with the values yaml that won't be exposed in the plan's diff" } -variable "irsa_tags" { - type = map(string) +variable "helm_postrender" { + type = map(any) default = {} - description = "IRSA resources tags" + description = "Value block with a path to a binary file to run after helm renders the manifest which can alter the manifest contents" } diff --git a/versions.tf b/versions.tf index ea8c3a1..0fe7fd2 100644 --- a/versions.tf +++ b/versions.tf @@ -8,7 +8,7 @@ terraform { } kubernetes = { source = "hashicorp/kubernetes" - version = ">= 2.11.0" + version = ">= 2.16.0" } helm = { source = "hashicorp/helm"