diff --git a/examples/kubectl_wrapper_example/main.tf b/examples/kubectl_wrapper_example/main.tf index 757b17a9..f2547238 100644 --- a/examples/kubectl_wrapper_example/main.tf +++ b/examples/kubectl_wrapper_example/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2020 Google LLC + * Copyright 2020-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ module "enabled_google_apis" { "monitoring.googleapis.com", "container.googleapis.com", "stackdriver.googleapis.com", + "gkehub.googleapis.com", + "connectgateway.googleapis.com", ] } @@ -97,7 +99,42 @@ module "kubectl-local-yaml" { cluster_name = module.gke.name cluster_location = module.gke.location module_depends_on = [module.kubectl-imperative.wait, module.gke.endpoint] - kubectl_create_command = "kubectl apply -f ${local.manifest_path}" - kubectl_destroy_command = "kubectl delete -f ${local.manifest_path}" + kubectl_create_command = "kubectl apply -f ${local.manifest_path}/nginx.yaml" + kubectl_destroy_command = "kubectl delete -f ${local.manifest_path}/nginx.yaml" skip_download = false } + +module "fleet" { + source = "terraform-google-modules/kubernetes-engine/google//modules/fleet-membership" + version = "~> 28.0" + + depends_on = [module.gke] + + project_id = var.project_id + cluster_name = module.gke.name + location = module.gke.location +} + +module "kubectl-fleet-imperative" { + source = "../../modules/kubectl-fleet-wrapper" + + membership_name = module.fleet.cluster_membership_id + membership_project_id = module.fleet.project_id + membership_location = module.fleet.location + module_depends_on = [module.kubectl-local-yaml.wait, module.fleet.wait] + kubectl_create_command = "kubectl run nginx-fleet-imperative --image=nginx" + kubectl_destroy_command = "kubectl delete pod nginx-fleet-imperative" + skip_download = false +} + +module "kubectl-fleet-local-yaml" { + source = "../../modules/kubectl-fleet-wrapper" + + membership_name = module.fleet.cluster_membership_id + membership_project_id = module.fleet.project_id + membership_location = module.fleet.location + module_depends_on = [module.kubectl-fleet-imperative.wait, module.gke.endpoint] + kubectl_create_command = "kubectl apply -f ${local.manifest_path}/nginx-fleet.yaml" + kubectl_destroy_command = "kubectl delete -f ${local.manifest_path}/nginx-fleet.yaml" + skip_download = true +} diff --git a/examples/kubectl_wrapper_example/manifests/nginx-fleet.yaml b/examples/kubectl_wrapper_example/manifests/nginx-fleet.yaml new file mode 100644 index 00000000..c7684d05 --- /dev/null +++ b/examples/kubectl_wrapper_example/manifests/nginx-fleet.yaml @@ -0,0 +1,22 @@ +# Copyright 2020-2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Pod +metadata: + name: nginx-fleet-declarative +spec: + containers: + - name: nginx + image: nginx diff --git a/modules/kubectl-fleet-wrapper/README.md b/modules/kubectl-fleet-wrapper/README.md new file mode 100644 index 00000000..e5c306f0 --- /dev/null +++ b/modules/kubectl-fleet-wrapper/README.md @@ -0,0 +1,59 @@ +# kubectl fleet wrapper + +This submodule aims to make interactions with [GKE clusters with Fleet memberships](https://cloud.google.com/anthos/fleet-management/docs) using kubectl easier by utilizing the gcloud module and the kubectl_fleet_wrapper script. + +This module can be used to deploy any Kubernetes resource using imperative commands or declarative yaml files. An example can be found [here](../../examples/kubectl_wrapper_example). + +## Usage + +Basic imperative usage of this module is as follows: + +```hcl +module "kubectl" { + source = "terraform-google-modules/gcloud/google//modules/kubectl-fleet-wrapper" + + membership_project_id = var.project_id + membership_name = var.cluster_name + membership_location = var.cluster_location + kubectl_create_command = "kubectl create deploy nginx --image=nginx" + kubectl_destroy_command = "kubectl delete deploy nginx" +} +``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| additional\_components | Additional gcloud CLI components to install. Valid value are components listed in `gcloud components list` | `list(string)` |
[
"kubectl"
]
| no | +| create\_cmd\_triggers | List of any additional triggers for the create command execution. | `map(any)` | `{}` | no | +| enabled | Flag to optionally disable usage of this module. | `bool` | `true` | no | +| gcloud\_sdk\_version | The gcloud sdk version to download. | `string` | `"434.0.0"` | no | +| impersonate\_service\_account | An optional service account to impersonate for gcloud commands. If this service account is not specified, the module will use Application Default Credentials. | `string` | `null` | no | +| kubectl\_create\_command | The kubectl command to create resources. | `string` | n/a | yes | +| kubectl\_destroy\_command | The kubectl command to destroy resources. | `string` | n/a | yes | +| membership\_location | Membership location (Global/Region). | `string` | n/a | yes | +| membership\_name | Membership name. | `string` | n/a | yes | +| membership\_project\_id | Membership project ID. | `string` | n/a | yes | +| module\_depends\_on | List of modules or resources this module depends on. | `list(any)` | `[]` | no | +| service\_account\_key\_file | Path to service account key file to auth as for running `gcloud container clusters get-credentials`. | `string` | `""` | no | +| skip\_download | Whether to skip downloading gcloud (assumes `gcloud` and `kubectl` are already available outside the module). | `bool` | `true` | no | +| upgrade | Whether to upgrade gcloud at runtime. | `bool` | `true` | no | +| use\_tf\_google\_credentials\_env\_var | Use `GOOGLE_CREDENTIALS` environment variable to run `gcloud auth activate-service-account` with. Optional. | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| bin\_dir | The full bin path of the modules executables | +| create\_cmd\_bin | The full bin path & command used on create | +| destroy\_cmd\_bin | The full bin path & command used on destroy | +| wait | An output to use when you want to depend on cmd finishing | + + + +To provision this example, run the following from within this directory: +- `terraform init` to get the plugins +- `terraform plan` to see the infrastructure plan +- `terraform apply` to apply the infrastructure build +- `terraform destroy` to destroy the built infrastructure diff --git a/modules/kubectl-fleet-wrapper/main.tf b/modules/kubectl-fleet-wrapper/main.tf new file mode 100644 index 00000000..b92b8afd --- /dev/null +++ b/modules/kubectl-fleet-wrapper/main.tf @@ -0,0 +1,37 @@ +/** + * Copyright 2020-2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + base_cmd = "${var.membership_name} ${var.membership_location} ${var.membership_project_id} ${coalesce(var.impersonate_service_account, "false")}" +} + +module "gcloud_kubectl" { + source = "../.." + module_depends_on = var.module_depends_on + additional_components = var.additional_components + skip_download = var.skip_download + gcloud_sdk_version = var.gcloud_sdk_version + enabled = var.enabled + upgrade = var.upgrade + service_account_key_file = var.service_account_key_file + use_tf_google_credentials_env_var = var.use_tf_google_credentials_env_var + + create_cmd_entrypoint = "${path.module}/scripts/kubectl_fleet_wrapper.sh" + create_cmd_body = "${local.base_cmd} ${var.kubectl_create_command}" + create_cmd_triggers = var.create_cmd_triggers + destroy_cmd_entrypoint = "${path.module}/scripts/kubectl_fleet_wrapper.sh" + destroy_cmd_body = "${local.base_cmd} ${var.kubectl_destroy_command}" +} diff --git a/modules/kubectl-fleet-wrapper/outputs.tf b/modules/kubectl-fleet-wrapper/outputs.tf new file mode 100644 index 00000000..9557a410 --- /dev/null +++ b/modules/kubectl-fleet-wrapper/outputs.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "create_cmd_bin" { + description = "The full bin path & command used on create" + value = module.gcloud_kubectl.create_cmd_bin +} + +output "destroy_cmd_bin" { + description = "The full bin path & command used on destroy" + value = module.gcloud_kubectl.destroy_cmd_bin +} + +output "bin_dir" { + description = "The full bin path of the modules executables" + value = module.gcloud_kubectl.bin_dir +} + +output "wait" { + description = "An output to use when you want to depend on cmd finishing" + value = module.gcloud_kubectl.wait +} diff --git a/modules/kubectl-fleet-wrapper/scripts/kubectl_fleet_wrapper.sh b/modules/kubectl-fleet-wrapper/scripts/kubectl_fleet_wrapper.sh new file mode 100755 index 00000000..c5a83757 --- /dev/null +++ b/modules/kubectl-fleet-wrapper/scripts/kubectl_fleet_wrapper.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Copyright 2020-2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +set -xeo pipefail + +if [ "$#" -lt 4 ]; then + >&2 echo "Not all expected arguments set." + exit 1 +fi + +NAME=$1 +LOCATION=$2 +PROJECT_ID=$3 +IMPERSONATE_SERVICE_ACCOUNT=$4 + +shift 4 + +RANDOM_ID="${RANDOM}_${RANDOM}" +export TMPDIR="/tmp/kubectl_fleet_wrapper_${RANDOM_ID}" + +function cleanup { + rm -rf "${TMPDIR}" +} +trap cleanup EXIT + +mkdir "${TMPDIR}" + +export KUBECONFIG="${TMPDIR}/config" + +CMD="gcloud container fleet memberships get-credentials ${NAME} --project ${PROJECT_ID} --location ${LOCATION}" + +if [[ "${IMPERSONATE_SERVICE_ACCOUNT}" != false ]]; then + CMD+=" --impersonate-service-account ${IMPERSONATE_SERVICE_ACCOUNT}" + shift 2 +fi + +$CMD + +"$@" diff --git a/modules/kubectl-fleet-wrapper/variables.tf b/modules/kubectl-fleet-wrapper/variables.tf new file mode 100644 index 00000000..6e9d545e --- /dev/null +++ b/modules/kubectl-fleet-wrapper/variables.tf @@ -0,0 +1,100 @@ +/** + * Copyright 2020-2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "membership_project_id" { + type = string + description = "Membership project ID." +} + +variable "membership_name" { + type = string + description = "Membership name." +} + +variable "membership_location" { + type = string + description = "Membership location (Global/Region)." +} + +variable "kubectl_create_command" { + type = string + description = "The kubectl command to create resources." +} + +variable "kubectl_destroy_command" { + type = string + description = "The kubectl command to destroy resources." +} + +variable "enabled" { + description = "Flag to optionally disable usage of this module." + type = bool + default = true +} + +variable "module_depends_on" { + description = "List of modules or resources this module depends on." + type = list(any) + default = [] +} + +variable "create_cmd_triggers" { + description = "List of any additional triggers for the create command execution." + type = map(any) + default = {} +} + +variable "additional_components" { + description = "Additional gcloud CLI components to install. Valid value are components listed in `gcloud components list`" + type = list(string) + default = ["kubectl"] +} + +variable "skip_download" { + description = "Whether to skip downloading gcloud (assumes `gcloud` and `kubectl` are already available outside the module)." + type = bool + default = true +} + +variable "gcloud_sdk_version" { + description = "The gcloud sdk version to download." + type = string + default = "434.0.0" +} + +variable "upgrade" { + description = "Whether to upgrade gcloud at runtime." + type = bool + default = true +} + +variable "service_account_key_file" { + description = "Path to service account key file to auth as for running `gcloud container clusters get-credentials`." + type = string + default = "" +} + +variable "use_tf_google_credentials_env_var" { + description = "Use `GOOGLE_CREDENTIALS` environment variable to run `gcloud auth activate-service-account` with. Optional." + type = bool + default = false +} + +variable "impersonate_service_account" { + type = string + description = "An optional service account to impersonate for gcloud commands. If this service account is not specified, the module will use Application Default Credentials." + default = null +} diff --git a/modules/kubectl-fleet-wrapper/versions.tf b/modules/kubectl-fleet-wrapper/versions.tf new file mode 100644 index 00000000..17577fdb --- /dev/null +++ b/modules/kubectl-fleet-wrapper/versions.tf @@ -0,0 +1,31 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = ">= 0.13" + required_providers { + + google = { + source = "hashicorp/google" + version = ">= 3.53, < 5.0" + } + } + + provider_meta "google" { + module_name = "blueprints/terraform/terraform-google-gcloud:kubectl-wrapper/v3.2.1" + } + +} diff --git a/test/integration/kubectl_wrapper_example/controls/kubectl.rb b/test/integration/kubectl_wrapper_example/controls/kubectl.rb index bdb72197..17f86f4c 100644 --- a/test/integration/kubectl_wrapper_example/controls/kubectl.rb +++ b/test/integration/kubectl_wrapper_example/controls/kubectl.rb @@ -58,6 +58,22 @@ expect(nginxi).not_to be_nil end end + + describe "nginx-fleet-declarative" do + let(:nginxd) { client.get_pod("nginx-fleet-declarative", "default") } + + it "exists" do + expect(nginxd).not_to be_nil + end + end + + describe "nginx-fleet-imperative" do + let(:nginxi) { client.get_pod("nginx-fleet-imperative", "default") } + + it "exists" do + expect(nginxi).not_to be_nil + end + end end end end