diff --git a/AGENTS.md b/AGENTS.md index 0bec7a92..10a64928 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,58 +1,328 @@ -# Kubestack AGENTS.md file for framework development -** for framework usage separate AGENTS.md files are provided as part of the quickstarts ** - -Kubestack is an OpenTofu/Terraform framework for platform engineering teams building Kubernetes-based platforms. -It provides re-usable modules to manage clusters, node-pools and platform services from a unified configuration using a GitOps workflow. - - * cluster modules provision managed Kubernetes offerings from cloud providers and all their respective infrastructure requirements - * node-pool modules are used to configure the node pools attached to the respective cluster - * platform service modules install services that need to exist on clusters before applications can be deployed - -Inheritance-based configuration to avoid drift between environments, and keeping infrastructure and application environments separate to avoid blockers between infrastructure and application changes are cornerstones of Kubestack's design. - -Any Kubestack framework module aims to provide a unified developer experience and no surprises infrastructure automation. - -## Repository and module layout - - * the repository root has directories for `common/` modules shared between all providers, and provider-specific module directories - * provider-specific module directory names follow the OpenTofu/Terraform provider name for that cloud, e.g. `aws`, `azurerm` or `google` - * in each provider-specific module directory there is a `cluster/` directory holding the cluster module and its node-pool submodule in the `cluster/node-pool` directory - * the configuration and metadata module calls, as well as the main resource for the respective module (cluster resource for cluster modules, node-pool resource for node-pool modules) must always be in the file `main.tf` - * every cluster provisioned by Kubestack must have a default node pool; the default node pool must be provisioned by calling the cluster's node-pool submodule in a `node_pool.tf` file, with the following exceptions: - * if the cloud provider mandates a built-in default node pool that cannot be disabled (e.g. AKS), use the provider's built-in default node pool instead; `node_pool.tf` is not required in this case - * if the cloud provider includes a default node pool that can be disabled (e.g. GKE), it must be disabled and the node-pool submodule must still be used in `node_pool.tf` - * kubeconfig and default ingress related code must be in `kubeconfig.tf` and `ingress.tf` for all cluster modules - * outputs must be defined in `outputs.tf` - * moved blocks must be in `moved.tf` - * variables must be defined in `variables.tf` - * a data source used by only one resource must be placed directly in front of that resource; if a data source is used by multiple resources or supports conditional logic, it must be placed in a separate `data_sources.tf` file - * additional files like `vpc.tf`, `roles.tf`, `launch_template.tf` etc. may be created if grouping resources into separate files improves maintainability - -### Variables and Outputs - * all Kubestack modules share a common set of variables and outputs per module type - * configuration inheritance variables: - * every module written for the Kubestack framework must define both a `configuration` variable of type `map(object)` and a `configuration_base_key` variable of type `string`; the `configuration_base_key` variable defaults to `apps` so it is optional for users of the module, but both variables must always be defined when writing framework modules - * the map key of `configuration` is the environment name and the object defines the module and provider-specific configuration attributes - * all configuration object attributes must be marked optional and must not define a default, because the inheritance implementation relies on unset attributes being null to determine whether to inherit or overwrite an attribute for the current environment - * inside the module the configuration module must be called, passing in `var.configuration` and `var.configuration_base_key`; the resulting merged config for the current environment (`terraform.workspace`) must be made available as `local.cfg` - * `local.cfg.` must be used as the input to any resource that requires a value from the module's configuration - * Kubestack prefers upstream defaults by passing `null` to resource arguments - * if a default must be set, use the `try(coalesce(, null), )` pattern; this ensures that any nested key error or a null value from coalesce is caught by try, so the default only has to be specified once - * cluster module outputs: - * `cluster`: all attributes the cluster resource returns - * `current_config`: the merged output of the configuration module (`local.cfg`) - * `current_metadata`: the entire output of the metadata module - * `kubeconfig`: the yaml encoded kubeconfig for the provisioned cluster; the output is used to configure the provider for platform service modules to be installed on the cluster - * do not add additional outputs unless there is no alternative; exceptions must not be taken lightly to avoid module user experience drifting apart between providers - * node-pool module variables and outputs: - * in addition to the common `configuration` and `configuration_base_key` variables, node pools also require `cluster` and `cluster_metadata` variables that expect the data from the cluster's corresponding `cluster` and `current_metadata` outputs - * do not add additional variables or outputs unless there is no alternative; the `aws/cluster` and `aws/cluster/node-pool` modules are an example of a case where additional variables were necessary - -## Testing instructions - * testing the Kubestack framework is expensive because real cloud infrastructure has to be created and cleanly deprovisioned as part of the integration tests - * the multi-cloud platform configuration for the tests can be found in the `tests/` directory - * a Makefile provides common development targets to run locally and ensures they are run in a container with the correct dependencies and correct working directory - * never run OpenTofu/Terraform commands manually outside one of the specified make targets - * `make validate` is safe to run at any time and must be run to validate any changes - * never run `make test` or `make cleanup` unless explicitly asked to by the user - * the `common/configuration` and `common/metadata` modules have unit tests; after making any changes to these modules, run `tofu test` inside the respective module directory +# Kubestack AGENTS.md — Framework Development + +> **This file governs framework development.** +> For framework _usage_, see the AGENTS.md included with each quickstart. + +--- + +## Critical Constraints — Read First + +These rules have no exceptions and MUST be applied before taking any other action: + +- **NEVER** run `make test` or `make cleanup` unless the user explicitly instructs you to. These targets create and destroy real cloud infrastructure and incur real costs. +- **NEVER** run OpenTofu/Terraform commands (`tofu`, `terraform`) directly. Always use the Makefile targets. +- **ALWAYS** run `make validate` after making any change to module code or test configuration. It is safe to run at any time. +- **ALWAYS** update `tests/` when any module interface changes (new variables, removed variables, changed types). +- **ALWAYS** update the relevant quickstart examples when any module interface changes. + +--- + +## About Kubestack + +Kubestack is an OpenTofu/Terraform framework for platform engineering teams building Kubernetes-based platforms. It provides re-usable modules to manage clusters, node pools, and platform services from a unified configuration using a GitOps workflow. + +### Module Types + +| Type | Purpose | +|---|---| +| **Cluster modules** | Provision managed Kubernetes offerings from cloud providers along with all required infrastructure (VPC, IAM, etc.) | +| **Node-pool modules** | Configure and manage node pools attached to a cluster | +| **Platform service modules** | Install cluster-level services that must exist before applications can be deployed | + +### Core Design Philosophy + +- **Inheritance-based configuration** prevents drift between environments by deriving all environments from a single base configuration. +- **Separated infrastructure and application environments** prevent infrastructure changes from blocking application deployments. + +--- + +## Design Principles + +All Kubestack modules MUST adhere to every principle in this section. + +### Configuration Inheritance + +- All environments inherit their configuration from the base environment. The base environment key defaults to `apps` (set via `configuration_base_key`). An environment-specific value always overrides the inherited base value. The inheritance is implemented in the `common/configuration` module, which all Kubestack modules MUST use. + +### Multi-Zone Resilience + +- Node pools MUST be distributed across a minimum of three availability zones by default. The user MUST explicitly specify the zones and MAY specify fewer than three. +- Load balancers MUST be distributed across the same zones as the nodes. + +### Kubernetes Version Management + +- Auto-update for **patch versions** MUST be enabled by default. +- Users specify the Kubernetes version using one of two mutually exclusive methods: + 1. **Explicit**: specify major and minor version (e.g., `1.30`). + 2. **Channel**: select a release channel (e.g., `STABLE`). +- If both an explicit version and a release channel are specified, the explicit version MUST take precedence. +- Users MAY configure a maintenance window if the provider support it. When a default is required, it SHOULD target 3:00 AM UTC. + +### Node Pool Lifecycle + +- **Self-healing** MUST be enabled by default. Users MAY disable it via configuration. +- **Auto-scaling** MUST be enabled by default. Users MAY disable it via configuration. + - `min` and `max` node counts MUST always be user-configurable. The default `min` SHOULD be one per zone and the default `max` SHOULD be two times `min`. + - `current` / `desired` / `initial` node count (where the provider exposes it) MUST be owned by the autoscaler and MUST NOT be managed by OpenTofu/Terraform state. Implement this by adding a `lifecycle { ignore_changes }` block on the node-group or node-pool resource, targeting the provider's desired/initial count argument. + +### Cloud Provider Isolation + +- Provider-specific multi-region or multi-cloud features MUST be disabled. +- Kubestack assumes exactly **one cluster per region per cloud provider**. + +### Networking + +- Every cluster module MUST create its own dedicated network resources (VPC, subnets, route tables, gateways, and any other provider-required constructs). Sharing or accepting externally-created network resources is NOT supported. Place all network resources in a dedicated `network.tf` file inside the cluster module. +- The CNI plugin defaults to the provider's recommended option. Where the provider exposes a CNI choice, it MUST be user-configurable via a `cni` (or equivalent) configuration attribute. +- Network policies MUST be enabled by default where the provider or CNI supports enforcement at the cluster level. Users MAY disable them where the provider exposes that option. +- The Kubestack-preferred approach for cross-cluster and external connectivity is installing a service mesh via a platform service module. A provider-independent solution is preferred over provider-proprietary options. + +#### CIDR Defaults + +Kubestack defers to the upstream defaults of each cloud provider. Where a provider accepts CIDR arguments but does not require them, MUST NOT set values — pass `null` so the provider applies its own defaults. Where a provider requires explicit CIDR values to be set, use the defaults from that provider's official documentation. Users MAY override any CIDR via configuration to integrate with existing infrastructure. + +### Identity and Access + +- Support for OpenID Connect (OIDC) tokens for a workload's service account MUST be enabled by default. Users MAY disable it via configuration. + +### Tagging and Labelling + +- All cloud and Kubernetes resources MUST be tagged/labelled using the output from the `common/metadata` module. +- Users MUST be able to add extra tags/labels for cloud resources and Kubernetes resources **independently** via separate configuration attributes. + +### Node Taints + +- Users MUST be able to specify node taints on both the default node pool and any additional node pools. + +### Cross-Provider Developer Experience + +- Key configuration (zones, min/max node counts, instance types) MUST be available for any module, using provider-specific variable names. +- All configuration attribute names SHOULD mirror the respective provider resource attribute names. +- Provider resource blocks containing nested arguments SHOULD be reflected as nested configuration object attributes. + +--- + +## Repository and Module Layout + +### Directory Structure + +``` +/ + common/ # Modules shared across all cloud providers + / # Provider-specific modules; directory name = TF provider name (aws, azurerm, google, scaleway, …) + cluster/ # Cluster module + node-pool/ # Node-pool submodule +``` + +### Required Files + +Every module MUST use the following file layout. Do not consolidate these files. + +| File | Required In | Purpose | +|---|---|---| +| `main.tf` | All modules | Configuration module call, metadata module call, and the module's primary resource (cluster or node-pool resource). | +| `variables.tf` | All modules | All input variable definitions. | +| `outputs.tf` | All modules | All output definitions. | +| `moved.tf` | All modules | All `moved` blocks. No other content. | +| `versions.tf` | All modules | Provider requirements and minimum OpenTofu/Terraform version constraint. | +| `kubeconfig.tf` | Cluster modules | Kubeconfig local value. See kubeconfig rules below. | +| `ingress.tf` | Cluster modules | Default ingress feature resources. | +| `node_pool.tf` | Cluster modules | Default node pool (see decision tree below for exceptions). | +| `data_sources.tf` | When required | Data sources shared across resources or containing conditional logic (see rule below). | + +**Data source placement rule:** + +- A data source used by **exactly one resource** → place it directly before that resource in the same file. +- A data source used by **multiple resources** OR containing **conditional logic** → place it in `data_sources.tf`. + +**Additional files** (e.g., `vpc.tf`, `roles.tf`, `launch_template.tf`, `network.tf`) MAY be created when grouping resources into a dedicated file meaningfully improves readability. + +### Default Node Pool — Decision Tree + +Every cluster MUST have a default node pool. Determine the implementation approach by applying the following rules in order: + +1. **Provider mandates a built-in default node pool that cannot be disabled** (e.g., AKS / `azurerm`): + → Use the provider's built-in default node pool. `node_pool.tf` is NOT required. + +2. **Provider includes a default node pool that CAN be disabled** (e.g., GKE / `google`): + → Disable the provider's built-in default node pool AND provision the default node pool by calling the cluster's node-pool submodule in `node_pool.tf`. + +3. **All other providers** (e.g., EKS / `aws`): + → Provision the default node pool by calling the cluster's node-pool submodule in `node_pool.tf`. + +--- + +## Variables and Outputs + +### Configuration Inheritance Variables (All Module Types) + +Every Kubestack module MUST define both of the following variables exactly as shown: + +```hcl +variable "configuration" { + type = map(object({ ... })) # map key = environment name (workspace) + nullable = false +} + +variable "configuration_base_key" { + type = string + default = "apps" # optional for module users; but MUST be defined in all modules + nullable = false +} +``` + +Rules for the `configuration` object type: + +- ALL attributes MUST be marked `optional(...)`. +- Attributes MUST NOT specify a default value inside the `optional()` call. The inheritance implementation in `common/configuration` determines whether to inherit or override a value by checking whether the attribute is `null`. Adding defaults inside `optional()` breaks this check. + +### Using Configuration Inside a Module + +Inside every module, apply the following pattern: + +1. Call the `common/configuration` module in `main.tf`, passing `var.configuration` as `configuration` and `var.configuration_base_key` as `base_key`. +2. Expose the merged result as `local.cfg`, keyed on `terraform.workspace`. +3. Reference `local.cfg.` everywhere a resource needs a configuration value. + +```hcl +module "configuration" { + source = "../../common/configuration" + configuration = var.configuration + base_key = var.configuration_base_key +} + +locals { + cfg = module.configuration.merged[terraform.workspace] +} +``` + +### Setting Resource Argument Values + +Kubestack prefers upstream provider defaults. Apply the following rules when assigning `local.cfg` values to resource arguments: + +| Situation | Pattern | +|---|---| +| No Kubestack-level default is needed | Pass `null` directly: `argument = local.cfg.some_attribute` | +| A Kubestack-level default is required | `argument = try(coalesce(local.cfg.some_attribute, null), )` | + +**Explanation of `try(coalesce(local.cfg., null), )`:** + +- `coalesce(local.cfg., null)` returns the configured value if set, or `null` if the attribute was not configured. +- `try(..., )` catches both nested-key errors and the `null` from `coalesce`, falling through to the default. This ensures the default value is specified in exactly one place. + +**Example** (from `aws/cluster/main.tf`): + +```hcl +endpoint_private_access = try(coalesce(local.cfg.cluster_endpoint_private_access, null), false) +endpoint_public_access = try(coalesce(local.cfg.cluster_endpoint_public_access, null), true) +``` + +### Cluster Module Outputs + +Cluster modules MUST define exactly the following outputs. Do NOT add extra outputs without a compelling, provider-specific reason — inconsistent outputs across providers degrade the cross-provider user experience. + +| Output | Value | Notes | +|---|---|---| +| `cluster` | The full cluster resource object | All attributes the cluster resource returns. | +| `current_config` | `local.cfg` | The merged configuration for the active workspace. | +| `current_metadata` | The full metadata module output | The entire `module.cluster_metadata` output. | +| `kubeconfig` | YAML-encoded kubeconfig | Mark as `sensitive = true`. | + +### Kubeconfig Generation + +The kubeconfig MUST be constructed as a `local` value (not stored as a resource) inside `kubeconfig.tf`, using `yamlencode()`. It MUST follow the standard Kubernetes kubeconfig structure: + +```hcl +locals { + kubeconfig = yamlencode({ + apiVersion = "v1" + kind = "Config" + clusters = [{ + cluster = { + server = "" + certificate-authority-data = "" + } + name = "" + }] + users = [{ + user = { token = "" } # or client-certificate-data / client-key-data + name = "" + }] + contexts = [{ + context = { + cluster = "" + user = "" + } + name = "" + }] + current-context = "" + preferences = {} + }) +} +``` + +The authentication field inside `users[].user` is provider-specific: use a `token` for providers that issue short-lived tokens (like EKS, GKE), or `client-certificate-data` / `client-key-data` for providers that issue client certificates (like AKS). + +### Node-Pool Module Variables and Outputs + +Node-pool modules MUST define `configuration` and `configuration_base_key` as described above, PLUS the following additional variables: + +| Variable | Type | Source | +|---|---|---| +| `cluster` | `any` | The `cluster` output from the corresponding cluster module. | +| `cluster_metadata` | `any` | The `current_metadata` output from the corresponding cluster module. | + +Node-pool modules MUST define the following output: + +| Output | Value | Notes | +|---|---|---| +| `current_config` | `local.cfg` | The merged configuration for the active workspace. | + +Do NOT add extra variables or outputs without a compelling reason. + +> **Documented exception:** `aws/cluster/node-pool` defines two additional provider-specific variables — `cluster_default_node_pool_name` and `cluster_default_node_pool_subnet_ids` — because the node-pool module for extra node pools determines subnets from the default node pool's subnet IDs by default. When provisioning the default node pool itself, however, these variables are necessary to break the circular dependency. + +--- + +## Testing + +### Safety Rules + +> **NEVER run `make test` or `make cleanup` unless the user explicitly instructs you to.** +> **NEVER run OpenTofu/Terraform commands directly.** Always use the Makefile targets. + +These tests provision and destroy real cloud infrastructure across multiple providers and incur real costs. + +### Validation — Run After Every Change + +``` +make validate +``` + +This target is safe to run at any time. It MUST be run to confirm that all changes are syntactically and structurally valid before considering a task complete. + +### Unit Tests for Common Modules + +After making any change to `common/configuration` or `common/metadata`, run the module's unit tests: + +``` +cd common/configuration && tofu test +cd common/metadata && tofu test +``` + +### Integration Test Configuration + +- The multi-cloud test platform configuration lives in `tests/`. +- When any module interface changes (added/removed/renamed variables, changed types), `tests/` MUST be updated to reflect the new interface before running `make validate`. + +--- + +## Dist Assets + +The Kubestack framework is released as three asset types. When making changes, apply the corresponding update obligations: + +| Asset | Description | Update Obligation | +|---|---|---| +| **Versioned modules** | Consumed via `module` blocks using GitHub URLs | Update `tests/` and quickstarts to reflect interface changes. | +| **Container image** | Base image for CI/CD pipelines and manual tasks (`oci/Dockerfile`) | When adding a new cloud provider, add provider-specific CLI build and dist targets to `oci/Dockerfile`, following the pattern of existing providers. | +| **Quickstarts** | Example directory layouts for bootstrapping new repositories | MUST be updated to reflect any module interface or usage changes. Common files (`README.md`, `.gitignore`, `.user/`) are shared via symlinks pointing to `quickstart/src/configurations/_shared/`. When adding a new quickstart, symlink these files instead of duplicating them. When adding a new provider, add its auth instructions to the shared `README.md`. | diff --git a/Makefile b/Makefile index ac0f28e3..0abdbe29 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ validate: .init test: validate docker exec \ test-container-$(GIT_SHA) \ - entrypoint tofu apply --target module.aks_zero --target module.eks_zero --target module.gke_zero --input=false --auto-approve + entrypoint tofu apply --target module.aks_zero --target module.eks_zero --target module.gke_zero --target module.scw_zero --input=false --auto-approve docker exec \ test-container-$(GIT_SHA) \ entrypoint tofu apply --target module.eks_zero_nginx --input=false --auto-approve @@ -95,6 +95,7 @@ shell: .check-container -e KBST_AUTH_AWS \ -e KBST_AUTH_AZ \ -e KBST_AUTH_GCLOUD \ + -e KBST_AUTH_SCW \ -e HOME=/infra/tests/.user \ --workdir /infra/tests \ ghcr.io/kbst/terraform-kubestack/dev:test-$(GIT_SHA)-${DOCKER_TARGET} \ diff --git a/aws/cluster/default_node_pool.tf b/aws/cluster/node_pool.tf similarity index 100% rename from aws/cluster/default_node_pool.tf rename to aws/cluster/node_pool.tf diff --git a/oci/Dockerfile b/oci/Dockerfile index 978dc6d5..1736666d 100644 --- a/oci/Dockerfile +++ b/oci/Dockerfile @@ -97,6 +97,23 @@ RUN echo "GOOGLE_CLOUD_SDK_VERSION: ${GOOGLE_CLOUD_SDK_VERSION}" \ RUN /opt/google/bin/gcloud components install gke-gcloud-auth-plugin +# +# +# Scaleway builder +FROM builder AS scw-builder + +ARG TARGETARCH + +# https://github.com/scaleway/scaleway-cli/releases +ARG SCALEWAY_CLI_VERSION=2.35.0 + +RUN echo "SCALEWAY_CLI_VERSION: ${SCALEWAY_CLI_VERSION}" \ + && curl -LO https://github.com/scaleway/scaleway-cli/releases/download/v${SCALEWAY_CLI_VERSION}/scaleway-cli_${SCALEWAY_CLI_VERSION}_linux_${TARGETARCH} \ + && mv scaleway-cli_${SCALEWAY_CLI_VERSION}_linux_${TARGETARCH} /opt/bin/scw \ + && chmod +x /opt/bin/scw \ + && /opt/bin/scw version + + # # # Azure builder @@ -231,6 +248,15 @@ COPY --from=azure-builder /opt/azure /opt/azure ENV PATH=$PATH:/opt/azure/bin +# +# +# Scaleway variant +FROM final-base AS scw + +# Scaleway +COPY --from=scw-builder /opt/bin/scw /opt/bin/scw + + # # # Default (multi-cloud) variant @@ -247,3 +273,6 @@ ENV PATH=$PATH:/opt/google/bin # Azure COPY --from=azure-builder /opt/azure /opt/azure ENV PATH=$PATH:/opt/azure/bin + +# Scaleway +COPY --from=scw-builder /opt/bin/scw /opt/bin/scw diff --git a/quickstart/src/configurations/_shared/README.md b/quickstart/src/configurations/_shared/README.md index 9909400e..0790c177 100644 --- a/quickstart/src/configurations/_shared/README.md +++ b/quickstart/src/configurations/_shared/README.md @@ -111,6 +111,9 @@ In case of the automation being unavailable, upgrades requiring manual steps or # for GCP gcloud init gcloud auth application-default login + + # for Scaleway + scw init ``` 1. Select desired environment diff --git a/quickstart/src/configurations/aks/Dockerfile.loc b/quickstart/src/configurations/aks/Dockerfile.loc deleted file mode 100644 index 72a1ce21..00000000 --- a/quickstart/src/configurations/aks/Dockerfile.loc +++ /dev/null @@ -1,10 +0,0 @@ -FROM {{image_name}}:{{image_tag}}-kind - -ARG UID -ARG GID - -RUN mkdir -p /infra/terraform.tfstate.d &&\ - chown ${UID}:${GID} -R /infra - -COPY manifests /infra/manifests -COPY *.tf *.tfvars /infra/ diff --git a/quickstart/src/configurations/eks/Dockerfile.loc b/quickstart/src/configurations/eks/Dockerfile.loc deleted file mode 100644 index 72a1ce21..00000000 --- a/quickstart/src/configurations/eks/Dockerfile.loc +++ /dev/null @@ -1,10 +0,0 @@ -FROM {{image_name}}:{{image_tag}}-kind - -ARG UID -ARG GID - -RUN mkdir -p /infra/terraform.tfstate.d &&\ - chown ${UID}:${GID} -R /infra - -COPY manifests /infra/manifests -COPY *.tf *.tfvars /infra/ diff --git a/quickstart/src/configurations/gke/Dockerfile.loc b/quickstart/src/configurations/gke/Dockerfile.loc deleted file mode 100644 index 72a1ce21..00000000 --- a/quickstart/src/configurations/gke/Dockerfile.loc +++ /dev/null @@ -1,10 +0,0 @@ -FROM {{image_name}}:{{image_tag}}-kind - -ARG UID -ARG GID - -RUN mkdir -p /infra/terraform.tfstate.d &&\ - chown ${UID}:${GID} -R /infra - -COPY manifests /infra/manifests -COPY *.tf *.tfvars /infra/ diff --git a/quickstart/src/configurations/multi-cloud/Dockerfile.loc b/quickstart/src/configurations/multi-cloud/Dockerfile.loc deleted file mode 100644 index 72a1ce21..00000000 --- a/quickstart/src/configurations/multi-cloud/Dockerfile.loc +++ /dev/null @@ -1,10 +0,0 @@ -FROM {{image_name}}:{{image_tag}}-kind - -ARG UID -ARG GID - -RUN mkdir -p /infra/terraform.tfstate.d &&\ - chown ${UID}:${GID} -R /infra - -COPY manifests /infra/manifests -COPY *.tf *.tfvars /infra/ diff --git a/quickstart/src/configurations/multi-cloud/scw_zero_cluster.tf b/quickstart/src/configurations/multi-cloud/scw_zero_cluster.tf new file mode 100644 index 00000000..4c48e42d --- /dev/null +++ b/quickstart/src/configurations/multi-cloud/scw_zero_cluster.tf @@ -0,0 +1,60 @@ +module "scw_zero" { + source = "github.com/kbst/terraform-kubestack//scaleway/cluster?ref={{version}}" + + configuration = { + # apps environment + apps = { + # Set name_prefix used to generate the cluster name + # [name_prefix]-[workspace]-[region] + # e.g. name_prefix = kbst becomes: `kbst-apps-fr-par` + # for small orgs the name works well, + # for bigger orgs consider department or team names + name_prefix = "" + + # Set the base_domain used to generate the FQDN of the cluster + # [cluster_name].[provider_name].[base_domain] + # e.g. kbst-apps-fr-par.scw.infra.example.com + # The domain must be registered with Scaleway Domains and DNS + base_domain = "" + + # The Scaleway region to deploy the clusters in + # e.g. "fr-par", "nl-ams", "pl-waw" + region = "" + + # Kubernetes version for the cluster + # Use a minor version string to allow patch upgrades, e.g. "1.32" + cluster_version = "1.32" + + # Container Network Interface plugin + # Options: "cilium", "calico", "weave", "flannel", "kilo" + cni = "cilium" + + # Whether to delete additional resources (load-balancers, block volumes, + # the private network if empty) created in Kubernetes when deleting the cluster. + # Set to true only if you want these resources cleaned up automatically on destroy. + delete_additional_resources = false + + # Default node pool configuration + default_node_pool = { + # Commercial node type — see `scw k8s node-pool list-available-types` + node_type = "DEV1-M" + + # Initial number of nodes in the pool + size = 1 + + # Minimum and maximum size for autoscaling + min_size = 1 + max_size = 1 + + # Enable Scaleway's cluster autoscaler for this pool + autoscaling = false + + # Enable automatic node replacement on failure + autohealing = true + } + } + + # ops environment, inherits from apps + ops = {} + } +} diff --git a/quickstart/src/configurations/multi-cloud/scw_zero_providers.tf b/quickstart/src/configurations/multi-cloud/scw_zero_providers.tf new file mode 100644 index 00000000..cacb8f56 --- /dev/null +++ b/quickstart/src/configurations/multi-cloud/scw_zero_providers.tf @@ -0,0 +1,24 @@ +provider "scaleway" { + # region is read from SCW_DEFAULT_REGION environment variable + # or can be set explicitly, e.g.: + # region = "fr-par" +} + +provider "kustomization" { + alias = "scw_zero" + kubeconfig_raw = module.scw_zero.kubeconfig +} + +locals { + scw_zero_kubeconfig = yamldecode(module.scw_zero.kubeconfig) +} + +provider "kubernetes" { + alias = "scw_zero" + + host = local.scw_zero_kubeconfig["clusters"][0]["cluster"]["server"] + token = local.scw_zero_kubeconfig["users"][0]["user"]["token"] + cluster_ca_certificate = base64decode( + local.scw_zero_kubeconfig["clusters"][0]["cluster"]["certificate-authority-data"] + ) +} diff --git a/quickstart/src/configurations/multi-cloud/versions.tf b/quickstart/src/configurations/multi-cloud/versions.tf index 2c8242de..8164a83c 100644 --- a/quickstart/src/configurations/multi-cloud/versions.tf +++ b/quickstart/src/configurations/multi-cloud/versions.tf @@ -7,6 +7,10 @@ terraform { kustomization = { source = "kbst/kustomization" } + + scaleway = { + source = "scaleway/scaleway" + } } required_version = ">= 0.15" diff --git a/quickstart/src/configurations/scw/.gitignore b/quickstart/src/configurations/scw/.gitignore new file mode 120000 index 00000000..fcff0677 --- /dev/null +++ b/quickstart/src/configurations/scw/.gitignore @@ -0,0 +1 @@ +../_shared/tpl_gitignore \ No newline at end of file diff --git a/quickstart/src/configurations/scw/.user b/quickstart/src/configurations/scw/.user new file mode 120000 index 00000000..2683a88f --- /dev/null +++ b/quickstart/src/configurations/scw/.user @@ -0,0 +1 @@ +../_shared/.user/ \ No newline at end of file diff --git a/quickstart/src/configurations/scw/Dockerfile b/quickstart/src/configurations/scw/Dockerfile new file mode 100644 index 00000000..2e70de9b --- /dev/null +++ b/quickstart/src/configurations/scw/Dockerfile @@ -0,0 +1 @@ +FROM {{image_name}}:{{image_tag}}-scw diff --git a/quickstart/src/configurations/scw/README.md b/quickstart/src/configurations/scw/README.md new file mode 120000 index 00000000..b4e07dbc --- /dev/null +++ b/quickstart/src/configurations/scw/README.md @@ -0,0 +1 @@ +../_shared/README.md \ No newline at end of file diff --git a/quickstart/src/configurations/scw/scw_zero_cluster.tf b/quickstart/src/configurations/scw/scw_zero_cluster.tf new file mode 100644 index 00000000..4eea6d2a --- /dev/null +++ b/quickstart/src/configurations/scw/scw_zero_cluster.tf @@ -0,0 +1,44 @@ +module "scw_zero" { + source = "github.com/kbst/terraform-kubestack//scaleway/cluster?ref={{version}}" + + configuration = { + # apps environment + apps = { + # Set name_prefix used to generate the cluster name + # [name_prefix]-[workspace]-[region] + # e.g. name_prefix = kbst becomes: `kbst-apps-fr-par` + # for small orgs the name works well, + # for bigger orgs consider department or team names + name_prefix = "" + + # Set the base_domain used to generate the FQDN of the cluster + # [cluster_name].[provider_name].[base_domain] + # e.g. kbst-apps-fr-par.scw.infra.example.com + # The domain must be registered with Scaleway Domains and DNS + base_domain = "" + + # The Scaleway region to deploy the cluster in + # e.g. "fr-par", "nl-ams", "pl-waw" + region = "" + + default_node_pool = { + # Commercial node type — must be available in every zone listed below. + # Run `scw k8s node-pool list-available-types` to list types per zone. + node_type = "PRO2-XXS" + + # Availability zones to distribute the default node pool across. + # One pool is created per zone. Must be explicitly specified. + # e.g. fr-par: ["fr-par-1", "fr-par-2", "fr-par-3"] + # e.g. nl-ams: ["nl-ams-1", "nl-ams-2", "nl-ams-3"] + # e.g. pl-waw: ["pl-waw-1", "pl-waw-2", "pl-waw-3"] + zones = [] + + min_size = 1 + max_size = 2 + } + } + + # ops environment, inherits from apps + ops = {} + } +} diff --git a/quickstart/src/configurations/scw/scw_zero_providers.tf b/quickstart/src/configurations/scw/scw_zero_providers.tf new file mode 100644 index 00000000..cacb8f56 --- /dev/null +++ b/quickstart/src/configurations/scw/scw_zero_providers.tf @@ -0,0 +1,24 @@ +provider "scaleway" { + # region is read from SCW_DEFAULT_REGION environment variable + # or can be set explicitly, e.g.: + # region = "fr-par" +} + +provider "kustomization" { + alias = "scw_zero" + kubeconfig_raw = module.scw_zero.kubeconfig +} + +locals { + scw_zero_kubeconfig = yamldecode(module.scw_zero.kubeconfig) +} + +provider "kubernetes" { + alias = "scw_zero" + + host = local.scw_zero_kubeconfig["clusters"][0]["cluster"]["server"] + token = local.scw_zero_kubeconfig["users"][0]["user"]["token"] + cluster_ca_certificate = base64decode( + local.scw_zero_kubeconfig["clusters"][0]["cluster"]["certificate-authority-data"] + ) +} diff --git a/quickstart/src/configurations/scw/versions.tf b/quickstart/src/configurations/scw/versions.tf new file mode 100644 index 00000000..ff663d1f --- /dev/null +++ b/quickstart/src/configurations/scw/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + scaleway = { + source = "scaleway/scaleway" + } + + kustomization = { + source = "kbst/kustomization" + } + } + + required_version = ">= 0.15" +} diff --git a/scaleway/cluster/ingress.tf b/scaleway/cluster/ingress.tf new file mode 100644 index 00000000..84acae25 --- /dev/null +++ b/scaleway/cluster/ingress.tf @@ -0,0 +1,36 @@ +resource "scaleway_lb_ip" "current" { + count = try(coalesce(local.cfg.disable_default_ingress, null), false) ? 0 : 1 + + project_id = local.cfg.project_id + + tags = concat(module.cluster_metadata.tags, try(coalesce(local.cfg.extra_tags, null), [])) +} + +resource "scaleway_domain_zone" "current" { + count = try(coalesce(local.cfg.disable_default_ingress, null), false) ? 0 : 1 + + project_id = local.cfg.project_id + + domain = local.cfg.base_domain + subdomain = trimsuffix(module.cluster_metadata.fqdn, ".${local.cfg.base_domain}") +} + +resource "scaleway_domain_record" "host" { + count = try(coalesce(local.cfg.disable_default_ingress, null), false) ? 0 : 1 + + dns_zone = scaleway_domain_zone.current[0].id + name = "" + type = "A" + data = scaleway_lb_ip.current[0].ip_address + ttl = 300 +} + +resource "scaleway_domain_record" "wildcard" { + count = try(coalesce(local.cfg.disable_default_ingress, null), false) ? 0 : 1 + + dns_zone = scaleway_domain_zone.current[0].id + name = "*" + type = "A" + data = scaleway_lb_ip.current[0].ip_address + ttl = 300 +} diff --git a/scaleway/cluster/kubeconfig.tf b/scaleway/cluster/kubeconfig.tf new file mode 100644 index 00000000..65810521 --- /dev/null +++ b/scaleway/cluster/kubeconfig.tf @@ -0,0 +1,34 @@ +locals { + kubeconfig = yamlencode({ + apiVersion = "v1" + kind = "Config" + clusters = [ + { + cluster = { + server = scaleway_k8s_cluster.current.kubeconfig[0].host + certificate-authority-data = scaleway_k8s_cluster.current.kubeconfig[0].cluster_ca_certificate + } + name = scaleway_k8s_cluster.current.name + } + ] + users = [ + { + user = { + token = scaleway_k8s_cluster.current.kubeconfig[0].token + } + name = scaleway_k8s_cluster.current.name + } + ] + contexts = [ + { + context = { + cluster = scaleway_k8s_cluster.current.name + user = scaleway_k8s_cluster.current.name + } + name = scaleway_k8s_cluster.current.name + } + ] + current-context = scaleway_k8s_cluster.current.name + preferences = {} + }) +} diff --git a/scaleway/cluster/main.tf b/scaleway/cluster/main.tf new file mode 100644 index 00000000..3fa73866 --- /dev/null +++ b/scaleway/cluster/main.tf @@ -0,0 +1,65 @@ +module "configuration" { + source = "../../common/configuration" + configuration = var.configuration + base_key = var.configuration_base_key +} + +locals { + cfg = module.configuration.merged[terraform.workspace] +} + +module "cluster_metadata" { + source = "../../common/metadata" + + name_prefix = local.cfg.name_prefix + base_domain = local.cfg.base_domain + + provider_name = "scw" + provider_region = local.cfg.region +} + +resource "scaleway_k8s_cluster" "current" { + name = module.cluster_metadata.name + project_id = local.cfg.project_id + region = local.cfg.region + tags = concat(module.cluster_metadata.tags, try(coalesce(local.cfg.extra_tags, null), [])) + + type = "kapsule" + version = local.cfg.cluster_version + cni = try(coalesce(local.cfg.cni, null), "cilium") + + description = local.cfg.description + + private_network_id = scaleway_vpc_private_network.current.id + + delete_additional_resources = try(coalesce(local.cfg.delete_additional_resources, null), false) + + pod_cidr = local.cfg.pod_cidr + service_cidr = local.cfg.service_cidr + + feature_gates = try(coalesce(local.cfg.feature_gates, null), []) + admission_plugins = try(coalesce(local.cfg.admission_plugins, null), []) + + dynamic "autoscaler_config" { + for_each = local.cfg.autoscaler_config != null ? [1] : [] + + content { + disable_scale_down = try(coalesce(local.cfg.autoscaler_config.disable_scale_down, null), false) + scale_down_delay_after_add = local.cfg.autoscaler_config.scale_down_delay_after_add + scale_down_unneeded_time = local.cfg.autoscaler_config.scale_down_unneeded_time + estimator = try(coalesce(local.cfg.autoscaler_config.estimator, null), "binpacking") + expander = try(coalesce(local.cfg.autoscaler_config.expander, null), "random") + ignore_daemonsets_utilization = try(coalesce(local.cfg.autoscaler_config.ignore_daemonsets_utilization, null), false) + balance_similar_node_groups = try(coalesce(local.cfg.autoscaler_config.balance_similar_node_groups, null), false) + expendable_pods_priority_cutoff = local.cfg.autoscaler_config.expendable_pods_priority_cutoff + scale_down_utilization_threshold = local.cfg.autoscaler_config.scale_down_utilization_threshold + max_graceful_termination_sec = local.cfg.autoscaler_config.max_graceful_termination_sec + } + } + + auto_upgrade { + enable = try(coalesce(local.cfg.auto_upgrade.enable, null), true) + maintenance_window_start_hour = try(coalesce(local.cfg.auto_upgrade.maintenance_window_start_hour, null), 3) + maintenance_window_day = try(coalesce(local.cfg.auto_upgrade.maintenance_window_day, null), "any") + } +} diff --git a/scaleway/cluster/network.tf b/scaleway/cluster/network.tf new file mode 100644 index 00000000..b18e8818 --- /dev/null +++ b/scaleway/cluster/network.tf @@ -0,0 +1,21 @@ +resource "scaleway_vpc" "current" { + name = module.cluster_metadata.name + project_id = local.cfg.project_id + region = local.cfg.region + tags = concat(module.cluster_metadata.tags, try(coalesce(local.cfg.extra_tags, null), [])) + + enable_routing = try(coalesce(local.cfg.vpc_enable_routing, null), true) +} + +resource "scaleway_vpc_private_network" "current" { + name = module.cluster_metadata.name + project_id = local.cfg.project_id + region = local.cfg.region + tags = concat(module.cluster_metadata.tags, try(coalesce(local.cfg.extra_tags, null), [])) + + vpc_id = scaleway_vpc.current.id + + ipv4_subnet { + subnet = try(coalesce(local.cfg.private_network_subnet, null), "10.0.0.0/8") + } +} diff --git a/scaleway/cluster/node-pool/main.tf b/scaleway/cluster/node-pool/main.tf new file mode 100644 index 00000000..604958b7 --- /dev/null +++ b/scaleway/cluster/node-pool/main.tf @@ -0,0 +1,64 @@ +module "configuration" { + source = "../../../common/configuration" + configuration = var.configuration + base_key = var.configuration_base_key +} + +locals { + cfg = module.configuration.merged[terraform.workspace] + + taint_tags = [ + for t in try(coalesce(local.cfg.node_taints, null), []) : + "taint=${t.key}=${t.value}:${t.effect}" + ] + + all_tags = concat( + var.cluster_metadata.tags, + try(coalesce(local.cfg.tags, null), []), + local.taint_tags + ) +} + +resource "scaleway_k8s_pool" "current" { + for_each = toset(local.cfg.zones) + + cluster_id = var.cluster.id + name = "${local.cfg.name}-${each.value}" + region = var.cluster.region + zone = each.value + + node_type = try(coalesce(local.cfg.node_type, null), "GP1-XS") + size = try(coalesce(local.cfg.size, null), 1) + min_size = try(coalesce(local.cfg.min_size, null), 1) + max_size = try(coalesce(local.cfg.max_size, null), 2) + + autoscaling = try(coalesce(local.cfg.autoscaling, null), true) + autohealing = try(coalesce(local.cfg.autohealing, null), true) + + container_runtime = try(coalesce(local.cfg.container_runtime, null), "containerd") + root_volume_type = local.cfg.root_volume_type + root_volume_size_in_gb = local.cfg.root_volume_size_in_gb + + public_ip_disabled = try(coalesce(local.cfg.public_ip_disabled, null), false) + + wait_for_pool_ready = try(coalesce(local.cfg.wait_for_pool_ready, null), true) + + kubelet_args = try(coalesce(local.cfg.kubelet_args, null), {}) + + tags = local.all_tags + + dynamic "upgrade_policy" { + for_each = local.cfg.upgrade_policy != null ? [1] : [] + + content { + max_surge = try(coalesce(local.cfg.upgrade_policy.max_surge, null), 0) + max_unavailable = try(coalesce(local.cfg.upgrade_policy.max_unavailable, null), 1) + } + } + + # When autoscaling acts, the size gets changed by Scaleway, but it should not + # be forced back to match the configuration on the next plan. + lifecycle { + ignore_changes = [size] + } +} diff --git a/scaleway/cluster/node-pool/moved.tf b/scaleway/cluster/node-pool/moved.tf new file mode 100644 index 00000000..fe43057a --- /dev/null +++ b/scaleway/cluster/node-pool/moved.tf @@ -0,0 +1 @@ +# No moved blocks required — this is a new module with no prior resource addresses to migrate. diff --git a/scaleway/cluster/node-pool/outputs.tf b/scaleway/cluster/node-pool/outputs.tf new file mode 100644 index 00000000..871c9f88 --- /dev/null +++ b/scaleway/cluster/node-pool/outputs.tf @@ -0,0 +1,3 @@ +output "current_config" { + value = local.cfg +} diff --git a/scaleway/cluster/node-pool/variables.tf b/scaleway/cluster/node-pool/variables.tf new file mode 100644 index 00000000..00fa331b --- /dev/null +++ b/scaleway/cluster/node-pool/variables.tf @@ -0,0 +1,62 @@ +variable "configuration" { + type = map(object({ + name = optional(string) + + node_type = optional(string) + size = optional(number) + min_size = optional(number) + max_size = optional(number) + + autoscaling = optional(bool) + autohealing = optional(bool) + + container_runtime = optional(string) + root_volume_type = optional(string) + root_volume_size_in_gb = optional(number) + + public_ip_disabled = optional(bool) + + zones = optional(list(string)) + + wait_for_pool_ready = optional(bool) + + kubelet_args = optional(map(string)) + + upgrade_policy = optional(object({ + max_surge = optional(number) + max_unavailable = optional(number) + })) + + # node_taints follow the Scaleway tag convention: + # they are converted to "taint=key=value:Effect" tag strings automatically. + node_taints = optional(list(object({ + key = string + value = string + effect = string + }))) + + tags = optional(list(string)) + })) + + description = "Map with per workspace node pool configuration." + nullable = false +} + +variable "configuration_base_key" { + type = string + description = "The key in the configuration map all other keys inherit from." + default = "apps" + nullable = false +} + +variable "cluster" { + type = any + description = "The cluster output from the cluster module." + nullable = false +} + +variable "cluster_metadata" { + type = any + description = "Metadata of the cluster to attach the node pool to." + nullable = false +} diff --git a/scaleway/cluster/node-pool/versions.tf b/scaleway/cluster/node-pool/versions.tf new file mode 100644 index 00000000..10f3abc9 --- /dev/null +++ b/scaleway/cluster/node-pool/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + scaleway = { + source = "scaleway/scaleway" + } + } + required_version = ">= 1.3.0" +} diff --git a/scaleway/cluster/node_pool.tf b/scaleway/cluster/node_pool.tf new file mode 100644 index 00000000..81eefff5 --- /dev/null +++ b/scaleway/cluster/node_pool.tf @@ -0,0 +1,34 @@ +module "node_pool" { + source = "./node-pool" + + cluster = scaleway_k8s_cluster.current + cluster_metadata = module.cluster_metadata + + configuration = { + (terraform.workspace) = { + name = "default" + zones = local.cfg.default_node_pool.zones + + node_type = try(coalesce(local.cfg.default_node_pool.node_type, null), "GP1-XS") + size = try(coalesce(local.cfg.default_node_pool.size, null), 1) + min_size = try(coalesce(local.cfg.default_node_pool.min_size, null), 1) + max_size = try(coalesce(local.cfg.default_node_pool.max_size, null), 2) + autoscaling = try(coalesce(local.cfg.default_node_pool.autoscaling, null), true) + autohealing = try(coalesce(local.cfg.default_node_pool.autohealing, null), true) + + container_runtime = try(coalesce(local.cfg.default_node_pool.container_runtime, null), "containerd") + root_volume_type = try(local.cfg.default_node_pool.root_volume_type, null) + root_volume_size_in_gb = try(local.cfg.default_node_pool.root_volume_size_in_gb, null) + + public_ip_disabled = try(coalesce(local.cfg.default_node_pool.public_ip_disabled, null), false) + + upgrade_policy = try(local.cfg.default_node_pool.upgrade_policy, null) + + kubelet_args = try(local.cfg.default_node_pool.kubelet_args, null) + + node_taints = try(local.cfg.default_node_pool.node_taints, null) + tags = try(local.cfg.default_node_pool.tags, null) + } + } + configuration_base_key = terraform.workspace +} diff --git a/scaleway/cluster/outputs.tf b/scaleway/cluster/outputs.tf new file mode 100644 index 00000000..f65571b2 --- /dev/null +++ b/scaleway/cluster/outputs.tf @@ -0,0 +1,20 @@ +output "cluster" { + value = scaleway_k8s_cluster.current +} + +output "current_config" { + value = local.cfg +} + +output "current_metadata" { + value = module.cluster_metadata +} + +output "kubeconfig" { + sensitive = true + value = local.kubeconfig + + # The cluster kubeconfig is available immediately after cluster creation, but + # the API server DNS entry is not ready until at least one node pool exists. + depends_on = [module.node_pool] +} diff --git a/scaleway/cluster/variables.tf b/scaleway/cluster/variables.tf new file mode 100644 index 00000000..16bac604 --- /dev/null +++ b/scaleway/cluster/variables.tf @@ -0,0 +1,98 @@ +variable "configuration" { + type = map(object({ + # Required attributes + name_prefix = optional(string) + base_domain = optional(string) + region = optional(string) + + # Kubernetes cluster required + cluster_version = optional(string) + cni = optional(string) + + # Optional attributes without defaults + delete_additional_resources = optional(bool) + description = optional(string) + + project_id = optional(string) + + pod_cidr = optional(string) + service_cidr = optional(string) + + feature_gates = optional(list(string)) + admission_plugins = optional(list(string)) + + autoscaler_config = optional(object({ + disable_scale_down = optional(bool) + scale_down_delay_after_add = optional(string) + scale_down_unneeded_time = optional(string) + estimator = optional(string) + expander = optional(string) + ignore_daemonsets_utilization = optional(bool) + balance_similar_node_groups = optional(bool) + expendable_pods_priority_cutoff = optional(number) + scale_down_utilization_threshold = optional(number) + max_graceful_termination_sec = optional(number) + })) + + auto_upgrade = optional(object({ + enable = optional(bool) + maintenance_window_start_hour = optional(number) + maintenance_window_day = optional(string) + })) + + # VPC + vpc_enable_routing = optional(bool) + + # Private network + private_network_subnet = optional(string) + + # Extra tags added to all cloud resources in addition to the metadata tags + extra_tags = optional(list(string)) + + disable_default_ingress = optional(bool) + + default_node_pool = optional(object({ + node_type = optional(string) + size = optional(number) + min_size = optional(number) + max_size = optional(number) + autoscaling = optional(bool) + autohealing = optional(bool) + container_runtime = optional(string) + root_volume_type = optional(string) + root_volume_size_in_gb = optional(number) + public_ip_disabled = optional(bool) + + # Availability zones to deploy the default node pool into. + # One scaleway_k8s_pool is created per zone. + # e.g. ["fr-par-1", "fr-par-2", "fr-par-3"] + zones = optional(list(string)) + + kubelet_args = optional(map(string)) + + upgrade_policy = optional(object({ + max_surge = optional(number) + max_unavailable = optional(number) + })) + + node_taints = optional(list(object({ + key = string + value = string + effect = string + }))) + + # Extra tags added to node pool cloud resources in addition to the metadata tags + tags = optional(list(string)) + })) + })) + + description = "Map with per workspace cluster configuration." + nullable = false +} + +variable "configuration_base_key" { + type = string + description = "The key in the configuration map all other keys inherit from." + default = "apps" + nullable = false +} diff --git a/scaleway/cluster/versions.tf b/scaleway/cluster/versions.tf new file mode 100644 index 00000000..6f311746 --- /dev/null +++ b/scaleway/cluster/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + scaleway = { + source = "scaleway/scaleway" + } + + kubernetes = { + source = "hashicorp/kubernetes" + } + } + required_version = ">= 1.3.0" +} diff --git a/tests/scw_zero_cluster.tf b/tests/scw_zero_cluster.tf new file mode 100644 index 00000000..c32e706e --- /dev/null +++ b/tests/scw_zero_cluster.tf @@ -0,0 +1,31 @@ +module "scw_zero" { + source = "../scaleway/cluster" + + configuration = { + # Settings for Apps-cluster + apps = { + name_prefix = "kbstacctest" + base_domain = "infra.serverwolken.de" + + region = "fr-par" + + cluster_version = "1.32" + cni = "cilium" + + delete_additional_resources = false + + default_node_pool = { + node_type = "GP1-XS" + zones = ["fr-par-1", "fr-par-2", "fr-par-3"] + size = 1 + min_size = 1 + max_size = 2 + autoscaling = true + autohealing = true + } + } + + # Settings for Ops-cluster + ops = {} + } +} diff --git a/tests/scw_zero_node_pools.tf b/tests/scw_zero_node_pools.tf new file mode 100644 index 00000000..ce838d06 --- /dev/null +++ b/tests/scw_zero_node_pools.tf @@ -0,0 +1,32 @@ +module "scw_zero_node_pool" { + source = "../scaleway/cluster/node-pool" + + cluster = module.scw_zero.cluster + cluster_metadata = module.scw_zero.current_metadata + + configuration = { + # Settings for Apps-cluster + apps = { + name = "test1" + + node_type = "GP1-XS" + zones = ["fr-par-1", "fr-par-2", "fr-par-3"] + size = 1 + min_size = 1 + max_size = 2 + autoscaling = true + autohealing = true + + node_taints = [ + { + key = "dedicated" + value = "test" + effect = "NoSchedule" + }, + ] + } + + # Settings for Ops-cluster + ops = {} + } +} diff --git a/tests/scw_zero_providers.tf b/tests/scw_zero_providers.tf new file mode 100644 index 00000000..c3e5aceb --- /dev/null +++ b/tests/scw_zero_providers.tf @@ -0,0 +1,22 @@ +provider "scaleway" { + region = "fr-par" +} + +provider "kustomization" { + alias = "scw_zero" + kubeconfig_raw = module.scw_zero.kubeconfig +} + +locals { + scw_zero_kubeconfig = yamldecode(module.scw_zero.kubeconfig) +} + +provider "kubernetes" { + alias = "scw_zero" + + host = local.scw_zero_kubeconfig["clusters"][0]["cluster"]["server"] + token = local.scw_zero_kubeconfig["users"][0]["user"]["token"] + cluster_ca_certificate = base64decode( + local.scw_zero_kubeconfig["clusters"][0]["cluster"]["certificate-authority-data"] + ) +} diff --git a/tests/versions.tf b/tests/versions.tf index 2c8242de..8164a83c 100644 --- a/tests/versions.tf +++ b/tests/versions.tf @@ -7,6 +7,10 @@ terraform { kustomization = { source = "kbst/kustomization" } + + scaleway = { + source = "scaleway/scaleway" + } } required_version = ">= 0.15"