@@ -3,11 +3,87 @@ package main
3
3
import (
4
4
"bufio"
5
5
"context"
6
+ "path/filepath"
7
+ "regexp"
6
8
"strings"
7
9
8
10
"golang.org/x/xerrors"
9
11
)
10
12
13
+ var (
14
+ terraformSourceRe = regexp .MustCompile (`^\s*source\s*=\s*"([^"]+)"` )
15
+ )
16
+
17
+ func normalizeModuleName (name string ) string {
18
+ // Normalize module names by replacing hyphens with underscores for comparison
19
+ // since Terraform allows both but directory names typically use hyphens
20
+ return strings .ReplaceAll (name , "-" , "_" )
21
+ }
22
+
23
+ func extractNamespaceAndModuleFromPath (filePath string ) (string , string , error ) {
24
+ // Expected path format: registry/<namespace>/modules/<module-name>/README.md
25
+ parts := strings .Split (filepath .Clean (filePath ), string (filepath .Separator ))
26
+ if len (parts ) < 5 || parts [0 ] != "registry" || parts [2 ] != "modules" || parts [4 ] != "README.md" {
27
+ return "" , "" , xerrors .Errorf ("invalid module path format: %s" , filePath )
28
+ }
29
+ namespace := parts [1 ]
30
+ moduleName := parts [3 ]
31
+ return namespace , moduleName , nil
32
+ }
33
+
34
+ func validateModuleSourceURL (body string , filePath string ) []error {
35
+ var errs []error
36
+
37
+ namespace , moduleName , err := extractNamespaceAndModuleFromPath (filePath )
38
+ if err != nil {
39
+ return []error {err }
40
+ }
41
+
42
+ expectedSource := "registry.coder.com/" + namespace + "/" + moduleName + "/coder"
43
+
44
+ trimmed := strings .TrimSpace (body )
45
+ foundCorrectSource := false
46
+ isInsideTerraform := false
47
+ firstTerraformBlock := true
48
+
49
+ lineScanner := bufio .NewScanner (strings .NewReader (trimmed ))
50
+ for lineScanner .Scan () {
51
+ nextLine := lineScanner .Text ()
52
+
53
+ if strings .HasPrefix (nextLine , "```" ) {
54
+ if strings .HasPrefix (nextLine , "```tf" ) && firstTerraformBlock {
55
+ isInsideTerraform = true
56
+ firstTerraformBlock = false
57
+ } else if isInsideTerraform {
58
+ // End of first terraform block
59
+ break
60
+ }
61
+ continue
62
+ }
63
+
64
+ if isInsideTerraform {
65
+ // Check for any source line in the first terraform block
66
+ if matches := terraformSourceRe .FindStringSubmatch (nextLine ); matches != nil {
67
+ actualSource := matches [1 ]
68
+ if actualSource == expectedSource {
69
+ foundCorrectSource = true
70
+ break
71
+ } else if strings .HasPrefix (actualSource , "registry.coder.com/" ) && strings .Contains (actualSource , "/" + moduleName + "/coder" ) {
72
+ // Found source for this module but with wrong namespace/format
73
+ errs = append (errs , xerrors .Errorf ("incorrect source URL format: found %q, expected %q" , actualSource , expectedSource ))
74
+ return errs
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ if ! foundCorrectSource {
81
+ errs = append (errs , xerrors .Errorf ("did not find correct source URL %q in first Terraform code block" , expectedSource ))
82
+ }
83
+
84
+ return errs
85
+ }
86
+
11
87
func validateCoderModuleReadmeBody (body string ) []error {
12
88
var errs []error
13
89
@@ -94,6 +170,9 @@ func validateCoderModuleReadme(rm coderResourceReadme) []error {
94
170
for _ , err := range validateCoderModuleReadmeBody (rm .body ) {
95
171
errs = append (errs , addFilePathToError (rm .filePath , err ))
96
172
}
173
+ for _ , err := range validateModuleSourceURL (rm .body , rm .filePath ) {
174
+ errs = append (errs , addFilePathToError (rm .filePath , err ))
175
+ }
97
176
for _ , err := range validateResourceGfmAlerts (rm .body ) {
98
177
errs = append (errs , addFilePathToError (rm .filePath , err ))
99
178
}
0 commit comments