@@ -15,7 +15,14 @@ import (
1515 "gopkg.in/yaml.v3"
1616)
1717
18- var supportedResourceTypes = []string {"modules" , "templates" }
18+ var (
19+ supportedResourceTypes = []string {"modules" , "templates" }
20+
21+ // TODO: This is a holdover from the validation logic used by the Coder Modules repo. It gives us some assurance, but
22+ // realistically, we probably want to parse any Terraform code snippets, and make some deeper guarantees about how it's
23+ // structured. Just validating whether it *can* be parsed as Terraform would be a big improvement.
24+ terraformVersionRe = regexp .MustCompile ("^\\ s*\\ bversion\\ s+=" )
25+ )
1926
2027type coderResourceFrontmatter struct {
2128 Description string `yaml:"description"`
@@ -25,9 +32,8 @@ type coderResourceFrontmatter struct {
2532 Tags []string `yaml:"tags"`
2633}
2734
28- // coderResourceReadme represents a README describing a Terraform resource used
29- // to help create Coder workspaces. As of 2025-04-15, this encapsulates both
30- // Coder Modules and Coder Templates
35+ // coderResourceReadme represents a README describing a Terraform resource used to help create Coder workspaces.
36+ // As of 2025-04-15, this encapsulates both Coder Modules and Coder Templates.
3137type coderResourceReadme struct {
3238 resourceType string
3339 filePath string
@@ -50,35 +56,33 @@ func validateCoderResourceDescription(description string) error {
5056}
5157
5258func validateCoderResourceIconURL (iconURL string ) []error {
53- problems := []error {}
54-
5559 if iconURL == "" {
56- problems = append (problems , errors .New ("icon URL cannot be empty" ))
57- return problems
60+ return []error {errors .New ("icon URL cannot be empty" )}
5861 }
5962
63+ errs := []error {}
64+
6065 isAbsoluteURL := ! strings .HasPrefix (iconURL , "." ) && ! strings .HasPrefix (iconURL , "/" )
6166 if isAbsoluteURL {
6267 if _ , err := url .ParseRequestURI (iconURL ); err != nil {
63- problems = append (problems , errors .New ("absolute icon URL is not correctly formatted" ))
68+ errs = append (errs , errors .New ("absolute icon URL is not correctly formatted" ))
6469 }
6570 if strings .Contains (iconURL , "?" ) {
66- problems = append (problems , errors .New ("icon URLs cannot contain query parameters" ))
71+ errs = append (errs , errors .New ("icon URLs cannot contain query parameters" ))
6772 }
68- return problems
73+ return errs
6974 }
7075
71- // Would normally be skittish about having relative paths like this, but it
72- // should be safe because we have guarantees about the structure of the
73- // repo, and where this logic will run
76+ // Would normally be skittish about having relative paths like this, but it should be safe because we have guarantees
77+ // about the structure of the repo, and where this logic will run.
7478 isPermittedRelativeURL := strings .HasPrefix (iconURL , "./" ) ||
7579 strings .HasPrefix (iconURL , "/" ) ||
7680 strings .HasPrefix (iconURL , "../../../../.icons" )
7781 if ! isPermittedRelativeURL {
78- problems = append (problems , fmt .Errorf ("relative icon URL %q must either be scoped to that module's directory, or the top-level /.icons directory (this can usually be done by starting the path with \" ../../../.icons\" )" , iconURL ))
82+ errs = append (errs , fmt .Errorf ("relative icon URL %q must either be scoped to that module's directory, or the top-level /.icons directory (this can usually be done by starting the path with \" ../../../.icons\" )" , iconURL ))
7983 }
8084
81- return problems
85+ return errs
8286}
8387
8488func validateCoderResourceTags (tags []string ) error {
@@ -89,9 +93,8 @@ func validateCoderResourceTags(tags []string) error {
8993 return nil
9094 }
9195
92- // All of these tags are used for the module/template filter controls in the
93- // Registry site. Need to make sure they can all be placed in the browser
94- // URL without issue
96+ // All of these tags are used for the module/template filter controls in the Registry site. Need to make sure they
97+ // can all be placed in the browser URL without issue.
9598 invalidTags := []string {}
9699 for _ , t := range tags {
97100 if t != url .QueryEscape (t ) {
@@ -105,16 +108,11 @@ func validateCoderResourceTags(tags []string) error {
105108 return nil
106109}
107110
108- // Todo: This is a holdover from the validation logic used by the Coder Modules
109- // repo. It gives us some assurance, but realistically, we probably want to
110- // parse any Terraform code snippets, and make some deeper guarantees about how
111- // it's structured. Just validating whether it *can* be parsed as Terraform
112- // would be a big improvement.
113- var terraformVersionRe = regexp .MustCompile ("^\\ s*\\ bversion\\ s+=" )
114-
115111func validateCoderResourceReadmeBody (body string ) []error {
116- trimmed := strings .TrimSpace (body )
117112 var errs []error
113+
114+ trimmed := strings .TrimSpace (body )
115+ // TODO: this may cause unexpected behaviour since the errors slice may have a 0 length. Add a test.
118116 errs = append (errs , validateReadmeBody (trimmed )... )
119117
120118 foundParagraph := false
@@ -124,15 +122,15 @@ func validateCoderResourceReadmeBody(body string) []error {
124122 lineNum := 0
125123 isInsideCodeBlock := false
126124 isInsideTerraform := false
125+ nextLine := ""
127126
128127 lineScanner := bufio .NewScanner (strings .NewReader (trimmed ))
129128 for lineScanner .Scan () {
130129 lineNum ++
131- nextLine : = lineScanner .Text ()
130+ nextLine = lineScanner .Text ()
132131
133- // Code assumes that invalid headers would've already been handled by
134- // the base validation function, so we don't need to check deeper if the
135- // first line isn't an h1
132+ // Code assumes that invalid headers would've already been handled by the base validation function, so we don't
133+ // need to check deeper if the first line isn't an h1.
136134 if lineNum == 1 {
137135 if ! strings .HasPrefix (nextLine , "# " ) {
138136 break
@@ -159,15 +157,13 @@ func validateCoderResourceReadmeBody(body string) []error {
159157 continue
160158 }
161159
162- // Code assumes that we can treat this case as the end of the "h1
163- // section" and don't need to process any further lines
160+ // Code assumes that we can treat this case as the end of the "h1 section" and don't need to process any further lines.
164161 if lineNum > 1 && strings .HasPrefix (nextLine , "#" ) {
165162 break
166163 }
167164
168- // Code assumes that if we've reached this point, the only other options
169- // are: (1) empty spaces, (2) paragraphs, (3) HTML, and (4) asset
170- // references made via [] syntax
165+ // Code assumes that if we've reached this point, the only other options are:
166+ // (1) empty spaces, (2) paragraphs, (3) HTML, and (4) asset references made via [] syntax.
171167 trimmedLine := strings .TrimSpace (nextLine )
172168 isParagraph := trimmedLine != "" && ! strings .HasPrefix (trimmedLine , "![" ) && ! strings .HasPrefix (trimmedLine , "<" )
173169 foundParagraph = foundParagraph || isParagraph
@@ -257,9 +253,9 @@ func parseCoderResourceReadmeFiles(resourceType string, rms []readme) (map[strin
257253
258254 yamlValidationErrors := []error {}
259255 for _ , readme := range resources {
260- errors := validateCoderResourceReadme (readme )
261- if len (errors ) > 0 {
262- yamlValidationErrors = append (yamlValidationErrors , errors ... )
256+ errs := validateCoderResourceReadme (readme )
257+ if len (errs ) > 0 {
258+ yamlValidationErrors = append (yamlValidationErrors , errs ... )
263259 }
264260 }
265261 if len (yamlValidationErrors ) != 0 {
@@ -272,7 +268,7 @@ func parseCoderResourceReadmeFiles(resourceType string, rms []readme) (map[strin
272268 return resources , nil
273269}
274270
275- // Todo : Need to beef up this function by grabbing each image/video URL from
271+ // TODO : Need to beef up this function by grabbing each image/video URL from
276272// the body's AST
277273func validateCoderResourceRelativeUrls (resources map [string ]coderResourceReadme ) error {
278274 return nil
@@ -286,13 +282,14 @@ func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
286282
287283 var allReadmeFiles []readme
288284 var errs []error
285+ var resourceDirs []os.DirEntry
289286 for _ , rf := range registryFiles {
290287 if ! rf .IsDir () {
291288 continue
292289 }
293290
294291 resourceRootPath := path .Join (rootRegistryPath , rf .Name (), resourceType )
295- resourceDirs , err : = os .ReadDir (resourceRootPath )
292+ resourceDirs , err = os .ReadDir (resourceRootPath )
296293 if err != nil {
297294 if ! errors .Is (err , os .ErrNotExist ) {
298295 errs = append (errs , err )
@@ -345,8 +342,7 @@ func validateAllCoderResourceFilesOfType(resourceType string) error {
345342 }
346343 logger .Info (context .Background (), "Processed README files as valid Coder resources" , "num_files" , len (resources ), "type" , resourceType )
347344
348- err = validateCoderResourceRelativeUrls (resources )
349- if err != nil {
345+ if err = validateCoderResourceRelativeUrls (resources ); err != nil {
350346 return err
351347 }
352348 logger .Info (context .Background (), "All relative URLs for READMEs are valid" , "type" , resourceType )
0 commit comments