Skip to content

Commit 7bf1184

Browse files
authored
ARM API version linter: prevent preview API from being used in azurerm (#30612)
1 parent 0aac888 commit 7bf1184

File tree

11 files changed

+772
-0
lines changed

11 files changed

+772
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: Preview ARM API Version Linter
3+
4+
permissions:
5+
contents: read
6+
pull-requests: read
7+
8+
on:
9+
pull_request:
10+
types: ["opened", "synchronize"]
11+
paths:
12+
- ".github/workflows/preview-api-version-linter.yaml"
13+
- "internal/tools/preview-api-version-linter/**"
14+
- "vendor/**"
15+
16+
jobs:
17+
preview-api-version-linter:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
21+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
22+
with:
23+
go-version-file: ./.go-version
24+
- run: go run internal/tools/preview-api-version-linter/main.go
25+
comment-on-fail:
26+
if: ${{ needs.depscheck.result }} == 'failure'
27+
uses: ./.github/workflows/comment-failure.yaml
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Guide: ARM API Versions
2+
3+
The provider should be implemented using stable Azure Resource Manager (ARM) API/SDK version. Preview versions are prone to sudden breaking changes which can result in a less than ideal user experience (eg: removed property or behavioural change). There are [automated checks on azure-rest-api-specs that prevents breaking changes against stable version](https://github.com/Azure/azure-rest-api-specs/blob/main/documentation/ci-fix.md#sdk-breaking-change-review) but they do not catch everything and are not applicable to preview versions.
4+
5+
These breaking API changes often materialise into [breaking changes](guide-breaking-changes.md) which can involve non-trivial upgrade steps and/or require waiting until a major version release to make the breaking change. v3.0.0 was released in March 2022 and v4.0.0 in August 2024.
6+
7+
In November 2025 we implemented an API version check on PRs that prevents the use of preview versions. All historical usages of preview versions have been allow-listed as exceptions. See `internal/tools/preview-api-version-linter` for the implementation details.
8+
9+
## Rerunning checks locally
10+
11+
If you came to this page through a build failure, once you have removed the preview API dependency, rerun this check locally using the command:
12+
13+
```
14+
go run internal/tools/preview-api-version-linter/main.go
15+
```
16+
17+
## Obtaining exception to use preview API
18+
19+
> [!WARNING]
20+
> Using a preview API version can be risky, prone to human error, and can result in a substandard user experience. An exception is a last resort only when all the consequences are fully understood and there is no alternative.
21+
22+
To add an exception to use preview API version, the following criteria must be met:
23+
24+
1. There is a clear and compelling reason for not using a stable API version. For example: the fix for a critical security vulnerability or impactful bug is only available in the preview version.
25+
1. There is a commitment from the service that no breaking changes will be made to the relevant preview API that could negatively impact azurerm users.
26+
1. There is a commitment from the service team to release a stable API version in the near future, a specific target date has to be set.
27+
1. There is a responsible individual with deep knowledge of the API that can be contacted in the future if required.
28+
1. There is an agreement between Microsoft and Hashicorp that the exception is appropriate.
29+
30+
> [!NOTE]
31+
> A feature being in preview phase is not a sufficient reason to add this exception. The concept of preview should be decoupled between feature and ARM API. It is okay to leave the feature in preview phase while having the API promoted to stable. This will safeguard the API against breaking changes and ensure azurerm support for the feature can be shipped sooner to customers.
32+
33+
To add an exception, insert an entry to `internal/tools/preview-api-version-linter/exceptions.yml` as per below example:
34+
35+
```yml
36+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
37+
service: compute
38+
version: 2021-06-01-preview
39+
stableVersionTargetDate: 2026-01-01
40+
responsibleIndividual: github.com/gerrytan
41+
```
42+
43+
- `module`: go module name as per go.mod, see internal/tools/preview-api-version-linter/sdk/sdk_types.go for supported modules
44+
- `service`: service name as per the vendor path, for `vendor/github.com/hashicorp/go-azure-sdk/resource-manager/compute/2021-01-06-preview` the service name is `compute`
45+
- `version`: preview version as per the vendor path
46+
- `stableVersionTargetDate`: estimated stable API version release date, does not have to be the actual stable version string
47+
- `responsibleIndividual`: individual with deep expertise of the API that can be contacted in the future, has to be a `github.com/myuser` GitHub handle or an email address
48+
49+
Entries have to be sorted alphabetically by `module`, `service` and `version`.
50+
51+
Once added, check the linter is passing by running `go run internal/tools/preview-api-version-linter/main.go`.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Tool: `preview-api-version-linter`
2+
3+
Check and fail if preview ARM API version is imported in the vendor folder. Exceptions can be made for specific
4+
circumstances. More info: https://github.com/hashicorp/terraform-provider-azurerm/blob/main/contributing/topics/guide-api-version.md.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Warning! Preview API usage is risky and can cause poor azurerm user experience.
2+
# Please ensure all the consequences are fully understood before adding an exception:
3+
# https://github.com/hashicorp/terraform-provider-azurerm/tree/main/contributing/topics/guide-api-version.md
4+
5+
# Entries have to be sorted alphabetically by module, service, version.
6+
# Example:
7+
# - module: github.com/hashicorp/go-azure-sdk/resource-manager
8+
# service: compute
9+
# version: 2021-06-01-preview
10+
# stableVersionTargetDate: 2026-01-01
11+
# responsibleIndividual: github.com/gerrytan
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Exceptions for historical uses of preview API before the linter tool is setup
2+
# DO NOT ADD NEW ENTRY
3+
4+
- module: github.com/Azure/azure-sdk-for-go
5+
service: resources
6+
version: 2021-06-01-preview
7+
8+
- module: github.com/Azure/azure-sdk-for-go
9+
service: security
10+
version: v3.0
11+
12+
- module: github.com/Azure/azure-sdk-for-go
13+
service: securityinsight
14+
version: 2021-09-01-preview
15+
16+
- module: github.com/Azure/azure-sdk-for-go
17+
service: sql
18+
version: v5.0
19+
20+
- module: github.com/Azure/azure-sdk-for-go
21+
service: synapse
22+
version: v2.0
23+
24+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
25+
service: aadb2c
26+
version: 2021-04-01-preview
27+
28+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
29+
service: appplatform
30+
version: 2024-01-01-preview
31+
32+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
33+
service: authorization
34+
version: 2022-05-01-preview
35+
36+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
37+
service: automation
38+
version: 2020-01-13-preview
39+
40+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
41+
service: blueprints
42+
version: 2018-11-01-preview
43+
44+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
45+
service: codesigning
46+
version: 2024-09-30-preview
47+
48+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
49+
service: containerregistry
50+
version: 2019-06-01-preview
51+
52+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
53+
service: customproviders
54+
version: 2018-09-01-preview
55+
56+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
57+
service: databricks
58+
version: 2022-04-01-preview
59+
60+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
61+
service: databricks
62+
version: 2022-10-01-preview
63+
64+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
65+
service: eventgrid
66+
version: 2023-12-15-preview
67+
68+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
69+
service: eventhub
70+
version: 2022-01-01-preview
71+
72+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
73+
service: insights
74+
version: 2019-10-17-preview
75+
76+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
77+
service: insights
78+
version: 2021-05-01-preview
79+
80+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
81+
service: insights
82+
version: 2021-07-01-preview
83+
84+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
85+
service: insights
86+
version: 2023-03-15-preview
87+
88+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
89+
service: iotcentral
90+
version: 2021-11-01-preview
91+
92+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
93+
service: nginx
94+
version: 2024-11-01-preview
95+
96+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
97+
service: operationsmanagement
98+
version: 2015-11-01-preview
99+
100+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
101+
service: portal
102+
version: 2019-01-01-preview
103+
104+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
105+
service: security
106+
version: 2019-01-01-preview
107+
108+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
109+
service: security
110+
version: 2022-12-01-preview
111+
112+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
113+
service: securityinsights
114+
version: 2022-10-01-preview
115+
116+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
117+
service: securityinsights
118+
version: 2023-12-01-preview
119+
120+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
121+
service: sql
122+
version: 2023-08-01-preview
123+
124+
- module: github.com/hashicorp/go-azure-sdk/resource-manager
125+
service: streamanalytics
126+
version: 2021-10-01-preview
127+
128+
- module: github.com/jackofallops/kermit/sdk
129+
service: appplatform
130+
version: 2023-05-01-preview
131+
132+
- module: github.com/jackofallops/kermit/sdk
133+
service: botservice
134+
version: 2021-05-01-preview
135+
136+
- module: github.com/jackofallops/kermit/sdk
137+
service: iotcentral
138+
version: 2022-10-31-preview
139+
140+
- module: github.com/jackofallops/kermit/sdk
141+
service: iothub
142+
version: 2022-04-30-preview
143+
144+
- module: github.com/jackofallops/kermit/sdk
145+
service: securityinsights
146+
version: 2022-10-01-preview
147+
148+
- module: github.com/jackofallops/kermit/sdk
149+
service: synapse
150+
version: 2019-06-01-preview
151+
152+
- module: github.com/jackofallops/kermit/sdk
153+
service: synapse
154+
version: 2020-08-01-preview
155+
156+
- module: github.com/jackofallops/kermit/sdk
157+
service: synapse
158+
version: 2021-06-01-preview
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"sort"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/preview-api-version-linter/sdk"
11+
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/preview-api-version-linter/version"
12+
)
13+
14+
const (
15+
EXCEPTIONS_FILE = "internal/tools/preview-api-version-linter/exceptions.yml"
16+
HISTORICAL_EXCEPTIONS_FILE = "internal/tools/preview-api-version-linter/historical-exceptions.yml"
17+
VENDOR_DIR = "vendor"
18+
)
19+
20+
func main() {
21+
historicalExceptions, err := version.ParseHistoricalExceptions(filepath.FromSlash(HISTORICAL_EXCEPTIONS_FILE))
22+
if err != nil {
23+
fmt.Fprintf(os.Stderr, "failed to parse historical exceptions file %s:\n\n%s\n\n", HISTORICAL_EXCEPTIONS_FILE, err)
24+
printErrorFooterAndExit()
25+
}
26+
exceptions, err := version.ParseExceptions(filepath.FromSlash(EXCEPTIONS_FILE))
27+
if err != nil {
28+
fmt.Fprintf(os.Stderr, "failed to parse exceptions file %s:\n\n%s\n\n", EXCEPTIONS_FILE, err)
29+
printErrorFooterAndExit()
30+
}
31+
32+
previewVersions := map[version.Version]bool{}
33+
for _, sdkType := range sdk.SdkTypes {
34+
if err := populatePreviewVersions(sdkType, previewVersions); err != nil {
35+
fmt.Fprintf(os.Stderr, "failed to populate preview versions for SDK type %s:\n\n%s\n\n", sdkType.Module, err)
36+
printErrorFooterAndExit()
37+
}
38+
}
39+
40+
unusedExceptions := []string{}
41+
for _, exception := range historicalExceptions {
42+
if !previewVersions[exception] {
43+
unusedExceptions = append(unusedExceptions, fmt.Sprintf("module: %s, service: %s, version: %s", exception.Module, exception.Service, exception.Version))
44+
} else {
45+
delete(previewVersions, exception)
46+
}
47+
}
48+
failIfAnyUnusuedExceptions(unusedExceptions, HISTORICAL_EXCEPTIONS_FILE)
49+
50+
for _, exception := range exceptions {
51+
if !previewVersions[exception] {
52+
unusedExceptions = append(unusedExceptions, fmt.Sprintf("module: %s, service: %s, version: %s", exception.Module, exception.Service, exception.Version))
53+
} else {
54+
delete(previewVersions, exception)
55+
}
56+
}
57+
failIfAnyUnusuedExceptions(unusedExceptions, EXCEPTIONS_FILE)
58+
59+
if len(previewVersions) > 0 {
60+
invalidPreviewVersions := []string{}
61+
for svcVer := range previewVersions {
62+
invalidPreviewVersions = append(invalidPreviewVersions, fmt.Sprintf("module: %s, service: %s, version: %s", svcVer.Module, svcVer.Service, svcVer.Version))
63+
}
64+
sort.Strings(invalidPreviewVersions)
65+
66+
fmt.Fprintf(os.Stderr, "❌ Invalid use of preview ARM API versions detected in `vendor` folder:\n\n")
67+
for _, v := range invalidPreviewVersions {
68+
fmt.Fprintf(os.Stderr, "%s\n", v)
69+
}
70+
fmt.Fprintf(os.Stderr, `
71+
Preview versions are prone to sudden breaking changes which can result in a less than ideal user experience,
72+
please use stable version instead.
73+
74+
`)
75+
printErrorFooterAndExit()
76+
}
77+
78+
fmt.Printf("✅ No invalid use of preview ARM API versions detected in %s folder.\n", VENDOR_DIR)
79+
}
80+
81+
func populatePreviewVersions(sdkType sdk.SdkType, previewServiceVersions map[version.Version]bool) error {
82+
return filepath.WalkDir(filepath.FromSlash(VENDOR_DIR+"/"+sdkType.Module), func(path string, dir os.DirEntry, err error) error {
83+
if err != nil {
84+
return err
85+
}
86+
if dir.IsDir() {
87+
matches := sdkType.ServiceAndVersionRegex.FindStringSubmatch(filepath.ToSlash(path))
88+
if len(matches) == 3 {
89+
previewServiceVersions[version.Version{
90+
Module: sdkType.Module,
91+
Service: strings.ToLower(matches[1]),
92+
Version: strings.ToLower(matches[2]),
93+
}] = true
94+
}
95+
}
96+
return nil
97+
})
98+
}
99+
100+
func printErrorFooterAndExit() {
101+
fmt.Fprintf(os.Stderr, `More information: https://github.com/hashicorp/terraform-provider-azurerm/blob/main/contributing/topics/guide-api-version.md
102+
103+
To rerun this check locally, use: go run internal/tools/preview-api-version-linter/main.go
104+
`)
105+
os.Exit(1)
106+
}
107+
108+
func failIfAnyUnusuedExceptions(unusedExceptions []string, exceptionsFile string) {
109+
if len(unusedExceptions) > 0 {
110+
fmt.Fprintf(os.Stderr, "❌ Unused exceptions detected in `%s` file, remove these entries:\n\n", exceptionsFile)
111+
for _, unusedException := range unusedExceptions {
112+
fmt.Fprintln(os.Stderr, unusedException)
113+
}
114+
fmt.Fprintf(os.Stderr, "\n")
115+
printErrorFooterAndExit()
116+
}
117+
}

0 commit comments

Comments
 (0)