Skip to content

Commit 4926849

Browse files
committed
wip: commit progress on module validation
1 parent 05f58e3 commit 4926849

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

cmd/readmevalidation/coderResources.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import (
44
"bufio"
55
"errors"
66
"fmt"
7+
"log"
78
"net/url"
9+
"os"
10+
"path"
811
"regexp"
12+
"slices"
913
"strings"
14+
15+
"gopkg.in/yaml.v3"
1016
)
1117

1218
var supportedResourceTypes = []string{"modules", "templates"}
@@ -29,6 +35,13 @@ type coderResourceReadme struct {
2935
frontmatter coderResourceFrontmatter
3036
}
3137

38+
type coderResourceReadmes map[string]coderResourceReadme
39+
40+
func (crr coderResourceReadmes) Get(filePath string) (coderResourceReadme, bool) {
41+
rm, ok := crr[filePath]
42+
return rm, ok
43+
}
44+
3245
func validateCoderResourceDisplayName(displayName *string) error {
3346
if displayName != nil && *displayName == "" {
3447
return errors.New("if defined, display_name must not be empty string")
@@ -220,3 +233,139 @@ func validateCoderResourceReadme(rm coderResourceReadme) []error {
220233

221234
return errs
222235
}
236+
237+
func parseCoderResourceReadme(resourceType string, rm readme) (coderResourceReadme, error) {
238+
fm, body, err := separateFrontmatter(rm.rawText)
239+
if err != nil {
240+
return coderResourceReadme{}, fmt.Errorf("%q: failed to parse frontmatter: %v", rm.filePath, err)
241+
}
242+
243+
yml := coderResourceFrontmatter{}
244+
if err := yaml.Unmarshal([]byte(fm), &yml); err != nil {
245+
return coderResourceReadme{}, fmt.Errorf("%q: failed to parse: %v", rm.filePath, err)
246+
}
247+
248+
return coderResourceReadme{
249+
resourceType: resourceType,
250+
filePath: rm.filePath,
251+
body: body,
252+
frontmatter: yml,
253+
}, nil
254+
}
255+
256+
func parseCoderResourceReadmeFiles(resourceType string, rms []readme) (coderResourceReadmes, error) {
257+
resources := coderResourceReadmes(map[string]coderResourceReadme{})
258+
var yamlParsingErrs []error
259+
for _, rm := range rms {
260+
p, err := parseCoderResourceReadme(resourceType, rm)
261+
if err != nil {
262+
yamlParsingErrs = append(yamlParsingErrs, err)
263+
continue
264+
}
265+
266+
resources[p.filePath] = p
267+
}
268+
if len(yamlParsingErrs) != 0 {
269+
return nil, validationPhaseError{
270+
phase: validationPhaseReadmeParsing,
271+
errors: yamlParsingErrs,
272+
}
273+
}
274+
275+
yamlValidationErrors := []error{}
276+
for _, readme := range resources {
277+
errors := validateCoderResourceReadme(readme)
278+
if len(errors) > 0 {
279+
yamlValidationErrors = append(yamlValidationErrors, errors...)
280+
}
281+
}
282+
if len(yamlValidationErrors) != 0 {
283+
return nil, validationPhaseError{
284+
phase: validationPhaseReadmeParsing,
285+
errors: yamlValidationErrors,
286+
}
287+
}
288+
289+
return resources, nil
290+
}
291+
292+
// Todo: Need to beef up this function by grabbing each image/video URL from
293+
// the body's AST
294+
func validateCoderResourceRelativeUrls(resources coderResourceReadmes) error {
295+
return nil
296+
}
297+
298+
func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
299+
registryFiles, err := os.ReadDir(rootRegistryPath)
300+
if err != nil {
301+
return nil, err
302+
}
303+
304+
var allReadmeFiles []readme
305+
var errs []error
306+
for _, rf := range registryFiles {
307+
if !rf.IsDir() {
308+
continue
309+
}
310+
311+
resourceRootPath := path.Join(rootRegistryPath, rf.Name(), resourceType)
312+
resourceDirs, err := os.ReadDir(resourceRootPath)
313+
if err != nil {
314+
if !errors.Is(err, os.ErrNotExist) {
315+
errs = append(errs, err)
316+
}
317+
continue
318+
}
319+
320+
for _, rd := range resourceDirs {
321+
if !rd.IsDir() || rd.Name() == ".coder" {
322+
continue
323+
}
324+
325+
resourceReadmePath := path.Join(resourceRootPath, rd.Name(), "README.md")
326+
rm, err := os.ReadFile(resourceReadmePath)
327+
if err != nil {
328+
errs = append(errs, err)
329+
continue
330+
}
331+
332+
allReadmeFiles = append(allReadmeFiles, readme{
333+
filePath: resourceReadmePath,
334+
rawText: string(rm),
335+
})
336+
}
337+
}
338+
339+
if len(errs) != 0 {
340+
return nil, validationPhaseError{
341+
phase: validationPhaseFileLoad,
342+
errors: errs,
343+
}
344+
}
345+
return allReadmeFiles, nil
346+
}
347+
348+
func validateAllCoderResourceFilesOfType(resourceType string) error {
349+
if !slices.Contains(supportedResourceTypes, resourceType) {
350+
return fmt.Errorf("resource type %q is not part of supported list [%s]", resourceType, strings.Join(supportedResourceTypes, ", "))
351+
}
352+
353+
allReadmeFiles, err := aggregateCoderResourceReadmeFiles(resourceType)
354+
if err != nil {
355+
return err
356+
}
357+
358+
log.Printf("Processing %d README files\n", len(allReadmeFiles))
359+
resources, err := parseCoderResourceReadmeFiles(resourceType, allReadmeFiles)
360+
if err != nil {
361+
return err
362+
}
363+
log.Printf("Processed %d README files as valid Coder resources with type %q", len(resources), resourceType)
364+
365+
err = validateCoderResourceRelativeUrls(resources)
366+
if err != nil {
367+
return err
368+
}
369+
log.Printf("All relative URLs for %s READMEs are valid\n", resourceType)
370+
return nil
371+
}

cmd/readmevalidation/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@ func main() {
2323
os.Exit(1)
2424
}
2525

26-
errs := []error{}
26+
var errs []error
2727
err := validateAllContributorFiles()
2828
if err != nil {
2929
errs = append(errs, err)
3030
}
31+
err = validateAllCoderResourceFilesOfType("modules")
32+
if err != nil {
33+
errs = append(errs, err)
34+
}
3135

3236
if len(errs) == 0 {
37+
log.Printf("Processed all READMEs in the %q directory\n", rootRegistryPath)
3338
os.Exit(0)
3439
}
3540
for _, err := range errs {

0 commit comments

Comments
 (0)