diff --git a/README.md b/README.md index 3bb51275..9f38ea6a 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,55 @@ raptor import project-type --managed facets/azure --name "My Platform" --- +### Vultr — Preview + +VKE (Vultr Kubernetes Engine) clusters with node pools, Vultr VPC networking, S3-compatible object storage, managed PostgreSQL, and a cloud-agnostic Kubernetes service — plus the shared K8s platform and KubeBlocks datastores. + +**First, create a Vultr API key** at https://my.vultr.com/settings/#settingsapi and add your control plane egress IP to the API access-control allow-list. Full walkthrough: [`project-type/vultr/ONBOARDING.md`](project-type/vultr/ONBOARDING.md). + +**Prompt for Praxis:** + +``` +Import the Vultr project type for me from the facets-modules-redesign repo +(project-type/vultr/project-type.yml) along with its output types. +``` + +**Raptor CLI:** (imports the project type + modules + outputs; no base template — projects start empty) + +```bash +raptor import project-type -f ./project-type/vultr/project-type.yml \ + --modules-dir ./modules --outputs-dir ./outputs +``` + +With custom name: + +```bash +raptor import project-type -f ./project-type/vultr/project-type.yml \ + --modules-dir ./modules --outputs-dir ./outputs --name "Vultr Platform" +``` + +
+What's included + +**Infrastructure** +`Cloud Account (vultr_provider)` `Network/VPC (vultr_vpc)` `VKE Cluster (vke)` `Node Pool (vke)` `Object Storage (vultr)` `Service (k8s)` + +**Managed Datastores** +`PostgreSQL (vultr)` + +**Self-hosted via KubeBlocks** +`PostgreSQL` `MySQL` `MongoDB` `Redis` + +**K8s Platform** +`Helm` `Ingress/NGINX` `cert-manager` `ConfigMap` `Secrets` `PVC` `Access Controls` `Callbacks` `K8s Resources` `Gateway API CRD` `Artifactories` + +**Operators & Monitoring** +`KubeBlocks` `ECK` `Prometheus` `Grafana` `Alert Rules` `Monitoring` + +
+ +--- + ### Linode (Akamai) — Preview LKE (Linode Kubernetes Engine) clusters with node pools, Linode VPC networking, S3-compatible object storage, managed PostgreSQL, and a cloud-agnostic Kubernetes service — plus the shared K8s platform and KubeBlocks datastores. diff --git a/icons/object_storage.svg b/icons/object_storage.svg new file mode 100644 index 00000000..e2c392c5 --- /dev/null +++ b/icons/object_storage.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/cloud_account/vultr_provider/1.0/README.md b/modules/cloud_account/vultr_provider/1.0/README.md new file mode 100644 index 00000000..bc256367 --- /dev/null +++ b/modules/cloud_account/vultr_provider/1.0/README.md @@ -0,0 +1,31 @@ +# Vultr Provider Configuration + +Configures the Vultr Terraform provider with API key authentication for use by other Vultr modules. + +## Overview + +This module establishes the foundational Vultr provider configuration using a Vultr API key. It serves as the base provider configuration that other Vultr infrastructure modules (VPC, VKE, Object Storage, Managed Database) consume to access Vultr APIs and provision resources. It produces the `@facets/vultr_cloud_account` output type. + +## Environment as Dimension + +**Environment-specific provider configuration**: The API key is a secret reference and the default region is an override-only field, allowing different Vultr credentials and regions per environment. + +## Resources Created + +- **Vultr Provider Configuration**: Establishes an authenticated connection to the Vultr API +- **Provider Output Interface**: Exposes the configured provider and default region for consumption by other modules + +## Security Considerations + +- The API key is supplied via a secret reference and is never stored in plain text within the module configuration +- The key is declared in the `secrets` output attribute so the platform treats it as sensitive +- Provider configuration is scoped per environment for security isolation + +## Required Configuration + +- **Vultr API Key**: A Vultr API key with access to the resources you intend to manage (secret reference) +- **Vultr Region**: The default region for downstream resources, e.g. `ewr`, `lax`, `ord`, `fra`, `sgp` (override-only) + +## Usage Notes + +This module does not create any actual Vultr resources — it only configures the provider authentication. Other Vultr modules should consume this module's `@facets/vultr_cloud_account` output to access Vultr services with proper authentication. diff --git a/modules/cloud_account/vultr_provider/1.0/facets.yaml b/modules/cloud_account/vultr_provider/1.0/facets.yaml new file mode 100644 index 00000000..bacaf9c1 --- /dev/null +++ b/modules/cloud_account/vultr_provider/1.0/facets.yaml @@ -0,0 +1,54 @@ +intent: cloud_account +flavor: vultr_provider +version: "1.0" +description: Module to configure and provision resources within a Vultr cloud account +intentDetails: + type: Cloud & Infrastructure + description: Configure and provision resources within a Vultr cloud account + displayName: Cloud Account + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/cloud_account.svg +clouds: + - kubernetes +spec: + title: Vultr Provider Configuration + description: Configure Vultr API access credentials and the default region + type: object + properties: + api_key: + type: string + title: Vultr API Key + description: Vultr API key with access to the resources you intend to manage + x-ui-secret-ref: true + region: + type: string + title: Vultr Region + description: Default Vultr region for downstream resources (e.g. ewr, lax, ord, fra, sgp) + x-ui-placeholder: ewr + x-ui-overrides-only: true + required: + - api_key + - region + x-ui-order: + - api_key + - region +outputs: + default: + type: "@facets/vultr_cloud_account" + providers: + vultr: + source: vultr/vultr + version: 2.31.2 + attributes: + api_key: attributes.api_key +sample: + kind: cloud_account + flavor: vultr_provider + version: "1.0" + disabled: true + spec: + api_key: "" + region: ewr +iac: + validated_files: + - main.tf + - variables.tf diff --git a/modules/cloud_account/vultr_provider/1.0/main.tf b/modules/cloud_account/vultr_provider/1.0/main.tf new file mode 100644 index 00000000..c47a088d --- /dev/null +++ b/modules/cloud_account/vultr_provider/1.0/main.tf @@ -0,0 +1,4 @@ +# Vultr Provider Configuration Module +# This module configures the Vultr provider credentials and exposes them for other modules. +# +# Note: output_attributes and output_interfaces locals are defined in outputs.tf diff --git a/modules/cloud_account/vultr_provider/1.0/outputs.tf b/modules/cloud_account/vultr_provider/1.0/outputs.tf new file mode 100644 index 00000000..372c8487 --- /dev/null +++ b/modules/cloud_account/vultr_provider/1.0/outputs.tf @@ -0,0 +1,9 @@ +locals { + output_attributes = { + api_key = var.instance.spec.api_key + region = var.instance.spec.region + secrets = "[\"api_key\"]" + } + output_interfaces = { + } +} diff --git a/modules/cloud_account/vultr_provider/1.0/variables.tf b/modules/cloud_account/vultr_provider/1.0/variables.tf new file mode 100644 index 00000000..98a1ea0a --- /dev/null +++ b/modules/cloud_account/vultr_provider/1.0/variables.tf @@ -0,0 +1,31 @@ +variable "instance" { + description = "Configures the Vultr Terraform provider with API key authentication" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + api_key = string + region = string + }) + }) +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + }) +} diff --git a/modules/datastore/postgres/vultr/1.0/README.md b/modules/datastore/postgres/vultr/1.0/README.md new file mode 100644 index 00000000..077ab044 --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/README.md @@ -0,0 +1,30 @@ +# Vultr Managed PostgreSQL + +Creates a Vultr managed PostgreSQL database with SSL/TLS and trusted-IP allow-listing. Produces `@facets/postgres`. + +## Overview + +Provisions a Vultr Managed Database running the PostgreSQL engine. The module exposes `writer` and `reader` connection interfaces (both pointing at the primary host; Vultr handles read scaling via separate read replicas). The database is protected from accidental deletion with `prevent_destroy`. + +## Resources Created + +- **vultr_database**: A managed PostgreSQL cluster (`database_engine = "pg"`) + +## Required Configuration + +- **PostgreSQL Version**: one of `13`, `14`, `15`, `16`, `17` +- **Database Plan** (`sizing.plan`): a Vultr managed-database plan slug encoding vCPU/RAM/disk and node count, e.g. `vultr-dbaas-hobbyist-cc-1-25-1` (list via `GET /v2/databases/plans`) +- **Trusted IPs** (optional): CIDRs permitted to connect; empty denies all external access + +## Inputs + +- **Vultr Cloud Account** (`@facets/vultr_cloud_account`): provides the Vultr provider and region + +## Outputs + +- `@facets/postgres` (default): `writer` and `reader` interfaces with `host`, `port`, `username`, `password`, `connection_string` + +## Notes & Gotchas + +- **Trusted IPs**: the database is reachable only from CIDRs in `network_access.trusted_ips`. Add your VKE node egress CIDRs to allow the cluster to connect. +- **SSL required**: connection strings include `sslmode=require`. diff --git a/modules/datastore/postgres/vultr/1.0/facets.yaml b/modules/datastore/postgres/vultr/1.0/facets.yaml new file mode 100644 index 00000000..7d8fc3a6 --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/facets.yaml @@ -0,0 +1,99 @@ +intent: postgres +flavor: vultr +version: "1.0" +clouds: + - kubernetes +description: Managed PostgreSQL database on Vultr with SSL/TLS and trusted-IP allow-listing +intentDetails: + type: Datastores + description: Vultr managed PostgreSQL database service + displayName: PostgreSQL + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/postgres.svg +spec: + title: Vultr Managed PostgreSQL Database + description: Managed PostgreSQL database on Vultr + type: object + x-ui-order: + - version_config + - sizing + - network_access + properties: + version_config: + type: object + title: Version Configuration + x-ui-order: + - version + properties: + version: + type: string + title: PostgreSQL Version + description: PostgreSQL engine major version + enum: + - "13" + - "14" + - "15" + - "16" + - "17" + default: "16" + required: + - version + sizing: + type: object + title: Sizing & Performance + x-ui-order: + - plan + properties: + plan: + type: string + title: Database Plan + description: "Vultr managed database plan slug (encodes vCPU/RAM/disk and node count, e.g. vultr-dbaas-hobbyist-cc-1-25-1). List plans via the Vultr API (GET /v2/databases/plans)." + default: vultr-dbaas-hobbyist-cc-1-25-1 + x-ui-placeholder: vultr-dbaas-hobbyist-cc-1-25-1 + required: + - plan + network_access: + type: object + title: Network Access + properties: + trusted_ips: + type: array + title: Trusted IPs + description: List of CIDRs permitted to connect (empty denies all external access) + default: [] + items: + type: string + pattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ + required: [] + required: + - version_config + - sizing +inputs: + vultr_cloud_account: + type: "@facets/vultr_cloud_account" + optional: false + displayName: Vultr Cloud Account + description: Vultr provider configuration and default region + providers: + - vultr +outputs: + default: + type: "@facets/postgres" + title: Vultr PostgreSQL Database +sample: + kind: postgres + flavor: vultr + version: "1.0" + disabled: true + spec: + version_config: + version: "16" + sizing: + plan: vultr-dbaas-hobbyist-cc-1-25-1 + network_access: + trusted_ips: [] +iac: + validated_files: + - main.tf + - variables.tf + - outputs.tf + - versions.tf diff --git a/modules/datastore/postgres/vultr/1.0/main.tf b/modules/datastore/postgres/vultr/1.0/main.tf new file mode 100644 index 00000000..247d6ba2 --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/main.tf @@ -0,0 +1,27 @@ +# Vultr Managed PostgreSQL Module +# Creates a Vultr managed PostgreSQL database. + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "postgres" +} + +locals { + region = var.inputs.vultr_cloud_account.attributes.region +} + +resource "vultr_database" "db" { + label = module.name.name + database_engine = "pg" + database_engine_version = var.instance.spec.version_config.version + region = local.region + plan = var.instance.spec.sizing.plan + trusted_ips = var.instance.spec.network_access.trusted_ips + + lifecycle { + prevent_destroy = true + } +} diff --git a/modules/datastore/postgres/vultr/1.0/outputs.tf b/modules/datastore/postgres/vultr/1.0/outputs.tf new file mode 100644 index 00000000..5217b1eb --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/outputs.tf @@ -0,0 +1,26 @@ +locals { + # Vultr managed PostgreSQL exposes a single primary host; read scaling is handled via + # separate read replicas, so reader and writer share the primary endpoint here. + # Connection strings use format() rather than "${...}" interpolation so the contract + # validator's brace-counting HCL parser can discover the writer/reader interface keys. + output_attributes = {} + + output_interfaces = { + writer = { + host = vultr_database.db.host + port = tostring(vultr_database.db.port) + username = vultr_database.db.user + password = vultr_database.db.password + connection_string = format("postgresql://%s:%s@%s:%s/%s?sslmode=require", vultr_database.db.user, vultr_database.db.password, vultr_database.db.host, tostring(vultr_database.db.port), vultr_database.db.dbname) + secrets = ["password", "connection_string"] + } + reader = { + host = vultr_database.db.host + port = tostring(vultr_database.db.port) + username = vultr_database.db.user + password = vultr_database.db.password + connection_string = format("postgresql://%s:%s@%s:%s/%s?sslmode=require", vultr_database.db.user, vultr_database.db.password, vultr_database.db.host, tostring(vultr_database.db.port), vultr_database.db.dbname) + secrets = ["password", "connection_string"] + } + } +} diff --git a/modules/datastore/postgres/vultr/1.0/variables.tf b/modules/datastore/postgres/vultr/1.0/variables.tf new file mode 100644 index 00000000..1ed8cf13 --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/variables.tf @@ -0,0 +1,50 @@ +variable "instance" { + description = "Managed PostgreSQL database on Vultr with SSL/TLS and trusted-IP allow-listing" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + version_config = object({ + version = string + }) + sizing = object({ + plan = string + }) + network_access = optional(object({ + trusted_ips = optional(list(string), []) + }), {}) + }) + }) + + validation { + condition = contains(["13", "14", "15", "16", "17"], var.instance.spec.version_config.version) + error_message = "PostgreSQL version must be one of: 13, 14, 15, 16, 17." + } +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + cloud_tags = optional(map(string), {}) + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + vultr_cloud_account = object({ + attributes = object({ + api_key = string + region = string + }) + }) + }) +} diff --git a/modules/datastore/postgres/vultr/1.0/versions.tf b/modules/datastore/postgres/vultr/1.0/versions.tf new file mode 100644 index 00000000..1468f2c6 --- /dev/null +++ b/modules/datastore/postgres/vultr/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + vultr = { + source = "vultr/vultr" + version = ">= 2.0.0" + } + } +} diff --git a/modules/kubernetes_cluster/vke/1.0/README.md b/modules/kubernetes_cluster/vke/1.0/README.md new file mode 100644 index 00000000..2ec3146a --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/README.md @@ -0,0 +1,29 @@ +# Vultr Kubernetes Engine (VKE) Cluster + +Creates a VKE cluster with a default node pool and exposes the Kubernetes and Helm providers for downstream modules. Produces `@facets/vke` (cluster attributes) and `@facets/kubernetes-details` (generic attributes + provider configuration). + +## Overview + +VKE requires at least one node pool at creation, so this module provisions a configurable default pool alongside the control plane. Additional pools can be added with the `kubernetes_node_pool/vke` module. The Kubernetes and Helm providers are configured from the cluster kubeconfig using **client-certificate/key authentication** (Vultr VKE does not issue a static bearer token). + +## Resources Created + +- **vultr_kubernetes**: The managed Kubernetes control plane plus an inline default node pool + +## Required Configuration + +- **Kubernetes Version**: A Vultr VKE version string including the build suffix, e.g. `v1.35.0+1` (list current values via `GET /v2/kubernetes/versions`) +- **Default Node Pool**: `node_type` (Vultr plan, e.g. `vc2-2c-4gb`) and `node_count` +- **High Availability** (optional): HA control plane (additional cost), default off +- **Enable Managed Firewall** (optional): provision a Vultr-managed firewall group for the nodes, default off +- **Autoscaler** (optional): enable per-pool autoscaling with min/max + +## Inputs + +- **Vultr Cloud Account** (`@facets/vultr_cloud_account`): provides the Vultr provider and region +- **Vultr VPC** (`@facets/vultr-vpc-details`, optional): when wired, the cluster nodes are placed in the VPC (`vpc_id`) and the cluster region is taken from the VPC. VKE supports the original VPC only. + +## Outputs + +- `@facets/vke` (default): `cluster_id`, `cluster_name`, `cluster_endpoint`, `region`, `k8s_version`, `status`, `cluster_subnet`, `kubeconfig`, plus `cluster_ca_certificate`/`client_certificate`/`client_key` +- `@facets/kubernetes-details` (attributes): generic cluster details plus configured `kubernetes` and `helm` providers diff --git a/modules/kubernetes_cluster/vke/1.0/facets.yaml b/modules/kubernetes_cluster/vke/1.0/facets.yaml new file mode 100644 index 00000000..ea9decd1 --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/facets.yaml @@ -0,0 +1,162 @@ +intent: kubernetes_cluster +flavor: vke +version: "1.0" +clouds: + - kubernetes +description: Creates a Vultr Kubernetes Engine (VKE) cluster with a default node pool, exposing Kubernetes and Helm providers +intentDetails: + type: Cloud & Infrastructure + description: Vultr Kubernetes Engine (VKE) cluster with provider configuration + displayName: Kubernetes Cluster + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/kubernetes_cluster.svg +spec: + title: Vultr Kubernetes Engine Cluster + description: Configure a VKE cluster and its default node pool + type: object + x-ui-order: + - k8s_version + - high_availability + - enable_firewall + - default_pool + properties: + k8s_version: + type: string + title: Kubernetes Version + description: "VKE Kubernetes version, including the build suffix (e.g. v1.35.0+1). Vultr version strings are volatile; list current values with the Vultr API (GET /v2/kubernetes/versions)." + default: "v1.35.0+1" + x-ui-placeholder: "v1.35.0+1" + pattern: ^v[0-9]+\.[0-9]+\.[0-9]+\+[0-9]+$ + high_availability: + type: boolean + title: High Availability Control Plane + description: Enable the HA control plane (additional cost). Recommended for production. + default: false + enable_firewall: + type: boolean + title: Enable Managed Firewall + description: Provision a Vultr-managed firewall group for the cluster nodes + default: false + default_pool: + type: object + title: Default Node Pool + x-ui-order: + - node_type + - node_count + - autoscaler + properties: + node_type: + type: string + title: Node Plan + description: Vultr compute plan for the default node pool + enum: + - vc2-1c-2gb + - vc2-2c-4gb + - vc2-4c-8gb + - vc2-6c-16gb + - vhf-1c-2gb + - vhf-2c-4gb + - vhf-4c-8gb + default: vc2-2c-4gb + node_count: + type: integer + title: Node Count + description: Number of nodes in the default pool (initial count when autoscaling) + minimum: 1 + maximum: 100 + default: 3 + autoscaler: + type: object + title: Autoscaler + x-ui-toggle: true + properties: + enabled: + type: boolean + title: Enable Autoscaling + default: false + min: + type: integer + title: Minimum Nodes + minimum: 1 + default: 1 + max: + type: integer + title: Maximum Nodes + minimum: 1 + default: 5 + required: + - node_type + - node_count + required: + - k8s_version + - default_pool +inputs: + vultr_cloud_account: + type: "@facets/vultr_cloud_account" + optional: false + displayName: Vultr Cloud Account + description: Vultr provider configuration and default region + providers: + - vultr + network: + type: "@facets/vultr-vpc-details" + optional: true + displayName: Vultr VPC + description: Optional VPC to place the cluster nodes in. When wired, the cluster region is taken from the VPC. VKE supports the original VPC only (not VPC 2.0), and both must share a region. + providers: [] +outputs: + default: + type: "@facets/vke" + title: VKE Cluster Attributes + description: Vultr Kubernetes Engine cluster attributes without provider configuration + attributes: + type: "@facets/kubernetes-details" + title: Kubernetes Cluster Output + description: Generic Kubernetes cluster attributes with Kubernetes and Helm provider configuration + providers: + kubernetes: + source: hashicorp/kubernetes + version: 2.38.0 + attributes: + host: cluster_endpoint + cluster_ca_certificate: cluster_ca_certificate + client_certificate: client_certificate + client_key: client_key + kubernetes-alpha: + source: hashicorp/kubernetes-alpha + version: 0.6.0 + attributes: + host: cluster_endpoint + cluster_ca_certificate: cluster_ca_certificate + client_certificate: client_certificate + client_key: client_key + helm: + source: hashicorp/helm + version: 2.17.0 + attributes: + kubernetes: + host: cluster_endpoint + cluster_ca_certificate: cluster_ca_certificate + client_certificate: client_certificate + client_key: client_key +sample: + kind: kubernetes_cluster + flavor: vke + version: "1.0" + disabled: true + spec: + k8s_version: "v1.35.0+1" + high_availability: false + enable_firewall: false + default_pool: + node_type: vc2-2c-4gb + node_count: 3 + autoscaler: + enabled: false + min: 1 + max: 5 +iac: + validated_files: + - main.tf + - variables.tf + - outputs.tf + - versions.tf diff --git a/modules/kubernetes_cluster/vke/1.0/main.tf b/modules/kubernetes_cluster/vke/1.0/main.tf new file mode 100644 index 00000000..0a65fe0a --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/main.tf @@ -0,0 +1,59 @@ +# Vultr Kubernetes Engine (VKE) Cluster Module +# Creates a VKE cluster with a default node pool and exposes Kubernetes + Helm providers. + +locals { + # When a VPC is wired, the cluster region must match the VPC's region; otherwise fall + # back to the linked cloud account's default region. VKE supports the original VPC only. + region = coalesce(try(var.inputs.network.attributes.region, null), var.inputs.vultr_cloud_account.attributes.region) + vpc_id = try(var.inputs.network.attributes.vpc_id, null) + + autoscaler_enabled = try(var.instance.spec.default_pool.autoscaler.enabled, false) + + # Parse the cluster kubeconfig (base64-encoded YAML) to derive provider connection + # details. Vultr VKE authenticates with client certificate/key (not a bearer token). + # At plan time these resolve to unknown values, which is expected. + kubeconfig_decoded = try(yamldecode(base64decode(vultr_kubernetes.cluster.kube_config)), {}) + kubeconfig_server = try(local.kubeconfig_decoded["clusters"][0]["cluster"]["server"], "") + cluster_ca_certificate = try(base64decode(local.kubeconfig_decoded["clusters"][0]["cluster"]["certificate-authority-data"]), "") + client_certificate = try(base64decode(local.kubeconfig_decoded["users"][0]["user"]["client-certificate-data"]), "") + client_key = try(base64decode(local.kubeconfig_decoded["users"][0]["user"]["client-key-data"]), "") + + # Prefer the fully-qualified API server URL from the kubeconfig; fall back to the + # cluster endpoint attribute with the standard https scheme and port. + cluster_endpoint = local.kubeconfig_server != "" ? local.kubeconfig_server : "https://${vultr_kubernetes.cluster.endpoint}:6443" +} + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "kubernetes_cluster" + globally_unique = true +} + +resource "vultr_kubernetes" "cluster" { + label = module.name.name + region = local.region + version = var.instance.spec.k8s_version + ha_controlplanes = var.instance.spec.high_availability + enable_firewall = var.instance.spec.enable_firewall + + # Optional VPC placement (null when no network is wired). + vpc_id = local.vpc_id + + # VKE requires at least one node pool defined inline at cluster creation. + node_pools { + label = "default" + plan = var.instance.spec.default_pool.node_type + node_quantity = var.instance.spec.default_pool.node_count + auto_scaler = local.autoscaler_enabled + min_nodes = local.autoscaler_enabled ? var.instance.spec.default_pool.autoscaler.min : null + max_nodes = local.autoscaler_enabled ? var.instance.spec.default_pool.autoscaler.max : null + } + + lifecycle { + # When autoscaling is enabled, the node count drifts and should not be reconciled. + ignore_changes = [node_pools[0].node_quantity] + } +} diff --git a/modules/kubernetes_cluster/vke/1.0/outputs.tf b/modules/kubernetes_cluster/vke/1.0/outputs.tf new file mode 100644 index 00000000..99d0e48a --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/outputs.tf @@ -0,0 +1,30 @@ +locals { + output_attributes = { + cluster_id = tostring(vultr_kubernetes.cluster.id) + cluster_name = vultr_kubernetes.cluster.label + cluster_endpoint = local.cluster_endpoint + cluster_location = local.region + cloud_provider = "VULTR" + region = local.region + k8s_version = vultr_kubernetes.cluster.version + status = vultr_kubernetes.cluster.status + cluster_subnet = vultr_kubernetes.cluster.cluster_subnet + kubeconfig = vultr_kubernetes.cluster.kube_config + + # Derived from the kubeconfig for the Kubernetes/Helm provider configuration. + cluster_ca_certificate = local.cluster_ca_certificate + client_certificate = local.client_certificate + client_key = local.client_key + + secrets = ["kubeconfig", "client_certificate", "client_key", "cluster_ca_certificate"] + } + + output_interfaces = { + kubernetes = { + host = local.cluster_endpoint + cluster_ca_certificate = local.cluster_ca_certificate + client_certificate = local.client_certificate + client_key = local.client_key + } + } +} diff --git a/modules/kubernetes_cluster/vke/1.0/variables.tf b/modules/kubernetes_cluster/vke/1.0/variables.tf new file mode 100644 index 00000000..b30ee662 --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/variables.tf @@ -0,0 +1,64 @@ +variable "instance" { + description = "Creates a Vultr Kubernetes Engine (VKE) cluster with a default node pool" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + k8s_version = string + high_availability = optional(bool, false) + enable_firewall = optional(bool, false) + default_pool = object({ + node_type = string + node_count = number + autoscaler = optional(object({ + enabled = optional(bool, false) + min = optional(number, 1) + max = optional(number, 5) + }), {}) + }) + }) + }) + + validation { + condition = can(regex("^v[0-9]+\\.[0-9]+\\.[0-9]+\\+[0-9]+$", var.instance.spec.k8s_version)) + error_message = "k8s_version must be a Vultr VKE version string like v1.35.0+1." + } + + validation { + condition = var.instance.spec.default_pool.node_count >= 1 + error_message = "default_pool.node_count must be at least 1." + } +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + cloud_tags = optional(map(string), {}) + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + vultr_cloud_account = object({ + attributes = object({ + api_key = string + region = string + }) + }) + network = optional(object({ + attributes = object({ + vpc_id = optional(string) + region = optional(string) + }) + })) + }) +} diff --git a/modules/kubernetes_cluster/vke/1.0/versions.tf b/modules/kubernetes_cluster/vke/1.0/versions.tf new file mode 100644 index 00000000..1468f2c6 --- /dev/null +++ b/modules/kubernetes_cluster/vke/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + vultr = { + source = "vultr/vultr" + version = ">= 2.0.0" + } + } +} diff --git a/modules/kubernetes_node_pool/vke/1.0/README.md b/modules/kubernetes_node_pool/vke/1.0/README.md new file mode 100644 index 00000000..4e4f5d53 --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/README.md @@ -0,0 +1,27 @@ +# Vultr Kubernetes Engine (VKE) Node Pool + +Adds and manages an additional node pool on an existing VKE cluster. Produces `@facets/kubernetes_nodepool`. + +## Overview + +Provisions a standalone VKE node pool with optional autoscaling, labels, and taints, attached to a cluster created by `kubernetes_cluster/vke`. + +## Resources Created + +- **vultr_kubernetes_node_pools**: An additional node pool on the target cluster + +## Required Configuration + +- **Node Plan** (`node_type`): Vultr compute plan, e.g. `vc2-2c-4gb` +- **Node Count**: number of nodes (initial count when autoscaling) +- **Autoscaler** (optional): enable autoscaling with min/max +- **Labels / Taints** (optional): Kubernetes node labels and taints applied to the pool + +## Inputs + +- **Vultr Cloud Account** (`@facets/vultr_cloud_account`): provides the Vultr provider +- **VKE Cluster** (`@facets/vke`): the cluster to add the node pool to (defaults to the `default` kubernetes_cluster resource) + +## Outputs + +- `@facets/kubernetes_nodepool` (default): `node_pool_name`, `node_class_name`, `taints`, `node_selector` diff --git a/modules/kubernetes_node_pool/vke/1.0/facets.yaml b/modules/kubernetes_node_pool/vke/1.0/facets.yaml new file mode 100644 index 00000000..edc39045 --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/facets.yaml @@ -0,0 +1,151 @@ +intent: kubernetes_node_pool +flavor: vke +version: "1.0" +clouds: + - kubernetes +description: Creates and manages an additional node pool on a Vultr Kubernetes Engine (VKE) cluster +intentDetails: + type: Cloud & Infrastructure + description: Vultr Kubernetes Engine node pool with optional autoscaling + displayName: Kubernetes Node Pool + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/kubernetes_node_pool.svg +spec: + title: Vultr Kubernetes Engine Node Pool + description: Configure an additional VKE node pool + type: object + x-ui-order: + - node_type + - node_count + - autoscaler + - labels + - taints + properties: + node_type: + type: string + title: Node Plan + description: Vultr compute plan for the node pool + enum: + - vc2-1c-2gb + - vc2-2c-4gb + - vc2-4c-8gb + - vc2-6c-16gb + - vhf-1c-2gb + - vhf-2c-4gb + - vhf-4c-8gb + default: vc2-2c-4gb + node_count: + type: integer + title: Node Count + description: Number of nodes (initial count when autoscaling is enabled) + minimum: 1 + maximum: 100 + default: 2 + autoscaler: + type: object + title: Autoscaler + x-ui-toggle: true + properties: + enabled: + type: boolean + title: Enable Autoscaling + default: false + min: + type: integer + title: Minimum Nodes + minimum: 1 + default: 1 + max: + type: integer + title: Maximum Nodes + minimum: 1 + default: 5 + labels: + type: object + title: Node Labels + description: Labels applied to all nodes in the pool + default: {} + patternProperties: + ".*": + type: object + properties: + key: + type: string + title: Label Key + description: Label key + value: + type: string + title: Label Value + description: Label value + required: + - key + - value + taints: + type: object + title: Node Taints + description: "Map of taints keyed by taint key. Eg: my-key: {value: 'true', effect: NoSchedule}" + x-ui-yaml-editor: true + default: {} + patternProperties: + ".*": + type: object + properties: + value: + type: string + title: Taint Value + description: Taint value + default: "" + effect: + type: string + title: Taint Effect + description: Taint effect + enum: + - NoSchedule + - PreferNoSchedule + - NoExecute + required: + - effect + required: + - node_type + - node_count +inputs: + vultr_cloud_account: + type: "@facets/vultr_cloud_account" + optional: false + displayName: Vultr Cloud Account + description: Vultr provider configuration + providers: + - vultr + kubernetes_cluster: + type: "@facets/vke" + optional: false + displayName: VKE Cluster + description: The VKE cluster to add the node pool to + default: + resource_type: kubernetes_cluster + resource_name: default + providers: [] +outputs: + default: + type: "@facets/kubernetes_nodepool" + title: Kubernetes Node Pool + description: Node pool attributes including name, taints, and node selector +sample: + kind: kubernetes_node_pool + flavor: vke + version: "1.0" + disabled: true + spec: + node_type: vc2-2c-4gb + node_count: 2 + autoscaler: + enabled: false + min: 1 + max: 5 + labels: {} + taints: {} +iac: + validated_files: + - main.tf + - variables.tf + - outputs.tf + - versions.tf diff --git a/modules/kubernetes_node_pool/vke/1.0/main.tf b/modules/kubernetes_node_pool/vke/1.0/main.tf new file mode 100644 index 00000000..d8b3200d --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/main.tf @@ -0,0 +1,50 @@ +# Vultr Kubernetes Engine (VKE) Node Pool Module +# Adds an additional node pool to an existing VKE cluster. + +locals { + autoscaler_enabled = try(var.instance.spec.autoscaler.enabled, false) + node_selector = { for k, v in var.instance.spec.labels : v.key => v.value } + # Vultr cluster IDs are UUID strings (no numeric conversion needed). + cluster_id = var.inputs.kubernetes_cluster.attributes.cluster_id +} + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "nodepool" +} + +resource "vultr_kubernetes_node_pools" "pool" { + cluster_id = local.cluster_id + label = module.name.name + plan = var.instance.spec.node_type + node_quantity = var.instance.spec.node_count + tag = var.instance_name + auto_scaler = local.autoscaler_enabled + min_nodes = local.autoscaler_enabled ? var.instance.spec.autoscaler.min : null + max_nodes = local.autoscaler_enabled ? var.instance.spec.autoscaler.max : null + + dynamic "labels" { + for_each = var.instance.spec.labels + content { + key = labels.value.key + value = labels.value.value + } + } + + dynamic "taints" { + for_each = var.instance.spec.taints + content { + key = taints.key + value = taints.value.value + effect = taints.value.effect + } + } + + lifecycle { + # When autoscaling is enabled the node count drifts and should not be reconciled. + ignore_changes = [node_quantity] + } +} diff --git a/modules/kubernetes_node_pool/vke/1.0/outputs.tf b/modules/kubernetes_node_pool/vke/1.0/outputs.tf new file mode 100644 index 00000000..f2ecac0c --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/outputs.tf @@ -0,0 +1,20 @@ +locals { + # Built in a local (not inline) so the output_attributes block stays free of + # for-comprehensions, which the contract validator's HCL parser cannot descend into. + taints_out = [ + for key, taint in var.instance.spec.taints : { + key = key + value = taint.value + effect = taint.effect + } + ] + + output_attributes = { + node_pool_name = tostring(vultr_kubernetes_node_pools.pool.id) + node_class_name = module.name.name + taints = local.taints_out + node_selector = local.node_selector + } + + output_interfaces = {} +} diff --git a/modules/kubernetes_node_pool/vke/1.0/variables.tf b/modules/kubernetes_node_pool/vke/1.0/variables.tf new file mode 100644 index 00000000..cdee38d0 --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/variables.tf @@ -0,0 +1,63 @@ +variable "instance" { + description = "Creates and manages an additional node pool on a Vultr Kubernetes Engine (VKE) cluster" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + node_type = string + node_count = number + autoscaler = optional(object({ + enabled = optional(bool, false) + min = optional(number, 1) + max = optional(number, 5) + }), {}) + labels = optional(map(object({ + key = string + value = string + })), {}) + taints = optional(map(object({ + value = optional(string, "") + effect = string + })), {}) + }) + }) + + validation { + condition = var.instance.spec.node_count >= 1 + error_message = "node_count must be at least 1." + } +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + cloud_tags = optional(map(string), {}) + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + vultr_cloud_account = object({ + attributes = object({ + api_key = string + region = string + }) + }) + kubernetes_cluster = object({ + attributes = object({ + cluster_id = string + cluster_name = optional(string) + region = optional(string) + }) + }) + }) +} diff --git a/modules/kubernetes_node_pool/vke/1.0/versions.tf b/modules/kubernetes_node_pool/vke/1.0/versions.tf new file mode 100644 index 00000000..1468f2c6 --- /dev/null +++ b/modules/kubernetes_node_pool/vke/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + vultr = { + source = "vultr/vultr" + version = ">= 2.0.0" + } + } +} diff --git a/modules/network/vultr_vpc/1.0/README.md b/modules/network/vultr_vpc/1.0/README.md new file mode 100644 index 00000000..c0b9ad8c --- /dev/null +++ b/modules/network/vultr_vpc/1.0/README.md @@ -0,0 +1,26 @@ +# Vultr VPC Network + +Creates a Vultr (legacy) VPC private network for Kubernetes nodes and managed services. Produces `@facets/vultr-vpc-details`. + +## Overview + +Provisions a Vultr VPC with a private IPv4 range that downstream VKE clusters and managed databases attach to for private connectivity. + +> **Important:** Vultr Kubernetes Engine (VKE) is only compatible with the **original VPC** (`vultr_vpc`), not VPC 2.0 (`vultr_vpc2`, which is additionally deprecated in the provider). This module therefore uses `vultr_vpc`. An existing VPC can only be attached to a *new* VKE cluster, and both must share the same region. + +## Resources Created + +- **vultr_vpc**: A private network defined by its network address (`v4_subnet`) and subnet mask (`v4_subnet_mask`) + +## Required Configuration + +- **Subnet CIDR**: CIDR block for the VPC, e.g. `10.0.0.0/24` (split into network address + mask) +- **Region** (optional): defaults to the cloud account's region when not set + +## Inputs + +- **Vultr Cloud Account** (`@facets/vultr_cloud_account`): provides the Vultr provider and default region + +## Outputs + +- `@facets/vultr-vpc-details` (default): `vpc_id`, `vpc_description`, `region`, `ip_block`, `prefix_length`, `subnet_cidr` diff --git a/modules/network/vultr_vpc/1.0/facets.yaml b/modules/network/vultr_vpc/1.0/facets.yaml new file mode 100644 index 00000000..79e530ff --- /dev/null +++ b/modules/network/vultr_vpc/1.0/facets.yaml @@ -0,0 +1,59 @@ +intent: network +flavor: vultr_vpc +version: "1.0" +clouds: + - kubernetes +description: Creates a Vultr VPC 2.0 private network for Kubernetes and managed services +intentDetails: + type: Cloud & Infrastructure + description: Vultr VPC 2.0 network infrastructure for Kubernetes and managed services + displayName: Network + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/network.svg +spec: + title: Vultr VPC 2.0 Network + description: Configure a Vultr VPC 2.0 private network and its IP block + type: object + properties: + region: + type: string + title: Region + description: Vultr region for the VPC (defaults to the cloud account region if left empty) + x-ui-placeholder: ewr + x-ui-overrides-only: true + subnet_cidr: + type: string + title: Subnet CIDR + description: CIDR block for the VPC 2.0 private network (e.g. 10.0.0.0/24) + default: "10.0.0.0/24" + x-ui-overrides-only: true + required: + - subnet_cidr + x-ui-order: + - region + - subnet_cidr +inputs: + vultr_cloud_account: + type: "@facets/vultr_cloud_account" + optional: false + displayName: Vultr Cloud Account + description: Vultr provider configuration + providers: + - vultr +outputs: + default: + type: "@facets/vultr-vpc-details" + title: Vultr VPC Network Details +sample: + kind: network + flavor: vultr_vpc + version: "1.0" + disabled: true + spec: + region: ewr + subnet_cidr: "10.0.0.0/24" +iac: + validated_files: + - main.tf + - variables.tf + - outputs.tf + - versions.tf diff --git a/modules/network/vultr_vpc/1.0/main.tf b/modules/network/vultr_vpc/1.0/main.tf new file mode 100644 index 00000000..e931335a --- /dev/null +++ b/modules/network/vultr_vpc/1.0/main.tf @@ -0,0 +1,32 @@ +# Vultr VPC Network Module +# Creates a Vultr (legacy) VPC private network for Kubernetes nodes and managed services. +# +# NOTE: Vultr Kubernetes Engine (VKE) is only compatible with the original VPC +# (vultr_vpc), NOT VPC 2.0 (vultr_vpc2). vultr_vpc2 is also deprecated in the +# provider. The VPC defines its private IP range directly (no separate subnet +# resource); the cluster and the VPC must share a region. + +locals { + # Region falls back to the cloud account's default region when not overridden on the spec. + region = coalesce(try(var.instance.spec.region, null), var.inputs.vultr_cloud_account.attributes.region) + + # Vultr VPC takes the network address and subnet mask (prefix length) separately. + cidr_parts = split("/", var.instance.spec.subnet_cidr) + ip_block = local.cidr_parts[0] + prefix_length = tonumber(local.cidr_parts[1]) +} + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 64 + resource_name = var.instance_name + resource_type = "vpc" +} + +resource "vultr_vpc" "main" { + region = local.region + description = module.name.name + v4_subnet = local.ip_block + v4_subnet_mask = local.prefix_length +} diff --git a/modules/network/vultr_vpc/1.0/outputs.tf b/modules/network/vultr_vpc/1.0/outputs.tf new file mode 100644 index 00000000..bb002f9b --- /dev/null +++ b/modules/network/vultr_vpc/1.0/outputs.tf @@ -0,0 +1,12 @@ +locals { + output_attributes = { + vpc_id = tostring(vultr_vpc.main.id) + vpc_description = vultr_vpc.main.description + region = local.region + ip_block = vultr_vpc.main.v4_subnet + prefix_length = tostring(vultr_vpc.main.v4_subnet_mask) + subnet_cidr = "${vultr_vpc.main.v4_subnet}/${vultr_vpc.main.v4_subnet_mask}" + } + output_interfaces = { + } +} diff --git a/modules/network/vultr_vpc/1.0/variables.tf b/modules/network/vultr_vpc/1.0/variables.tf new file mode 100644 index 00000000..9d08f294 --- /dev/null +++ b/modules/network/vultr_vpc/1.0/variables.tf @@ -0,0 +1,43 @@ +variable "instance" { + description = "Creates a Vultr VPC 2.0 private network for Kubernetes and managed services" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + region = optional(string) + subnet_cidr = string + }) + }) + + validation { + condition = can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$", var.instance.spec.subnet_cidr)) + error_message = "subnet_cidr must be a valid CIDR block (e.g., 10.0.0.0/24)." + } +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + cloud_tags = optional(map(string), {}) + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + vultr_cloud_account = object({ + attributes = object({ + api_key = string + region = string + }) + }) + }) +} diff --git a/modules/network/vultr_vpc/1.0/versions.tf b/modules/network/vultr_vpc/1.0/versions.tf new file mode 100644 index 00000000..1468f2c6 --- /dev/null +++ b/modules/network/vultr_vpc/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + vultr = { + source = "vultr/vultr" + version = ">= 2.0.0" + } + } +} diff --git a/modules/object_storage/vultr/1.0/README.md b/modules/object_storage/vultr/1.0/README.md new file mode 100644 index 00000000..14eb8696 --- /dev/null +++ b/modules/object_storage/vultr/1.0/README.md @@ -0,0 +1,29 @@ +# Vultr Object Storage + +Creates an S3-compatible object storage subscription on Vultr and exposes its endpoint and access credentials. Produces `@facets/vultr-object-storage`. + +## Overview + +Unlike AWS S3 or Linode object storage (where the module creates a *bucket*), a Vultr object storage subscription is a **whole S3 endpoint** with its own access/secret key pair. Buckets are created against it via the S3 API by the consuming application. The subscription is region-scoped via the object storage cluster, resolved from the requested region. + +## Resources Created + +- **vultr_object_storage**: An S3-compatible object storage subscription (endpoint + keys) +- **data.vultr_object_storage_cluster**: Resolves the object storage cluster for the region + +## Required Configuration + +- **Region**: Vultr object storage region selecting the cluster, e.g. `ewr`, `ord`, `ams` +- **Storage Tier** (`tier_id`, optional): object storage tier ID (1 = Standard); list via `GET /v2/object-storage/tiers` + +## Inputs + +- **Vultr Cloud Account** (`@facets/vultr_cloud_account`): provides the Vultr provider + +## Outputs + +- `@facets/vultr-object-storage` (default): `object_storage_id`, `label`, `region`, `cluster_id`, `s3_endpoint`, `s3_url`, `access_key`, `secret_key` + +## Security Considerations + +- `access_key` and `secret_key` are declared in the `secrets` output so the platform treats them as sensitive. diff --git a/modules/object_storage/vultr/1.0/facets.yaml b/modules/object_storage/vultr/1.0/facets.yaml new file mode 100644 index 00000000..a697d567 --- /dev/null +++ b/modules/object_storage/vultr/1.0/facets.yaml @@ -0,0 +1,58 @@ +intent: object_storage +flavor: vultr +version: "1.0" +clouds: + - kubernetes +description: S3-compatible object storage subscription on Vultr with access credentials +intentDetails: + type: Cloud & Infrastructure + description: S3-compatible object storage on Vultr + displayName: Object Storage + iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/object_storage.svg +spec: + title: Vultr Object Storage + description: S3-compatible object storage subscription for storing unstructured data on Vultr + type: object + x-ui-order: + - cluster_hostname + - tier_id + properties: + cluster_hostname: + type: string + title: Object Storage Cluster + description: Vultr object storage cluster hostname; uniquely selects the cluster (a region may host several, e.g. ewr1/ewr2). List clusters via the Vultr API (GET /v2/object-storage/clusters). + default: ewr1.vultrobjects.com + x-ui-placeholder: ewr1.vultrobjects.com + tier_id: + type: integer + title: Storage Tier + description: Vultr object storage tier ID (1 = Legacy, 2 = Standard, ...). The tier must be available at the chosen cluster — list tiers and their locations via the Vultr API (GET /v2/object-storage/tiers). + default: 2 + required: + - cluster_hostname +inputs: + vultr_cloud_account: + type: "@facets/vultr_cloud_account" + optional: false + displayName: Vultr Cloud Account + description: Vultr provider configuration + providers: + - vultr +outputs: + default: + type: "@facets/vultr-object-storage" + title: Vultr Object Storage +sample: + kind: object_storage + flavor: vultr + version: "1.0" + disabled: true + spec: + cluster_hostname: ewr1.vultrobjects.com + tier_id: 2 +iac: + validated_files: + - main.tf + - variables.tf + - outputs.tf + - versions.tf diff --git a/modules/object_storage/vultr/1.0/main.tf b/modules/object_storage/vultr/1.0/main.tf new file mode 100644 index 00000000..d4a60ef0 --- /dev/null +++ b/modules/object_storage/vultr/1.0/main.tf @@ -0,0 +1,35 @@ +# Vultr Object Storage Module +# Creates an S3-compatible object storage subscription and exposes its S3 endpoint +# and access credentials. Unlike AWS S3 / Linode buckets, a Vultr object storage +# subscription is a full S3 endpoint with its own keys; buckets are created against +# it via the S3 API by the consuming application. + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 63 + resource_name = var.instance_name + resource_type = "objstore" +} + +locals { + # Labels are normalized to lowercase, DNS-compatible strings. + store_label = lower(replace(module.name.name, "_", "-")) +} + +# Resolve the object storage cluster by its unique hostname. Vultr exposes +# multiple object storage clusters per region (e.g. ewr -> ewr1, ewr2), so a +# region filter is ambiguous and the data source errors on >1 match. The +# hostname uniquely identifies a single cluster. +data "vultr_object_storage_cluster" "selected" { + filter { + name = "hostname" + values = [var.instance.spec.cluster_hostname] + } +} + +resource "vultr_object_storage" "store" { + cluster_id = data.vultr_object_storage_cluster.selected.id + tier_id = var.instance.spec.tier_id + label = local.store_label +} diff --git a/modules/object_storage/vultr/1.0/outputs.tf b/modules/object_storage/vultr/1.0/outputs.tf new file mode 100644 index 00000000..ff9d5c14 --- /dev/null +++ b/modules/object_storage/vultr/1.0/outputs.tf @@ -0,0 +1,15 @@ +locals { + output_attributes = { + object_storage_id = tostring(vultr_object_storage.store.id) + label = vultr_object_storage.store.label + region = vultr_object_storage.store.region + cluster_id = tostring(vultr_object_storage.store.cluster_id) + s3_endpoint = vultr_object_storage.store.s3_hostname + s3_url = "https://${vultr_object_storage.store.s3_hostname}" + access_key = vultr_object_storage.store.s3_access_key + secret_key = vultr_object_storage.store.s3_secret_key + secrets = ["access_key", "secret_key"] + } + + output_interfaces = {} +} diff --git a/modules/object_storage/vultr/1.0/variables.tf b/modules/object_storage/vultr/1.0/variables.tf new file mode 100644 index 00000000..1d45984d --- /dev/null +++ b/modules/object_storage/vultr/1.0/variables.tf @@ -0,0 +1,38 @@ +variable "instance" { + description = "S3-compatible object storage subscription on Vultr with access credentials" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + cluster_hostname = optional(string, "ewr1.vultrobjects.com") + tier_id = optional(number, 2) + }) + }) +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + cloud_tags = optional(map(string), {}) + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = object({ + vultr_cloud_account = object({ + attributes = object({ + api_key = string + region = string + }) + }) + }) +} diff --git a/modules/object_storage/vultr/1.0/versions.tf b/modules/object_storage/vultr/1.0/versions.tf new file mode 100644 index 00000000..1468f2c6 --- /dev/null +++ b/modules/object_storage/vultr/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + vultr = { + source = "vultr/vultr" + version = ">= 2.0.0" + } + } +} diff --git a/outputs/vke/outputs.yaml b/outputs/vke/outputs.yaml new file mode 100644 index 00000000..5d2cd6c5 --- /dev/null +++ b/outputs/vke/outputs.yaml @@ -0,0 +1,51 @@ +name: '@facets/vke' +title: "VKE Cluster" +description: "Vultr Kubernetes Engine (VKE) cluster attributes" +properties: + type: object + properties: + attributes: + type: object + properties: + cluster_id: + type: string + description: "VKE cluster ID" + cluster_name: + type: string + description: "VKE cluster label" + cluster_endpoint: + type: string + description: "Kubernetes API server endpoint" + region: + type: string + description: "Vultr region where the cluster is deployed" + k8s_version: + type: string + description: "Kubernetes version" + status: + type: string + description: "Cluster status" + cluster_subnet: + type: string + description: "Pod/cluster subnet CIDR" + cluster_ca_certificate: + type: string + description: "Base64-decoded cluster CA certificate (PEM)" + client_certificate: + type: string + description: "Base64-decoded client certificate (PEM)" + client_key: + type: string + description: "Base64-decoded client key (PEM)" + kubeconfig: + type: string + description: "Base64-encoded kubeconfig" + secrets: + type: array + items: + type: string + description: "List of secret field names" + interfaces: + type: object + properties: {} +providers: [] diff --git a/outputs/vultr-object-storage/outputs.yaml b/outputs/vultr-object-storage/outputs.yaml new file mode 100644 index 00000000..7ab9ef2c --- /dev/null +++ b/outputs/vultr-object-storage/outputs.yaml @@ -0,0 +1,42 @@ +name: "@facets/vultr-object-storage" +title: "Vultr Object Storage" +description: "Vultr S3-compatible object storage subscription with access credentials" +properties: + type: object + properties: + attributes: + type: object + properties: + object_storage_id: + type: string + description: "Vultr object storage subscription ID" + label: + type: string + description: "Object storage label/name" + region: + type: string + description: "Vultr region where the object storage is located" + cluster_id: + type: string + description: "Vultr object storage cluster ID" + s3_endpoint: + type: string + description: "S3-compatible endpoint hostname" + s3_url: + type: string + description: "Direct S3 endpoint URL" + access_key: + type: string + description: "S3-compatible access key" + secret_key: + type: string + description: "S3-compatible secret key" + secrets: + type: array + items: + type: string + description: "List of secret field names" + interfaces: + type: object + properties: {} +providers: [] diff --git a/outputs/vultr-vpc-details/outputs.yaml b/outputs/vultr-vpc-details/outputs.yaml new file mode 100644 index 00000000..e9f689b8 --- /dev/null +++ b/outputs/vultr-vpc-details/outputs.yaml @@ -0,0 +1,31 @@ +name: '@facets/vultr-vpc-details' +title: "Vultr VPC Network Details" +description: "Vultr VPC 2.0 network details for downstream Kubernetes and managed-service connectivity" +properties: + type: object + properties: + attributes: + type: object + properties: + vpc_id: + type: string + description: "Vultr VPC 2.0 ID" + vpc_description: + type: string + description: "Vultr VPC description/label" + region: + type: string + description: "Vultr region where the VPC is created" + ip_block: + type: string + description: "Private network IP block (network address)" + prefix_length: + type: string + description: "Private network prefix length" + subnet_cidr: + type: string + description: "Convenience CIDR (ip_block/prefix_length)" + interfaces: + type: object + properties: {} +providers: [] diff --git a/outputs/vultr_cloud_account/outputs.yaml b/outputs/vultr_cloud_account/outputs.yaml new file mode 100644 index 00000000..32cbdc32 --- /dev/null +++ b/outputs/vultr_cloud_account/outputs.yaml @@ -0,0 +1,27 @@ +name: '@facets/vultr_cloud_account' +title: "Vultr Cloud Account" +description: "Vultr cloud provider configuration with API key authentication and default region" +properties: + type: object + properties: + attributes: + type: object + properties: + api_key: + description: Vultr API key (sensitive) + type: string + region: + description: Default Vultr region + type: string + secrets: + description: List of secret field names + type: array + items: + type: string + interfaces: + type: object + properties: {} +providers: +- name: vultr + source: vultr/vultr + version: 2.31.2 diff --git a/project-type/vultr/ONBOARDING.md b/project-type/vultr/ONBOARDING.md new file mode 100644 index 00000000..ea798a8e --- /dev/null +++ b/project-type/vultr/ONBOARDING.md @@ -0,0 +1,140 @@ +# Onboarding Vultr to Facets + +This guide walks you through standing up Vultr as a target cloud in Facets — from credentials to a deployed VKE cluster with networking, storage, and a managed database. + +> **Status:** Preview. The Vultr module set is published and importable from this repository. A managed-registry shortcut (`--managed facets/vultr`) will be available once the project type is published to the Facets registry. + +--- + +## 1. Prerequisites + +- A **Vultr account** with billing enabled. +- The **Raptor CLI** authenticated to your control plane (`raptor login`). +- A **Vultr API key** — created in the next step. + +--- + +## 2. Create a Vultr API key + +1. Go to **https://my.vultr.com/settings/#settingsapi** → enable the API and copy your **Personal Access Token (API Key)**. +2. Under **Access Control**, add the public IP(s) that will run Terraform (your control plane egress) to the allow-list, or temporarily allow all while testing. +3. The same key authorizes all the resources the modules use: Kubernetes (VKE), VPCs, Object Storage, and Managed Databases. +4. Store the key as a secret on the cloud account resource. + +--- + +## 3. Import the Vultr project type + +The project type bundles the Vultr cloud modules plus the cloud-agnostic Kubernetes modules (service, KubeBlocks datastores, helm, ingress, cert-manager, monitoring, operators). + +```bash +# From a clone of facets-modules-redesign. +# --modules-dir uploads the module code, --outputs-dir registers the output types. +# No --include-base-template, so projects created from this type start empty. +raptor import project-type -f ./project-type/vultr/project-type.yml \ + --modules-dir ./modules --outputs-dir ./outputs + +# With a custom display name +raptor import project-type -f ./project-type/vultr/project-type.yml \ + --modules-dir ./modules --outputs-dir ./outputs --name "Vultr Platform" +``` + +> If the Vultr modules are already published to your control plane, you can omit `--modules-dir`. Omit `--include-base-template` always — this project type intentionally has no base blueprint. + +**Prompt for Praxis:** + +``` +Import the Vultr project type for me from the facets-modules-redesign repo +(project-type/vultr/project-type.yml) along with its output types. +``` + +--- + +## 4. Create a project + +```bash +raptor create project my-vultr-app --project-type vultr --clouds KUBERNETES +``` + +--- + +## 5. Configure the cloud account + +Add the `cloud_account/vultr_provider` resource and set the API key (as a secret) and a default region: + +```bash +raptor apply resource cloud_account/vultr_provider/1.0 -p my-vultr-app -n cloud \ + --spec '{"api_key":"","region":"ewr"}' +``` + +> Treat the API key as a secret. In the Facets UI it is a secret-reference field; via CLI you can wire it to a project secret/variable instead of inlining it. + +**Common regions:** `ewr` (New Jersey), `ord` (Chicago), `lax` (Los Angeles), `atl` (Atlanta), `ams` (Amsterdam), `fra` (Frankfurt), `lhr` (London), `sgp` (Singapore), `nrt` (Tokyo), `syd` (Sydney). + +--- + +## 6. Add the infrastructure + +This project type has **no base template** — a new project starts empty and you add resources to the blueprint. Apply in this order (each wires to the resources before it): + +```bash +# VPC (legacy VPC — required for VKE; VPC 2.0 is NOT compatible with VKE) +raptor apply resource network/vultr_vpc/1.0 -p my-vultr-app -n network \ + --spec '{"region":"ewr","subnet_cidr":"10.0.0.0/24"}' \ + --input vultr_cloud_account=cloud_account/cloud + +# VKE cluster (placed in the VPC; cluster and VPC must share a region) +raptor apply resource kubernetes_cluster/vke/1.0 -p my-vultr-app -n cluster \ + --spec '{"k8s_version":"v1.35.0+1","high_availability":false,"default_pool":{"node_type":"vc2-2c-4gb","node_count":3}}' \ + --input vultr_cloud_account=cloud_account/cloud --input network=network/network + +# Extra node pool (optional) +raptor apply resource kubernetes_node_pool/vke/1.0 -p my-vultr-app -n workers \ + --spec '{"node_type":"vc2-2c-4gb","node_count":2}' \ + --input vultr_cloud_account=cloud_account/cloud --input kubernetes_cluster=kubernetes_cluster/cluster + +# Object storage +raptor apply resource object_storage/vultr/1.0 -p my-vultr-app -n assets \ + --spec '{"region":"ewr","tier_id":1}' \ + --input vultr_cloud_account=cloud_account/cloud + +# Managed PostgreSQL +raptor apply resource postgres/vultr/1.0 -p my-vultr-app -n database \ + --spec '{"version_config":{"version":"16"},"sizing":{"plan":"vultr-dbaas-hobbyist-cc-1-25-1"},"network_access":{"trusted_ips":[]}}' \ + --input vultr_cloud_account=cloud_account/cloud +``` + +Deploy an app workload with the cloud-agnostic `service/k8s` module, wiring it to the cluster's `attributes` (Kubernetes details) output and a node pool: + +```bash +raptor apply resource service/k8s/1.0 -p my-vultr-app -n my-api \ + --input kubernetes_details=kubernetes_cluster/cluster/attributes \ + --input kubernetes_node_pool_details=kubernetes_node_pool/workers +``` + +--- + +## 7. Plan and deploy + +```bash +raptor plan -p my-vultr-app -e # server-side terraform plan +raptor create release -p my-vultr-app -e +``` + +--- + +## Reference + +**VKE node plans:** `vc2-1c-2gb`, `vc2-2c-4gb`, `vc2-4c-8gb`, `vc2-6c-16gb`, `vhf-2c-4gb`, `vhf-4c-8gb` +**Managed DB plans:** Vultr DBaaS plan slugs, e.g. `vultr-dbaas-hobbyist-cc-1-25-1` (list via `GET /v2/databases/plans`) +**Kubernetes versions:** full Vultr version strings with build suffix, e.g. `v1.34.1+3`, `v1.35.0+1`, `v1.35.2+1` (list via `GET /v2/kubernetes/versions`) + +## Notes & gotchas + +- **VKE requires the original VPC, not VPC 2.0:** the `network/vultr_vpc` module uses `vultr_vpc` (legacy). VKE clusters cannot attach to VPC 2.0 networks, and `vultr_vpc2` is deprecated in the provider. An existing VPC can only be attached to a *new* VKE cluster, and both must share a region. +- **VKE provider auth is client-certificate based:** Vultr VKE does not issue a static bearer token. The cluster module derives `client_certificate`/`client_key`/`cluster_ca_certificate` from the kubeconfig and wires them into the Kubernetes and Helm providers. +- **Object storage is a subscription, not a bucket:** `object_storage/vultr` provisions a full S3 endpoint with its own access/secret keys; create buckets against it via the S3 API from your application. There is no module-level ACL/versioning (those are S3-API operations). +- **Managed PostgreSQL access:** the database is reachable only from CIDRs in `network_access.trusted_ips` (empty denies all external access). Add your VKE node egress CIDRs to allow the cluster to connect. +- **Kubernetes & DB versions are volatile full strings/slugs:** unlike a fixed enum, Vultr version strings (`v1.35.0+1`) and DB plan slugs (`vultr-dbaas-*`) change over time. List current values via the Vultr API before deploying. +- **No pod-level cloud IAM:** Vultr has no IRSA/Workload-Identity equivalent. Apps consume object storage via the access key/secret exposed by `object_storage/vultr`, and use Kubernetes-native service accounts (via `service/k8s`). +- **HA control plane** (`high_availability: true`) and larger multi-node DB plans incur additional cost — defaults are single-node/cost-friendly. diff --git a/project-type/vultr/project-type.yml b/project-type/vultr/project-type.yml new file mode 100644 index 00000000..467a6e14 --- /dev/null +++ b/project-type/vultr/project-type.yml @@ -0,0 +1,91 @@ +name: vultr +description: Vultr project type with VKE, VPC, object storage, and managed PostgreSQL + +# No base template: projects created from this type start empty. +# Resources are added to the blueprint after project creation. + +iacTool: OPENTOFU +iacToolVersion: "1.11.5" + +allowedClouds: + - KUBERNETES + +# IaC modules to include +modules: + # Cloud Account Setup + - intent: cloud_account + flavor: vultr_provider + + # Networking + - intent: network + flavor: vultr_vpc + + # Kubernetes Cluster + - intent: kubernetes_cluster + flavor: vke + + # Node Pools + - intent: kubernetes_node_pool + flavor: vke + + # Object Storage + - intent: object_storage + flavor: vultr + + # Managed Database + - intent: postgres + flavor: vultr + + # Self-hosted datastores via KubeBlocks (run on the VKE cluster) + - intent: postgres + flavor: kubeblocks + - intent: mysql + flavor: kubeblocks + - intent: mongo + flavor: kubeblocks + - intent: redis + flavor: kubeblocks + + # K8s platform (cloud-agnostic, run on any cluster) + - intent: service + flavor: k8s + - intent: helm + flavor: k8s_standard + - intent: helm + flavor: k8s_git + - intent: ingress + flavor: nginx_k8s + - intent: cert_manager + flavor: standard + - intent: config_map + flavor: k8s_standard + - intent: kubernetes_secret + flavor: k8s_standard + - intent: pvc + flavor: k8s_standard + - intent: k8s_resource + flavor: k8s_standard + - intent: k8s_access_controls + flavor: k8s_standard + - intent: k8s_callback + flavor: k8s_standard + - intent: gateway_api_crd + flavor: default + - intent: artifactories + flavor: standard + + # Operators & monitoring (cloud-agnostic) + - intent: kubeblocks-operator + flavor: standard + - intent: kubeblocks-crd + flavor: standard + - intent: eck-operator + flavor: helm + - intent: prometheus + flavor: k8s_standard + - intent: grafana_dashboards + flavor: k8s + - intent: alert_rules + flavor: prometheus + - intent: monitoring + flavor: mongo