Help us shape the future of Go! Take the 2026 Go Developer Survey

armtypes

package
v0.32.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MPL-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package armtypes is the Azure epic's (#1120) answer to "can this azapi resource carry a tag": a table of ARM resource types and API versions, generated from the type data azure/azapi embeds, so that the stamp never writes `tags` on an azapi resource whose ARM type cannot take them.

Where the answer comes from

azapi ships Azure's bicep-types data in its own binary (internal/azure/generated in the provider repository: index.json, which maps "<type>@<api-version>" to a ResourceType, and the types.json files it references). The #1122 ruling (2026-10-19) makes that data the taggability source for azapi, because it is what the provider itself validates a body against and what it consults before it adds default_tags. A type is taggable at an API version when its request body declares a `tags` property without the ReadOnly flag. That is azapi's own test, canResourceHaveProperty in internal/services/resource.go at v2.13.0:

objectType, ok := (*resourceDef.Body.Type).(*aztypes.ObjectType)
...
if prop, ok := objectType.Properties[property]; ok {
	if !prop.IsReadOnly() {
		return true
	}
}

with one widening, recorded in the table's header: a body that is a DiscriminatedObjectType counts its baseProperties. canResourceHaveProperty returns false for every discriminated body, but it gates only azapi's default_tags. A `tags` argument the configuration sets is copied into the body unconditionally (azapi_resource.go, expandBody) and azapi's body validator accepts it through discriminated_object_type.go's baseProperties. So a discriminated body with a writable base `tags` does take tags, and the stamp may write them there. At v2.13.0 that is 13 of 33,979 type-versions.

The same test answers whether `name` and `location` are writable, for the identity and readiness work that needs them.

Keyed by type and API version

Taggability moves between API versions of one type (84 types at v2.13.0), so the key is both. The type compares case-insensitively and the version exactly, as azapi's own GetResourceDefinition does (internal/azure/loader.go: strings.EqualFold on the type, == on the version). A version azapi does not know is "unknown", never a guess from a neighbouring version: azapi itself has no schema for it either.

Tag facts

