agentleFS
Microsoft AzureKnown publisherAzure/azure-verified-modules-managed-files/terraform/root/.github/skills/avm-tf-telemetry/SKILL.md

avm-tf-telemetry

Skill1 starsChanged 2 months agoMITView on GitHub

Use this skill whenever an Azure Verified Module (AVM) for Terraform needs to wire up, debug, or explain telemetry — the `main.telemetry.tf` file with `modtm_telemetry` + `random_uuid.telemetry` + `data.azapi_client_config.telemetry` + `data.modtm_module_source.telemetry`, the `enable_telemetry` consumer-facing variable (which MUST default to `true` per SFR4), the `modtm` provider's role in shipping anonymous deployment counts to Application Insights, and the AzAPI request-header telemetry that flows alongside it. Also covers the fork-detection logic (whether the module source comes from an `Azure/*` registry/repo vs a fork), and what to do when consumers ask "how do I turn telemetry off". Trigger on phrases like "modtm", "main.telemetry.tf", "enable_telemetry", "AVM telemetry", "turn off telemetry", "telemetry provider", "SFR3", "SFR4", "Data Collection notice", "avm_module_source", "fork_avm".

232 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every AVM Terraform module that deploys resources collects anonymous deployment/usage telemetry, on by default, with a single opt-out …

    line 8
  2. Distinctive:

    No resource names, no resource configurations, no consumer code, no Azure resource IDs.

    line 108
  3. Very specific:

    If your module consumes another AVM module (typical for pattern modules), pass enable_telemetry through so the consumer's opt-out reliably …

    line 144
  4. Distinctive:

    Editing the modtm logic to add custom tags: Don't — telemetry shape is standardised.

    line 190

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

AVM Terraform telemetry

Every AVM Terraform module that deploys resources collects anonymous deployment/usage telemetry, on by default, with a single opt-out variable. The wiring lives in main.telemetry.tf and is maintained by the Mapotf transforms bundled with Avm.Authoring.

New resource-deploying module repositories use AzAPI for every control-plane and supported direct Azure operation. Each permitted azurerm_* resource or data-source block must independently implement one specific unsupported data-plane/non-ARM operation, document the exact block and AzAPI gap with an upstream AzAPI issue or pull request, and be replaced when support ships. One valid block does not authorize another. Telemetry's AzAPI client and request headers complement that requirement.

Fetch https://azure.github.io/Azure-Verified-Modules/llms.txt and confirm the current versions of these sources:

The headline rule

Telemetry MUST be on/enabled by default. Consumers MUST be able to disable it by setting enable_telemetry = false. (SFR4)

A module that deploys resources cannot opt out of telemetry; it exposes the opt-out to the consumer. Removing or defaulting enable_telemetry to false fails AVM linting.

What main.telemetry.tf does

data "azapi_client_config" "telemetry" {
  count = var.enable_telemetry ? 1 : 0
}

data "modtm_module_source" "telemetry" {
  count = var.enable_telemetry ? 1 : 0
  module_path = path.module
}

locals {
  # If the module deploys to a single location, surface it on the telemetry record.
  # If the module has no location concept, set this to "unknown".
  main_location = var.location
}

resource "random_uuid" "telemetry" {
  count = var.enable_telemetry ? 1 : 0
}

resource "modtm_telemetry" "telemetry" {
  count = var.enable_telemetry ? 1 : 0

  tags = merge({
    subscription_id = one(data.azapi_client_config.telemetry).subscription_id
    tenant_id       = one(data.azapi_client_config.telemetry).tenant_id
    module_source   = one(data.modtm_module_source.telemetry).module_source
    module_version  = one(data.modtm_module_source.telemetry).module_version
    random_id       = one(random_uuid.telemetry).result
  }, { location = local.main_location })
}

# Derived headers passed to AzAPI requests so server-side telemetry can correlate
locals {
  valid_module_source_regex = [
    "registry.terraform.io/[A|a]zure/.+",
    "registry.opentofu.io/[A|a]zure/.+",
    "git::https://github\\.com/[A|a]zure/.+",
    "git::ssh:://git@github\\.com/[A|a]zure/.+",
  ]

  fork_avm = !anytrue([
    for r in local.valid_module_source_regex :
    can(regex(r, one(data.modtm_module_source.telemetry).module_source))
  ])

  avm_azapi_headers = !var.enable_telemetry ? {} : (local.fork_avm ? {
    fork_avm  = "true"
    random_id = one(random_uuid.telemetry).result
    } : {
    avm                = "true"
    random_id          = one(random_uuid.telemetry).result
    avm_module_source  = one(data.modtm_module_source.telemetry).module_source
    avm_module_version = one(data.modtm_module_source.telemetry).module_version
  })

  # tflint-ignore: terraform_unused_declarations
  avm_azapi_header = join(" ", [for k, v in local.avm_azapi_headers : "${k}=${v}"])
}

The moving parts

