MO

module-conventions

Mandatory structural guidelines for consistent infrastructure-as-code module organization.

Install

mkdir -p .claude/skills/module-conventions && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13886" && unzip -o skill.zip -d .claude/skills/module-conventions && rm skill.zip

Installs to .claude/skills/module-conventions

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Binding rules for every module in rad-modules — TF file layout, variables.tf structure with UIMeta, provider-auth impersonation, and common deployment-ID / project / trusted-users patterns.
189 charsno explicit “when” trigger
Intermediate

Key capabilities

  • →Define a standard directory layout for OpenTofu modules
  • →Annotate variables with UIMeta tags for UI rendering
  • →Standardize provider authentication configurations
  • →Enforce common variable patterns like deployment_id and trusted_users
  • →Specify content for README.md and module-specific documentation

How it works

This skill establishes binding rules for OpenTofu module structure, including file layout, variable definitions with UIMeta tags, and provider authentication patterns. It ensures consistency across modules for `rad-launcher` variable validation and RAD UI rendering.

Inputs & outputs

You give it
OpenTofu module files, variable definitions, UIMeta tags
You get back
Standardized OpenTofu module structure, UI-renderable variables, consistent documentation

When to use module-conventions

  • →Organize new infrastructure module files
  • →Apply mandatory UIMeta tags to variables
  • →Standardize module directory layout

About this skill

Module Conventions

Every module under modules/ is an independent OpenTofu root module and shares the same structural conventions. Deviating from them breaks either rad-launcher variable validation or the RAD UI rendering. Treat these rules as load-bearing.

Directory Layout

A module directory looks like this (Bank_GKE shown as the canonical multi-file example; AKS_GKE and EKS_GKE are simpler):

modules/<Module_Name>/
├── README.md              # Short summary + Usage + Requirements/Providers/Resources/Inputs/Outputs tables
├── tests/                 # Module test fixtures (the long-form deep dive lives at docs/modules/<Module_Name>.md, NOT inside the module)
├── main.tf                # Locals, random_id, data.google_project, google_project_service.enabled_services
├── variables.tf           # All inputs, annotated with UIMeta tags (see below)
├── versions.tf            # OR provider.tf — required_providers + required_version
├── provider-auth.tf       # OR provider.tf — google / azurerm / aws provider config
├── network.tf             # VPC / subnet / firewall / NAT
├── <feature>.tf           # e.g. gke.tf, asm.tf, hub.tf, deploy.tf, glb.tf, mcs.tf, istiosidecar.tf
├── outputs.tf             # deployment_id + project_id at minimum
├── manifests/             # or templates/ — static or templated Kubernetes YAML
└── modules/               # optional, nested module-local helpers (not cross-module)
    └── <helper>/
        ├── main.tf
        ├── variables.tf
        └── ...