Two more generated tables answer what a stamp needs beyond "may it tag" (epic #1120, unit 9):

  • taglimits_generated.txt, from the same azapi data: the ARM types whose `tags` description documents fewer than ARM's 50 tags, or shorter keys or values ("A maximum of 15 tags can be provided for a resource", Cosmos DB and Storage among them). TagLimit reads it.
  • azurerm_tags_generated.json, from azurerm's Go source with go/parser: for every azurerm type whose `tags` is ARM's tags map, whether its Create sends the tags in the create call or writes them in a later call (rmanalyze.go has the rule), and what azurerm's own `tags` ValidateFunc enforces. PostCreateTags and AzurermTagRules read it; CheckTag applies both to one key and value.

Regenerating

go run ./tools/armtypes-gen -azapi-src <azapi checkout at the pinned tag>
go run ./tools/azurerm-tags-gen -src <azurerm checkout at the pinned tag> -schema <tofu providers schema -json>

Each table records the source tag and commit it was generated from. TestTableMatchesPinnedSource and TestTagLimitsMatchPinnedSource regenerate the azapi tables when ARMTYPES_AZAPI_SRC names such a checkout; TestRMTagsMatchPinnedSource regenerates the azurerm facts when AZURERM_SRC and AZURERM_SCHEMA name the checkout and schema, the same variables tools/azurerm-id-gen's pin test reads.

Index

Constants

View Source
const (
	DefaultTagLimit = 50
	MaxKeyLength    = 512
	MaxValueLength  = 256
)

ARM's tag limits, as azurerm enforces them for every resource whose `tags` schema is commonschema.Tags() or TagsForceNew(): go-azure-helpers v0.82.0 (vendored by azurerm 5.10.0), resourcemanager/tags/validate.go, Validate:

if len(tagsMap) > 50 { "a maximum of 50 tags can be applied to each resource" }
if len(k) > 512      { "the maximum length for a tag key is 512 characters" }
if len(value) > 256  { "the maximum length for a tag value is 256 characters" }

The same three figures are Azure's documented limits (Microsoft Learn, "Use tags to organize your Azure resources", Limitations), which also list the types that allow fewer; TagLimit carries the ones azapi's type data documents. A marker value longer than MaxValueLength has to be split, as the AWS continuation-tag scheme splits values over 256.

View Source
const ForbiddenKeyChars = `<>%&\?/`

ForbiddenKeyChars are the characters Azure documents that a tag name cannot contain (Microsoft Learn, "Use tags to organize your Azure resources", Limitations: "Tag names can't contain these characters: <, >, %, &, \, ?, /"). Neither azurerm 5.10.0 nor go-azure-helpers checks them: a key with one is refused by ARM at apply, not at plan. That is a docs-sourced fact, unlike the rest of this package, and the maintainer's floci-az measurement (ADDING-A-PLATFORM.md requirement 2) is what proves or moves it.

View Source
const GeneratedDir = "internal/azure/generated"

GeneratedDir is where azapi keeps its embedded bicep-types data, relative to the provider repository's root.

View Source
const RMARMTypesPath = "azurerm_armtypes_generated.txt"

RMARMTypesPath is the committed azurerm-to-ARM type join, relative to this package's directory.

View Source
const RMTagsPath = "azurerm_tags_generated.json"

RMTagsPath is the committed azurerm tag facts, relative to this package's directory.

View Source
const TablePath = "table_generated.txt"

TablePath is the committed table, relative to this package's directory.

View Source
const TagLimitsPath = "taglimits_generated.txt"

TagLimitsPath is the committed per-type limits, relative to this package's directory.

Variables

This section is empty.

Functions

func AzurermAPIVersion

func AzurermAPIVersion(tfType string) (apiVersion string, ok bool)

AzurermAPIVersion is the API version azurerm sends for an azurerm resource type: the version of the go-azure-sdk package its id type lives in (live/azurerm-ids.json). ok is false for a type whose id parser is a commonids type or a hand parser, and for a type with no ARM type row.

func AzurermARMType

func AzurermARMType(tfType string) (armType string, ok bool)

AzurermARMType is the ARM resource type an azurerm resource type creates, read off its id template (live/azurerm-ids.json); ok is false for a type with no ARM tags map, or one whose template names no fixed ARM type.

func AzurermSourcePin

func AzurermSourcePin(src string) (version, commit string, err error)

AzurermSourcePin reads the version (version/VERSION) and git commit of an azurerm checkout, the pair the facts record.

func AzurermTypesForARMType

func AzurermTypesForARMType(armType string) (tfTypes []string, spelled string)

AzurermTypesForARMType is every azurerm resource type whose id template names armType, compared without regard to case (Resource Graph lowercases the types it lists), sorted, with the join's own spelling of armType. Several azurerm types can create one ARM type (the Linux and Windows virtual machines), so a caller naming an object by it says which it is only when exactly one does. GitHub issue #2138: the Resource Graph leg names a listed object by its own type this way.

func CheckTag

func CheckTag(tfType, key, value string) []error

CheckTag applies azurerm's validation of one tag to a Terraform type (AzurermTagRules, ARM's defaults when azurerm declares none or the type is unknown) and ARM's documented forbidden key characters. It returns every rule the pair breaks; a lint uses it to prove a stamped marker fits before plan does.

func DefaultTagsApply

func DefaultTagsApply(armType, apiVersion string) (applies, known bool)

DefaultTagsApply reports whether azapi 2.13.0 adds its provider block's default_tags to an azapi_resource of armType at apiVersion whose own tags argument is null. That is azapi's canResourceHaveProperty(resourceDef, "tags") (internal/services/resource.go lines 135-149), the gate of tagsWithDefaultTags step 3 (internal/services/azapi_resource.go lines 1570-1572): a writable `tags` on an ObjectType body, never a discriminated body's baseProperties. So it is Taggable less the CapTagsViaBase type-versions. known is false as for Taggable.

func ParseAzapiType

func ParseAzapiType(s string) (armType, apiVersion string, ok bool)

ParseAzapiType splits azapi's `type` argument, "Microsoft.X/y@2024-01-01", the way azapi does (utils.GetAzureResourceTypeApiVersion: strings.Split on "@", exactly two parts). It is stricter in one place: an empty type or version is not ok, since azapi has no schema for either. armType keeps the configuration's spelling; the table lowercases on lookup.

func PostCreateTags

func PostCreateTags(tfType string) (after bool, known bool)

PostCreateTags answers, for an azurerm resource type, whether its Create writes `tags` in a separate call after the create call returns. known is false for a type with no `tags` argument, a type the provider does not register, and a type the analysis classed unknown; a caller treats unknown as "may be after", the conservative answer. This is the Azure counterpart of live/registry.json's tag_on_create false, which substrate's AWS PostCreateNeeded reads.

func Source

func Source() (azapiVersion, commit string)

Source returns the azapi tag and commit the committed table came from.

