diff --git a/README.md b/README.md
index eff8ebcd..3bb51275 100644
--- a/README.md
+++ b/README.md
@@ -152,6 +152,55 @@ raptor import project-type --managed facets/azure --name "My Platform"
---
+### 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.
+
+**First, create a Linode API token** at https://cloud.linode.com/profile/tokens with read/write on Linodes, Kubernetes, VPCs, IPs, Object Storage, and Databases. Full walkthrough: [`project-type/linode/ONBOARDING.md`](project-type/linode/ONBOARDING.md).
+
+**Prompt for Praxis:**
+
+```
+Import the Linode project type for me from the facets-modules-redesign repo
+(project-type/linode/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/linode/project-type.yml \
+ --modules-dir ./modules --outputs-dir ./outputs
+```
+
+With custom name:
+
+```bash
+raptor import project-type -f ./project-type/linode/project-type.yml \
+ --modules-dir ./modules --outputs-dir ./outputs --name "Linode Platform"
+```
+
+
+What's included
+
+**Infrastructure**
+`Cloud Account (linode_provider)` `Network/VPC (linode_vpc)` `LKE Cluster (lke)` `Node Pool (lke)` `Object Storage (linode)` `Service (k8s)`
+
+**Managed Datastores**
+`PostgreSQL (linode)`
+
+**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`
+
+
+
+---
+
## Links
- [Facets Control Plane](https://facets.cloud)
diff --git a/modules/cloud_account/linode_provider/1.0/README.md b/modules/cloud_account/linode_provider/1.0/README.md
new file mode 100644
index 00000000..e5e22184
--- /dev/null
+++ b/modules/cloud_account/linode_provider/1.0/README.md
@@ -0,0 +1,31 @@
+# Linode (Akamai) Provider Configuration
+
+Configures the Linode Terraform provider with API token authentication for use by other Linode modules.
+
+## Overview
+
+This module establishes the foundational Linode (Akamai Cloud) provider configuration using a Linode Personal Access Token (PAT). It serves as the base provider configuration that other Linode infrastructure modules (VPC, LKE, Object Storage, Managed Database) consume to access Linode APIs and provision resources. It produces the `@facets/linode_cloud_account` output type.
+
+## Environment as Dimension
+
+**Environment-specific provider configuration**: The API token is a secret reference and the default region is an override-only field, allowing different Linode credentials and regions per environment.
+
+## Resources Created
+
+- **Linode Provider Configuration**: Establishes an authenticated connection to the Linode API
+- **Provider Output Interface**: Exposes the configured provider and default region for consumption by other modules
+
+## Security Considerations
+
+- The API token is supplied via a secret reference and is never stored in plain text within the module configuration
+- The token 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
+
+- **Linode API Token**: A Linode Personal Access Token with read/write scopes (secret reference)
+- **Linode Region**: The default region for downstream resources, e.g. `us-east`, `us-ord`, `eu-west`, `ap-south` (override-only)
+
+## Usage Notes
+
+This module does not create any actual Linode resources — it only configures the provider authentication. Other Linode modules should consume this module's `@facets/linode_cloud_account` output to access Linode services with proper authentication.
diff --git a/modules/cloud_account/linode_provider/1.0/facets.yaml b/modules/cloud_account/linode_provider/1.0/facets.yaml
new file mode 100644
index 00000000..22ea55f5
--- /dev/null
+++ b/modules/cloud_account/linode_provider/1.0/facets.yaml
@@ -0,0 +1,54 @@
+intent: cloud_account
+flavor: linode_provider
+version: "1.0"
+description: Module to configure and provision resources within a Linode (Akamai) cloud account
+intentDetails:
+ type: Cloud & Infrastructure
+ description: Configure and provision resources within a Linode (Akamai) cloud account
+ displayName: Cloud Account
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/cloud_account.svg
+clouds:
+ - kubernetes
+spec:
+ title: Linode Provider Configuration
+ description: Configure Linode (Akamai) API access credentials and the default region
+ type: object
+ properties:
+ token:
+ type: string
+ title: Linode API Token
+ description: Linode Personal Access Token with read/write scopes for the resources you intend to manage
+ x-ui-secret-ref: true
+ region:
+ type: string
+ title: Linode Region
+ description: Default Linode region for downstream resources (e.g. us-east, us-ord, eu-west, ap-south)
+ x-ui-placeholder: us-east
+ x-ui-overrides-only: true
+ required:
+ - token
+ - region
+ x-ui-order:
+ - token
+ - region
+outputs:
+ default:
+ type: "@facets/linode_cloud_account"
+ providers:
+ linode:
+ source: linode/linode
+ version: 3.13.0
+ attributes:
+ token: attributes.token
+sample:
+ kind: cloud_account
+ flavor: linode_provider
+ version: "1.0"
+ disabled: true
+ spec:
+ token: ""
+ region: us-east
+iac:
+ validated_files:
+ - main.tf
+ - variables.tf
diff --git a/modules/cloud_account/linode_provider/1.0/main.tf b/modules/cloud_account/linode_provider/1.0/main.tf
new file mode 100644
index 00000000..459e752c
--- /dev/null
+++ b/modules/cloud_account/linode_provider/1.0/main.tf
@@ -0,0 +1,4 @@
+# Linode (Akamai) Provider Configuration Module
+# This module configures the Linode 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/linode_provider/1.0/outputs.tf b/modules/cloud_account/linode_provider/1.0/outputs.tf
new file mode 100644
index 00000000..7f5a4354
--- /dev/null
+++ b/modules/cloud_account/linode_provider/1.0/outputs.tf
@@ -0,0 +1,9 @@
+locals {
+ output_attributes = {
+ token = var.instance.spec.token
+ region = var.instance.spec.region
+ secrets = "[\"token\"]"
+ }
+ output_interfaces = {
+ }
+}
diff --git a/modules/cloud_account/linode_provider/1.0/variables.tf b/modules/cloud_account/linode_provider/1.0/variables.tf
new file mode 100644
index 00000000..ce377eb3
--- /dev/null
+++ b/modules/cloud_account/linode_provider/1.0/variables.tf
@@ -0,0 +1,31 @@
+variable "instance" {
+ description = "Configures the Linode (Akamai) Terraform provider with API token authentication"
+ type = object({
+ kind = string
+ flavor = string
+ version = string
+ spec = object({
+ token = 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/linode/1.0/README.md b/modules/datastore/postgres/linode/1.0/README.md
new file mode 100644
index 00000000..d2b9c198
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/README.md
@@ -0,0 +1,26 @@
+# Linode Managed PostgreSQL
+
+Creates a Linode (Akamai) managed PostgreSQL database using the v2 API. Produces the `@facets/postgres` output type (reader/writer connection interfaces).
+
+## Overview
+
+Provisions a managed PostgreSQL cluster (single node or 3-node HA). Connection details are exposed via the standard `@facets/postgres` reader and writer interfaces, making the database swappable with PostgreSQL modules on other clouds. SSL is required on connections.
+
+## Resources Created
+
+- **linode_database_postgresql_v2**: The managed PostgreSQL cluster (`prevent_destroy` enabled)
+
+## Required Configuration
+
+- **Version**: PostgreSQL major version (13–16)
+- **Type**: Linode instance type (e.g. `g6-nanode-1`)
+- **Cluster Size**: `1` (single) or `3` (HA)
+- **Allowed CIDRs** (optional): IP allow-list for client access (empty denies external access — populate with your LKE node egress CIDRs to connect)
+
+## Inputs
+
+- **Linode Cloud Account** (`@facets/linode_cloud_account`): provides the Linode provider and region
+
+## Outputs
+
+- `@facets/postgres`: `writer` and `reader` interfaces with `host`, `port`, `username`, `password`, `connection_string`
diff --git a/modules/datastore/postgres/linode/1.0/facets.yaml b/modules/datastore/postgres/linode/1.0/facets.yaml
new file mode 100644
index 00000000..281a6406
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/facets.yaml
@@ -0,0 +1,114 @@
+intent: postgres
+flavor: linode
+version: "1.0"
+clouds:
+ - kubernetes
+description: Managed PostgreSQL database on Linode (Akamai) with SSL/TLS and IP allow-listing
+intentDetails:
+ type: Datastores
+ description: Linode (Akamai) managed PostgreSQL database service
+ displayName: PostgreSQL
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/postgres.svg
+spec:
+ title: Linode Managed PostgreSQL Database
+ description: Managed PostgreSQL database on Linode (Akamai)
+ 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"
+ default: "16"
+ required:
+ - version
+ sizing:
+ type: object
+ title: Sizing & Performance
+ x-ui-order:
+ - type
+ - cluster_size
+ properties:
+ type:
+ type: string
+ title: Instance Type
+ description: Linode instance type for database nodes
+ enum:
+ - g6-nanode-1
+ - g6-standard-1
+ - g6-standard-2
+ - g6-dedicated-2
+ - g6-dedicated-4
+ default: g6-nanode-1
+ cluster_size:
+ type: integer
+ title: Cluster Size
+ description: Number of nodes (1 = single node, 3 = high availability)
+ enum:
+ - 1
+ - 3
+ default: 1
+ required:
+ - type
+ - cluster_size
+ network_access:
+ type: object
+ title: Network Access
+ properties:
+ allow_list:
+ type: array
+ title: Allowed CIDRs
+ 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:
+ linode_cloud_account:
+ type: "@facets/linode_cloud_account"
+ optional: false
+ displayName: Linode Cloud Account
+ description: Linode provider configuration and default region
+ providers:
+ - linode
+outputs:
+ default:
+ type: "@facets/postgres"
+ title: Linode PostgreSQL Database
+sample:
+ kind: postgres
+ flavor: linode
+ version: "1.0"
+ disabled: true
+ spec:
+ version_config:
+ version: "16"
+ sizing:
+ type: g6-nanode-1
+ cluster_size: 1
+ network_access:
+ allow_list: []
+iac:
+ validated_files:
+ - main.tf
+ - variables.tf
+ - outputs.tf
+ - versions.tf
diff --git a/modules/datastore/postgres/linode/1.0/main.tf b/modules/datastore/postgres/linode/1.0/main.tf
new file mode 100644
index 00000000..b00fb634
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/main.tf
@@ -0,0 +1,28 @@
+# Linode Managed PostgreSQL Module
+# Creates a Linode managed PostgreSQL database (v2 API).
+
+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.linode_cloud_account.attributes.region
+ engine_id = "postgresql/${var.instance.spec.version_config.version}"
+}
+
+resource "linode_database_postgresql_v2" "db" {
+ label = module.name.name
+ engine_id = local.engine_id
+ region = local.region
+ type = var.instance.spec.sizing.type
+ cluster_size = var.instance.spec.sizing.cluster_size
+ allow_list = var.instance.spec.network_access.allow_list
+
+ lifecycle {
+ prevent_destroy = true
+ }
+}
diff --git a/modules/datastore/postgres/linode/1.0/outputs.tf b/modules/datastore/postgres/linode/1.0/outputs.tf
new file mode 100644
index 00000000..69f0c430
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/outputs.tf
@@ -0,0 +1,30 @@
+locals {
+ # The v2 managed-database API exposes a single primary host; read scaling is handled
+ # by the platform, so reader and writer share the primary endpoint.
+ writer_host = linode_database_postgresql_v2.db.host_primary
+ reader_host = linode_database_postgresql_v2.db.host_primary
+ db_port = tostring(linode_database_postgresql_v2.db.port)
+ db_user = linode_database_postgresql_v2.db.root_username
+ db_password = linode_database_postgresql_v2.db.root_password
+
+ output_attributes = {}
+
+ output_interfaces = {
+ writer = {
+ host = local.writer_host
+ port = local.db_port
+ username = local.db_user
+ password = local.db_password
+ connection_string = "postgresql://${local.db_user}:${local.db_password}@${local.writer_host}:${local.db_port}/defaultdb?sslmode=require"
+ secrets = ["password", "connection_string"]
+ }
+ reader = {
+ host = local.reader_host
+ port = local.db_port
+ username = local.db_user
+ password = local.db_password
+ connection_string = "postgresql://${local.db_user}:${local.db_password}@${local.reader_host}:${local.db_port}/defaultdb?sslmode=require"
+ secrets = ["password", "connection_string"]
+ }
+ }
+}
diff --git a/modules/datastore/postgres/linode/1.0/variables.tf b/modules/datastore/postgres/linode/1.0/variables.tf
new file mode 100644
index 00000000..f8e1ebf4
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/variables.tf
@@ -0,0 +1,56 @@
+variable "instance" {
+ description = "Managed PostgreSQL database on Linode (Akamai) with SSL/TLS and IP allow-listing"
+ type = object({
+ kind = string
+ flavor = string
+ version = string
+ spec = object({
+ version_config = object({
+ version = string
+ })
+ sizing = object({
+ type = string
+ cluster_size = number
+ })
+ network_access = optional(object({
+ allow_list = optional(list(string), [])
+ }), {})
+ })
+ })
+
+ validation {
+ condition = contains(["13", "14", "15", "16"], var.instance.spec.version_config.version)
+ error_message = "PostgreSQL version must be one of: 13, 14, 15, 16."
+ }
+
+ validation {
+ condition = contains([1, 3], var.instance.spec.sizing.cluster_size)
+ error_message = "cluster_size must be 1 or 3."
+ }
+}
+
+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({
+ linode_cloud_account = object({
+ attributes = object({
+ token = string
+ region = string
+ })
+ })
+ })
+}
diff --git a/modules/datastore/postgres/linode/1.0/versions.tf b/modules/datastore/postgres/linode/1.0/versions.tf
new file mode 100644
index 00000000..52bf7a3f
--- /dev/null
+++ b/modules/datastore/postgres/linode/1.0/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ linode = {
+ source = "linode/linode"
+ version = ">= 3.0.0"
+ }
+ }
+}
diff --git a/modules/kubernetes_cluster/lke/1.0/README.md b/modules/kubernetes_cluster/lke/1.0/README.md
new file mode 100644
index 00000000..9161c44b
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/README.md
@@ -0,0 +1,28 @@
+# Linode Kubernetes Engine (LKE) Cluster
+
+Creates an LKE cluster with a default node pool and exposes the Kubernetes and Helm providers for downstream modules. Produces `@facets/lke` (cluster attributes) and `@facets/kubernetes-details` (generic attributes + provider configuration).
+
+## Overview
+
+LKE 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/lke` module. The Kubernetes and Helm providers are configured from the cluster kubeconfig (token-based authentication).
+
+## Resources Created
+
+- **linode_lke_cluster**: The managed Kubernetes control plane plus a default node pool
+
+## Required Configuration
+
+- **Kubernetes Version**: One of `1.31`, `1.32`, `1.33`
+- **Default Node Pool**: `node_type` (e.g. `g6-standard-2`) and `node_count`
+- **High Availability** (optional): HA control plane (additional cost), default off
+- **Autoscaler** (optional): enable per-pool autoscaling with min/max
+
+## Inputs
+
+- **Linode Cloud Account** (`@facets/linode_cloud_account`): provides the Linode provider and region
+- **Linode VPC** (`@facets/linode-vpc-details`, optional): when wired, the cluster nodes are placed in the VPC's subnet (`vpc_id` + `subnet_id`) and the cluster region is taken from the VPC
+
+## Outputs
+
+- `@facets/lke` (default): `cluster_id`, `cluster_name`, `cluster_endpoint`, `region`, `k8s_version`, `status`, `dashboard_url`, `kubeconfig`
+- `@facets/kubernetes-details` (attributes): generic cluster details plus configured `kubernetes` and `helm` providers
diff --git a/modules/kubernetes_cluster/lke/1.0/facets.yaml b/modules/kubernetes_cluster/lke/1.0/facets.yaml
new file mode 100644
index 00000000..da3bc907
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/facets.yaml
@@ -0,0 +1,167 @@
+intent: kubernetes_cluster
+flavor: lke
+version: "1.0"
+clouds:
+ - kubernetes
+description: Creates a Linode Kubernetes Engine (LKE) cluster with a default node pool, exposing Kubernetes and Helm providers
+intentDetails:
+ type: Cloud & Infrastructure
+ description: Linode Kubernetes Engine (LKE) cluster with provider configuration
+ displayName: Kubernetes Cluster
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/kubernetes_cluster.svg
+spec:
+ title: Linode Kubernetes Engine Cluster
+ description: Configure an LKE cluster and its default node pool
+ type: object
+ x-ui-order:
+ - k8s_version
+ - high_availability
+ - default_pool
+ properties:
+ k8s_version:
+ type: string
+ title: Kubernetes Version
+ description: Kubernetes version for the LKE cluster
+ enum:
+ - "1.34"
+ - "1.35"
+ default: "1.35"
+ high_availability:
+ type: boolean
+ title: High Availability Control Plane
+ description: Enable the HA control plane (additional cost). Recommended for production.
+ default: false
+ audit_logs_enabled:
+ type: boolean
+ title: Enable Control-Plane Audit Logs
+ description: Enable Kubernetes API server audit logging on the control plane
+ default: false
+ api_server_allowed_cidrs:
+ type: array
+ title: API Server Allowed CIDRs
+ description: "If set, restrict Kubernetes API server access to these IPv4 CIDRs (control-plane ACL). Empty means open to all sources. The allow-list must include whatever runs deployments against the cluster."
+ default: []
+ x-ui-overrides-only: true
+ items:
+ type: string
+ pattern: ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$
+ default_pool:
+ type: object
+ title: Default Node Pool
+ x-ui-order:
+ - node_type
+ - node_count
+ - autoscaler
+ properties:
+ node_type:
+ type: string
+ title: Node Type
+ description: Linode instance type for the default node pool
+ enum:
+ - g6-standard-1
+ - g6-standard-2
+ - g6-standard-4
+ - g6-standard-6
+ - g6-dedicated-2
+ - g6-dedicated-4
+ - g6-dedicated-8
+ default: g6-standard-2
+ 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:
+ linode_cloud_account:
+ type: "@facets/linode_cloud_account"
+ optional: false
+ displayName: Linode Cloud Account
+ description: Linode provider configuration and default region
+ providers:
+ - linode
+ network:
+ type: "@facets/linode-vpc-details"
+ optional: true
+ displayName: Linode VPC
+ description: Optional VPC and subnet to place the cluster nodes in. When wired, the cluster region is taken from the VPC.
+ providers: []
+outputs:
+ default:
+ type: "@facets/lke"
+ title: LKE Cluster Attributes
+ description: Linode 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
+ token: token
+ kubernetes-alpha:
+ source: hashicorp/kubernetes-alpha
+ version: 0.6.0
+ attributes:
+ host: cluster_endpoint
+ cluster_ca_certificate: cluster_ca_certificate
+ token: token
+ helm:
+ source: hashicorp/helm
+ version: 2.17.0
+ attributes:
+ kubernetes:
+ host: cluster_endpoint
+ cluster_ca_certificate: cluster_ca_certificate
+ token: token
+sample:
+ kind: kubernetes_cluster
+ flavor: lke
+ version: "1.0"
+ disabled: true
+ spec:
+ k8s_version: "1.35"
+ high_availability: false
+ default_pool:
+ node_type: g6-standard-2
+ 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/lke/1.0/main.tf b/modules/kubernetes_cluster/lke/1.0/main.tf
new file mode 100644
index 00000000..b0373c58
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/main.tf
@@ -0,0 +1,75 @@
+# Linode Kubernetes Engine (LKE) Cluster Module
+# Creates an LKE 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.
+ region = coalesce(try(var.inputs.network.attributes.region, null), var.inputs.linode_cloud_account.attributes.region)
+
+ # Optional VPC placement for cluster nodes (numbers expected by the provider).
+ vpc_id = try(tonumber(var.inputs.network.attributes.vpc_id), null)
+ subnet_id = try(tonumber(var.inputs.network.attributes.subnet_id), null)
+
+ autoscaler_enabled = try(var.instance.spec.default_pool.autoscaler.enabled, false)
+
+ # Parse the cluster kubeconfig (base64-encoded) to derive provider connection details.
+ # At plan time these resolve to unknown values, which is expected.
+ kubeconfig_decoded = try(yamldecode(base64decode(linode_lke_cluster.cluster.kubeconfig)), {})
+ cluster_ca_certificate = try(base64decode(local.kubeconfig_decoded["clusters"][0]["cluster"]["certificate-authority-data"]), "")
+ cluster_token = try(local.kubeconfig_decoded["users"][0]["user"]["token"], "")
+}
+
+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 "linode_lke_cluster" "cluster" {
+ label = module.name.name
+ region = local.region
+ k8s_version = var.instance.spec.k8s_version
+ tags = ["facets", var.environment.unique_name]
+
+ # Optional VPC placement (both null when no network is wired).
+ vpc_id = local.vpc_id
+ subnet_id = local.subnet_id
+
+ control_plane {
+ high_availability = var.instance.spec.high_availability
+ audit_logs_enabled = var.instance.spec.audit_logs_enabled
+
+ # Optional API-server IP allow-list. Only enabled when CIDRs are provided,
+ # otherwise the API server stays open (required for the deployment runner to reach it).
+ dynamic "acl" {
+ for_each = length(var.instance.spec.api_server_allowed_cidrs) > 0 ? [1] : []
+ content {
+ enabled = true
+ addresses {
+ ipv4 = var.instance.spec.api_server_allowed_cidrs
+ }
+ }
+ }
+ }
+
+ pool {
+ type = var.instance.spec.default_pool.node_type
+ count = var.instance.spec.default_pool.node_count
+
+ dynamic "autoscaler" {
+ for_each = local.autoscaler_enabled ? [1] : []
+ content {
+ min = var.instance.spec.default_pool.autoscaler.min
+ max = var.instance.spec.default_pool.autoscaler.max
+ }
+ }
+ }
+
+ lifecycle {
+ # When autoscaling is enabled, the node count drifts and should not be reconciled.
+ ignore_changes = [pool[0].count]
+ }
+}
diff --git a/modules/kubernetes_cluster/lke/1.0/outputs.tf b/modules/kubernetes_cluster/lke/1.0/outputs.tf
new file mode 100644
index 00000000..dd7a529e
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/outputs.tf
@@ -0,0 +1,28 @@
+locals {
+ output_attributes = {
+ cluster_id = tostring(linode_lke_cluster.cluster.id)
+ cluster_name = linode_lke_cluster.cluster.label
+ cluster_endpoint = try(linode_lke_cluster.cluster.api_endpoints[0], "")
+ cluster_location = local.region
+ cloud_provider = "LINODE"
+ region = local.region
+ k8s_version = linode_lke_cluster.cluster.k8s_version
+ status = linode_lke_cluster.cluster.status
+ dashboard_url = linode_lke_cluster.cluster.dashboard_url
+ kubeconfig = linode_lke_cluster.cluster.kubeconfig
+
+ # Derived from the kubeconfig for the Kubernetes/Helm provider configuration.
+ cluster_ca_certificate = local.cluster_ca_certificate
+ token = local.cluster_token
+
+ secrets = ["kubeconfig", "token", "cluster_ca_certificate"]
+ }
+
+ output_interfaces = {
+ kubernetes = {
+ host = try(linode_lke_cluster.cluster.api_endpoints[0], "")
+ cluster_ca_certificate = local.cluster_ca_certificate
+ token = local.cluster_token
+ }
+ }
+}
diff --git a/modules/kubernetes_cluster/lke/1.0/variables.tf b/modules/kubernetes_cluster/lke/1.0/variables.tf
new file mode 100644
index 00000000..1f99af73
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/variables.tf
@@ -0,0 +1,66 @@
+variable "instance" {
+ description = "Creates a Linode Kubernetes Engine (LKE) cluster with a default node pool"
+ type = object({
+ kind = string
+ flavor = string
+ version = string
+ spec = object({
+ k8s_version = string
+ high_availability = optional(bool, false)
+ audit_logs_enabled = optional(bool, false)
+ api_server_allowed_cidrs = optional(list(string), [])
+ 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 = contains(["1.34", "1.35"], var.instance.spec.k8s_version)
+ error_message = "k8s_version must be one of: 1.34, 1.35."
+ }
+
+ 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({
+ linode_cloud_account = object({
+ attributes = object({
+ token = string
+ region = string
+ })
+ })
+ network = optional(object({
+ attributes = object({
+ vpc_id = optional(string)
+ subnet_id = optional(string)
+ region = optional(string)
+ })
+ }))
+ })
+}
diff --git a/modules/kubernetes_cluster/lke/1.0/versions.tf b/modules/kubernetes_cluster/lke/1.0/versions.tf
new file mode 100644
index 00000000..52bf7a3f
--- /dev/null
+++ b/modules/kubernetes_cluster/lke/1.0/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ linode = {
+ source = "linode/linode"
+ version = ">= 3.0.0"
+ }
+ }
+}
diff --git a/modules/kubernetes_node_pool/lke/1.0/README.md b/modules/kubernetes_node_pool/lke/1.0/README.md
new file mode 100644
index 00000000..3ad9a8b2
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/README.md
@@ -0,0 +1,27 @@
+# Linode Kubernetes Engine (LKE) Node Pool
+
+Adds an additional node pool to an existing LKE cluster. Produces the `@facets/kubernetes_nodepool` output type.
+
+## Overview
+
+The LKE cluster module always creates a default pool; use this module to attach extra pools with different instance types, autoscaling settings, labels, or taints.
+
+## Resources Created
+
+- **linode_lke_node_pool**: An additional node pool attached to the target LKE cluster
+
+## Required Configuration
+
+- **Node Type**: Linode instance type (e.g. `g6-standard-2`)
+- **Node Count**: Number of nodes (initial count when autoscaling)
+- **Autoscaler** (optional): enable with min/max
+- **Labels / Taints** (optional): node labels and taints
+
+## Inputs
+
+- **Linode Cloud Account** (`@facets/linode_cloud_account`): provides the Linode provider
+- **LKE Cluster** (`@facets/lke`): the cluster to attach the pool to
+
+## Outputs
+
+- `@facets/kubernetes_nodepool`: `node_pool_name`, `taints`, `node_selector`
diff --git a/modules/kubernetes_node_pool/lke/1.0/facets.yaml b/modules/kubernetes_node_pool/lke/1.0/facets.yaml
new file mode 100644
index 00000000..80547f6b
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/facets.yaml
@@ -0,0 +1,147 @@
+intent: kubernetes_node_pool
+flavor: lke
+version: "1.0"
+clouds:
+ - kubernetes
+description: Creates and manages an additional node pool on a Linode Kubernetes Engine (LKE) cluster
+intentDetails:
+ type: Cloud & Infrastructure
+ description: Linode 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: Linode Kubernetes Engine Node Pool
+ description: Configure an additional LKE node pool
+ type: object
+ x-ui-order:
+ - node_type
+ - node_count
+ - autoscaler
+ - labels
+ - taints
+ properties:
+ node_type:
+ type: string
+ title: Node Type
+ description: Linode instance type for the node pool
+ enum:
+ - g6-standard-1
+ - g6-standard-2
+ - g6-standard-4
+ - g6-standard-6
+ - g6-dedicated-2
+ - g6-dedicated-4
+ - g6-dedicated-8
+ default: g6-standard-2
+ 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
+ description: Label key
+ value:
+ type: string
+ 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
+ description: Taint value
+ default: ""
+ effect:
+ type: string
+ description: Taint effect
+ enum:
+ - NoSchedule
+ - PreferNoSchedule
+ - NoExecute
+ required:
+ - effect
+ required:
+ - node_type
+ - node_count
+inputs:
+ linode_cloud_account:
+ type: "@facets/linode_cloud_account"
+ optional: false
+ displayName: Linode Cloud Account
+ description: Linode provider configuration
+ providers:
+ - linode
+ kubernetes_cluster:
+ type: "@facets/lke"
+ optional: false
+ displayName: LKE Cluster
+ description: The LKE 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: lke
+ version: "1.0"
+ disabled: true
+ spec:
+ node_type: g6-standard-2
+ 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/lke/1.0/main.tf b/modules/kubernetes_node_pool/lke/1.0/main.tf
new file mode 100644
index 00000000..867a3de7
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/main.tf
@@ -0,0 +1,38 @@
+# Linode Kubernetes Engine (LKE) Node Pool Module
+# Adds an additional node pool to an existing LKE 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 }
+ cluster_id = tonumber(var.inputs.kubernetes_cluster.attributes.cluster_id)
+}
+
+resource "linode_lke_node_pool" "pool" {
+ cluster_id = local.cluster_id
+ type = var.instance.spec.node_type
+ node_count = var.instance.spec.node_count
+ tags = ["facets", var.environment.unique_name, var.instance_name]
+ labels = local.node_selector
+
+ dynamic "autoscaler" {
+ for_each = local.autoscaler_enabled ? [1] : []
+ content {
+ min = var.instance.spec.autoscaler.min
+ max = var.instance.spec.autoscaler.max
+ }
+ }
+
+ dynamic "taint" {
+ for_each = var.instance.spec.taints
+ content {
+ key = taint.key
+ value = taint.value.value
+ effect = taint.value.effect
+ }
+ }
+
+ lifecycle {
+ # When autoscaling is enabled the node count drifts and should not be reconciled.
+ ignore_changes = [node_count]
+ }
+}
diff --git a/modules/kubernetes_node_pool/lke/1.0/outputs.tf b/modules/kubernetes_node_pool/lke/1.0/outputs.tf
new file mode 100644
index 00000000..d51e90e4
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/outputs.tf
@@ -0,0 +1,15 @@
+locals {
+ output_attributes = {
+ node_pool_name = tostring(linode_lke_node_pool.pool.id)
+ taints = [
+ for key, taint in var.instance.spec.taints : {
+ key = key
+ value = taint.value
+ effect = taint.effect
+ }
+ ]
+ node_selector = local.node_selector
+ }
+
+ output_interfaces = {}
+}
diff --git a/modules/kubernetes_node_pool/lke/1.0/variables.tf b/modules/kubernetes_node_pool/lke/1.0/variables.tf
new file mode 100644
index 00000000..a8863161
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/variables.tf
@@ -0,0 +1,63 @@
+variable "instance" {
+ description = "Creates and manages an additional node pool on a Linode Kubernetes Engine (LKE) 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({
+ linode_cloud_account = object({
+ attributes = object({
+ token = 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/lke/1.0/versions.tf b/modules/kubernetes_node_pool/lke/1.0/versions.tf
new file mode 100644
index 00000000..52bf7a3f
--- /dev/null
+++ b/modules/kubernetes_node_pool/lke/1.0/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ linode = {
+ source = "linode/linode"
+ version = ">= 3.0.0"
+ }
+ }
+}
diff --git a/modules/network/linode_vpc/1.0/README.md b/modules/network/linode_vpc/1.0/README.md
new file mode 100644
index 00000000..42fa0285
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/README.md
@@ -0,0 +1,25 @@
+# Linode VPC Network
+
+Creates a Linode VPC with a primary subnet for Kubernetes nodes and managed services. Produces the `@facets/linode-vpc-details` output type.
+
+## Overview
+
+This module provisions a Linode VPC and a single primary subnet in the chosen region. The VPC and subnet provide private, segmented connectivity for LKE node pools, Linode instances, and managed databases. The region defaults to the cloud account's default region when not overridden.
+
+## Resources Created
+
+- **linode_vpc**: The VPC container, scoped to a single region
+- **linode_vpc_subnet**: The primary subnet (CIDR configurable via spec)
+
+## Required Configuration
+
+- **Subnet CIDR**: CIDR block for the primary subnet (default `10.0.0.0/24`)
+- **Region** (optional): Linode region; defaults to the cloud account region
+
+## Inputs
+
+- **Linode Cloud Account** (`@facets/linode_cloud_account`): provides the Linode provider and default region
+
+## Outputs
+
+- `@facets/linode-vpc-details`: `vpc_id`, `vpc_label`, `region`, `subnet_id`, `subnet_label`, `subnet_cidr`
diff --git a/modules/network/linode_vpc/1.0/facets.yaml b/modules/network/linode_vpc/1.0/facets.yaml
new file mode 100644
index 00000000..8ac84445
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/facets.yaml
@@ -0,0 +1,59 @@
+intent: network
+flavor: linode_vpc
+version: "1.0"
+clouds:
+ - kubernetes
+description: Creates a Linode VPC with a subnet for Kubernetes and managed services
+intentDetails:
+ type: Cloud & Infrastructure
+ description: Linode VPC 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: Linode VPC Network
+ description: Configure a Linode VPC and primary subnet
+ type: object
+ properties:
+ region:
+ type: string
+ title: Region
+ description: Linode region for the VPC (defaults to the cloud account region if left empty)
+ x-ui-placeholder: us-east
+ x-ui-overrides-only: true
+ subnet_cidr:
+ type: string
+ title: Subnet CIDR
+ description: CIDR block for the primary subnet (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:
+ linode_cloud_account:
+ type: "@facets/linode_cloud_account"
+ optional: false
+ displayName: Linode Cloud Account
+ description: Linode provider configuration
+ providers:
+ - linode
+outputs:
+ default:
+ type: "@facets/linode-vpc-details"
+ title: Linode VPC Network Details
+sample:
+ kind: network
+ flavor: linode_vpc
+ version: "1.0"
+ disabled: true
+ spec:
+ region: us-east
+ subnet_cidr: "10.0.0.0/24"
+iac:
+ validated_files:
+ - main.tf
+ - variables.tf
+ - outputs.tf
+ - versions.tf
diff --git a/modules/network/linode_vpc/1.0/main.tf b/modules/network/linode_vpc/1.0/main.tf
new file mode 100644
index 00000000..496b1dbd
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/main.tf
@@ -0,0 +1,27 @@
+# Linode VPC Network Module
+# Creates a Linode VPC with a primary subnet for Kubernetes nodes and managed services.
+
+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.linode_cloud_account.attributes.region)
+}
+
+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 "linode_vpc" "main" {
+ label = module.name.name
+ region = local.region
+ description = "Managed by Facets for ${var.environment.unique_name}/${var.instance_name}"
+}
+
+resource "linode_vpc_subnet" "main" {
+ vpc_id = linode_vpc.main.id
+ label = module.name.name
+ ipv4 = var.instance.spec.subnet_cidr
+}
diff --git a/modules/network/linode_vpc/1.0/outputs.tf b/modules/network/linode_vpc/1.0/outputs.tf
new file mode 100644
index 00000000..cc664238
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/outputs.tf
@@ -0,0 +1,12 @@
+locals {
+ output_attributes = {
+ vpc_id = tostring(linode_vpc.main.id)
+ vpc_label = linode_vpc.main.label
+ region = local.region
+ subnet_id = tostring(linode_vpc_subnet.main.id)
+ subnet_label = linode_vpc_subnet.main.label
+ subnet_cidr = linode_vpc_subnet.main.ipv4
+ }
+ output_interfaces = {
+ }
+}
diff --git a/modules/network/linode_vpc/1.0/variables.tf b/modules/network/linode_vpc/1.0/variables.tf
new file mode 100644
index 00000000..a8894bf6
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/variables.tf
@@ -0,0 +1,43 @@
+variable "instance" {
+ description = "Creates a Linode VPC with a subnet 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({
+ linode_cloud_account = object({
+ attributes = object({
+ token = string
+ region = string
+ })
+ })
+ })
+}
diff --git a/modules/network/linode_vpc/1.0/versions.tf b/modules/network/linode_vpc/1.0/versions.tf
new file mode 100644
index 00000000..52bf7a3f
--- /dev/null
+++ b/modules/network/linode_vpc/1.0/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ linode = {
+ source = "linode/linode"
+ version = ">= 3.0.0"
+ }
+ }
+}
diff --git a/modules/object_storage/linode/1.0/README.md b/modules/object_storage/linode/1.0/README.md
new file mode 100644
index 00000000..4cf50ca6
--- /dev/null
+++ b/modules/object_storage/linode/1.0/README.md
@@ -0,0 +1,28 @@
+# Linode Object Storage
+
+Creates an S3-compatible object storage bucket on Linode (Akamai) and a scoped access key. Produces the `@facets/linode-object-storage` output type.
+
+## Overview
+
+Provisions a bucket plus a read/write access key restricted to that bucket. The output exposes the S3-compatible endpoint and credentials so applications can connect via any S3 client. Object storage on Linode is region/cluster-scoped, so the region is an override-only field.
+
+> Note: `@facets/s3` (the AWS contract) models IRSA-based access and carries no credentials, so it is not a good fit for Linode. This module follows the OVH precedent and exposes S3-compatible credentials directly via a Linode-specific output type.
+
+## Resources Created
+
+- **linode_object_storage_bucket**: The S3-compatible bucket
+- **linode_object_storage_key**: A read/write access key scoped to the bucket
+
+## Required Configuration
+
+- **Region** (override-only): object storage region (default `us-east`)
+- **ACL**: canned ACL (default `private`)
+- **Versioning / CORS** (optional): toggles
+
+## Inputs
+
+- **Linode Cloud Account** (`@facets/linode_cloud_account`): provides the Linode provider
+
+## Outputs
+
+- `@facets/linode-object-storage`: `bucket_id`, `bucket_name`, `region`, `s3_endpoint`, `bucket_url`, `access_key`, `secret_key`
diff --git a/modules/object_storage/linode/1.0/facets.yaml b/modules/object_storage/linode/1.0/facets.yaml
new file mode 100644
index 00000000..8e3b52a6
--- /dev/null
+++ b/modules/object_storage/linode/1.0/facets.yaml
@@ -0,0 +1,77 @@
+intent: object_storage
+flavor: linode
+version: "1.0"
+clouds:
+ - kubernetes
+description: S3-compatible object storage bucket on Linode (Akamai) with access credentials
+intentDetails:
+ type: Cloud & Infrastructure
+ description: S3-compatible object storage with versioning on Linode (Akamai)
+ displayName: Object Storage
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/object_storage.svg
+spec:
+ title: Linode Object Storage
+ description: S3-compatible object storage bucket for storing unstructured data on Linode
+ type: object
+ x-ui-order:
+ - region
+ - acl
+ - versioning_enabled
+ - cors_enabled
+ properties:
+ region:
+ type: string
+ title: Region
+ description: Linode object storage region (region/cluster-scoped, e.g. us-east, us-ord, eu-central)
+ default: us-east
+ x-ui-placeholder: us-east
+ x-ui-overrides-only: true
+ acl:
+ type: string
+ title: Access Control
+ description: Canned ACL for the bucket
+ enum:
+ - private
+ - public-read
+ - authenticated-read
+ - public-read-write
+ default: private
+ versioning_enabled:
+ type: boolean
+ title: Enable Versioning
+ description: Enable object versioning for the bucket
+ default: false
+ cors_enabled:
+ type: boolean
+ title: Enable CORS
+ description: Enable CORS for the bucket
+ default: false
+ required: []
+inputs:
+ linode_cloud_account:
+ type: "@facets/linode_cloud_account"
+ optional: false
+ displayName: Linode Cloud Account
+ description: Linode provider configuration
+ providers:
+ - linode
+outputs:
+ default:
+ type: "@facets/linode-object-storage"
+ title: Linode Object Storage Bucket
+sample:
+ kind: object_storage
+ flavor: linode
+ version: "1.0"
+ disabled: true
+ spec:
+ region: us-east
+ acl: private
+ versioning_enabled: false
+ cors_enabled: false
+iac:
+ validated_files:
+ - main.tf
+ - variables.tf
+ - outputs.tf
+ - versions.tf
diff --git a/modules/object_storage/linode/1.0/main.tf b/modules/object_storage/linode/1.0/main.tf
new file mode 100644
index 00000000..2627289b
--- /dev/null
+++ b/modules/object_storage/linode/1.0/main.tf
@@ -0,0 +1,33 @@
+# Linode Object Storage Module
+# Creates an S3-compatible bucket and a scoped access key.
+
+module "name" {
+ source = "github.com/Facets-cloud/facets-utility-modules//name"
+ environment = var.environment
+ limit = 63
+ resource_name = var.instance_name
+ resource_type = "bucket"
+}
+
+locals {
+ # Bucket labels must be lowercase and DNS-compatible.
+ bucket_label = lower(replace(module.name.name, "_", "-"))
+}
+
+resource "linode_object_storage_bucket" "bucket" {
+ region = var.instance.spec.region
+ label = local.bucket_label
+ acl = var.instance.spec.acl
+ cors_enabled = var.instance.spec.cors_enabled
+ versioning = var.instance.spec.versioning_enabled
+}
+
+resource "linode_object_storage_key" "key" {
+ label = "${local.bucket_label}-key"
+
+ bucket_access {
+ bucket_name = linode_object_storage_bucket.bucket.label
+ region = var.instance.spec.region
+ permissions = "read_write"
+ }
+}
diff --git a/modules/object_storage/linode/1.0/outputs.tf b/modules/object_storage/linode/1.0/outputs.tf
new file mode 100644
index 00000000..7fc15d32
--- /dev/null
+++ b/modules/object_storage/linode/1.0/outputs.tf
@@ -0,0 +1,14 @@
+locals {
+ output_attributes = {
+ bucket_id = linode_object_storage_bucket.bucket.id
+ bucket_name = linode_object_storage_bucket.bucket.label
+ region = var.instance.spec.region
+ s3_endpoint = linode_object_storage_bucket.bucket.hostname
+ bucket_url = "https://${linode_object_storage_bucket.bucket.hostname}"
+ access_key = linode_object_storage_key.key.access_key
+ secret_key = linode_object_storage_key.key.secret_key
+ secrets = ["access_key", "secret_key"]
+ }
+
+ output_interfaces = {}
+}
diff --git a/modules/object_storage/linode/1.0/variables.tf b/modules/object_storage/linode/1.0/variables.tf
new file mode 100644
index 00000000..27396bdb
--- /dev/null
+++ b/modules/object_storage/linode/1.0/variables.tf
@@ -0,0 +1,45 @@
+variable "instance" {
+ description = "S3-compatible object storage bucket on Linode (Akamai) with access credentials"
+ type = object({
+ kind = string
+ flavor = string
+ version = string
+ spec = object({
+ region = optional(string, "us-east")
+ acl = optional(string, "private")
+ versioning_enabled = optional(bool, false)
+ cors_enabled = optional(bool, false)
+ })
+ })
+
+ validation {
+ condition = contains(["private", "public-read", "authenticated-read", "public-read-write"], var.instance.spec.acl)
+ error_message = "acl must be one of: private, public-read, authenticated-read, public-read-write."
+ }
+}
+
+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({
+ linode_cloud_account = object({
+ attributes = object({
+ token = string
+ region = string
+ })
+ })
+ })
+}
diff --git a/modules/object_storage/linode/1.0/versions.tf b/modules/object_storage/linode/1.0/versions.tf
new file mode 100644
index 00000000..52bf7a3f
--- /dev/null
+++ b/modules/object_storage/linode/1.0/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ linode = {
+ source = "linode/linode"
+ version = ">= 3.0.0"
+ }
+ }
+}
diff --git a/modules/service/k8s/1.0/README.md b/modules/service/k8s/1.0/README.md
new file mode 100644
index 00000000..8379f683
--- /dev/null
+++ b/modules/service/k8s/1.0/README.md
@@ -0,0 +1,25 @@
+# Kubernetes Service
+
+Cloud-agnostic service module for deploying containerized workloads on any Kubernetes cluster (including LKE, GKE, EKS, AKS, OVH MKS). Produces the `@facets/service` output type.
+
+## Overview
+
+A unified deployment interface supporting `application` (Deployment), `cronjob`, `job`, and `statefulset` workloads. It consumes the Kubernetes and Helm providers from a `@facets/kubernetes-details` input, so it has no cloud-specific logic — it runs the same on every cluster. There is no cloud IAM/workload-identity wiring (Kubernetes-native service accounts only); use cloud-specific service flavors when pod-level cloud IAM is required.
+
+## Features
+
+- Workload types: application, cronjob, job, statefulset
+- Resource sizing (CPU/memory requests + limits), health checks, autoscaling (HPA)
+- Environment variables, secrets, config maps, persistent volume claims
+- Container registry (artifactories) and optional VPA integration
+
+## Inputs
+
+- **Kubernetes Cluster** (`@facets/kubernetes-details`): k8s + helm providers
+- **Node Pool** (`@facets/kubernetes_nodepool`)
+- **Container Registries** (`@facets/artifactories`, optional)
+- **Vertical Pod Autoscaler** (`@facets/vpa`, optional)
+
+## Outputs
+
+- `@facets/service`
diff --git a/modules/service/k8s/1.0/facets.yaml b/modules/service/k8s/1.0/facets.yaml
new file mode 100644
index 00000000..6c56a06b
--- /dev/null
+++ b/modules/service/k8s/1.0/facets.yaml
@@ -0,0 +1,1894 @@
+intent: service
+flavor: k8s
+version: '1.0'
+description: Unified Kubernetes service module supporting deployment, cronjob, job,
+ and statefulset workloads
+intentDetails:
+ type: Cloud & Infrastructure
+ description: Deploy and manage containerized applications on Kubernetes
+ displayName: Service
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/service.svg
+clouds:
+- kubernetes
+artifact_inputs:
+ primary:
+ attribute_path: spec.release.image
+ artifact_type: docker_image
+controlPlaneUISettings:
+ enableKubernetesExplorer: true
+inputs:
+ kubernetes_details:
+ optional: false
+ type: '@facets/kubernetes-details'
+ displayName: Kubernetes Cluster
+ default:
+ resource_type: kubernetes_cluster
+ resource_name: default
+ providers:
+ - kubernetes
+ - kubernetes-alpha
+ - helm
+ kubernetes_node_pool_details:
+ type: '@facets/kubernetes_nodepool'
+ displayName: Node Pool
+ optional: false
+ artifactories:
+ optional: true
+ type: '@facets/artifactories'
+ displayName: Container Registries
+ default:
+ resource_type: artifactories
+ resource_name: default
+ vpa_details:
+ optional: true
+ type: '@facets/vpa'
+ displayName: Vertical Pod Autoscaler
+ default:
+ resource_type: vpa
+ resource_name: default
+outputs:
+ default:
+ type: '@facets/service'
+spec:
+ title: Kubernetes Service
+ type: object
+ properties:
+ type:
+ type: string
+ title: Service Type
+ description: Type of Kubernetes workload to deploy
+ enum:
+ - application
+ - cronjob
+ - job
+ - statefulset
+ default: application
+ restart_policy:
+ type: string
+ title: Restart Policy
+ description: Restart Policy - Always, OnFailure, Never
+ x-ui-override-disable: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ enum:
+ - Always
+ - OnFailure
+ - Never
+ enable_host_anti_affinity:
+ type: boolean
+ title: Enable Host Anti-Affinity
+ description: Distribute instances across different physical hosts within cluster
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ pod_distribution_enabled:
+ type: boolean
+ title: Enable Pod Distribution
+ description: Enable topology spread constraints for pod distribution
+ default: false
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ pod_distribution:
+ type: object
+ title: Pod Distribution
+ description: Configure how pods are distributed across the cluster topology
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.pod_distribution_enabled
+ values:
+ - true
+ patternProperties:
+ ^[a-zA-Z0-9_-]*$:
+ type: object
+ title: Topology Configuration
+ properties:
+ topology_key:
+ type: string
+ title: Topology Key
+ description: The key of node labels used for grouping nodes
+ default: kubernetes.io/hostname
+ enum:
+ - kubernetes.io/hostname
+ - topology.kubernetes.io/zone
+ - kubernetes.io/arch
+ - lifecycle
+ when_unsatisfiable:
+ type: string
+ title: When Unsatisfiable
+ description: How to deal with pods that don't satisfy the spread constraints
+ default: DoNotSchedule
+ enum:
+ - DoNotSchedule
+ - ScheduleAnyway
+ max_skew:
+ type: integer
+ title: Max Skew
+ description: Maximum difference between the number of matching pods
+ in any two topology domains
+ default: 1
+ minimum: 1
+ maximum: 100
+ node_taints_policy:
+ type: string
+ title: Node Taints Policy
+ description: How to handle node taints when scheduling pods
+ enum:
+ - Honor
+ - Ignore
+ node_affinity_policy:
+ type: string
+ title: Node Affinity Policy
+ description: How to handle node affinity when scheduling pods
+ enum:
+ - Honor
+ - Ignore
+ required:
+ - topology_key
+ - when_unsatisfiable
+ - max_skew
+ cronjob:
+ type: object
+ title: Cronjob
+ description: Cron scheduler to be specified in cron format
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - cronjob
+ properties:
+ schedule:
+ title: Schedule
+ type: string
+ description: Cron scheduler to be specified in cron format
+ x-ui-placeholder: Enter the cron schedule (e.g., * * * * *)
+ suspend:
+ title: Suspend
+ type: boolean
+ description: Suspend all executions
+ concurrency_policy:
+ title: Concurrent Policy
+ type: string
+ description: Specifies how to treat concurrent executions of a job created
+ by this cron job
+ enum:
+ - Allow
+ - Forbid
+ - Replace
+ required:
+ - schedule
+ - suspend
+ - concurrency_policy
+ job:
+ type: object
+ title: Job
+ description: Manage execution of a one-off task or batch process, ensuring it
+ completes successfully
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - job
+ - cronjob
+ properties:
+ retry:
+ type: string
+ default: 5
+ minimum: 1
+ x-ui-placeholder: Enter the minimum retry count for your job (default is
+ 5)
+ description: Number of time you want to retry the job before calling it
+ a failure
+ pattern: ^(100|[1-9][0-9]?)$
+ x-ui-error-message: The retry count must be a positive integer between 1
+ and 100.
+ required:
+ - retry
+ persistent_volume_claims:
+ type: object
+ title: Persistent Volume Claim
+ Description: PVCs that you will create and mount to your statefulset
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - statefulset
+ patternProperties:
+ ^[a-zA-Z0-9_-]*$:
+ type: object
+ title: PVC
+ description: Define volume details
+ properties:
+ access_mode:
+ title: Access Mode
+ type: string
+ enum:
+ - ReadWriteOnce
+ - ReadWriteMany
+ description: Define read-write access of the pvc
+ storage_size:
+ type: string
+ title: Storage Size
+ description: The storage size of the pvc , it should be specified like
+ `10Gi`
+ minLength: 1
+ pattern: ^(0\.[1-9]|[1-9](\.[0-9]+)?|[1-5][0-9](\.[0-9]+)?|6[0-4])Gi$|^([1-9](\.[0-9]+)?|[1-9][0-9]{1,3}(\.[0-9]+)?|[1-5][0-9]{4}(\.[0-9]+)?|6[0-3][0-9]{3}(\.[0-9]+)?|64000)Mi$
+ x-ui-placeholder: e.g., '800Mi' or '1.5Gi'
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ path:
+ type: string
+ title: Path
+ description: Path to mount the PVC
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / or /
+ etc'
+ x-ui-placeholder: Enter the storage size, eg. /data/temp
+ required:
+ - access_mode
+ - path
+ - storage_size
+ runtime:
+ type: object
+ title: Runtime
+ description: Customize service runtime settings like ports, healthchecks etc.
+ properties:
+ command:
+ type: array
+ title: Command
+ description: Command to run in the container
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ args:
+ type: array
+ title: Arguments
+ description: Arguments to pass to the command
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ size:
+ type: object
+ title: Size
+ description: Configure container size to meet resource requirements
+ properties:
+ cpu:
+ type: string
+ title: CPU
+ description: CPU size
+ x-ui-compare:
+ field: spec.runtime.size.cpu_limit
+ comparator: <=
+ x-ui-error-message: CPU cannot be more than CPU limit
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1 to 32 or 1m to 32000m
+ memory:
+ type: string
+ title: Memory
+ description: Memory size
+ x-ui-compare:
+ field: spec.runtime.size.memory_limit
+ comparator: <=
+ x-ui-error-message: Memory cannot be more than memory limit
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ cpu_limit:
+ type: string
+ title: CPU Limit
+ description: CPU limit size
+ x-ui-compare:
+ field: spec.runtime.size.cpu
+ comparator: '>='
+ x-ui-error-message: CPU limit cannot be less than CPU
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1 to 32 or 1m to 32000m
+ memory_limit:
+ type: string
+ title: Memory Limit
+ description: Memory limit size
+ x-ui-compare:
+ field: spec.runtime.size.memory
+ comparator: '>='
+ x-ui-error-message: Memory limit cannot be less than memory
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ required:
+ - cpu
+ - memory
+ ports:
+ type: object
+ title: Ports
+ x-ui-override-disable: true
+ description: Port mappings
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ patternProperties:
+ ^[0-9]+[m]?$:
+ type: object
+ title: Port Name
+ description: Define port allocation to facilitate communication with
+ service
+ properties:
+ port:
+ type: string
+ title: Port
+ description: Port on which application is running
+ pattern: ^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$
+ x-ui-unique: true
+ x-ui-placeholder: Enter the port number to expose for your application
+ container
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1-65535
+ service_port:
+ type: string
+ title: Service Port
+ description: Port from which application service can be exposed
+ pattern: ^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$
+ x-ui-unique: true
+ x-ui-placeholder: Enter the port number to expose for your application
+ service
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 1-65535
+ protocol:
+ type: string
+ title: Protocol
+ description: TCP or UDP
+ enum:
+ - tcp
+ - udp
+ required:
+ - protocol
+ - port
+ health_checks:
+ type: object
+ title: Health Checks
+ description: Ensure service remains responsive and operational. Assess its
+ status and performance.
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ properties:
+ readiness_check_type:
+ type: string
+ title: Readiness Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ readiness_start_up_time:
+ type: integer
+ title: Readiness Start-Up Time
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for readiness check (in sec)
+ readiness_timeout:
+ type: integer
+ title: Readiness Timeout
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for readiness (in sec)
+ readiness_period:
+ type: integer
+ title: Readiness Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Repeated interval for health-check (in sec)
+ readiness_port:
+ type: string
+ title: Readiness Port
+ x-ui-placeholder: Enter readiness port for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-65535
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ description: Port for Health Checks
+ x-ui-dynamic-enum: spec.runtime.ports.*.port
+ x-ui-disable-tooltip: No Ports Added
+ readiness_url:
+ type: string
+ title: Readiness URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter readiness url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - HttpCheck
+ description: URL for readiness
+ readiness_exec_command:
+ type: array
+ title: Readiness Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate line
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.readiness_check_type
+ values:
+ - ExecCheck
+ description: Commands for readiness check
+ x-ui-override-disable: true
+ liveness_check_type:
+ type: string
+ title: Liveness Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ liveness_start_up_time:
+ type: integer
+ default: 10
+ title: Liveness Start-Up Time
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for liveness (in sec)
+ liveness_timeout:
+ type: integer
+ default: 10
+ title: Liveness Timeout
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for liveness (in sec)
+ liveness_period:
+ type: integer
+ title: Liveness Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Interval for Liveness (in sec)
+ liveness_port:
+ type: string
+ title: Liveness Port
+ x-ui-placeholder: Enter liveness port for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-65535
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ description: Port for Health Checks
+ x-ui-dynamic-enum: spec.runtime.ports.*.port
+ x-ui-disable-tooltip: No Ports Added
+ liveness_url:
+ type: string
+ title: Liveness URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter liveness url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - HttpCheck
+ description: URL for liveness
+ liveness_exec_command:
+ type: array
+ title: Liveness Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate line
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.liveness_check_type
+ values:
+ - ExecCheck
+ description: Commands for liveness
+ x-ui-override-disable: true
+ startup_check_type:
+ type: string
+ title: Startup Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ startup_start_up_time:
+ type: integer
+ title: Startup Start-Up Time
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for startup check (in sec)
+ startup_timeout:
+ type: integer
+ title: Startup Timeout
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for startup (in sec)
+ startup_period:
+ type: integer
+ title: Startup Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-10000
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Repeated interval for health-check (in sec)
+ startup_port:
+ type: string
+ title: Startup Port
+ x-ui-placeholder: Enter startup port for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values are
+ from 0-65535
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ description: Port for Health Checks
+ x-ui-dynamic-enum: spec.runtime.ports.*.port
+ x-ui-disable-tooltip: No Ports Added
+ startup_url:
+ type: string
+ title: Startup URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter startup url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - HttpCheck
+ description: URL for startup
+ startup_headers:
+ type: object
+ title: Startup Headers
+ description: Headers to be sent with startup check request
+ x-ui-yaml-editor: true
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - HttpCheck
+ startup_exec_command:
+ type: array
+ title: Startup Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate line
+ x-ui-visible-if:
+ field: spec.runtime.health_checks.startup_check_type
+ values:
+ - ExecCheck
+ description: Commands for startup check
+ x-ui-override-disable: true
+ autoscaling:
+ type: object
+ title: Autoscaling
+ description: Automatically adjust resources based on demand for optimal
+ performance
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ properties:
+ min:
+ type: integer
+ title: Min
+ default: 1
+ minimum: 1
+ maximum: 200
+ description: Min replicas
+ max:
+ type: integer
+ title: Max
+ default: 1
+ minimum: 1
+ maximum: 200
+ description: Max replicas
+ scaling_on:
+ type: string
+ title: Scale based on
+ default: CPU
+ enum:
+ - CPU
+ - RAM
+ cpu_threshold:
+ type: string
+ title: CPU Threshold
+ description: Max CPU threshold
+ pattern: ^(100|[1-9][0-9]?)$
+ x-ui-visible-if:
+ field: spec.runtime.autoscaling.scaling_on
+ values:
+ - CPU
+ ram_threshold:
+ type: string
+ title: RAM Threshold
+ description: Max RAM threshold
+ pattern: ^(1?[0-9]?[0-9]|100)$
+ x-ui-visible-if:
+ field: spec.runtime.autoscaling.scaling_on
+ values:
+ - RAM
+ metrics:
+ type: object
+ title: Metrics
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ description: Metrics port mappings
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Metrics Name
+ description: Track and analyze performance metrics to monitor service
+ health and efficiency
+ properties:
+ path:
+ type: string
+ title: Path
+ description: Metrics path
+ pattern: ^(/[^/]+)*(/)?$
+ port_name:
+ type: string
+ title: Port Name
+ description: Port name for metrics
+ x-ui-dynamic-enum: spec.runtime.ports.*
+ x-ui-disable-tooltip: No Ports Available
+ required:
+ - path
+ - port_name
+ volumes:
+ type: object
+ title: Volumes
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ description: Manage data storage volumes to ensure access and persistence
+ properties:
+ config_maps:
+ type: object
+ title: Config Maps
+ description: Config map mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Config Map Name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Config map name
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${config_map.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: config_map
+ mount_path:
+ type: string
+ title: Mount Path
+ pattern: ^/.*
+ description: Mount path for config maps
+ x-ui-error-message: Value doesn't match pattern, it should start
+ with a forward slash (/)
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the ConfigMap to mount a specific key
+ or file
+ required:
+ - name
+ - mount_path
+ secrets:
+ type: object
+ title: Secrets
+ description: Secret mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Secret name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Secret name
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${kubernetes_secret.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: kubernetes_secret
+ mount_path:
+ type: string
+ title: Mount Path
+ pattern: ^/.*
+ description: Mount path for secrets
+ x-ui-error-message: Value doesn't match pattern, it should start
+ with a forward slash (/)
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the Secret to mount a specific key
+ or file
+ required:
+ - name
+ - mount_path
+ pvc:
+ type: object
+ title: PVC
+ description: PVC mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: PVC Name
+ properties:
+ claim_name:
+ type: string
+ title: Claim Name
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{1,18}[a-zA-Z0-9])$
+ description: PVC name
+ x-ui-error-message: Value doesn't match pattern, it should start
+ with an alphanumeric character and should be in range of 3-20
+ characters
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${pvc.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: pvc
+ mount_path:
+ type: string
+ title: Mount Path
+ pattern: ^/.*
+ description: Mount path for PVC
+ x-ui-error-message: Value doesn't match pattern, it should start
+ with a forward slash (/)
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the PVC to mount a specific subdirectory
+ required:
+ - claim_name
+ - mount_path
+ host_path:
+ type: object
+ title: Host Path
+ description: Host path mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Host Path Name
+ properties:
+ mount_path:
+ type: string
+ title: Mount Path
+ pattern: ^/.*
+ description: Mount path for host path
+ x-ui-error-message: Value doesn't match pattern, it should start
+ with a forward slash (/)
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the host filesystem to mount a specific
+ directory or file
+ required:
+ - name
+ - mount_path
+ required:
+ - ports
+ - size
+ release:
+ title: Release
+ description: Manage version releases to deploy updates and maintain software
+ integrity
+ type: object
+ properties:
+ image:
+ type: string
+ title: Image
+ description: The docker image of the application that you want to run
+ x-ui-skip: true
+ x-ui-error-message: Value doesn't match pattern for a container image
+ image_pull_policy:
+ type: string
+ title: Image Pull Policy
+ description: Specifies when the image should be pulled from the registry.
+ default: IfNotPresent
+ enum:
+ - IfNotPresent
+ - Always
+ - Never
+ strategy:
+ type: object
+ title: Strategy
+ description: The type of upgrade strategy to be followed by this service
+ properties:
+ type:
+ type: string
+ title: Type
+ description: 'Your kubernetes rollout type eg: RollingUpdate or Recreate'
+ enum:
+ - RollingUpdate
+ - Recreate
+ max_available:
+ type: string
+ title: Max Available
+ description: If type RollingUpdate, this is the max number of pods that
+ can be created from the default replicas
+ pattern: ^((100|[1-9]?[0-9])%?)$
+ x-ui-placeholder: Enter the max number or percentage of pods that needs
+ to be available during rolling restart
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 0 to 100 or 0% to 100%
+ max_unavailable:
+ type: string
+ title: Max Unavailable
+ description: If type RollingUpdate, this is the max number of pods that
+ can be unavailable from the default replicas
+ pattern: ^((100|[1-9]?[0-9])%?)$
+ x-ui-placeholder: Enter the max number or percentage of pods that needs
+ to be unavailable during rolling restart
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 0 to 100 or 0% to 100%
+ disruption_policy:
+ type: object
+ title: Disruption Policy
+ description: The disruption policy for the service. Both min available and
+ max unavailable cannot be specified simultaneously in disruption policy.
+ You must fill one of the two fields when defining a disruption policy
+ to avoid release failures.
+ properties:
+ min_available:
+ type: string
+ title: Min Available
+ description: This is the min number of pods should be available in case
+ of failures
+ pattern: ^((100|[1-9]?[0-9])%?)$
+ x-ui-placeholder: Enter the min number or percentage of pods that needs
+ to be available during disruption, this is pod disruption budget
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 0 to 100 or 0% to 100%
+ max_unavailable:
+ type: string
+ title: Max Unavailable
+ description: This is the max number of pods that can be unavailable
+ during a failure.
+ pattern: ^((100|[1-9]?[0-9])%?)$
+ x-ui-placeholder: Enter the max number or percentage of pods that needs
+ to be unavailable during disruption, this is pod disruption budget
+ x-ui-error-message: Value doesn't match pattern, it should be number
+ ranging from 0 to 100 or 0% to 100%
+ env:
+ title: Environment Variables
+ description: Map of environment variables passed to the main container.
+ type: object
+ x-ui-yaml-editor: true
+ init_containers:
+ title: Init Containers
+ description: Map of specialized containers that run before app containers in
+ a Pod. Init containers can contain utilities or setup scripts not present
+ in an app image.
+ type: object
+ x-ui-toggle: true
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ patternProperties:
+ ^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$:
+ title: Init Container
+ description: Init Container Specification.
+ type: object
+ properties:
+ image:
+ type: string
+ title: Image
+ description: Docker image of the init container.
+ x-ui-error-message: Value doesn't match pattern for a container image
+ pull_policy:
+ title: Image Pull Policy
+ description: Specifies when the image should be pulled from the registry.
+ type: string
+ default: IfNotPresent
+ enum:
+ - IfNotPresent
+ - Always
+ - Never
+ env:
+ title: Environment Variables
+ description: Map of environment variables passed to init container.
+ type: object
+ x-ui-yaml-editor: true
+ additional_k8s_env:
+ title: Additional Environment Variables
+ description: List of evironment variables to be created from referencing
+ key from configmap or secret. Eg https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/pod-configmap-env-var-valueFrom.yaml.
+ type: object
+ x-ui-yaml-editor: true
+ additional_k8s_env_from:
+ title: Import Additional Environment Variables
+ description: List of configmap or secret reference from which environments
+ varibales to be imported. Eg. https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/inject/pod-secret-envFrom.yaml.
+ type: object
+ x-ui-yaml-editor: true
+ runtime:
+ type: object
+ title: Runtime
+ description: Customize runtime settings like size, healthchecks etc.
+ properties:
+ command:
+ type: array
+ title: Command
+ description: Command to run in the container
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ args:
+ type: array
+ title: Arguments
+ description: Arguments to pass to the command
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ size:
+ type: object
+ title: Size
+ description: Configure container size to meet resource requirements.
+ properties:
+ cpu:
+ type: string
+ title: CPU
+ description: CPU size
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1 to 32 or 1m to 32000m
+ memory:
+ type: string
+ title: Memory
+ description: Memory size
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ cpu_limit:
+ type: string
+ title: CPU Limit
+ description: CPU limit size
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1 to 32 or 1m to 32000m
+ memory_limit:
+ type: string
+ title: Memory Limit
+ description: Memory limit size
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ required:
+ - cpu
+ - memory
+ volumes:
+ type: object
+ title: Volumes
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ description: Manage data storage volumes to ensure access and persistence.
+ properties:
+ config_maps:
+ type: object
+ title: Config Maps
+ description: Config map mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Config Map Name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Config map name
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{1,18}[a-zA-Z0-9])$
+ x-ui-error-message: Value doesn't match pattern, it
+ should start with an alphanumeric character and should
+ be in range of 3-20 characters
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${config_map.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: config_map
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for config maps
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the ConfigMap to mount a specific
+ key or file
+ required:
+ - name
+ - mount_path
+ secrets:
+ type: object
+ title: Secrets
+ description: Secret mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Secret name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Secret name
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${kubernetes_secret.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: kubernetes_secret
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for secrets
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the Secret to mount a specific
+ key or file
+ required:
+ - name
+ - mount_path
+ pvc:
+ type: object
+ title: PVC
+ description: PVC mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: PVC Name
+ properties:
+ claim_name:
+ type: string
+ title: Claim Name
+ description: PVC name
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{1,18}[a-zA-Z0-9])$
+ x-ui-error-message: Value doesn't match pattern, it
+ should start with an alphanumeric character and should
+ be in range of 3-20 characters
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${pvc.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: pvc
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for PVC
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the PVC to mount a specific
+ subdirectory
+ required:
+ - claim_name
+ - mount_path
+ host_path:
+ type: object
+ title: Host Path
+ description: Host path mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Host Path Name
+ properties:
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for host path
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the host filesystem to mount
+ a specific directory or file
+ required:
+ - name
+ - mount_path
+ required:
+ - size
+ x-ui-order:
+ - size
+ - volumes
+ - command
+ - args
+ required:
+ - image
+ - pull_policy
+ - runtime
+ sidecars:
+ title: Sidecar Containers
+ description: Map of specialized additional containers that run alongside a primary
+ application container within a Pod.
+ type: object
+ x-ui-visible-if:
+ field: spec.type
+ values:
+ - application
+ - statefulset
+ x-ui-toggle: true
+ patternProperties:
+ ^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$:
+ title: Sidecar Container
+ description: Sidecar Container Specification
+ type: object
+ properties:
+ image:
+ type: string
+ title: Image
+ description: Docker image of the init container
+ x-ui-error-message: Value doesn't match pattern for a container image
+ pull_policy:
+ title: Image Pull Policy
+ description: Specifies when the image should be pulled from the registry.
+ type: string
+ default: IfNotPresent
+ enum:
+ - IfNotPresent
+ - Always
+ - Never
+ env:
+ title: Environment Variables
+ description: Map of environment variables passed to init container
+ type: object
+ x-ui-yaml-editor: true
+ additional_k8s_env:
+ title: Additional Environment Variables
+ description: List of evironment variables to be created from referencing
+ key from configmap or secret. Eg https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/pod-configmap-env-var-valueFrom.yaml
+ type: object
+ x-ui-yaml-editor: true
+ additional_k8s_env_from:
+ title: Import Additional Environment Variables
+ description: List of configmap or secret reference from which environments
+ varibales to be imported. Eg. https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/inject/pod-secret-envFrom.yaml
+ type: object
+ x-ui-yaml-editor: true
+ runtime:
+ type: object
+ title: Runtime
+ description: Customize runtime settings like size, healthchecks etc.
+ properties:
+ command:
+ type: array
+ title: Command
+ description: Command to run in the container
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ args:
+ type: array
+ title: Arguments
+ description: Arguments to pass to the command
+ items:
+ type: string
+ x-ui-command: true
+ x-ui-override-disable: true
+ size:
+ type: object
+ title: Size
+ description: Configure container size to meet resource requirements
+ properties:
+ cpu:
+ type: string
+ title: CPU
+ description: CPU size
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1 to 32 or 1m to 32000m
+ memory:
+ type: string
+ title: Memory
+ description: Memory size
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory requests for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ cpu_limit:
+ type: string
+ title: CPU Limit
+ description: CPU limit size
+ pattern: ^([1-9]|[12][0-9]|3[0-2])$|^(3[0-1][0-9]{3}|[1-2][0-9]{4}|[1-9][0-9]{0,3}|32000)m$
+ x-ui-placeholder: Enter CPU limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1 to 32 or 1m to 32000m
+ memory_limit:
+ type: string
+ title: Memory Limit
+ description: Memory limit size
+ pattern: ^([1-9]|[1-5][0-9]|6[0-4])Gi$|^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-3][0-9]{3}|64000)Mi$
+ x-ui-placeholder: Enter Memory limits for your application
+ x-ui-error-message: Value doesn't match pattern, it should be
+ number ranging from 1Gi to 64Gi or 1Mi to 64000Mi
+ required:
+ - cpu
+ - memory
+ volumes:
+ type: object
+ title: Volumes
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ description: Manage data storage volumes to ensure access and persistence
+ properties:
+ config_maps:
+ type: object
+ title: Config Maps
+ description: Config map mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Config Map Name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Config map name
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{1,18}[a-zA-Z0-9])$
+ x-ui-error-message: Value doesn't match pattern, it
+ should start with an alphanumeric character and should
+ be in range of 3-20 characters
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${config_map.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: config_map
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for config maps
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the ConfigMap to mount a specific
+ key or file
+ required:
+ - name
+ - mount_path
+ secrets:
+ type: object
+ title: Secrets
+ description: Secret mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Secret name
+ properties:
+ name:
+ type: string
+ title: Name
+ description: Secret name
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${kubernetes_secret.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: kubernetes_secret
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for secrets
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the Secret to mount a specific
+ key or file
+ required:
+ - name
+ - mount_path
+ pvc:
+ type: object
+ title: PVC
+ description: PVC mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: PVC Name
+ properties:
+ claim_name:
+ type: string
+ title: Claim Name
+ description: PVC name
+ pattern: ^[a-zA-Z0-9]([a-zA-Z0-9-]{1,18}[a-zA-Z0-9])$
+ x-ui-error-message: Value doesn't match pattern, it
+ should start with an alphanumeric character and should
+ be in range of 3-20 characters
+ x-ui-typeable: true
+ x-ui-api-source:
+ endpoint: /cc-ui/v1/dropdown/stack/{{stackName}}/resources-info
+ method: GET
+ params:
+ includeContent: false
+ labelKey: resourceName
+ valueKey: resourceName
+ valueTemplate: ${pvc.{{value}}.out.attributes.name}
+ filterConditions:
+ - field: resourceType
+ value: pvc
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for PVC
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the PVC to mount a specific
+ subdirectory
+ required:
+ - claim_name
+ - mount_path
+ host_path:
+ type: object
+ title: Host Path
+ description: Host path mounts
+ patternProperties:
+ ^[a-zA-Z0-9_.-]*$:
+ type: object
+ title: Host Path Name
+ properties:
+ mount_path:
+ type: string
+ title: Mount Path
+ description: Mount path for host path
+ sub_path:
+ type: string
+ title: Sub Path
+ description: Path within the host filesystem to mount
+ a specific directory or file
+ required:
+ - name
+ - mount_path
+ ports:
+ type: object
+ title: Ports
+ x-ui-override-disable: true
+ description: Port mappings for sidecar container
+ patternProperties:
+ ^[0-9]+[m]?$:
+ type: object
+ title: Port Name
+ description: Define port to be exposed from sidecar
+ properties:
+ port:
+ type: string
+ title: Port
+ description: Port on which sidecar application is running
+ pattern: ^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$
+ x-ui-placeholder: Enter the port number to expose for your
+ sidecar container
+ x-ui-error-message: Value doesn't match pattern, it should
+ be number ranging from 1-65535
+ required:
+ - port
+ health_checks:
+ type: object
+ title: Health Checks
+ description: Ensure service remains responsive and operational.
+ Assess its status and performance.
+ x-ui-override-disable: true
+ x-ui-toggle: true
+ properties:
+ readiness_check_type:
+ type: string
+ title: Readiness Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ readiness_start_up_time:
+ type: integer
+ title: Readiness Start-Up Time
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for readiness check (in sec)
+ readiness_timeout:
+ type: integer
+ title: Readiness Timeout
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for readiness (in sec)
+ readiness_period:
+ type: integer
+ title: Readiness Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter readiness period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Repeated interval for health-check (in sec)
+ readiness_url:
+ type: string
+ title: Readiness URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter readiness url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - HttpCheck
+ description: URL for readiness
+ readiness_exec_command:
+ type: array
+ title: Readiness Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate
+ line
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - ExecCheck
+ description: Commands for readiness check
+ x-ui-override-disable: true
+ liveness_check_type:
+ type: string
+ title: Liveness Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ liveness_start_up_time:
+ type: integer
+ default: 10
+ title: Liveness Start-Up Time
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for liveness (in sec)
+ liveness_timeout:
+ type: integer
+ default: 10
+ title: Liveness Timeout
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for liveness (in sec)
+ liveness_period:
+ type: integer
+ title: Liveness Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter liveness period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.liveness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Interval for Liveness (in sec)
+ liveness_url:
+ type: string
+ title: Liveness URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter liveness url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.liveness_check_type
+ values:
+ - HttpCheck
+ description: URL for liveness
+ liveness_exec_command:
+ type: array
+ title: Liveness Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate
+ line
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.liveness_check_type
+ values:
+ - ExecCheck
+ description: Commands for liveness
+ x-ui-override-disable: true
+ startup_check_type:
+ type: string
+ title: Startup Check Type
+ default: None
+ x-ui-no-sort: true
+ enum:
+ - None
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ startup_start_up_time:
+ type: integer
+ title: Startup Start-Up Time
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup start up time for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Wait time for startup check (in sec)
+ startup_timeout:
+ type: integer
+ title: Startup Timeout
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup timeout for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Max time for startup (in sec)
+ startup_period:
+ type: integer
+ title: Startup Check Interval
+ default: 10
+ minimum: 0
+ maximum: 10000
+ x-ui-placeholder: Enter startup period for the Pod
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-10000
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.startup_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ - ExecCheck
+ description: Repeated interval for health-check (in sec)
+ startup_url:
+ type: string
+ title: Startup URL
+ pattern: ^(/[^/]+)*(/)?$
+ x-ui-placeholder: Enter startup url for the Pod
+ x-ui-error-message: 'Value doesn''t match pattern, eg: / , /'
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.startup_check_type
+ values:
+ - HttpCheck
+ description: URL for startup
+ startup_exec_command:
+ type: array
+ title: Startup Exec Command
+ x-ui-placeholder: Add each command argument as a new entry
+ x-ui-error-message: Enter each command/argument in a separate
+ line
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.startup_check_type
+ values:
+ - ExecCheck
+ description: Commands for startup check
+ x-ui-override-disable: true
+ port:
+ type: string
+ title: Health Check Port
+ x-ui-placeholder: Enter health check port for the Sidecar
+ x-ui-error-message: Value doesn't match pattern, accepted values
+ are from 0-65535
+ x-ui-visible-if:
+ field: spec.sidecars.{{this}}.runtime.health_checks.readiness_check_type
+ values:
+ - PortCheck
+ - HttpCheck
+ description: Port for Health Checks
+ x-ui-disable-tooltip: No Ports Added
+ x-ui-typeable: true
+ required:
+ - readiness_url
+ - readiness_exec_command
+ - liveness_url
+ - liveness_exec_command
+ - startup_url
+ - startup_exec_command
+ - port
+ required:
+ - size
+ x-ui-order:
+ - size
+ - volumes
+ - command
+ - args
+ - ports
+ - health_checks
+ required:
+ - image
+ - pull_policy
+ - runtime
+ required:
+ - type
+ - runtime
+ x-ui-order:
+ - type
+ - restart_policy
+ - enable_host_anti_affinity
+ - pod_distribution_enabled
+ - pod_distribution
+ - cronjob
+ - job
+ - runtime
+ - release
+ - init_containers
+ - sidecars
+ - persistent_volume_claims
+ - env
+sample:
+ $schema: https://facets-cloud.github.io/facets-schemas/schemas/service/service.schema.json
+ flavor: k8s
+ kind: service
+ disabled: true
+ version: '1.0'
+ spec:
+ type: application
+ enable_host_anti_affinity: false
+ restart_policy: Always
+ pod_distribution_enabled: false
+ pod_distribution: {}
+ release:
+ strategy:
+ type: RollingUpdate
+ runtime:
+ args: []
+ command: []
+ autoscaling:
+ scaling_on: CPU
+ min: 1
+ max: 1
+ cpu_threshold: '50'
+ ports: {}
+ size:
+ cpu: 300m
+ memory: 1Gi
+ cpu_limit: 1000m
+ memory_limit: 5Gi
+ health_checks:
+ readiness_check_type: None
+ liveness_check_type: None
+ env:
+ LOG_LEVEL: INFO
+ init_containers: {}
+ sidecars: {}
diff --git a/modules/service/k8s/1.0/main.tf b/modules/service/k8s/1.0/main.tf
new file mode 100644
index 00000000..cc0d6f1d
--- /dev/null
+++ b/modules/service/k8s/1.0/main.tf
@@ -0,0 +1,123 @@
+locals {
+ # Core instance spec
+ spec = lookup(var.instance, "spec", {})
+ sa_name = lower(var.instance_name)
+
+ # Spec type for actions (application, cronjob, job, statefulset)
+ spec_type = lookup(local.spec, "type", "application")
+ actions_required_vars_set = can(var.instance.kind) && can(var.instance.version) && can(var.instance.flavor) && !contains(["cronjob", "job"], local.spec_type)
+
+ enable_actions = lookup(var.instance.spec, "enable_actions", true) && local.actions_required_vars_set ? true : false
+ enable_deployment_actions = local.enable_actions && local.spec_type == "application" ? 1 : 0
+ enable_statefulset_actions = local.enable_actions && local.spec_type == "statefulset" ? 1 : 0
+
+ namespace = var.environment.namespace
+ annotations = {}
+ labels = {}
+ resource_type = "service"
+ resource_name = var.instance_name
+
+ image_pull_secrets = try(var.inputs.artifactories.attributes.registry_secrets_list, [])
+
+ # Transform taints from object format to string format for utility module compatibility
+ kubernetes_node_pool_details = lookup(var.inputs, "kubernetes_node_pool_details", {})
+ node_pool_taints = lookup(local.kubernetes_node_pool_details, "taints", [])
+ node_pool_labels = lookup(local.kubernetes_node_pool_details, "node_selector", {})
+
+ # Convert taints from {key: "key", value: "value", effect: "effect"} to "key=value:effect" format
+ transformed_taints = [
+ for taint_name, taint_config in local.node_pool_taints :
+ "${taint_config.key}=${taint_config.value}:${taint_config.effect}"
+ ]
+
+ # Create modified inputs with transformed taints
+ modified_inputs = merge(var.inputs, {
+ kubernetes_node_pool_details = merge(local.kubernetes_node_pool_details, {
+ taints = local.transformed_taints
+ node_selector = local.node_pool_labels
+ })
+ })
+
+ # Check if VPA is available and configure accordingly
+ vpa_available = try(var.inputs.vpa_details != null, false)
+
+ # Configure pod distribution directly from spec
+ enable_host_anti_affinity = lookup(local.spec, "enable_host_anti_affinity", false)
+ pod_distribution_enabled = lookup(local.spec, "pod_distribution_enabled", false)
+ pod_distribution_spec = lookup(local.spec, "pod_distribution", {})
+
+ # Convert pod_distribution object to array format expected by helm chart
+ pod_distribution_array = [
+ for key, config in local.pod_distribution_spec : {
+ topology_key = config.topology_key
+ when_unsatisfiable = config.when_unsatisfiable
+ max_skew = config.max_skew
+ node_taints_policy = lookup(config, "node_taints_policy", null)
+ node_affinity_policy = lookup(config, "node_affinity_policy", null)
+ }
+ ]
+
+ # Determine final pod_distribution configuration
+ pod_distribution = local.pod_distribution_enabled ? (
+ length(local.pod_distribution_spec) > 0 ? local.pod_distribution_array : (
+ local.enable_host_anti_affinity ? [{
+ topology_key = "kubernetes.io/hostname"
+ when_unsatisfiable = "DoNotSchedule"
+ max_skew = 1
+ }] : []
+ )
+ ) : []
+
+ # Create instance configuration with VPA settings and topology spread constraints
+ instance_with_vpa_config = merge(var.instance, {
+ advanced = merge(
+ lookup(var.instance, "advanced", {}),
+ {
+ common = merge(
+ lookup(lookup(var.instance, "advanced", {}), "common", {}),
+ {
+ app_chart = merge(
+ lookup(lookup(lookup(var.instance, "advanced", {}), "common", {}), "app_chart", {}),
+ {
+ values = merge(
+ lookup(lookup(lookup(lookup(var.instance, "advanced", {}), "common", {}), "app_chart", {}), "values", {}),
+ {
+ enable_vpa = local.vpa_available
+ # Configure pod distribution for the application chart
+ pod_distribution_enabled = local.pod_distribution_enabled
+ pod_distribution = local.pod_distribution
+ },
+ {
+ image_pull_secrets = local.image_pull_secrets
+ }
+ )
+ }
+ )
+ }
+ )
+ }
+ )
+ })
+}
+
+module "sr-name" {
+ source = "github.com/Facets-cloud/facets-utility-modules//name"
+ is_k8s = false
+ globally_unique = true
+ resource_name = local.resource_name
+ resource_type = local.resource_type
+ limit = 60
+ environment = var.environment
+}
+
+module "app-helm-chart" {
+ source = "github.com/Facets-cloud/facets-utility-modules//application/2.0"
+ namespace = local.namespace
+ chart_name = lower(var.instance_name)
+ values = local.instance_with_vpa_config
+ annotations = local.annotations
+ labels = local.labels
+ environment = var.environment
+ inputs = local.modified_inputs
+ vpa_release_id = try(var.inputs.vpa_details.attributes.helm_release_id, "")
+}
diff --git a/modules/service/k8s/1.0/outputs.tf b/modules/service/k8s/1.0/outputs.tf
new file mode 100644
index 00000000..80eb660d
--- /dev/null
+++ b/modules/service/k8s/1.0/outputs.tf
@@ -0,0 +1,38 @@
+locals {
+ release = lookup(local.spec, "release", {})
+ strategy = lookup(local.release, "strategy", {})
+ runtime = lookup(var.instance.spec, "runtime", {})
+ strategy_type = lookup(local.strategy, "type", null)
+ output_interfaces = merge({
+ for k, v in lookup(local.runtime, "ports", {}) : k => {
+ host = "${var.instance_name}.${local.namespace}.svc.cluster.local"
+ username = ""
+ password = ""
+ port = lookup(v, "service_port", v.port)
+ port_name = k
+ name = var.instance_name
+ secrets = ["password"]
+ }
+ },
+ local.strategy_type == "BlueGreen" || local.strategy_type == "Canary" ?
+ { for k, v in lookup(local.runtime, "ports", {}) : "${k}-preview" => {
+ host = "${var.instance_name}-preview.${local.namespace}.svc.cluster.local"
+ username = ""
+ password = ""
+ port = lookup(v, "service_port", v.port)
+ port_name = k
+ name = "${var.instance_name}-preview"
+ secrets = ["password"]
+ }
+ } : {}
+ )
+
+ output_attributes = {
+ selector_labels = module.app-helm-chart.selector_labels
+ namespace = module.app-helm-chart.namespace
+ resource_type = local.resource_type
+ resource_name = local.resource_name
+ service_name = var.instance_name
+ service_account_arn = ""
+ }
+}
diff --git a/modules/service/k8s/1.0/variables.tf b/modules/service/k8s/1.0/variables.tf
new file mode 100644
index 00000000..857a8969
--- /dev/null
+++ b/modules/service/k8s/1.0/variables.tf
@@ -0,0 +1,229 @@
+variable "instance" {
+ description = "The service resource instance containing the complete configuration"
+ type = object({
+ # Module identification
+ kind = string
+ flavor = string
+ version = string
+ disabled = optional(bool, false)
+
+ # Main specification from facets.yaml
+ spec = object({
+ # Workload type: application, cronjob, job, or statefulset
+ type = optional(string, "application")
+
+ # Restart policy (for application/statefulset)
+ restart_policy = optional(string)
+
+ # Pod distribution settings
+ enable_host_anti_affinity = optional(bool, false)
+
+ # Runtime configuration
+ runtime = object({
+ # Container command and args
+ command = optional(list(string), [])
+ args = optional(list(string), [])
+
+ # Resource sizing
+ size = object({
+ cpu = string
+ memory = string
+ cpu_limit = optional(string)
+ memory_limit = optional(string)
+ })
+
+ # Port mappings
+ ports = optional(map(object({
+ port = string
+ service_port = optional(string)
+ protocol = string
+ })), {})
+
+ # Health checks (optional)
+ health_checks = optional(object({
+ readiness_check_type = string
+ liveness_check_type = string
+ }))
+
+ # Autoscaling (optional)
+ autoscaling = optional(object({
+ min = number
+ max = number
+ scaling_on = string
+ cpu_threshold = optional(string)
+ ram_threshold = optional(string)
+ }))
+
+ # Metrics configuration (optional)
+ metrics = optional(map(object({
+ path = string
+ port_name = string
+ })), {})
+
+ # Volume mounts
+ volumes = optional(object({
+ config_maps = optional(map(object({
+ name = string
+ mount_path = string
+ sub_path = optional(string)
+ })), {})
+ secrets = optional(map(object({
+ name = string
+ mount_path = string
+ sub_path = optional(string)
+ })), {})
+ pvc = optional(map(object({
+ claim_name = string
+ mount_path = string
+ sub_path = optional(string)
+ })), {})
+ host_path = optional(map(object({
+ mount_path = string
+ sub_path = optional(string)
+ })), {})
+ }), {})
+ })
+
+ # Release configuration
+ release = optional(object({
+ image = optional(string)
+ image_pull_policy = optional(string, "IfNotPresent")
+ }), {})
+
+ # Environment variables
+ env = optional(map(string), {})
+
+ # Init containers
+ init_containers = optional(map(object({
+ image = string
+ pull_policy = string
+ env = optional(map(string), {})
+ runtime = object({
+ command = optional(list(string), [])
+ args = optional(list(string), [])
+ size = object({
+ cpu = string
+ memory = string
+ cpu_limit = optional(string)
+ memory_limit = optional(string)
+ })
+ volumes = optional(any, {})
+ })
+ })), {})
+
+ # Sidecar containers
+ sidecars = optional(map(object({
+ image = string
+ pull_policy = string
+ env = optional(map(string), {})
+ runtime = object({
+ command = optional(list(string), [])
+ args = optional(list(string), [])
+ size = object({
+ cpu = string
+ memory = string
+ cpu_limit = optional(string)
+ memory_limit = optional(string)
+ })
+ ports = optional(map(object({
+ port = string
+ })), {})
+ health_checks = optional(any)
+ volumes = optional(any, {})
+ })
+ })), {})
+
+ # Enable actions (deployment/statefulset actions)
+ enable_actions = optional(bool, true)
+ })
+
+ # Advanced configuration (no AWS-specific block)
+ advanced = optional(object({
+ common = optional(object({
+ app_chart = optional(object({
+ values = optional(any, {})
+ }), {})
+ }), {})
+ }), {})
+ })
+}
+
+variable "inputs" {
+ description = "Input dependencies from other resources defined in facets.yaml inputs section"
+ type = object({
+ # Required: Kubernetes cluster details (generic)
+ kubernetes_details = object({
+ attributes = optional(object({
+ cloud_provider = optional(string)
+ cluster_endpoint = optional(string)
+ cluster_ca_certificate = optional(string)
+ cluster_name = optional(string)
+ cluster_location = optional(string)
+ cluster_id = optional(string)
+ }))
+ })
+
+ # Required: Kubernetes node pool details
+ kubernetes_node_pool_details = object({
+ attributes = optional(object({
+ node_pool_name = optional(string)
+ taints = optional(list(object({
+ key = string
+ value = string
+ effect = string
+ })), [])
+ node_selector = optional(map(string), {})
+ flavor_name = optional(string)
+ }))
+ interfaces = optional(object({}))
+ })
+
+ # Optional: Container registry access
+ artifactories = optional(object({
+ attributes = optional(object({
+ registry_secret_objects = optional(map(list(object({
+ name = string
+ }))), {})
+ registry_secrets_list = optional(list(object({
+ name = string
+ })), [])
+ }))
+ interfaces = optional(object({}))
+ }))
+
+ # Optional: Vertical Pod Autoscaler
+ vpa_details = optional(object({
+ attributes = optional(object({
+ helm_release_id = optional(string)
+ helm_release_name = optional(string)
+ namespace = optional(string)
+ version = optional(string)
+ recommender_enabled = optional(bool)
+ updater_enabled = optional(bool)
+ admission_controller_enabled = optional(bool)
+ }))
+ interfaces = optional(object({}))
+ }))
+ })
+}
+
+variable "instance_name" {
+ description = "The name of the service instance (from metadata.name or filename)"
+ type = string
+ default = "test_instance"
+}
+
+variable "environment" {
+ description = "Environment configuration including namespace and other environment-specific settings"
+ type = object({
+ name = optional(string)
+ unique_name = string
+ namespace = string
+ cloud_tags = optional(map(string), {})
+ default_tolerations = optional(list(any), [])
+ global_variables = optional(map(string), {})
+ common_environment_variables = optional(map(string), {})
+ deployment_id = optional(string, "")
+ secrets = optional(map(string), {})
+ })
+}
diff --git a/outputs/linode-object-storage/outputs.yaml b/outputs/linode-object-storage/outputs.yaml
new file mode 100644
index 00000000..f8a098c2
--- /dev/null
+++ b/outputs/linode-object-storage/outputs.yaml
@@ -0,0 +1,39 @@
+name: "@facets/linode-object-storage"
+title: "Linode Object Storage Bucket"
+description: "Linode S3-compatible object storage bucket with access credentials"
+properties:
+ type: object
+ properties:
+ attributes:
+ type: object
+ properties:
+ bucket_id:
+ type: string
+ description: "Linode object storage bucket ID"
+ bucket_name:
+ type: string
+ description: "Bucket label/name"
+ region:
+ type: string
+ description: "Linode region where the bucket is located"
+ s3_endpoint:
+ type: string
+ description: "S3-compatible endpoint hostname"
+ bucket_url:
+ type: string
+ description: "Direct bucket access 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/linode-vpc-details/outputs.yaml b/outputs/linode-vpc-details/outputs.yaml
new file mode 100644
index 00000000..10d25aa2
--- /dev/null
+++ b/outputs/linode-vpc-details/outputs.yaml
@@ -0,0 +1,31 @@
+name: '@facets/linode-vpc-details'
+title: "Linode VPC Network Details"
+description: "Linode VPC and subnet details for downstream Kubernetes and managed-service connectivity"
+properties:
+ type: object
+ properties:
+ attributes:
+ type: object
+ properties:
+ vpc_id:
+ type: string
+ description: "Linode VPC ID"
+ vpc_label:
+ type: string
+ description: "Linode VPC label"
+ region:
+ type: string
+ description: "Linode region where the VPC is created"
+ subnet_id:
+ type: string
+ description: "Primary subnet ID"
+ subnet_label:
+ type: string
+ description: "Primary subnet label"
+ subnet_cidr:
+ type: string
+ description: "Primary subnet CIDR block"
+ interfaces:
+ type: object
+ properties: {}
+providers: []
diff --git a/outputs/linode_cloud_account/outputs.yaml b/outputs/linode_cloud_account/outputs.yaml
new file mode 100644
index 00000000..f85195a7
--- /dev/null
+++ b/outputs/linode_cloud_account/outputs.yaml
@@ -0,0 +1,27 @@
+name: '@facets/linode_cloud_account'
+title: "Linode Cloud Account"
+description: "Linode (Akamai) cloud provider configuration with API token authentication and default region"
+properties:
+ type: object
+ properties:
+ attributes:
+ type: object
+ properties:
+ token:
+ description: Linode API token (sensitive)
+ type: string
+ region:
+ description: Default Linode region
+ type: string
+ secrets:
+ description: List of secret field names
+ type: array
+ items:
+ type: string
+ interfaces:
+ type: object
+ properties: {}
+providers:
+- name: linode
+ source: linode/linode
+ version: 3.13.0
diff --git a/outputs/lke/outputs.yaml b/outputs/lke/outputs.yaml
new file mode 100644
index 00000000..58b0899d
--- /dev/null
+++ b/outputs/lke/outputs.yaml
@@ -0,0 +1,42 @@
+name: '@facets/lke'
+title: "LKE Cluster"
+description: "Linode Kubernetes Engine (LKE) cluster attributes"
+properties:
+ type: object
+ properties:
+ attributes:
+ type: object
+ properties:
+ cluster_id:
+ type: string
+ description: "LKE cluster ID"
+ cluster_name:
+ type: string
+ description: "LKE cluster label"
+ cluster_endpoint:
+ type: string
+ description: "Kubernetes API server endpoint"
+ region:
+ type: string
+ description: "Linode region where the cluster is deployed"
+ k8s_version:
+ type: string
+ description: "Kubernetes version"
+ status:
+ type: string
+ description: "Cluster status"
+ dashboard_url:
+ type: string
+ description: "LKE dashboard URL"
+ 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/project-type/linode/ONBOARDING.md b/project-type/linode/ONBOARDING.md
new file mode 100644
index 00000000..4924bbf7
--- /dev/null
+++ b/project-type/linode/ONBOARDING.md
@@ -0,0 +1,147 @@
+# Onboarding Linode (Akamai) to Facets
+
+This guide walks you through standing up Linode (Akamai Cloud) as a target cloud in Facets — from credentials to a deployed LKE cluster with networking, storage, and a managed database.
+
+> **Status:** Preview. The Linode module set is published and importable from this repository. A managed-registry shortcut (`--managed facets/linode`) will be available once the project type is published to the Facets registry.
+
+---
+
+## 1. Prerequisites
+
+- A **Linode (Akamai Cloud) account** with billing enabled.
+- The **Raptor CLI** authenticated to your control plane (`raptor login`).
+- A **Linode Personal Access Token (PAT)** — created in the next step.
+
+---
+
+## 2. Create a Linode API token
+
+1. Go to **https://cloud.linode.com/profile/tokens** → **Create a Personal Access Token**.
+2. Give it a label (e.g. `facets-control-plane`) and an expiry that suits your policy.
+3. Grant **Read/Write** on the scopes the modules use:
+
+ | Scope | Used by |
+ |---|---|
+ | Linodes | LKE nodes |
+ | Kubernetes | LKE cluster + node pools |
+ | VPCs | network/linode_vpc |
+ | IPs | LKE / VPC networking |
+ | Object Storage | object_storage/linode |
+ | Databases | postgres/linode |
+
+4. **Copy the token now** — Linode shows it only once. You'll store it as a secret on the cloud account resource.
+
+---
+
+## 3. Import the Linode project type
+
+The project type bundles the Linode 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/linode/project-type.yml \
+ --modules-dir ./modules --outputs-dir ./outputs
+
+# With a custom display name
+raptor import project-type -f ./project-type/linode/project-type.yml \
+ --modules-dir ./modules --outputs-dir ./outputs --name "Linode Platform"
+```
+
+> If the Linode 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 Linode project type for me from the facets-modules-redesign repo
+(project-type/linode/project-type.yml) along with its output types.
+```
+
+---
+
+## 4. Create a project
+
+```bash
+raptor create project my-linode-app --project-type linode --clouds KUBERNETES
+```
+
+---
+
+## 5. Configure the cloud account
+
+Add the `cloud_account/linode_provider` resource and set the API token (as a secret) and a default region:
+
+```bash
+raptor apply resource cloud_account/linode_provider/1.0 -p my-linode-app -n cloud \
+ --spec '{"token":"","region":"us-east"}'
+```
+
+> Treat the token 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:** `us-east` (Newark), `us-ord` (Chicago), `us-central` (Dallas), `eu-west` (London), `eu-central` (Frankfurt), `ap-south` (Singapore).
+
+---
+
+## 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
+raptor apply resource network/linode_vpc/1.0 -p my-linode-app -n network \
+ --spec '{"region":"us-east","subnet_cidr":"10.0.0.0/24"}' \
+ --input linode_cloud_account=cloud_account/cloud
+
+# LKE cluster (placed in the VPC subnet)
+raptor apply resource kubernetes_cluster/lke/1.0 -p my-linode-app -n cluster \
+ --spec '{"k8s_version":"1.32","high_availability":false,"default_pool":{"node_type":"g6-standard-2","node_count":3}}' \
+ --input linode_cloud_account=cloud_account/cloud --input network=network/network
+
+# Extra node pool (optional)
+raptor apply resource kubernetes_node_pool/lke/1.0 -p my-linode-app -n workers \
+ --spec '{"node_type":"g6-standard-2","node_count":2}' \
+ --input linode_cloud_account=cloud_account/cloud --input kubernetes_cluster=kubernetes_cluster/cluster
+
+# Object storage
+raptor apply resource object_storage/linode/1.0 -p my-linode-app -n assets \
+ --spec '{"region":"us-east","acl":"private"}' \
+ --input linode_cloud_account=cloud_account/cloud
+
+# Managed PostgreSQL
+raptor apply resource postgres/linode/1.0 -p my-linode-app -n database \
+ --spec '{"version_config":{"version":"16"},"sizing":{"type":"g6-nanode-1","cluster_size":1},"network_access":{"allow_list":[]}}' \
+ --input linode_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-linode-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-linode-app -e # server-side terraform plan
+raptor create release -p my-linode-app -e
+```
+
+---
+
+## Reference
+
+**LKE node types:** `g6-standard-1/2/4/6`, `g6-dedicated-2/4/8`
+**Managed DB types:** `g6-nanode-1`, `g6-standard-1/2`, `g6-dedicated-2/4`
+**Kubernetes versions:** `1.31`, `1.32`, `1.33`
+
+## Notes & gotchas
+
+- **Managed PostgreSQL access:** the database is reachable only from CIDRs in `network_access.allow_list` (empty denies all external access). Add your LKE node egress CIDRs to allow the cluster to connect.
+- **Object storage is region-scoped:** the bucket region is independent of the cluster region; set it explicitly.
+- **No pod-level cloud IAM:** Linode has no IRSA/Workload-Identity equivalent. Apps consume object storage via the access key/secret exposed by `object_storage/linode`, and use Kubernetes-native service accounts (via `service/k8s`).
+- **HA control plane** (`high_availability: true`) and multi-node DB clusters (`cluster_size: 3`) incur additional cost — defaults are single-node/cost-friendly.
diff --git a/project-type/linode/project-type.yml b/project-type/linode/project-type.yml
new file mode 100644
index 00000000..7856dcf0
--- /dev/null
+++ b/project-type/linode/project-type.yml
@@ -0,0 +1,91 @@
+name: linode
+description: Linode (Akamai) project type with LKE, 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: linode_provider
+
+ # Networking
+ - intent: network
+ flavor: linode_vpc
+
+ # Kubernetes Cluster
+ - intent: kubernetes_cluster
+ flavor: lke
+
+ # Node Pools
+ - intent: kubernetes_node_pool
+ flavor: lke
+
+ # Object Storage
+ - intent: object_storage
+ flavor: linode
+
+ # Managed Database
+ - intent: postgres
+ flavor: linode
+
+ # Self-hosted datastores via KubeBlocks (run on the LKE 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