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
- func AzurermAPIVersion(tfType string) (apiVersion string, ok bool)
- func AzurermARMType(tfType string) (armType string, ok bool)
- func AzurermSourcePin(src string) (version, commit string, err error)
- func AzurermTypesForARMType(armType string) (tfTypes []string, spelled string)
- func CheckTag(tfType, key, value string) []error
- func DefaultTagsApply(armType, apiVersion string) (applies, known bool)
- func ParseAzapiType(s string) (armType, apiVersion string, ok bool)
- func PostCreateTags(tfType string) (after bool, known bool)
- func Source() (azapiVersion, commit string)
- func SourcePin(repoDir string) (version, commit string, err error)
- func TagLimit(armType, apiVersion string) (max int, source string)
- func Taggable(armType, apiVersion string) (taggable, known bool)
- func Types() []string
- type Caps
- type Counts
- type CreateClass
- type LimitCounts
- type RMARMTypes
- type RMTagCounts
- type RMTagRow
- type RMTags
- type Table
- func (t *Table) Capabilities(armType, apiVersion string) (caps Caps, known bool)
- func (t *Table) Counts() Counts
- func (t *Table) Latest(armType string) (apiVersion string, caps Caps, known bool)
- func (t *Table) Render() []byte
- func (t *Table) Types() []string
- func (t *Table) VersionDependent(armType string) bool
- func (t *Table) Versions(armType string) []string
- type TagLimits
- type TagRules
- type TypeTagLimit
Constants ¶
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.
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.
const GeneratedDir = "internal/azure/generated"
GeneratedDir is where azapi keeps its embedded bicep-types data, relative to the provider repository's root.
const RMARMTypesPath = "azurerm_armtypes_generated.txt"
RMARMTypesPath is the committed azurerm-to-ARM type join, relative to this package's directory.
const RMTagsPath = "azurerm_tags_generated.json"
RMTagsPath is the committed azurerm tag facts, relative to this package's directory.
const TablePath = "table_generated.txt"
TablePath is the committed table, relative to this package's directory.
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 ¶
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 ¶
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 ¶
AzurermSourcePin reads the version (version/VERSION) and git commit of an azurerm checkout, the pair the facts record.
func AzurermTypesForARMType ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 )
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.
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 ¶
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 ¶
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
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 ¶
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 ¶
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 ¶
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) VersionDependent ¶
VersionDependent reports whether armType's taggability differs between two of its API versions.
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 ¶
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 ¶
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).
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 ¶
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.