Skip to content

Commit

Permalink
Allow custom org/repo name and apply better validation to git cmds
Browse files Browse the repository at this point in the history
  • Loading branch information
chainchad committed Nov 23, 2024
1 parent f8fbc9b commit bf12443
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 107 deletions.
33 changes: 29 additions & 4 deletions tools/gomod-required-updater/internal/updater/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ package updater

import (
"flag"
"fmt"
)

const (
DefaultRepoRemote = "origin"
DefaultBranchTrunk = "develop"
DefaultOrgName = "smartcontractkit"
DefaultRepoName = "chainlink"
)

type Config struct {
Expand All @@ -21,12 +29,12 @@ func ParseFlags(args []string, version string) (*Config, error) {
ModulesToUpdate: make([]string, 0),
}

flags.StringVar(&cfg.RepoRemote, "repo-remote", "origin", "Git remote to use")
flags.StringVar(&cfg.BranchTrunk, "branch-trunk", "develop", "Branch to get SHA from")
flags.StringVar(&cfg.RepoRemote, "repo-remote", DefaultRepoRemote, "Git remote to use")
flags.StringVar(&cfg.BranchTrunk, "branch-trunk", DefaultBranchTrunk, "Branch to get SHA from")
flags.BoolVar(&cfg.DryRun, "dry-run", false, "Preview changes without applying them")
flags.BoolVar(&cfg.ShowVersion, "version", false, "Show version information")
flags.StringVar(&cfg.OrgName, "org-name", "smartcontractkit", "GitHub organization name")
flags.StringVar(&cfg.RepoName, "repo-name", "chainlink", "GitHub repository name")
flags.StringVar(&cfg.OrgName, "org-name", DefaultOrgName, "GitHub organization name")
flags.StringVar(&cfg.RepoName, "repo-name", DefaultRepoName, "GitHub repository name")

if err := flags.Parse(args); err != nil {
return nil, err
Expand All @@ -39,5 +47,22 @@ func (c *Config) Validate() error {
if c.ShowVersion {
return nil
}

if c.RepoRemote == "" {
return fmt.Errorf("%w: repo-remote is required", ErrInvalidConfig)
}

if c.BranchTrunk == "" {
return fmt.Errorf("%w: branch-trunk is required", ErrInvalidConfig)
}

if c.OrgName == "" {
return fmt.Errorf("%w: org-name is required", ErrInvalidConfig)
}

if c.RepoName == "" {
return fmt.Errorf("%w: repo-name is required", ErrInvalidConfig)
}

return nil
}
50 changes: 44 additions & 6 deletions tools/gomod-required-updater/internal/updater/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,67 @@ import "testing"
func TestConfig_Validate(t *testing.T) {
tests := []struct {
name string
cfg *Config
config *Config
wantErr bool
}{
{
name: "valid config",
cfg: &Config{
RepoRemote: "origin",
BranchTrunk: "main",
config: &Config{
RepoRemote: DefaultRepoRemote,
BranchTrunk: DefaultBranchTrunk,
OrgName: DefaultOrgName,
RepoName: DefaultRepoName,
},
wantErr: false,
},
{
name: "version flag bypasses validation",
cfg: &Config{
config: &Config{
ShowVersion: true,
},
wantErr: false,
},
{
name: "missing repo remote",
config: &Config{
BranchTrunk: DefaultBranchTrunk,
OrgName: DefaultOrgName,
RepoName: DefaultRepoName,
},
wantErr: true,
},
{
name: "missing branch trunk",
config: &Config{
RepoRemote: DefaultRepoRemote,
OrgName: DefaultOrgName,
RepoName: DefaultRepoName,
},
wantErr: true,
},
{
name: "missing org name",
config: &Config{
RepoRemote: DefaultRepoRemote,
BranchTrunk: DefaultBranchTrunk,
RepoName: DefaultRepoName,
},
wantErr: true,
},
{
name: "missing repo name",
config: &Config{
RepoRemote: DefaultRepoRemote,
BranchTrunk: DefaultBranchTrunk,
OrgName: DefaultOrgName,
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.cfg.Validate()
err := tt.config.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
34 changes: 25 additions & 9 deletions tools/gomod-required-updater/internal/updater/interfaces.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Package updater provides functionality to update Go module versions in go.mod files.
// It specializes in handling local replace directives and maintaining consistent
// versioning across related modules.
package updater

import (
Expand All @@ -9,20 +12,33 @@ import (
"golang.org/x/mod/module"
)

// Errors
const (
gitSHALength = 12
)

// Errors that can be returned by the updater package
var (
ErrModOperation = fmt.Errorf("module operation failed")
// ErrModOperation indicates a failure in a module-related operation
ErrModOperation = fmt.Errorf("module operation failed")
// ErrInvalidConfig indicates invalid configuration parameters
ErrInvalidConfig = fmt.Errorf("invalid configuration")
)

// ModuleOperator handles module-related operations
// ModuleOperator handles Git repository operations and module version management.
// It provides functionality to retrieve Git information and manage module versions.
type ModuleOperator interface {
GetGitInfo(remote, branch string) (string, time.Time, error)
GetLatestVersion(modulePath string) (module.Version, error)
UpdateRequiredVersions(modFile *modfile.File, newVersion string) ([]string, error)
// GetGitInfo retrieves the latest commit SHA and timestamp from a Git repository
GetGitInfo(remote, branch string) (string, time.Time, error)
// GetLatestVersion constructs a pseudo-version for a module based on Git information
GetLatestVersion(modulePath string) (module.Version, error)
// UpdateRequiredVersions identifies modules that need version updates
UpdateRequiredVersions(modFile *modfile.File, newVersion string) ([]string, error)
}

// SystemOperator handles file system operations
// SystemOperator provides an interface for file system operations.
type SystemOperator interface {
ReadFile(path string) ([]byte, error)
WriteFile(path string, data []byte, perm os.FileMode) error
// ReadFile reads the entire contents of a file
ReadFile(path string) ([]byte, error)
// WriteFile writes data to a file with specific permissions
WriteFile(path string, data []byte, perm os.FileMode) error
}
47 changes: 43 additions & 4 deletions tools/gomod-required-updater/internal/updater/module_operator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package updater

import (
"context"
"fmt"
"os/exec"
"regexp"
Expand All @@ -13,6 +14,10 @@ import (

const (
majorVersionPattern = `/v\d+$`
gitTimeout = 30 * time.Second
gitTimeFormat = time.RFC3339
gitRemotePattern = `^[a-zA-Z0-9][-a-zA-Z0-9_.]*$`
gitBranchPattern = `^[a-zA-Z0-9][-a-zA-Z0-9/_]*$`
)

// getMajorVersion extracts the major version number from a module path
Expand All @@ -29,6 +34,7 @@ type moduleOperator struct {
config *Config
}

// NewModuleOperator creates a new ModuleOperator
func NewModuleOperator(config *Config) ModuleOperator {
if config.RepoRemote == "" {
config.RepoRemote = "origin"
Expand All @@ -41,29 +47,62 @@ func NewModuleOperator(config *Config) ModuleOperator {
}
}

// validateGitInput checks if the remote and branch are in the correct format
func validateGitInput(remote, branch string) error {
remoteRE := regexp.MustCompile(gitRemotePattern)
if !remoteRE.MatchString(remote) {
return fmt.Errorf("%w: invalid git remote format", ErrModOperation)
}

branchRE := regexp.MustCompile(gitBranchPattern)
if !branchRE.MatchString(branch) {
return fmt.Errorf("%w: invalid git branch format", ErrModOperation)
}
return nil
}

// GetGitInfo retrieves the latest commit SHA and timestamp from a Git repository
func (m *moduleOperator) GetGitInfo(remote, branch string) (string, time.Time, error) {
if err := validateGitInput(remote, branch); err != nil {
return "", time.Time{}, err
}

ctx, cancel := context.WithTimeout(context.Background(), gitTimeout)
defer cancel()

// Get latest SHA
cmd := exec.Command("git", "ls-remote", remote, "refs/heads/"+branch)
cmd := exec.CommandContext(ctx, "git", "ls-remote", remote, "refs/heads/"+branch)
out, err := cmd.Output()
if err != nil {
return "", time.Time{}, fmt.Errorf("%w: failed to get SHA: %v", ErrModOperation, err)
}
if len(out) == 0 {
return "", time.Time{}, fmt.Errorf("%w: no output from git ls-remote", ErrModOperation)
}
sha := strings.Split(string(out), "\t")[0]
if len(sha) == 0 {
return "", time.Time{}, fmt.Errorf("%w: empty SHA from git ls-remote", ErrModOperation)
}

// Get commit timestamp
cmd = exec.Command("git", "show", "-s", "--format=%cI", sha)
cmd = exec.CommandContext(ctx, "git", "show", "-s", "--format=%cI", sha)
out, err = cmd.Output()
if err != nil {
return "", time.Time{}, fmt.Errorf("%w: failed to get commit time: %v", ErrModOperation, err)
}
commitTime, err := time.Parse(time.RFC3339, strings.TrimSpace(string(out)))
if len(out) == 0 {
return "", time.Time{}, fmt.Errorf("%w: no output from git show", ErrModOperation)
}

commitTime, err := time.Parse(gitTimeFormat, strings.TrimSpace(string(out)))
if err != nil {
return "", time.Time{}, fmt.Errorf("%w: failed to parse commit time: %v", ErrModOperation, err)
}

return sha[:12], commitTime, nil
return sha[:gitSHALength], commitTime, nil
}

// GetLatestVersion retrieves the latest pseudo-version for a module
func (m *moduleOperator) GetLatestVersion(modulePath string) (module.Version, error) {
sha, commitTime, err := m.GetGitInfo(m.config.RepoRemote, m.config.BranchTrunk)
if err != nil {
Expand Down
25 changes: 15 additions & 10 deletions tools/gomod-required-updater/internal/updater/system_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@ package updater

import (
"os"
"path/filepath"
)

type systemOperator struct {
stdout *os.File
stderr *os.File
}
const (
defaultFileMode = 0644
)

type systemOperator struct{}

func NewSystemOperator() SystemOperator {
return &systemOperator{
stdout: os.Stdout,
stderr: os.Stderr,
}
return &systemOperator{}
}

func (s *systemOperator) ReadFile(path string) ([]byte, error) {
return os.ReadFile(path)
path = filepath.Clean(path)
return os.ReadFile(path)
}

func (s *systemOperator) WriteFile(path string, data []byte, perm os.FileMode) error {
return os.WriteFile(path, data, perm)
path = filepath.Clean(path)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
return os.WriteFile(path, data, perm)
}
Loading

0 comments on commit bf12443

Please sign in to comment.