chore: add directory validation in separate file

This commit is contained in:
Michael Smith 2025-04-15 16:02:33 +00:00
parent 94ca584b9e
commit 18680d0a15
5 changed files with 147 additions and 35 deletions

View File

@ -194,10 +194,6 @@ func validateContributorAvatarURL(avatarURL *string) []error {
return problems
}
func addFilePathToError(filePath string, err error) error {
return fmt.Errorf("%q: %v", filePath, err)
}
func validateContributorProfile(yml contributorProfile) []error {
allProblems := []error{}
@ -313,7 +309,6 @@ func aggregateContributorReadmeFiles() ([]readme, error) {
for _, e := range dirEntries {
dirPath := path.Join(rootRegistryPath, e.Name())
if !e.IsDir() {
problems = append(problems, fmt.Errorf("detected non-directory file %q at base of main Registry directory", dirPath))
continue
}
@ -331,7 +326,7 @@ func aggregateContributorReadmeFiles() ([]readme, error) {
if len(problems) != 0 {
return nil, validationPhaseError{
phase: validationPhaseFilesystemRead,
phase: validationPhaseFileLoad,
errors: problems,
}
}

View File

@ -0,0 +1,28 @@
package main
import "fmt"
var _ error = validationPhaseError{}
// validationPhaseError represents an error that occurred during a specific
// phase of README validation. It should be used to collect ALL validation
// errors that happened during a specific phase, rather than the first one
// encountered.
type validationPhaseError struct {
phase validationPhase
errors []error
}
func (vpe validationPhaseError) Error() string {
msg := fmt.Sprintf("Error during %q phase of README validation:", vpe.phase.String())
for _, e := range vpe.errors {
msg += fmt.Sprintf("\n- %v", e)
}
msg += "\n"
return msg
}
func addFilePathToError(filePath string, err error) error {
return fmt.Errorf("%q: %v", filePath, err)
}

View File

@ -55,10 +55,17 @@ func main() {
} else {
log.Println("Provided API token does not belong to a Coder employee. Some README validation steps will be skipped compared to when they run in CI.")
}
fmt.Printf("actor %q is %s\n", actorUsername, actorOrgStatus.String())
fmt.Printf("Script GitHub actor %q has Coder organization status %q\n", actorUsername, actorOrgStatus.String())
log.Println("Starting README validation")
// Validate file structure of main README directory
err = validateRepoStructure()
if err != nil {
log.Panic(err)
}
// Validate contributor README files
allReadmeFiles, err := aggregateContributorReadmeFiles()
if err != nil {
log.Panic(err)
@ -75,4 +82,8 @@ func main() {
}
log.Println("All relative URLs for READMEs are valid")
log.Printf("Processed all READMEs in the %q directory\n", rootRegistryPath)
// Validate modules
// Validate templates
}

View File

@ -3,7 +3,6 @@ package main
import (
"bufio"
"errors"
"fmt"
"strings"
)
@ -72,12 +71,15 @@ func separateFrontmatter(readmeText string) (string, string, error) {
type validationPhase int
const (
// validationPhaseFilesystemRead indicates when a README file is being read
// from the file system
validationPhaseFilesystemRead validationPhase = iota
//
validationPhaseStructureValidation validationPhase = iota
// validationPhaseReadmeParsing indicates when a README's frontmatter is being
// parsed as YAML. This phase does not include YAML validation.
// validationPhaseFileLoad indicates when a README file is being read from
// the file system
validationPhaseFileLoad
// validationPhaseReadmeParsing indicates when a README's frontmatter is
// being parsed as YAML. This phase does not include YAML validation.
validationPhaseReadmeParsing
// validationPhaseReadmeValidation indicates when a README's frontmatter is
@ -92,7 +94,7 @@ const (
func (p validationPhase) String() string {
switch p {
case validationPhaseFilesystemRead:
case validationPhaseFileLoad:
return "Filesystem reading"
case validationPhaseReadmeParsing:
return "README parsing"
@ -104,24 +106,3 @@ func (p validationPhase) String() string {
return "Unknown validation phase"
}
}
var _ error = validationPhaseError{}
// validationPhaseError represents an error that occurred during a specific
// phase of README validation. It should be used to collect ALL validation
// errors that happened during a specific phase, rather than the first one
// encountered.
type validationPhaseError struct {
phase validationPhase
errors []error
}
func (vpe validationPhaseError) Error() string {
msg := fmt.Sprintf("Error during %q phase of README validation:", vpe.phase.String())
for _, e := range vpe.errors {
msg += fmt.Sprintf("\n- %v", e)
}
msg += "\n"
return msg
}

View File

@ -0,0 +1,97 @@
package main
import (
"errors"
"fmt"
"os"
"path"
)
func validateCoderResourceDirectory(directoryPath string) []error {
errs := []error{}
dir, err := os.Stat(directoryPath)
if err != nil {
// It's valid for a specific resource directory not to exist. It's just
// that if it does exist, it must follow specific rules
if !errors.Is(err, os.ErrNotExist) {
errs = append(errs, addFilePathToError(directoryPath, err))
}
return errs
}
if !dir.IsDir() {
errs = append(errs, fmt.Errorf("%q: path is not a directory", directoryPath))
return errs
}
files, err := os.ReadDir(directoryPath)
if err != nil {
errs = append(errs, fmt.Errorf("%q: %v", directoryPath, err))
return errs
}
for _, f := range files {
if !f.IsDir() {
continue
}
resourceReadmePath := path.Join(directoryPath, f.Name(), "README.md")
_, err := os.Stat(resourceReadmePath)
if err == nil {
continue
}
if errors.Is(err, os.ErrNotExist) {
errs = append(errs, fmt.Errorf("%q: README file does not exist", resourceReadmePath))
} else {
errs = append(errs, addFilePathToError(resourceReadmePath, err))
}
}
return errs
}
func validateRegistryDirectory() []error {
dirEntries, err := os.ReadDir(rootRegistryPath)
if err != nil {
return []error{err}
}
problems := []error{}
for _, e := range dirEntries {
dirPath := path.Join(rootRegistryPath, e.Name())
if !e.IsDir() {
problems = append(problems, fmt.Errorf("detected non-directory file %q at base of main Registry directory", dirPath))
continue
}
readmePath := path.Join(dirPath, "README.md")
_, err := os.Stat(readmePath)
if err != nil {
problems = append(problems, err)
}
modulesPath := path.Join(dirPath, "modules")
if errs := validateCoderResourceDirectory(modulesPath); len(errs) != 0 {
problems = append(problems, errs...)
}
templatesPath := path.Join(dirPath, "templates")
if errs := validateCoderResourceDirectory(templatesPath); len(errs) != 0 {
problems = append(problems, errs...)
}
}
return problems
}
func validateRepoStructure() error {
errs := validateRegistryDirectory()
if len(errs) != 0 {
return validationPhaseError{
phase: validationPhaseFileLoad,
errors: errs,
}
}
return nil
}