Skip to content

Repository files navigation

azproviderlint

GitHub release Go Version License build lint CodeQL coverage

Lint rules for the Terraform AzureRM provider. Each rule catches one mistake that comes up in provider code, from schema fields that break at plan time to pointer dereferences that panic when Azure leaves a field out. Most rules can fix what they find.

It runs on its own or as a golangci-lint plugin. The rules are ordinary Go analysis passes.

Quick start

go install github.com/katbyte/azproviderlint@latest
azproviderlint ./...

That runs every rule. To run some of them, name them. A category on its own (-AZG) means every rule in it.

azproviderlint -AZG001 ./...
azproviderlint -AZR001 -AZR003 ./...
azproviderlint -AZG ./...
azproviderlint -AZG -AZR001 ./...

Add -fix to apply the suggested fixes. Read the diff afterwards. Each rule's README says what its fix does and when to be careful.

Rules

Rules are named AZ, a category letter, and a number. The letters follow tfproviderlint where the categories overlap.

AZG - General Go Style / Readability

Rule Description
AZG000 azignore directives must give a reason
AZG001 combine err assignment and check into one if
AZG002 use new() instead of a single-use temporary's address
AZG003 use pointer.ToEnum for enum conversions
AZG004 use pointer.From instead of nil-check dereference
AZG005 inline single-use variable only used in a later assignment or return
AZG006 inline single-use variable only used in a later function call
AZG007 omit struct literal fields explicitly set to their zero value
AZG008 pointer dereferences must have a nil guard or use pointer.From
AZG009 use pointer.FromEnum for enum conversions

AZP - Provider-Wide Conventions

Rule Description
AZP001 Microsoft docs URLs must not carry a locale segment like /en-us/
AZP002 registered resources must have a same-named data source
AZP003 data sources must expose their same-named resource's properties
AZP004 registration entries must be sorted alphabetically
AZP005 do not set the case-insensitive segments feature flag

AZR - Resource Implementation

Rule Description
AZR001 SetId must use a resource id formatter/parser
AZR002 separate Create and Update methods
AZR003 no d.Get in Delete functions
AZR004 compare resource id types with resourceids.Match
AZR006 use timeouts wrappers, not StopContext
AZR007 use custom pollers instead of StateChangeConf
AZR008 flatten functions must return empty slices/maps, not nil
AZR009 no lifecycle narration logging in Create/Read/Update/Delete
AZR010 flatten functions handle nil input themselves, not their callers

AZD - Data Sources

Rule Description
AZD001 data sources must error when not found, not SetId("")
AZD002 data sources must error when not found, not MarkAsGone

AZS - Schema & Typed SDK Models

Rule Description
AZS001 typed SDK model numeric fields must be 64-bit (int64/float64)
AZS002 schema Default values must match the declared Type
AZS003 TypeList blocks must not allow empty blocks
AZS004 enum validation must use the SDK's possible-values helper
AZS007 optional+computed fields must have a Note: O+C comment
AZS009 computed-only fields must not set input-only schema attributes

AZC - Clients & SDK Usage

Rule Description
AZC001 clients must set an explicit resource manager endpoint

AZT - Acceptance Testing

Rule Description
AZT001 acceptance tests must use a _test package
AZT002 acceptance tests must not read credentials from the environment

AZN - Naming Conventions

No rules yet. Reserved for property naming rules, such as percentages using a _percentage suffix rather than _in_percent.

AZV - Validation

Rule Description
AZV001 'invalid format' error messages must describe the expected format

Glossary

If you are new to the provider, these come up a lot:

  • Untyped resource: the original plugin SDK style. A map[string]*pluginsdk.Schema, and d.Get / d.Set to move values in and out of state.
  • Typed resource: the newer internal/sdk style. A Go struct with tfschema tags, Arguments() / Attributes() for the schema, and metadata.Decode / metadata.Encode instead of Get / Set.
  • Framework resource: the Terraform Plugin Framework style, registered through FrameworkResources().
  • Expand / flatten: expandFoo turns config into an SDK request. flattenFoo turns an SDK response into what goes in state.
  • Resource ID: the provider's own parsed form of an Azure resource ID, with a generated formatter and parser per type. See AZR001.
  • go-azure-sdk / go-azure-helpers: the SDK the provider calls Azure with, and the helper library that provides pointer.To, pointer.From, and friends.

Ignoring reports

To skip one rule on one line, add a comment at the end of the line or on the line above it. Say why. A directive without a reason is reported by AZG000.

d.SetId(*read.ID) //azignore:AZR001 - legacy resource, ID formatter tracked in #1234

//azignore:AZG001,AZR003 combined form obscures the retry loop here
err := client.Delete(ctx, id)

Several rules can be listed with commas. The - before the reason is optional. This works under every driver, standalone or golangci-lint.

Under golangci-lint, //nolint:azproviderlint in the same place also works, but it silences every azproviderlint rule on that line, so prefer //azignore when you can.

Using it with golangci-lint

The plugin has to be compiled into a custom golangci-lint binary. Add it to .custom-gcl.yml:

version: v2.12.2
plugins:
  - module: "github.com/katbyte/azproviderlint"
    import: "github.com/katbyte/azproviderlint/plugin"
    version: v0.1.0

Build the binary and enable the linter:

golangci-lint custom
linters:
  enable:
    - azproviderlint
  settings:
    custom:
      azproviderlint:
        type: module

To run only azproviderlint through the custom binary:

custom-gcl run --enable-only azproviderlint ./...

Choosing rules

Every rule runs by default. Under settings, enable narrows that to a list and disable removes from whatever is enabled. Both accept rule names or whole categories.

linters:
  settings:
    custom:
      azproviderlint:
        type: module
        settings:
          enable: [AZG]         # only the AZG rules...
          disable: [AZG005]     # ...except AZG005

There is no per-rule flag on the golangci-lint command line. Use the settings above, or the standalone binary.

Options

Some rules take options. Each rule's README lists them. In golangci-lint they go under the rule's name in the same settings block. On the standalone binary they are -<RULE>.<option> flags.

        settings:
          AZS004: {allow-extra-values: true}
          AZG005: {max-gap: 50}
azproviderlint -AZG005 -AZG005.max-gap=50 ./...

Why bother with the plugin?

Building a custom binary is a one-time cost, and on a codebase the size of azurerm it pays off:

  • Loading the provider once, not twice. Type-checking azurerm and its vendor tree takes minutes. A separate binary does it all over again. Inside golangci-lint it is shared, and the result cache makes warm re-runs fast.
  • One config, one output. The path exclusions already in the provider's .golangci.yml (generated files, /sdk/, third_party) apply for free. So do SARIF, annotations, and --new-from-rev, which lets a rule be enforced on new code while a decade of existing findings is left alone.
  • //nolint works alongside //azignore.
  • One pass for every provider linter. tfproviderlint-golangci wraps tfproviderlint as a module plugin too, so a custom binary can carry golangci-lint, tfproviderlint, and azproviderlint together: three tools doing three passes become one.

The standalone binary is still the right tool for a quick one-rule run, for editors that expect a plain analysis vet tool, and for developing new rules.

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages