Skip to content

Commit a20a97e

Browse files
authored
document - a batch of fix for documents (hashicorp#31081)
1 parent 08ea01e commit a20a97e

138 files changed

Lines changed: 657 additions & 327 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/tools/document-lint/check/check_possible_value.go

Lines changed: 180 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ package check
66
import (
77
"fmt"
88
"log"
9+
"os"
910
"path"
11+
"path/filepath"
12+
"sort"
1013
"strings"
14+
"sync"
1115

1216
"github.com/fatih/color"
17+
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/document-lint/md"
1318
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/document-lint/model"
1419
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/document-lint/schema"
1520
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/document-lint/util"
@@ -112,9 +117,62 @@ func (p possibleValueDiff) Fix(line string) (result string, err error) {
112117

113118
var _ Checker = (*possibleValueDiff)(nil)
114119

120+
// sortPossibleValues sorts possible values in a sensible order
121+
// For weekdays, it maintains chronological order (Monday -> Sunday)
122+
// For other values, it sorts alphabetically (case-insensitive)
123+
func sortPossibleValues(values []string) []string {
124+
if len(values) <= 1 {
125+
return values
126+
}
127+
128+
// Check if all values are weekdays
129+
weekdayOrder := map[string]int{
130+
"monday": 1,
131+
"tuesday": 2,
132+
"wednesday": 3,
133+
"thursday": 4,
134+
"friday": 5,
135+
"saturday": 6,
136+
"sunday": 7,
137+
"Monday": 1,
138+
"Tuesday": 2,
139+
"Wednesday": 3,
140+
"Thursday": 4,
141+
"Friday": 5,
142+
"Saturday": 6,
143+
"Sunday": 7,
144+
}
145+
146+
allWeekdays := true
147+
for _, v := range values {
148+
if _, ok := weekdayOrder[v]; !ok {
149+
allWeekdays = false
150+
break
151+
}
152+
}
153+
154+
sorted := make([]string, len(values))
155+
copy(sorted, values)
156+
157+
if allWeekdays {
158+
sort.Slice(sorted, func(i, j int) bool {
159+
return weekdayOrder[sorted[i]] < weekdayOrder[sorted[j]]
160+
})
161+
} else {
162+
sort.Slice(sorted, func(i, j int) bool {
163+
return strings.ToLower(sorted[i]) < strings.ToLower(sorted[j])
164+
})
165+
}
166+
167+
return sorted
168+
}
169+
115170
func patchWantEnums(want []string) string {
116-
res := make([]string, len(want))
117-
for idx, val := range want {
171+
// Sort the values before formatting
172+
sorted := sortPossibleValues(want)
173+
174+
res := make([]string, len(sorted))
175+
for idx, val := range sorted {
118176
res[idx] = "`" + val + "`"
119177
}
120178
if len(res) == 1 {
@@ -154,12 +212,21 @@ func diffField(r *schema.Resource, mdField *model.Field, xPath []string) (res []
154212

155213
// if end property
156214
if mdField.Subs == nil {
157-
want := r.PossibleValues[fullPath]
215+
wanted := r.PossibleValues[fullPath]
158216
docVal := mdField.PossibleValues()
159-
if missed, spare := SliceDiff(want, docVal, true); len(missed)+len(spare) > 0 {
160-
if !mayExistsInDoc(mdField.Content, want) {
217+
if missed, spare := SliceDiff(wanted, docVal, true); len(missed)+len(spare) > 0 {
218+
// Check if this property has version changes before reporting error
219+
// Skip possible value diff if this property has changes in new version upgrade.
220+
// This is because it's not consistent whether possible values change should be documented or not when it's in the upgrade feature flag
221+
if hasVersionChanges(r.ResourceType, fullPath) {
222+
log.Printf("[SKIP] %s.%s: Skipping possible values validation due to version changes detected in upgrade guide (missed: %v, spare: %v)",
223+
r.ResourceType, fullPath, missed, spare)
224+
return
225+
}
226+
227+
if !mayExistsInDoc(mdField.Content, wanted) {
161228
base := newCheckBase(mdField.Line, fullPath, mdField)
162-
res = append(res, newPossibleValueDiff(base, want, docVal, missed, spare))
229+
res = append(res, newPossibleValueDiff(base, wanted, docVal, missed, spare))
163230
}
164231
}
165232
return
@@ -220,3 +287,110 @@ func mayExistsInDoc(docLine string, want []string) bool {
220287
}
221288
return true
222289
}
290+
291+
// hasVersionChanges checks if the field has version-related changes by parsing the upgrade guide
292+
func hasVersionChanges(resourceType, fieldPath string) bool {
293+
upgradeGuideContent := getUpgradeGuideContent()
294+
if upgradeGuideContent == "" {
295+
return false
296+
}
297+
298+
// Look for the resource section
299+
resourceSectionPattern := fmt.Sprintf("### `%s`", resourceType)
300+
301+
// Find the resource section
302+
lines := strings.Split(upgradeGuideContent, "\n")
303+
resourceSectionStart := -1
304+
305+
for i, line := range lines {
306+
if strings.Contains(line, resourceSectionPattern) {
307+
resourceSectionStart = i
308+
break
309+
}
310+
}
311+
312+
// Resource not found in upgrade guide
313+
if resourceSectionStart == -1 {
314+
return false
315+
}
316+
317+
// Find the end of this resource section (next resource or major section)
318+
resourceSectionEnd := len(lines)
319+
for i := resourceSectionStart + 1; i < len(lines); i++ {
320+
line := lines[i]
321+
if strings.HasPrefix(line, "### `azurerm_") || strings.HasPrefix(line, "## ") {
322+
resourceSectionEnd = i
323+
break
324+
}
325+
}
326+
327+
// Get the content of this resource section
328+
resourceSection := strings.Join(lines[resourceSectionStart:resourceSectionEnd], "\n")
329+
330+
// Check if the property is mentioned in this resource section
331+
propertyPatterns := []string{
332+
fmt.Sprintf("`%s`", fieldPath), // `property_name`
333+
fmt.Sprintf(" %s ", fieldPath), // property_name with spaces
334+
fmt.Sprintf(".%s ", fieldPath), // .property_name
335+
fmt.Sprintf("`%s.", fieldPath), // `property_name.
336+
}
337+
338+
// For nested properties like "site_config.remote_debugging_version", also check the last part
339+
if strings.Contains(fieldPath, ".") {
340+
parts := strings.Split(fieldPath, ".")
341+
lastPart := parts[len(parts)-1]
342+
propertyPatterns = append(propertyPatterns,
343+
fmt.Sprintf("`%s`", lastPart), // `last_part`
344+
fmt.Sprintf(" %s ", lastPart), // last_part with spaces
345+
)
346+
}
347+
348+
// Check if any pattern is found in the resource section
349+
for _, pattern := range propertyPatterns {
350+
if strings.Contains(resourceSection, pattern) {
351+
return true
352+
}
353+
}
354+
355+
return false
356+
}
357+
358+
var (
359+
upgradeGuideContent string
360+
loadUpgradeGuideOnce sync.Once
361+
)
362+
363+
// getUpgradeGuideContent loads and caches the upgrade guide content
364+
func getUpgradeGuideContent() string {
365+
loadUpgradeGuideOnce.Do(func() {
366+
docsDir := md.DocDir()
367+
368+
if upgradeFile := findUpgradeGuide(docsDir); upgradeFile != "" {
369+
if content, err := os.ReadFile(upgradeFile); err == nil {
370+
upgradeGuideContent = string(content)
371+
log.Printf("Loaded upgrade guide from: %s", upgradeFile)
372+
return
373+
}
374+
}
375+
376+
log.Printf("Warning: Could not find any *-upgrade-guide.html.markdown file in docs directory: %s", docsDir)
377+
upgradeGuideContent = ""
378+
})
379+
380+
return upgradeGuideContent
381+
}
382+
383+
// findUpgradeGuide searches for upgrade guide files in the given directory
384+
func findUpgradeGuide(dir string) string {
385+
if _, err := os.Stat(dir); os.IsNotExist(err) {
386+
return ""
387+
}
388+
389+
pattern := filepath.Join(dir, "*-upgrade-guide.html.markdown")
390+
matches, err := filepath.Glob(pattern)
391+
if err != nil || len(matches) == 0 {
392+
return ""
393+
}
394+
395+
return matches[0]
396+
}

internal/tools/document-lint/check/register.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"github.com/hashicorp/terraform-provider-azurerm/internal/provider"
10+
"github.com/hashicorp/terraform-provider-azurerm/internal/tools/document-lint/schema"
1011
)
1112

1213
type resource struct {
@@ -77,6 +78,13 @@ func AzurermAllResources(service, skipService string, resources, skipResources s
7778
if shouldSKipResource(svc.ResourceType()) {
7879
continue
7980
}
81+
82+
// Skip deprecated resources, as some of these don't have documents
83+
sch := schema.NewResource(svc, svc.ResourceType())
84+
if sch.IsDeprecated() {
85+
continue
86+
}
87+
8088
res.resources = append(res.resources, resource{
8189
name: svc.ResourceType(),
8290
schema: svc,
@@ -93,6 +101,13 @@ func AzurermAllResources(service, skipService string, resources, skipResources s
93101
if shouldSKipResource(name) {
94102
continue
95103
}
104+
105+
// Skip deprecated resources, as some of these don't have documents
106+
sch := schema.NewResource(svc, name)
107+
if sch.IsDeprecated() {
108+
continue
109+
}
110+
96111
res.resources = append(res.resources, resource{
97112
name: name,
98113
schema: svc,

internal/tools/document-lint/check/skip_config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ var skipProps = []string{
1616
"all.timezone",
1717
"all.time_zone",
1818
"all.time_zone_id",
19+
// Skip Key Vault reference properties - these reference external resources and shouldn't have "Possible values" documented
20+
"all.key_vault_secret_id",
21+
"all.key_vault_key_id",
22+
"all.key_vault_certificate_id",
1923
"azurerm_nginx_deployment.identity.type", // there is a diff between real supported values and common identity schema
2024
"azurerm_kubernetes_cluster.default_node_pool.os_sku",
2125
"azurerm_kubernetes_cluster_node_pool.os_sku",

internal/tools/document-lint/md/doc_helper.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,8 @@ func ResourceDir() string {
8080
}
8181
return docRDir
8282
}
83+
84+
// DocDir returns the base documentation directory
85+
func DocDir() string {
86+
return docDir()
87+
}

internal/tools/document-lint/md/normalize.go

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -246,31 +246,34 @@ func tryFixProp(line string) string {
246246
}
247247
}
248248
}
249-
// need a dash after property name
250-
idx := strings.Index(line, "`")
251-
if idx += strings.Index(line[idx+1:], "`") + 1; idx > 0 {
252-
for idx2 := idx + 1; idx2 < len(line); idx2++ {
253-
if line[idx2] == ' ' {
254-
continue
255-
}
256-
if line[idx2] != '-' {
257-
line2 := line[:idx+2]
258-
if line[idx2-1] != ' ' {
259-
line2 += " "
260-
}
261-
line2 += "- "
262-
line2 += line[idx2:]
263-
line = line2
264-
break
265-
} else {
266-
// if line[idx2] == '-' exists
267-
if line[idx2-1] != ' ' {
268-
line = line[:idx2] + " " + line[idx2:]
249+
// Skip adding dashes for note sections
250+
if !strings.HasPrefix(line, "~>") && !strings.HasPrefix(line, "->") && !strings.HasPrefix(line, "!>") {
251+
// need a dash after property name
252+
idx := strings.Index(line, "`")
253+
if idx += strings.Index(line[idx+1:], "`") + 1; idx > 0 {
254+
for idx2 := idx + 1; idx2 < len(line); idx2++ {
255+
if line[idx2] == ' ' {
256+
continue
269257
}
270-
if line[idx2+1] != ' ' {
271-
line = line[:idx2+1] + " " + line[idx2+1:]
258+
if line[idx2] != '-' {
259+
line2 := line[:idx+2]
260+
if line[idx2-1] != ' ' {
261+
line2 += " "
262+
}
263+
line2 += "- "
264+
line2 += line[idx2:]
265+
line = line2
266+
break
267+
} else {
268+
// if line[idx2] == '-' exists
269+
if line[idx2-1] != ' ' {
270+
line = line[:idx2] + " " + line[idx2:]
271+
}
272+
if line[idx2+1] != ' ' {
273+
line = line[:idx2+1] + " " + line[idx2+1:]
274+
}
275+
break
272276
}
273-
break
274277
}
275278
}
276279
}

internal/tools/document-lint/md/parse_md.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func newMarkFromString(content string, filepath string) *Mark {
165165
}
166166
case strings.HasPrefix(line, "```"):
167167
result.addLineOrItem(idx, line, ItemExample)
168-
case strings.HasPrefix(line, "->"), strings.HasPrefix(line, "~>"):
168+
case strings.HasPrefix(line, "->"), strings.HasPrefix(line, "~>"), strings.HasPrefix(line, "!>"):
169169
result.addItemWith(idx, line, ItemNote)
170170
case isBlockHead(line):
171171
result.addItemWith(idx, line, ItemBlockHead)
@@ -317,11 +317,48 @@ func (m *Mark) blockOfName(name string, parent string, pos model.PosType) (b *Bl
317317
}
318318

319319
if len(res) > 1 {
320-
msg = fmt.Sprintf("duplicate block exists as name `%s`", util.Blue(name))
320+
// Check if these are actual duplicate block definitions or just shared references
321+
// Look at the actual block definitions to see if they have different content
322+
uniqueDefinitions := make(map[string]Block)
323+
for _, block := range res {
324+
// Create a key based on the block's actual content/structure
325+
key := fmt.Sprintf("%s:%d", block.Name, len(block.Fields))
326+
if existing, exists := uniqueDefinitions[key]; exists {
327+
// Check if they're actually different blocks
328+
if !blocksHaveSameDefinition(existing, block) {
329+
msg = fmt.Sprintf("duplicate block exists as name `%s`", util.Blue(name))
330+
break
331+
}
332+
} else {
333+
uniqueDefinitions[key] = block
334+
}
335+
}
336+
// If we only have one unique definition, it's just a shared block reference, not a duplicate
321337
}
322338
return &res[0], msg
323339
}
324340

341+
// blocksHaveSameDefinition checks if two blocks have the same definition/content
342+
func blocksHaveSameDefinition(b1, b2 Block) bool {
343+
// Blocks are considered the same if they have the same fields
344+
if len(b1.Fields) != len(b2.Fields) {
345+
return false
346+
}
347+
348+
// Compare each field
349+
for i, f1 := range b1.Fields {
350+
if i >= len(b2.Fields) {
351+
return false
352+
}
353+
f2 := b2.Fields[i]
354+
if f1.Name != f2.Name || f1.Required != f2.Required {
355+
return false
356+
}
357+
}
358+
359+
return true
360+
}
361+
325362
// buildStruct build struct of blocks
326363
func (m *Mark) buildStruct() {
327364
fillField := func(f *model.Field, parent string) {

internal/tools/document-lint/md/parse_md_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func Test_unmarshalFile(t *testing.T) {
2222
itemNum int
2323
argsNum int
2424
}{
25-
{"key_vault.html.markdown", 64, 16},
25+
{"key_vault.html.markdown", 65, 16},
2626
{"media_transform.html.markdown", 270, 5},
2727
}
2828
for _, arg := range args {

0 commit comments

Comments
 (0)