Skip to content

Commit 9c00c57

Browse files
Address PR review comments
- Make regex more specific for registry.coder.com patterns only - Refactor to add namespace and resourceName fields to coderResourceReadme struct - Inline path parsing logic into parseCoderResourceReadme - Update validateModuleSourceURL to use struct fields instead of filePath parameter - Simplify Terraform block detection logic - Reduce nesting with early continue statements - Add comment explaining regex pattern - Extract registry.coder.com into a constant - Improve test readability with extracted variables - Remove redundant checks in tests - Replace custom contains function with strings.Contains Co-authored-by: matifali <[email protected]>
1 parent 5419676 commit 9c00c57

File tree

3 files changed

+111
-80
lines changed

3 files changed

+111
-80
lines changed

cmd/readmevalidation/codermodules.go

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,71 +3,64 @@ package main
33
import (
44
"bufio"
55
"context"
6-
"path/filepath"
76
"regexp"
87
"strings"
98

109
"golang.org/x/xerrors"
1110
)
1211

1312
var (
14-
terraformSourceRe = regexp.MustCompile(`^\s*source\s*=\s*"([^"]+)"`)
13+
// Matches Terraform source lines with registry.coder.com URLs
14+
// Pattern: source = "registry.coder.com/namespace/module/coder"
15+
terraformSourceRe = regexp.MustCompile(`^\s*source\s*=\s*"` + registryDomain + `/([^/]+)/([^/]+)/coder"`)
1516
)
1617

