tools/mod-validator/main.go (138 lines of code) (raw):

package main /* Go Module Version Validator This tool validates that Go module dependencies comply with pinned version directives specified in go.mod comments. It helps enforce consistent dependency versioning across projects by detecting when actual versions don't match pinned requirements. Usage: go-mod-validator --file /path/to/go.mod Directive format in go.mod: // +gitaly pinVersion github.com/example/module v1.2.3 // Reason for pinning this specific version require github.com/example/module v1.1.0 When a mismatch is found, the tool will: 1. Display all dependencies that don't match their pinned versions 2. Show the expected version, current version, and Reason 3. Exit with status code 1 This is particularly useful in CI/CD pipelines to prevent accidental dependency updates that might introduce compatibility issues or security vulnerabilities. */ import ( "context" "fmt" "io" "os" "regexp" "strings" "github.com/urfave/cli/v3" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) // Regular expression to match Gitaly pin version directives // Format: // +gitaly pinVersion {module_path} {version} var pinVersionDirective = regexp.MustCompile(`\+gitaly pinVersion (.+) (.+)`) // pinnedDependency represents a dependency with pinned version requirements type pinnedDependency struct { path string // Module path found string // Version found in go.mod pinned string // Version that should be used according to directive reason string // Reason for pinning as specified in comments } // Configuration options for the application type config struct { modFilePath string } func main() { // Initialize logger var cfg config // Set up CLI application using urfave/cli app := &cli.Command{ Name: "go-mod-validator", Usage: "Validate go.mod files for version pinning compliance", Version: "1.0.0", Authors: []any{"Gitaly Team"}, Flags: []cli.Flag{ &cli.StringFlag{ Name: "file", Aliases: []string{"f"}, Usage: "Path to go.mod file", Destination: &cfg.modFilePath, Required: true, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { exitCode := runValidator(cfg, cmd.Writer, cmd.ErrWriter) if exitCode != 0 { // Exit with the code without showing an error message os.Exit(exitCode) } return nil }, } // Run the application if err := app.Run(context.Background(), os.Args); err != nil { fmt.Fprint(os.Stderr, err.Error()) } } // runValidator validates the specified go.mod file and outputs the results // Returns 0 for success, 1 for validation failures func runValidator(cfg config, stdout io.Writer, stderr io.Writer) int { // Read and parse go.mod file data, err := os.ReadFile(cfg.modFilePath) if err != nil { fmt.Fprintf(stderr, "failed to read go.mod file %s: %v", cfg.modFilePath, err) return 1 } // Parse the go.mod file f, err := modfile.Parse(cfg.modFilePath, data, nil) if err != nil { fmt.Fprintf(stderr, "failed to parse go.mod file %s: %v", cfg.modFilePath, err) return 1 } // Extract Gitaly directives dependencies, err := extractGitalyDirectives(f) if err != nil { fmt.Fprintf(stderr, "failed to validate directives in go.mod %s: %v", cfg.modFilePath, err) return 1 } // Handle results if len(dependencies) == 0 { // No mismatches found return 0 } // Output results writeOutput(dependencies, stdout) // Exit with code 1 for mismatches return 1 } // extractGitalyDirectives parses go.mod file for Gitaly pinning directives // and returns any dependencies that don't match their pinned versions func extractGitalyDirectives(f *modfile.File) ([]pinnedDependency, error) { var dependencies []pinnedDependency analyseDependencies := func(syntax *modfile.Line, mod module.Version) error { comments := syntax.Before if len(comments) == 0 { return nil } // Extract and validate the directive directive := strings.TrimSpace(strings.TrimPrefix(comments[0].Token, "//")) matches := pinVersionDirective.FindStringSubmatch(directive) if len(matches) != 3 { return nil } // Parse pinned module information pinned := module.Version{ Path: strings.TrimSpace(matches[1]), Version: matches[2], } // Verify module path matches if mod.Path != pinned.Path { return fmt.Errorf( "mismatched pinned dependency path: expected %s, found %s", mod.Path, pinned.Path, ) } // Check if version matches pinning directive if mod.Version != pinned.Version { // Extract reason from comments (if any) var reason []string for i := 1; i < len(comments); i++ { reasonLine := strings.TrimSpace(strings.TrimPrefix(comments[i].Token, "//")) reason = append(reason, reasonLine) } // Add to list of mismatched dependencies dependencies = append(dependencies, pinnedDependency{ path: mod.Path, found: mod.Version, pinned: pinned.Version, reason: strings.Join(reason, "\n"), }) } return nil } for _, require := range f.Require { if err := analyseDependencies(require.Syntax, require.Mod); err != nil { return nil, err } } for _, replace := range f.Replace { if err := analyseDependencies(replace.Syntax, replace.New); err != nil { return nil, err } } return dependencies, nil } // writeOutput writes formatted text output for mismatched dependencies func writeOutput(deps []pinnedDependency, out io.Writer) { fmt.Fprintln(out, "❌ VERSION PINNING VALIDATION FAILED") fmt.Fprintf(out, " %d dependencies violate version pinning requirements:\n", len(deps)) for i, dep := range deps { fmt.Fprintf(out, "\n[%d/%d] Module: %s\n", i+1, len(deps), dep.path) fmt.Fprintf(out, " ✓ Expected version: %s\n", dep.pinned) fmt.Fprintf(out, " ✗ Current version: %s\n", dep.found) if dep.reason != "" { fmt.Fprintf(out, "\n Reason:\n %s\n", strings.ReplaceAll(dep.reason, "\n", "\n ")) } else { fmt.Fprintln(out, "\n No rationale provided for pinning requirement") } // Print resolution suggestion fmt.Fprintf(out, "\n To resolve: Update dependency in go.mod to version %s\n", dep.pinned) } }