Rules:

  • No symlinks. Modules do not share TF files. If Bank_GKE and MC_Bank_GKE need similar asm.tf, each has its own copy.
  • Nested modules (e.g. modules/AKS_GKE/modules/attached-install-manifest/) are scoped to one parent module only; they must not be referenced from other modules in the repo.
  • Kubernetes templates live under manifests/ (raw YAML) or templates/ (Go-template .yaml.tpl rendered by templatefile(...)). Pick one per module based on whether any values are substituted. MC_Bank_GKE is the only module that actually renders templates (manifests.tf writes templates/*.yaml.tpl out to manifests/ via local_file); the templates/ and manifests/ directories in Istio_GKE and the templates/ directory in Bank_GKE are unreferenced by any .tf file and have been since those modules' initial commits. Don't copy a pattern out of them assuming it is live.
  • License header: every .tf file should begin with the Apache 2.0 block-comment header. Copy it from a neighbouring file when creating a new one. Three existing versions.tf files (Bank_GKE, Migration_Center, VMware_Engine) currently lack it, which is why scripts/check_conventions.py reports a missing header as WARN rather than FAIL.
  • Naming: files are lowercase with hyphens (provider-auth.tf), module directory names are PascalCase_WithUnderscores, HCL resource names are snake_case.

variables.tf Structure

Variables are organized into numbered sections using // SECTION N: or # SECTION N: comments. The ordering below is the established convention:

# SECTION 1: Deployment   → module_description, module_dependency, module_services,
#                           credit_cost, require_credit_purchases, enable_purge,
#                           public_access, deployment_id, resource_creator_identity,
#                           trusted_users, enable_services
# SECTION 2: Project      → project_id
# SECTION 3: Network      → create_network, network_name, subnet_name, ip_cidr_ranges, ...
# SECTION 4: Cluster      → create_cluster, cluster_name_prefix, k8s_version, release_channel, ...
# SECTION 5: IAM / Creds  → client_id/tenant_id/subscription_id/client_secret (Azure),
#                           aws_access_key/aws_secret_key (AWS)
# SECTION 6+: Feature-specific (e.g. service mesh, config management, application)

enable_services belongs in group 0 (SECTION 1: Deployment). Place it at the end of the Deployment section (order=109) so the API-enabling toggle is grouped with other platform-level deployment controls rather than with project-specific inputs. Use {{UIMeta group=0 order=109 }}.

Not every module needs every section — AKS_GKE has no dedicated network section because AKS manages its own VNet, and Istio_GKE merges IAM into cluster setup. The numbering should still follow this order wherever the section is present.

Every Module Ships These Ten Standard Variables

The variables below exist in nearly every module and must keep their exact names, types, and defaults. rad-launcher looks for them; the RAD UI renders them in a standard panel. Two carry documented exceptions: trusted_users is Kubernetes-specific and is deliberately omitted by Container_Migration, Gemini_Enterprise, Migration_Center and VMware_Engine, and enable_services is omitted by AKS_GKE, EKS_GKE and Migration_Center. Three further variables belong in the same group-0 panel: module_documentation (docs URL) and shared_users (platform-only visibility list), declared by all nine modules, and enable_rad_gcpproject (bool, default false, {{UIMeta group=0 order=110 }}), declared by eight — every module except Istio_GKE. Setting it false hides the "GCP Project on RAD" option so the module can only be deployed into a customer's own GCP project; each module's description names the specific APIs the RAD-managed tier policies deny that make it necessary (e.g. vmwareengine/vmmigration for VMware_Engine, modelarmor/aiplatform for Gemini_Enterprise, fifteen Anthos/mesh/multi-cluster APIs for MC_Bank_GKE). Keep that list in step with the module's default_apis. scripts/check_conventions.py enforces this list at WARN level; run it before opening a PR.

VariableTypeDefaultNotes
module_descriptionstringmodule-specific textShown in catalog
module_dependencylist(string)e.g. ["GCP Project"]Deploy order
module_serviceslist(string)e.g. ["GCP","GKE",...]UI tags
credit_costnumber0Platform credits; every module in this repo currently ships 0
require_credit_purchasesboolfalse
enable_purgebooltrue
public_accessbooltrueCatalog visibility
deployment_idstringnull4-char suffix; null ⇒ auto
resource_creator_identitystring"[email protected]"Impersonated SA
trusted_userslist(string)[]Cluster-admin emails

trusted_users should carry the duplicate-and-whitespace validations from AKS_GKE/variables.tf; copy them when adding to a new module.

UIMeta Tags

Every variable description ends with a {{UIMeta ...}} tag (inside the description string, not a comment) that drives UI rendering. A variable with no tag at all has no group and renders VISIBLE — group 0 is stripped and everything else is shown, so missing metadata fails open rather than closed. Platform-injected values need an explicit group=0.

variable "region" {
  description = "GCP region where the GKE cluster ... Defaults to 'us-central1'. {{UIMeta group=1 order=103 }}"
  type        = string
  default     = "us-central1"
}

(The input is named region, never gcp_region — see the standard-variable list above. And it carries no updatesafe: changing the region relocates every regional resource.)

Parameters:

  • group=N — UI panel grouping, corresponding loosely to SECTION (0=Deployment, 1=Project, 2=Network, etc.).

  • order=NNN — sort order within the group. Gaps are fine; leave room to insert new variables.

  • updatesafe — presence flag, not a key=value. Include it for variables that can change in place without recreating the module (e.g. trusted_users, resource_creator_identity, tenant_id, cloud credentials, node-pool sizing). Omit it for variables that force replacement (e.g. cluster names, name prefixes, network CIDRs, project_id, and every region variable — region, gcp_location, azure_region, aws_region).

    Its ABSENCE is what the platform acts on, as of 2026-08-19. An unflagged field raises a "this update will destroy project resources" confirmation when edited on an existing deployment, and is rendered read-only when the admin setting Enforce Update Safe is on. Until then the webapp's matcher looked for a token no module writes, so the flag had never been read by anything and wrong flags accumulated unchecked — region carried it in 422 modules across the catalogue.

    The asymmetry settles any doubtful case: an over-generous flag is a silent data-loss path (it tells the user an edit is safe when it will replace the resource), while omitting it costs a needless warning. When in doubt, leave it off. Two traps beyond "forces replacement": a variable that appears in a resource's count/for_each condition gates that resource's existence, so turning it off destroys it; and a comparison against a literal (var.mode == "custom") is a mode switch that destroys on any change, whereas a comparison against emptiness (!= "", != null, length(...) > 0) only destroys when the value is cleared — the latter keeps the flag. Verify with ../rad-automation/scripts/check_updatesafe_flags.py (sibling repo).

  • notradmanaged — presence flag. Removes the variable from the deploy form when the deployment lands in a RAD-managed project (RAD's own organisation, tier folders, RAD's billing account), and reverts it server-side on update; a customer's own project keeps it. Tag anything that lets the tenant reach past their own project into RAD's organisation — writing an org-scoped resource (google_access_context_manager_* has one access policy per ORG; google_scc_notification_config), or federating an external identity inward (Workload Identity Federation is project-scoped yet grant


Content truncated.

Limitations

  • →Deviating from these rules breaks `rad-launcher` variable validation or RAD UI rendering
  • →Modules do not share TF files; no symlinks are allowed
  • →Nested modules are scoped to one parent module only

How it compares

This skill provides a prescriptive set of conventions for OpenTofu modules, ensuring uniformity and compatibility with specific tooling, unlike ad-hoc module development.

Compared to similar skills

module-conventions side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
module-conventions (this skill)04moNo flagsIntermediate
aws-solution-architect205moReviewAdvanced
terraform-module-library76moNo flagsAdvanced
azure-deployment-preflight78moReviewAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

aws-solution-architect

alirezarezvani

Design AWS architectures for startups using serverless patterns and IaC templates. Use when asked to design serverless architecture, create CloudFormation templates, optimize AWS costs, set up CI/CD pipelines, or migrate to AWS. Covers Lambda, API Gateway, DynamoDB, ECS, Aurora, and cost optimization.

2047

terraform-module-library

wshobson

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

759

azure-deployment-preflight

github

Performs comprehensive preflight validation of Bicep deployments to Azure, including template syntax validation, what-if analysis, and permission checks. Use this skill before any deployment to Azure to preview changes, identify potential issues, and ensure the deployment will succeed. Activate when users mention deploying to Azure, validating Bicep files, checking deployment permissions, previewing infrastructure changes, running what-if, or preparing for azd provision.

746

terraform-azurerm-set-diff-analyzer

github

Analyze Terraform plan JSON output for AzureRM Provider to distinguish between false-positive diffs (order-only changes in Set-type attributes) and actual resource changes. Use when reviewing terraform plan output for Azure resources like Application Gateway, Load Balancer, Firewall, Front Door, NSG, and other resources with Set-type attributes that cause spurious diffs due to internal ordering changes.

534

terraform-skill

sickn33

Terraform infrastructure as code best practices

829

aws-advisor

tech-leads-club

Expert AWS Cloud Advisor for architecture design, security review, and implementation guidance. Leverages AWS MCP tools for accurate, documentation-backed answers. Use when user asks about AWS architecture, security, service selection, migrations, troubleshooting, or learning AWS. Triggers on AWS, Lambda, S3, EC2, ECS, EKS, DynamoDB, RDS, CloudFormation, CDK, Terraform, Serverless, SAM, IAM, VPC, API Gateway, or any AWS service.

529

Search skills

Search the agent skills registry