17-
func extractNamespaceAndModuleFromPath(filePath string) (namespace string, moduleName string, err error) {
18-
// Expected path format: registry/<namespace>/modules/<module-name>/README.md.
19-
parts := strings.Split(filepath.Clean(filePath), string(filepath.Separator))
20-
if len(parts) < 5 || parts[0] != "registry" || parts[2] != "modules" || parts[4] != "README.md" {
21-
return "", "", xerrors.Errorf("invalid module path format: %s", filePath)
22-
}
23-
namespace = parts[1]
24-
moduleName = parts[3]
25-
return namespace, moduleName, nil
26-
}
27-
28-
func validateModuleSourceURL(body string, filePath string) []error {
18+
func validateModuleSourceURL(rm coderResourceReadme) []error {
2919
var errs []error
3020

31-
namespace, moduleName, err := extractNamespaceAndModuleFromPath(filePath)
32-
if err != nil {
33-
return []error{err}
21+
// Skip validation if we couldn't parse namespace/resourceName from path
22+
if rm.namespace == "" || rm.resourceName == "" {
23+
return []error{xerrors.Errorf("invalid module path format: %s", rm.filePath)}
3424
}
3525

36-
expectedSource := "registry.coder.com/" + namespace + "/" + moduleName + "/coder"
26+
expectedSource := registryDomain + "/" + rm.namespace + "/" + rm.resourceName + "/coder"
3727

38-
trimmed := strings.TrimSpace(body)
28+
trimmed := strings.TrimSpace(rm.body)
3929
foundCorrectSource := false
4030
isInsideTerraform := false
41-
firstTerraformBlock := true
4231

4332
lineScanner := bufio.NewScanner(strings.NewReader(trimmed))
4433
for lineScanner.Scan() {
4534
nextLine := lineScanner.Text()
4635

4736
if strings.HasPrefix(nextLine, "```") {
48-
if strings.HasPrefix(nextLine, "```tf") && firstTerraformBlock {
37+
if strings.HasPrefix(nextLine, "```tf") {
4938
isInsideTerraform = true
50-
firstTerraformBlock = false
51-
} else if isInsideTerraform {
52-
// End of first terraform block.
39+
continue
40+
}
41+
if isInsideTerraform {
5342
break
5443
}
5544
continue
5645
}
5746

58-
if isInsideTerraform {
59-
// Check for any source line in the first terraform block.
60-
if matches := terraformSourceRe.FindStringSubmatch(nextLine); matches != nil {
61-
actualSource := matches[1]
62-
if actualSource == expectedSource {
63-
foundCorrectSource = true
64-
break
65-
} else if strings.HasPrefix(actualSource, "registry.coder.com/") && strings.Contains(actualSource, "/"+moduleName+"/coder") {
66-
// Found source for this module but with wrong namespace/format.
67-
errs = append(errs, xerrors.Errorf("incorrect source URL format: found %q, expected %q", actualSource, expectedSource))
68-
return errs
69-
}
47+
if !isInsideTerraform {
48+
continue
49+
}
50+
51+
// Check for source line in the first terraform block
52+
if matches := terraformSourceRe.FindStringSubmatch(nextLine); matches != nil {
53+
actualNamespace := matches[1]
54+
actualModule := matches[2]
55+
actualSource := registryDomain + "/" + actualNamespace + "/" + actualModule + "/coder"
56+
57+
if actualSource == expectedSource {
58+
foundCorrectSource = true
59+
break
7060
}
61+
// Found a registry.coder.com source but with wrong namespace/module
62+
errs = append(errs, xerrors.Errorf("incorrect source URL format: found %q, expected %q", actualSource, expectedSource))
63+
return errs
7164
}
7265
}
7366

@@ -164,7 +157,7 @@ func validateCoderModuleReadme(rm coderResourceReadme) []error {
164157
for _, err := range validateCoderModuleReadmeBody(rm.body) {
165158
errs = append(errs, addFilePathToError(rm.filePath, err))
166159
}
167-
for _, err := range validateModuleSourceURL(rm.body, rm.filePath) {
160+
for _, err := range validateModuleSourceURL(rm) {
168161
errs = append(errs, addFilePathToError(rm.filePath, err))
169162
}
170163
for _, err := range validateResourceGfmAlerts(rm.body) {
@@ -216,4 +209,4 @@ func validateAllCoderModules() error {
216209
}
217210
logger.Info(context.Background(), "all relative URLs for READMEs are valid", "resource_type", resourceType)
218211
return nil
219-
}
212+
}

cmd/readmevalidation/codermodules_test.go

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,42 @@ package main
22

33
import (
44
_ "embed"
5+
"strings"
56
"testing"
67
)
78

89
//go:embed testSamples/sampleReadmeBody.md
910
var testBody string
1011

12+
// Test bodies extracted for better readability
13+
var (
14+
validModuleBody = `# Test Module
15+
16+
` + "```tf\n" + `module "test-module" {
17+
source = "registry.coder.com/test-namespace/test-module/coder"
18+
version = "1.0.0"
19+
agent_id = coder_agent.example.id
20+
}
21+
` + "```\n"
22+
23+
wrongNamespaceBody = `# Test Module
24+
25+
` + "```tf\n" + `module "test-module" {
26+
source = "registry.coder.com/wrong-namespace/test-module/coder"
27+
version = "1.0.0"
28+
agent_id = coder_agent.example.id
29+
}
30+
` + "```\n"
31+
32+
missingSourceBody = `# Test Module
33+
34+
` + "```tf\n" + `module "test-module" {
35+
version = "1.0.0"
36+
agent_id = coder_agent.example.id
37+
}
38+
` + "```\n"
39+
)
40+
1141
func TestValidateCoderResourceReadmeBody(t *testing.T) {
1242
t.Parallel()
1343

@@ -27,9 +57,14 @@ func TestValidateModuleSourceURL(t *testing.T) {
2757
t.Run("Valid source URL format", func(t *testing.T) {
2858
t.Parallel()
2959

30-
body := "# Test Module\n\n```tf\nmodule \"test-module\" {\n source = \"registry.coder.com/test-namespace/test-module/coder\"\n version = \"1.0.0\"\n agent_id = coder_agent.example.id\n}\n```\n"
31-
filePath := "registry/test-namespace/modules/test-module/README.md"
32-
errs := validateModuleSourceURL(body, filePath)
60+
rm := coderResourceReadme{
61+
resourceType: "modules",
62+
filePath: "registry/test-namespace/modules/test-module/README.md",
63+
namespace: "test-namespace",
64+
resourceName: "test-module",
65+
body: validModuleBody,
66+
}
67+
errs := validateModuleSourceURL(rm)
3368
if len(errs) != 0 {
3469
t.Errorf("Expected no errors, got: %v", errs)
3570
}
@@ -38,68 +73,57 @@ func TestValidateModuleSourceURL(t *testing.T) {
3873
t.Run("Invalid source URL format - wrong namespace", func(t *testing.T) {
3974
t.Parallel()
4075

41-
body := "# Test Module\n\n```tf\nmodule \"test-module\" {\n source = \"registry.coder.com/wrong-namespace/test-module/coder\"\n version = \"1.0.0\"\n agent_id = coder_agent.example.id\n}\n```\n"
42-
filePath := "registry/test-namespace/modules/test-module/README.md"
43-
errs := validateModuleSourceURL(body, filePath)
76+
rm := coderResourceReadme{
77+
resourceType: "modules",
78+
filePath: "registry/test-namespace/modules/test-module/README.md",
79+
namespace: "test-namespace",
80+
resourceName: "test-module",
81+
body: wrongNamespaceBody,
82+
}
83+
errs := validateModuleSourceURL(rm)
4484
if len(errs) != 1 {
4585
t.Errorf("Expected 1 error, got %d: %v", len(errs), errs)
4686
}
47-
if len(errs) > 0 && !contains(errs[0].Error(), "incorrect source URL format") {
87+
if !strings.Contains(errs[0].Error(), "incorrect source URL format") {
4888
t.Errorf("Expected source URL format error, got: %s", errs[0].Error())
4989
}
5090
})
5191

5292
t.Run("Missing source URL", func(t *testing.T) {
5393
t.Parallel()
5494

55-
body := "# Test Module\n\n```tf\nmodule \"other-module\" {\n source = \"registry.coder.com/other/other-module/coder\"\n version = \"1.0.0\"\n agent_id = coder_agent.example.id\n}\n```\n"
56-
filePath := "registry/test-namespace/modules/test-module/README.md"
57-
errs := validateModuleSourceURL(body, filePath)
95+
rm := coderResourceReadme{
96+
resourceType: "modules",
97+
filePath: "registry/test-namespace/modules/test-module/README.md",
98+
namespace: "test-namespace",
99+
resourceName: "test-module",
100+
body: missingSourceBody,
101+
}
102+
errs := validateModuleSourceURL(rm)
58103
if len(errs) != 1 {
59104
t.Errorf("Expected 1 error, got %d: %v", len(errs), errs)
60105
}
61-
if len(errs) > 0 && !contains(errs[0].Error(), "did not find correct source URL") {
106+
if !strings.Contains(errs[0].Error(), "did not find correct source URL") {
62107
t.Errorf("Expected missing source URL error, got: %s", errs[0].Error())
63108
}
64109
})
65110

66-
t.Run("Module name with hyphens vs underscores", func(t *testing.T) {
67-
t.Parallel()
68-
69-
body := "# Test Module\n\n```tf\nmodule \"test_module\" {\n source = \"registry.coder.com/test-namespace/test-module/coder\"\n version = \"1.0.0\"\n agent_id = coder_agent.example.id\n}\n```\n"
70-
filePath := "registry/test-namespace/modules/test-module/README.md"
71-
errs := validateModuleSourceURL(body, filePath)
72-
if len(errs) != 0 {
73-
t.Errorf("Expected no errors for hyphen/underscore variation, got: %v", errs)
74-
}
75-
})
76-
77111
t.Run("Invalid file path format", func(t *testing.T) {
78112
t.Parallel()
79113

80-
body := "# Test Module"
81-
filePath := "invalid/path/format"
82-
errs := validateModuleSourceURL(body, filePath)
114+
rm := coderResourceReadme{
115+
resourceType: "modules",
116+
filePath: "invalid/path/format",
117+
namespace: "", // Empty because path parsing failed
118+
resourceName: "", // Empty because path parsing failed
119+
body: "# Test Module",
120+
}
121+
errs := validateModuleSourceURL(rm)
83122
if len(errs) != 1 {
84123
t.Errorf("Expected 1 error, got %d: %v", len(errs), errs)
85124
}
86-
if len(errs) > 0 && !contains(errs[0].Error(), "invalid module path format") {
125+
if !strings.Contains(errs[0].Error(), "invalid module path format") {
87126
t.Errorf("Expected path format error, got: %s", errs[0].Error())
88127
}
89128
})
90-
}
91-
92-
func contains(s, substr string) bool {
93-
return len(s) >= len(substr) && (s == substr || (len(s) > len(substr) &&
94-
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
95-
indexOfSubstring(s, substr) >= 0)))
96-
}
97-
98-
func indexOfSubstring(s, substr string) int {
99-
for i := 0; i <= len(s)-len(substr); i++ {
100-
if s[i:i+len(substr)] == substr {
101-
return i
102-
}
103-
}
104-
return -1
105-
}
129+
}

cmd/readmevalidation/coderresources.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var (
1818
supportedResourceTypes = []string{"modules", "templates"}
1919
operatingSystems = []string{"windows", "macos", "linux"}
2020
gfmAlertTypes = []string{"NOTE", "IMPORTANT", "CAUTION", "WARNING", "TIP"}
21+
registryDomain = "registry.coder.com"
2122

2223
// TODO: This is a holdover from the validation logic used by the Coder Modules repo. It gives us some assurance, but
2324
// realistically, we probably want to parse any Terraform code snippets, and make some deeper guarantees about how it's
@@ -53,6 +54,8 @@ var supportedCoderResourceStructKeys = []string{
5354
type coderResourceReadme struct {
5455
resourceType string
5556
filePath string
57+
namespace string
58+
resourceName string
5659
body string
5760
frontmatter coderResourceFrontmatter
5861
}
@@ -183,9 +186,20 @@ func parseCoderResourceReadme(resourceType string, rm readme) (coderResourceRead
183186
return coderResourceReadme{}, []error{xerrors.Errorf("%q: failed to parse: %v", rm.filePath, err)}
184187
}
185188

189+
// Extract namespace and resource name from file path
190+
// Expected path format: registry/<namespace>/<resourceType>/<resource-name>/README.md
191+
var namespace, resourceName string
192+
parts := strings.Split(path.Clean(rm.filePath), "/")
193+
if len(parts) >= 5 && parts[0] == "registry" && parts[2] == resourceType && parts[4] == "README.md" {
194+
namespace = parts[1]
195+
resourceName = parts[3]
196+
}
197+
186198
return coderResourceReadme{
187199
resourceType: resourceType,
188200
filePath: rm.filePath,
201+
namespace: namespace,
202+
resourceName: resourceName,
189203
body: body,
190204
frontmatter: yml,
191205
}, nil

0 commit comments

Comments
 (0)