func SourcePin

func SourcePin(repoDir string) (version, commit string, err error)

SourcePin reads the tag and commit an azapi checkout is at, the pair the table records. The checkout must sit exactly on a tag: a table generated from an untagged commit could not say which release it describes.

func TagLimit

func TagLimit(armType, apiVersion string) (max int, source string)

TagLimit returns how many tags an ARM type takes at one API version and where the figure comes from: the count the type's `tags` description in azapi's data documents at that version, or ARM's default of 50 when the type documents none at any version. The type and version compare case-insensitively. azurerm's own validation, which is per Terraform type, is AzurermTagRules; a stamp checks both.

Seven types document a count at some versions and none at others (GitHub issue #2133, finding 8; at azapi 2.13.0 no version of any type documents a second, different count). A version whose description gives no count gets ARM's default, as a type that never documents one does: API Management's 10 is in its 2016-07-07 description and in none of the 24 since. An empty apiVersion (an azurerm type whose id parser names no version, AzurermAPIVersion) and a version azapi's data does not know get the lowest count any version documents, and the source says so.

func Taggable

func Taggable(armType, apiVersion string) (taggable, known bool)

Taggable reports whether azapi's type data gives armType a writable `tags` at apiVersion. known is false when azapi has no schema for that pair, and a caller must then not stamp: a guess from another version is exactly what this table exists to avoid.

func Types

func Types() []string

Types returns every ARM type azapi's data knows, lowercased and sorted.

Types

type Caps

type Caps uint8

Caps is what an ARM type's request body lets a caller write at one API version. Each bit is set when the body declares the property without the ReadOnly flag (see the package doc for the rule and its source).

const (
	CapTags     Caps = 1 << 0
	CapName     Caps = 1 << 1
	CapLocation Caps = 1 << 2
	// CapTagsViaBase is set beside CapTags when the body is a
	// DiscriminatedObjectType whose `tags` is writable only through its
	// baseProperties: the type-versions the package doc's one widening
	// admits, for which azapi's canResourceHaveProperty answers false. It
	// is what tells [DefaultTagsApply] from [Taggable].
	CapTagsViaBase Caps = 1 << 3
)

func (Caps) Has

func (c Caps) Has(want Caps) bool

Has reports whether every bit of want is set.

type Counts

type Counts struct {
	// Types is the number of distinct ARM resource types (lowercased).
	Types int
	// TypeVersions is the number of (type, API version) pairs.
	TypeVersions int
	// TaggableAtLatest counts types taggable at their latest API version.
	TaggableAtLatest int
	// VersionDependent counts types whose taggability differs between
	// two of their API versions.
	VersionDependent int
}

Counts is the table's summary, the numbers the docs and the readiness generator quote.

func Summary

func Summary() Counts

Summary returns the committed table's counts.

type CreateClass

type CreateClass string

CreateClass says when an azurerm resource's Create writes its tags.

const (
	// CreateInCreate: the create call's payload carries the tags.
	CreateInCreate CreateClass = "in-create"
	// CreateAfterCreate: the create call does not carry them and a later
	// call in the same Create does (Call names it). A stamped marker is
	// absent between the two, which is what
	// substrate's PostCreateNeeded answers for.
	CreateAfterCreate CreateClass = "after-create"
	// CreateUnknown: the analysis could not decide (Reason says why).
	CreateUnknown CreateClass = "unknown"
)

type LimitCounts

type LimitCounts struct {
	// Types is the number of rows; Fewer those with MaxTags below
	// DefaultTagLimit; Varies those whose count limit differs by version.
	Types, Fewer, Varies int
}

LimitCounts is the table's summary.

type RMARMTypes

type RMARMTypes struct {
	// Version and Commit are the azurerm pin live/azurerm-ids.json names.
	Version string
	Commit  string
	// Tagged is how many azurerm types with an ARM tags map the join was
	// asked about; the rows are the ones whose id template names an ARM type.
	Tagged int
	// contains filtered or unexported fields
}

RMARMTypes is the parsed or generated join.

func DefaultRMARMTypes

func DefaultRMARMTypes() *RMARMTypes

DefaultRMARMTypes is the committed join; it panics if the embedded file does not parse, which TestCommittedRMARMTypesParse catches first.

func GenerateAzurermARMTypes

func GenerateAzurermARMTypes(idsJSON []byte) (*RMARMTypes, error)

GenerateAzurermARMTypes builds the join from the bytes of live/azurerm-ids.json, for every type DefaultRMTags holds. A tagged type the id table has no template for, or whose template names no fixed ARM type, has no row, and AzurermARMType answers false for it.

func ParseRMARMTypes

func ParseRMARMTypes(data []byte) (*RMARMTypes, error)

ParseRMARMTypes reads the committed format and checks the header's counts against the rows.

func (*RMARMTypes) Render

func (t *RMARMTypes) Render() []byte

Render writes the join in its committed format: a header naming the pin and counts, then "<azurerm type> <ARM type>[ <API version>]" per line, sorted.

func (*RMARMTypes) Rows

func (t *RMARMTypes) Rows() map[string]string

Rows returns the join as azurerm type to ARM type.

type RMTagCounts

type RMTagCounts struct {
	Tagged      int `json:"tagged"`
	InCreate    int `json:"in_create"`
	AfterCreate int `json:"after_create"`
	Unknown     int `json:"unknown"`
	// Limited counts types whose azurerm validation allows fewer than
	// DefaultTagLimit tags or stricter keys than ARM's.
	Limited int `json:"limited"`
}

RMTagCounts is the facts' summary.

type RMTagRow

type RMTagRow struct {
	Type   string      `json:"type"`
	Create CreateClass `json:"create"`
	// Call is the API call that carries the tags (the create call when
	// in-create, the later call when after-create), At its file:line in
	// the provider checkout.
	Call string `json:"call,omitempty"`
	At   string `json:"at,omitempty"`
	// CreateCall, on an after-create or unknown row, is the create call
	// that does not carry the tags.
	CreateCall string   `json:"create_call,omitempty"`
	Reason     string   `json:"reason,omitempty"`
	Rules      TagRules `json:"rules"`
}

RMTagRow is one azurerm resource type's tag facts.

type RMTags

type RMTags struct {
	Version      string
	Commit       string
	SchemaSHA256 string
	// NotARMTags are the types whose `tags` argument is not ARM's tags
	// map (a list of labels kept in the service's own properties): never
	// a marker carrier, and so not rows.
	NotARMTags []string
	// contains filtered or unexported fields
}

RMTags is the parsed or generated azurerm tag facts.

func DefaultRMTags

func DefaultRMTags() *RMTags

DefaultRMTags is the committed azurerm tag facts. It panics if the embedded file does not parse, which TestCommittedRMTagsParse turns into a test failure first.

func GenerateAzurermTags

func GenerateAzurermTags(src, schemaPath, version, commit string) (*RMTags, error)

GenerateAzurermTags builds the azurerm tag facts from a provider checkout and its schema: for every managed resource type whose schema has `tags`, whether its Create sends them in the create call or a later call, and what azurerm's own `tags` validation allows.

func ParseRMTags

func ParseRMTags(data []byte) (*RMTags, error)

ParseRMTags reads the committed format and checks the counts against the rows and each row's class against its fields.

func (*RMTags) Counts

func (t *RMTags) Counts() RMTagCounts

func (*RMTags) Render

func (t *RMTags) Render() []byte

Render writes the facts in their committed format.

func (*RMTags) Row

func (t *RMTags) Row(tfType string) (RMTagRow, bool)

Row returns one type's facts.

func (*RMTags) Rows

func (t *RMTags) Rows() []RMTagRow

Rows returns every row, sorted by type.

type Table

type Table struct {
	// AzapiVersion and AzapiCommit are the azure/terraform-provider-azapi
	// tag and commit the data came from.
	AzapiVersion string
	AzapiCommit  string
	// DiscriminatedTags counts the type-versions whose `tags` is writable
	// only through a DiscriminatedObjectType's baseProperties - the ones
	// azapi's canResourceHaveProperty would answer false for.
	DiscriminatedTags int
	// contains filtered or unexported fields
}

Table is the parsed or generated type-capability table.

func Default

func Default() *Table

Default is the committed table. It panics if the embedded file does not parse, which TestCommittedTableParses turns into a test failure first.

func Generate

func Generate(repoDir, version, commit string) (*Table, error)

Generate builds the table from an azapi checkout's embedded type data.

For every "<type>@<api-version>" key in index.json it loads the ResourceType the key references, resolves its body, and reads three properties off it with azapi's canResourceHaveProperty test (present, ReadOnly flag clear), counting a DiscriminatedObjectType body's baseProperties as the package doc explains. Anything azapi itself could not load - a non-ResourceType entry, a body reference that is not in-file - is an error here rather than a silently absent row, so a pin bump that changes the data's shape fails loudly.

func Parse

func Parse(data []byte) (*Table, error)

Parse reads the committed format, and checks the "# counts:" header against the rows so that a hand edit to either is caught.

func (*Table) Capabilities

func (t *Table) Capabilities(armType, apiVersion string) (caps Caps, known bool)

Capabilities answers for one ARM type and API version. known is false when azapi's data has no such pair; the type compares case-insensitively and the version exactly, as azapi's GetResourceDefinition does.

func (*Table) Counts

func (t *Table) Counts() Counts

Counts recomputes the summary from the rows.

func (*Table) Latest

func (t *Table) Latest(armType string) (apiVersion string, caps Caps, known bool)

Latest returns armType's lexically greatest API version and its caps.

func (*Table) Render

func (t *Table) Render() []byte

Render writes the table in its committed format.

func (*Table) Types

func (t *Table) Types() []string

Types returns every ARM type in the table, lowercased and sorted.

func (*Table) VersionDependent

func (t *Table) VersionDependent(armType string) bool

VersionDependent reports whether armType's taggability differs between two of its API versions.

func (*Table) Versions

func (t *Table) Versions(armType string) []string

Versions returns armType's API versions, sorted; nil when unknown.

type TagLimits

type TagLimits struct {
	AzapiVersion string
	AzapiCommit  string
	// contains filtered or unexported fields
}

TagLimits is the per-type limits table.

func DefaultTagLimits

func DefaultTagLimits() *TagLimits

DefaultTagLimits is the committed table; it panics if the embedded file does not parse, which TestCommittedTagLimitsParse catches first.

func GenerateTagLimits

func GenerateTagLimits(repoDir, version, commit string) (*TagLimits, error)

GenerateTagLimits reads every taggable (type, API version) body in azapi's data and keeps, per type, the tightest limits its `tags` description documents. Only types documenting something tighter than ARM's defaults get a row.

func ParseTagLimits

func ParseTagLimits(data []byte) (*TagLimits, error)

ParseTagLimits reads the committed format and checks the counts header.

func (*TagLimits) Counts

func (t *TagLimits) Counts() LimitCounts

func (*TagLimits) Limit

func (t *TagLimits) Limit(armType string) (TypeTagLimit, bool)

Limit returns one type's row; ok is false when the type documents nothing tighter than ARM's defaults (or is not in azapi's data).

func (*TagLimits) Render

func (t *TagLimits) Render() []byte

Render writes the table in its committed format: one line per type, "<type> max=<n> key=<n> value=<n> from=<api version>[ varies=<api version>:<n>,...]", with 0 for an undocumented field and from=- when MaxTags is 0. varies lists ByVersion, sorted by version.

func (*TagLimits) Types

func (t *TagLimits) Types() []string

Types returns the types with a row, lowercased and sorted.

type TagRules

type TagRules struct {
	// Validator is the schema expression the rules were read from.
	Validator     string   `json:"validator"`
	MaxTags       int      `json:"max_tags,omitempty"`
	MaxKeyLen     int      `json:"max_key_length,omitempty"`
	MaxValueLen   int      `json:"max_value_length,omitempty"`
	LowercaseKeys bool     `json:"lowercase_keys,omitempty"`
	ForbiddenKeys []string `json:"forbidden_keys,omitempty"`
}

TagRules is what azurerm's own validation of a resource's `tags` argument enforces, read from the schema's ValidateFunc. A zero limit means azurerm does not check it, which leaves ARM's own limit (see TagLimit, MaxKeyLength, MaxValueLength).

func AzurermTagRules

func AzurermTagRules(tfType string) (TagRules, bool)

AzurermTagRules returns azurerm's own validation of a resource type's `tags`; ok is false for a type with no `tags` argument.

type TypeTagLimit

type TypeTagLimit struct {
	MaxTags     int
	MaxKeyLen   int
	MaxValueLen int
	// APIVersion is the version whose `tags` description gave MaxTags (the
	// first, in sorted order, giving the lowest).
	APIVersion string
	// Varies is true when the type's taggable versions do not all
	// document the same MaxTags: some say more, or nothing.
	Varies bool
	// ByVersion is, for a type that varies, every taggable API version
	// whose figure is not MaxTags, keyed lowercased: the count it documents,
	// or 0 when its `tags` description gives none. A taggable version not
	// listed documents MaxTags (GitHub issue #2133, finding 8).
	ByVersion map[string]int
}

TypeTagLimit is one ARM type's documented tag limits. A zero field is undocumented for that type.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL