Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

Commit

Permalink
πŸ“– Documentation and naming amendments (#44)
Browse files Browse the repository at this point in the history
* πŸ“– Add comments to most things
* 🌱 Rename program.URLProgram to program.Lock
* 🌱 Rename PName to Name
  • Loading branch information
wilsonehusin authored Mar 25, 2022
1 parent 1fe60eb commit f2ed232
Show file tree
Hide file tree
Showing 22 changed files with 124 additions and 67 deletions.
1 change: 1 addition & 0 deletions cmd/bindl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
)

func main() {
// Put any logic inside run() so that defer calls are honored.
if err := run(); err != nil {
os.Exit(1)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/bindl/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
internalversion "go.xargs.dev/bindl/internal/version"
)

// These variables are stamped by ldflags on build, configured through goreleaser.
// In the event that they aren't available on init(), the values will be populated
// through debug.ReadBuildInfo in internal/version.
var (
version = ""
commit = ""
Expand Down
2 changes: 1 addition & 1 deletion command/cli/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var BindlList = &cobra.Command{
separator = " "
}
for _, p := range l.Programs {
fmt.Printf("%s%s", p.PName, separator)
fmt.Printf("%s%s", p.Name, separator)
}
if bindlListOneline {
fmt.Println()
Expand Down
14 changes: 9 additions & 5 deletions command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ import (
// preserving the real error can be difficult with concurrent setup.
var ErrFailExec = errors.New("failed to execute command, please troubleshoot logs")

type ProgramCommandFunc func(context.Context, *config.Runtime, *program.URLProgram) error
// ProgramCommandFunc is a shorthand to command execution function signature,
// allowing the command to be run concurrently for each program.
type ProgramCommandFunc func(context.Context, *config.Runtime, *program.Lock) error

// IterateLockfilePrograms is an iterator which spawns a goroutine for each
// selected programs. Any subcommand can leverage this by honoring ProgramCommandFunc.
func IterateLockfilePrograms(ctx context.Context, conf *config.Runtime, names []string, fn ProgramCommandFunc) error {
progs := make(chan *program.URLProgram, 1)
progs := make(chan *program.Lock, 1)
errs := make(chan error, 1)

var wg sync.WaitGroup
Expand All @@ -44,7 +48,7 @@ func IterateLockfilePrograms(ctx context.Context, conf *config.Runtime, names []

for p := range progs {
wg.Add(1)
go func(p *program.URLProgram) {
go func(p *program.Lock) {
errs <- fn(ctx, conf, p)
wg.Done()
}(p)
Expand All @@ -71,7 +75,7 @@ func IterateLockfilePrograms(ctx context.Context, conf *config.Runtime, names []
return nil
}

func filterPrograms(ctx context.Context, conf *config.Runtime, names []string, progs chan<- *program.URLProgram) error {
func filterPrograms(ctx context.Context, conf *config.Runtime, names []string, progs chan<- *program.Lock) error {
defer close(progs)

l, err := config.ParseLock(conf.LockfilePath)
Expand Down Expand Up @@ -99,7 +103,7 @@ func filterPrograms(ctx context.Context, conf *config.Runtime, names []string, p
}
found := false
for _, p := range l.Programs {
if p.PName == name {
if p.Name == name {
progs <- p
found = true
break
Expand Down
34 changes: 20 additions & 14 deletions command/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import (
"go.xargs.dev/bindl/program"
)

func symlink(binDir, progDir string, p *program.URLProgram) error {
relProgDir := filepath.Join(progDir, p.PName)
func symlink(binDir, progDir string, p *program.Lock) error {
relProgDir := filepath.Join(progDir, p.Name)

// Update program atime and mtime to prevent Makefile from rebuilding
// ref: https://stackoverflow.com/a/35276091
Expand All @@ -37,28 +37,34 @@ func symlink(binDir, progDir string, p *program.URLProgram) error {
return err
}

symlinkPath := filepath.Join(binDir, p.PName)
symlinkPath := filepath.Join(binDir, p.Name)
internal.Log().Debug().
Str("program", p.PName).
Str("program", p.Name).
Dict("symlink", zerolog.Dict().
Str("ref", relProgDir).
Str("target", symlinkPath)).
Msg("symlink program")
_ = os.Remove(symlinkPath)
return os.Symlink(filepath.Join(progDir, p.PName), symlinkPath)
return os.Symlink(filepath.Join(progDir, p.Name), symlinkPath)
}

// Get implements ProgramCommandFunc, therefore needs to be concurrent-safe.
func Get(ctx context.Context, conf *config.Runtime, p *program.URLProgram) error {
// Before downloading, Get attempts to:
// - validate the existing installation
// - if it failed, redo symlink, then validate again
// - if it still fails, then attempt to download
// This is useful when a project is working on branches with different versions of
// a given program, ensuring that we only download when absolutely necessary.
func Get(ctx context.Context, conf *config.Runtime, p *program.Lock) error {
archiveName, err := p.ArchiveName(conf.OS, conf.Arch)
if err != nil {
return err
}

progDir := filepath.Join(conf.ProgDir, p.Checksums[archiveName].Binaries[p.PName]+"-"+p.PName)
progDir := filepath.Join(conf.ProgDir, p.Checksums[archiveName].Binaries[p.Name]+"-"+p.Name)
if err := Verify(ctx, conf, p); err == nil {
// Re-run symlink to renew atime and mtime, so that GNU Make will not rebuild in the future
internal.Log().Debug().Str("program", p.PName).Msg("found valid existing, re-linking")
internal.Log().Debug().Str("program", p.Name).Msg("found valid existing, re-linking")
return symlink(conf.BinDir, progDir, p)
}

Expand All @@ -70,7 +76,7 @@ func Get(ctx context.Context, conf *config.Runtime, p *program.URLProgram) error
internal.Log().Debug().Err(err).Msg("failed symlink, donwloading program")
} else {
if err := Verify(ctx, conf, p); err == nil {
internal.Log().Debug().Str("program", p.PName).Msg("re-linked to appropriate version")
internal.Log().Debug().Str("program", p.Name).Msg("re-linked to appropriate version")
// No need to return symlink() here, because we just ran symlink()
return nil
}
Expand All @@ -82,23 +88,23 @@ func Get(ctx context.Context, conf *config.Runtime, p *program.URLProgram) error
if err != nil {
return err
}
internal.Log().Debug().Str("program", p.PName).Msg("extracting archive")
bin, err := a.Extract(p.PName)
internal.Log().Debug().Str("program", p.Name).Msg("extracting archive")
bin, err := a.Extract(p.Name)
if err != nil {
return err
}
internal.Log().Debug().Str("program", p.PName).Msg("found binary")
internal.Log().Debug().Str("program", p.Name).Msg("found binary")

fullProgDir := filepath.Join(conf.BinDir, progDir)
if err = os.MkdirAll(fullProgDir, 0755); err != nil {
return err
}
binPath := filepath.Join(fullProgDir, p.PName)
binPath := filepath.Join(fullProgDir, p.Name)
err = os.WriteFile(binPath, bin, 0755)
if err != nil {
return err
}
internal.Log().Debug().Str("output", binPath).Str("program", p.PName).Msg("downloaded")
internal.Log().Debug().Str("output", binPath).Str("program", p.Name).Msg("downloaded")

return symlink(conf.BinDir, progDir, p)
}
1 change: 1 addition & 0 deletions command/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func getIgnoreEntry(binDir string, numPrefixNewlines int) string {
return prefix + "# Development and tool binaries\n" + filepath.Join(binDir, "*") + "\n"
}

// UpdateIgnoreFile appends bindl ignore entries if none exist.
func UpdateIgnoreFile(conf *config.Runtime, path string) error {
internal.Log().Debug().Str("ignore", path).Msg("attempting to update ignore file")

Expand Down
1 change: 1 addition & 0 deletions command/make.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ $(BINDL_PROGRAMS_PATH): {{ .Lockfile }} | {{ .BinDir }}/bindl

var makefileTmpl = template.Must(template.New("makefile").Parse(rawMakefileTmpl))

// GenerateMakefile writes a generated Makefile blindly (overwrites).
func GenerateMakefile(conf *config.Runtime, path string) error {
f, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
Expand Down
9 changes: 6 additions & 3 deletions command/purge.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import (
"go.xargs.dev/bindl/internal"
)

// Purge deletes downloaded binaries. By default, it only deletes
// downloaded binaries which no longer mentioned by the lockfile.
// Passing `all` will ignore lockfile check and deletes all programs.
func Purge(ctx context.Context, conf *config.Runtime, all, dryRun bool) error {
progDir := filepath.Join(conf.BinDir, conf.ProgDir)
if all {
Expand All @@ -42,10 +45,10 @@ func Purge(ctx context.Context, conf *config.Runtime, all, dryRun bool) error {
for _, p := range l.Programs {
archiveName, err := p.ArchiveName(conf.OS, conf.Arch)
if err != nil {
return fmt.Errorf("generating archive name for '%s': %w", p.PName, err)
return fmt.Errorf("generating archive name for '%s': %w", p.Name, err)
}
checksum := p.Checksums[archiveName].Binaries[p.PName]
keepPath := checksum + "-" + p.PName
checksum := p.Checksums[archiveName].Binaries[p.Name]
keepPath := checksum + "-" + p.Name
internal.Log().Debug().Str("program", keepPath).Msg("to keep")
keep[keepPath] = true
}
Expand Down
15 changes: 9 additions & 6 deletions command/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import (
"sigs.k8s.io/yaml"
)

// Sync reads the configuration file (conf.Path) and generates the lockfile (conf.LockfilePath).
// By default, it overwrites the lockfile blindly.
// If `writeToStdout` is true, then it writes to STDOUT and lockfile will not be touched.
func Sync(ctx context.Context, conf *config.Runtime, writeToStdout bool) error {
c := &config.Config{}
raw, err := os.ReadFile(conf.Path)
Expand All @@ -37,7 +40,7 @@ func Sync(ctx context.Context, conf *config.Runtime, writeToStdout bool) error {
return fmt.Errorf("parsing yaml: %w", err)
}

parsed := make(chan *program.URLProgram, 4)
parsed := make(chan *program.Lock, 4)
hasError := false

var wg sync.WaitGroup
Expand All @@ -47,10 +50,10 @@ func Sync(ctx context.Context, conf *config.Runtime, writeToStdout bool) error {
go func(prog *program.Config) {
defer wg.Done()

internal.Log().Info().Str("program", prog.PName).Msg("building program spec")
p, err := prog.URLProgram(ctx, c.Platforms)
internal.Log().Info().Str("program", prog.Name).Msg("building program spec")
p, err := prog.Lock(ctx, c.Platforms)
if err != nil {
internal.Log().Err(err).Str("program", prog.PName).Msg("parsing configuration")
internal.Log().Err(err).Str("program", prog.Name).Msg("parsing configuration")
hasError = true
return
}
Expand All @@ -63,9 +66,9 @@ func Sync(ctx context.Context, conf *config.Runtime, writeToStdout bool) error {
close(parsed)
}()

programs := []*program.URLProgram{}
programs := []*program.Lock{}
for p := range parsed {
internal.Log().Info().Str("program", p.PName).Msg("built program spec")
internal.Log().Info().Str("program", p.Name).Msg("built program spec")
programs = append(programs, p)
}

Expand Down
14 changes: 8 additions & 6 deletions command/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import (
)

// Verify implements ProgramCommandFunc, therefore needs to be concurrent-safe
func Verify(ctx context.Context, conf *config.Runtime, prog *program.URLProgram) error {
binPath := filepath.Join(conf.BinDir, prog.PName)
// It verifies existing if the exiting program is consistent with what is declared
// by the lockfile.
func Verify(ctx context.Context, conf *config.Runtime, prog *program.Lock) error {
binPath := filepath.Join(conf.BinDir, prog.Name)
f, err := os.Open(binPath)
if err != nil {
return fmt.Errorf("opening '%v': %w", binPath, err)
Expand All @@ -37,21 +39,21 @@ func Verify(ctx context.Context, conf *config.Runtime, prog *program.URLProgram)

archiveName, err := prog.ArchiveName(conf.OS, conf.Arch)
if err != nil {
return fmt.Errorf("generating filename for '%v': %w", prog.PName, err)
return fmt.Errorf("generating filename for '%v': %w", prog.Name, err)
}
expected := prog.Checksums[archiveName].Binaries[prog.PName]
expected := prog.Checksums[archiveName].Binaries[prog.Name]

c := &program.ChecksumCalculator{}
w := c.SHA256(io.Discard)
if _, err = io.Copy(w, f); err != nil {
return fmt.Errorf("reading checksum for '%v': %w", prog.PName, err)
return fmt.Errorf("reading checksum for '%v': %w", prog.Name, err)
}

if err := c.Error([]byte(expected)); err != nil {
return err
}

internal.Log().Debug().Str("program", prog.PName).Msg("validated")
internal.Log().Debug().Str("program", prog.Name).Msg("validated")

return nil
}
6 changes: 6 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ package config

import "go.xargs.dev/bindl/program"

// Config is a configuration which is used to declare a project's dependencies.
// By default, this is the content of bindl.yaml
type Config struct {
// Platforms is a matrix of OS and Arch for the binaries which
// the project would like to save checksums on.
// i.e. map["linux"][]{"amd64", "arm64"}
Platforms map[string][]string `json:"platforms"`

// Programs is a list of program specification
Programs []*program.Config `json:"programs"`
}
7 changes: 5 additions & 2 deletions config/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import (
"sigs.k8s.io/yaml"
)

// Lock is a configuration which was generated from Config.
// By default, this is the content of .bindl-lock.yaml
type Lock struct {
Updated time.Time `json:"updated"`
Programs []*program.URLProgram `json:"programs"`
Updated time.Time `json:"updated"`
Programs []*program.Lock `json:"programs"`
}

// ParseLock reads a file from path and returns *Lock
func ParseLock(path string) (*Lock, error) {
l := &Lock{}
raw, err := os.ReadFile(path)
Expand Down
3 changes: 3 additions & 0 deletions config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

package config

// Runtime is a configuration which is primarily used by command/cli.
// All variables which users can change at runtime with global effect
// can be found here.
type Runtime struct {
Path string `envconfig:"CONFIG"`
LockfilePath string `envconfig:"LOCK"`
Expand Down
2 changes: 2 additions & 0 deletions download/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"io"
)

// Downloader is an interface to download programs
type Downloader interface {
Get(ctx context.Context, url string) (io.Reader, error)
Close()
}

// LocalFile implements Downloader, which simulates download through filesystem.
type LocalFile struct {
r io.ReadCloser
}
Expand Down
1 change: 1 addition & 0 deletions download/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net/http"
)

// HTTP implements Downloader which downloads programs through net/http
type HTTP struct {
response *http.Response
}
Expand Down
5 changes: 5 additions & 0 deletions program/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (
archiveZipSuffix = ".zip"
)

// BinaryChecksum retrieves checksum value of binary in archive.
// Returned value is base64-encoded []byte.
// TODO: maybe we should just return string(checksumSHA256(binary))?
func (a *Archive) BinaryChecksum(binaryName string) ([]byte, error) {
binary, err := a.extractBinaryNoChecksum(binaryName)
if err != nil {
Expand All @@ -42,6 +45,8 @@ func (a *Archive) BinaryChecksum(binaryName string) ([]byte, error) {
return checksumSHA256(binary), nil
}

// Extract returns the binary data from archive with checksum guarantee.
// That is, if checksum fails, then the binary will not be returned.
func (a *Archive) Extract(binaryName string) ([]byte, error) {
binary, err := a.extractBinaryNoChecksum(binaryName)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions program/checksum.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ func assertChecksumSHA256(binary []byte, expect []byte) error {
return nil
}

// ChecksumCalculator calculates checksum by passing in data through io.Writer
type ChecksumCalculator struct {
h hash.Hash
}

// SHA256 takes in io.Writer and returns a io.Writer which writes to the
// original io.Writer as well as the hash calculator
func (c *ChecksumCalculator) SHA256(dst io.Writer) io.Writer {
h := sha256.New()
c.h = h
Expand Down
Loading

0 comments on commit f2ed232

Please sign in to comment.