SymbolRole
data.azapi_client_config.telemetryReads the current subscription + tenant ID for the telemetry record. Same data source other places in the module use; the telemetry instance is independent so you can read it even if no AzAPI resource is created.
data.modtm_module_source.telemetryInspects path.module and figures out where the module was loaded from (Registry, GitHub, OpenTofu Registry, etc.) and what version.
random_uuid.telemetryGenerates a random ID retained with the module instance's Terraform state so telemetry can be correlated without identifying a person.
modtm_telemetry.telemetryThe actual telemetry "resource" — its lifecycle hooks send a HTTP POST to the AVM telemetry collector with the tags map.
local.fork_avmTrue if the module wasn't loaded from an official Azure/* source — i.e. someone forked the module. Telemetry still flows but is tagged differently.
local.avm_azapi_headers / avm_azapi_headerBuilds the User-Agent value that avm transform merges into the applicable AzAPI create, read, update, and delete header attributes.

What gets sent

The telemetry record contains:

  • subscription_id, tenant_id (from the current ARM client context — not consumer's identity)
  • module_source, module_version (so AVM team knows which module + version)
  • location (so we know regional adoption)
  • random_id (correlation; not personally identifying)

No resource names, no resource configurations, no consumer code, no Azure resource IDs. The telemetry is genuinely lightweight — it answers "how often is this module used and at what version" and nothing else.

Required terraform.tf entry

The modtm provider MUST appear in required_providers:

modtm = {
  source  = "Azure/modtm"
  version = "~> 0.3"
}

AVM linting checks for this.

The consumer-facing variable

In variables.tf:

variable "enable_telemetry" {
  type        = bool
  default     = true
  nullable    = false
  description = <<DESCRIPTION
This variable controls whether or not telemetry is enabled for the module.
For more information see <https://aka.ms/avm/telemetryinfo>.
If it is set to false, then no telemetry will be collected.
DESCRIPTION
}

Convention: put enable_telemetry last in variables.tf, after all interface variables (see avm-tf-codestyle).

When a module references other AVM modules (cross-references)

If your module consumes another AVM module (typical for pattern modules), pass enable_telemetry through so the consumer's opt-out reliably disables telemetry across the whole module graph:

module "kv" {
  source  = "Azure/avm-res-keyvault-vault/azurerm"
  version = "~> 0.10"

  enable_telemetry = var.enable_telemetry   # pass through
  # ...
}

This is called out in SFR4: "the telemetry parameter value MUST be passed through to these modules".

The /azurerm suffix above is the Registry namespace of an existing legacy module, not an AzureRM provider declaration and not permission to generate hashicorp/azurerm or azurerm_* outside the narrow unsupported data-plane/non-ARM exception. New Registry namespaces use /azure.

The Data Collection notice

Per SFR3 the README MUST include a Data Collection notice with the canonical wording (drawn from Microsoft's open-source guidance). For Terraform, this notice lives in _footer.md (NOT in README.md directly — README.md is auto-generated, see avm-tf-documentation):

<!-- _footer.md -->
## Data Collection

The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at <https://go.microsoft.com/fwlink/?LinkID=824704>. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.

The template ships _footer.md with this notice — don't delete it.

When do you edit main.telemetry.tf?

Almost never. The file is governance-managed and meant to stay identical across modules. Run avm sync and avm transform rather than copying a potentially stale template. The only normal module-specific edit is to local.main_location if your module:

  • Doesn't accept a location variable (e.g. a global resource) — set main_location = "unknown".
  • Sources its location from a collection or computed value — set main_location = <the right expression>.

Everything else is part of the standard, validated wiring. If you find yourself wanting to modify how telemetry is collected, that's a conversation for the AVM core team, not a per-module change.

Utility modules

Per SFR3, utility modules that deploy no resources MUST NOT include telemetry. The modtm_telemetry resource itself counts as a "resource that gets deployed", so adding it to a pure-logic utility module triggers a no-op resource in consumer plans for no benefit.

Common pitfalls

  • Defaulting enable_telemetry = false. SFR4 violation. Defaults to true, period.
  • Removing main.telemetry.tf because "I don't see why my module needs it". SFR3 violation; CI fails. The whole point is that telemetry is uniform across the AVM ecosystem.
  • Editing the modtm logic to add custom tags. Don't — telemetry shape is standardised. If you genuinely need richer telemetry (e.g. for a new module class), raise it with the AVM core team.
  • Not passing enable_telemetry through to child AVM modules. A consumer who sets enable_telemetry = false on the pattern module expects telemetry off for the whole graph; if you don't pass it through, child resource modules still emit telemetry.
  • Forgetting the Data Collection notice in _footer.md. Required by SFR3. The template includes it; deletions will fail review.
  • Putting the Data Collection notice in README.md directly. It gets overwritten on the next avm pre-commit run. Put it in _footer.md.
  • Using a modtm version other than ~> 0.3. Pinned by spec and validated by lint.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.