From f2ed2324b4ffbe0cdf71b0a8624ecaaea32b4629 Mon Sep 17 00:00:00 2001 From: "Wilson E. Husin" <14004487+wilsonehusin@users.noreply.github.com> Date: Thu, 24 Mar 2022 18:43:56 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=96=20Documentation=20and=20naming=20a?= =?UTF-8?q?mendments=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📖 Add comments to most things * 🌱 Rename program.URLProgram to program.Lock * 🌱 Rename PName to Name --- cmd/bindl/main.go | 1 + cmd/bindl/version.go | 3 ++ command/cli/list.go | 2 +- command/command.go | 14 +++++--- command/get.go | 34 +++++++++++-------- command/ignore.go | 1 + command/make.go | 1 + command/purge.go | 9 +++-- command/sync.go | 15 ++++---- command/verify.go | 14 ++++---- config/config.go | 6 ++++ config/lock.go | 7 ++-- config/runtime.go | 3 ++ download/download.go | 2 ++ download/http.go | 1 + program/archive.go | 5 +++ program/checksum.go | 3 ++ program/{url.go => lock.go} | 33 +++++++++++------- ...url_online_test.go => lock_online_test.go} | 2 +- program/{url_test.go => lock_test.go} | 8 ++--- program/program.go | 25 +++++++------- program/program_online_test.go | 2 +- 22 files changed, 124 insertions(+), 67 deletions(-) rename program/{url.go => lock.go} (75%) rename program/{url_online_test.go => lock_online_test.go} (97%) rename program/{url_test.go => lock_test.go} (95%) diff --git a/cmd/bindl/main.go b/cmd/bindl/main.go index dba6018..cedc5ba 100644 --- a/cmd/bindl/main.go +++ b/cmd/bindl/main.go @@ -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) } diff --git a/cmd/bindl/version.go b/cmd/bindl/version.go index 04ea7a5..fe63634 100644 --- a/cmd/bindl/version.go +++ b/cmd/bindl/version.go @@ -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 = "" diff --git a/command/cli/list.go b/command/cli/list.go index ec7b0e8..d17f5f8 100644 --- a/command/cli/list.go +++ b/command/cli/list.go @@ -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() diff --git a/command/command.go b/command/command.go index 06446c9..076912b 100644 --- a/command/command.go +++ b/command/command.go @@ -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 @@ -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) @@ -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) @@ -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 diff --git a/command/get.go b/command/get.go index fddb962..6287a72 100644 --- a/command/get.go +++ b/command/get.go @@ -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 @@ -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) } @@ -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 } @@ -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) } diff --git a/command/ignore.go b/command/ignore.go index 2775c7c..cb2f20c 100644 --- a/command/ignore.go +++ b/command/ignore.go @@ -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") diff --git a/command/make.go b/command/make.go index a420726..7f13ef6 100644 --- a/command/make.go +++ b/command/make.go @@ -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 { diff --git a/command/purge.go b/command/purge.go index 4e72122..33c7b92 100644 --- a/command/purge.go +++ b/command/purge.go @@ -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 { @@ -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 } diff --git a/command/sync.go b/command/sync.go index b5962a1..c683044 100644 --- a/command/sync.go +++ b/command/sync.go @@ -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) @@ -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 @@ -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 } @@ -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) } diff --git a/command/verify.go b/command/verify.go index 4777360..9998039 100644 --- a/command/verify.go +++ b/command/verify.go @@ -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) @@ -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 } diff --git a/config/config.go b/config/config.go index a7e96f0..689db73 100644 --- a/config/config.go +++ b/config/config.go @@ -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"` } diff --git a/config/lock.go b/config/lock.go index 6c63a4a..b372757 100644 --- a/config/lock.go +++ b/config/lock.go @@ -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) diff --git a/config/runtime.go b/config/runtime.go index d5e1e61..5c085f8 100644 --- a/config/runtime.go +++ b/config/runtime.go @@ -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"` diff --git a/download/download.go b/download/download.go index 61b5e22..0f2717b 100644 --- a/download/download.go +++ b/download/download.go @@ -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 } diff --git a/download/http.go b/download/http.go index f286320..e14fe56 100644 --- a/download/http.go +++ b/download/http.go @@ -21,6 +21,7 @@ import ( "net/http" ) +// HTTP implements Downloader which downloads programs through net/http type HTTP struct { response *http.Response } diff --git a/program/archive.go b/program/archive.go index 146a1b8..6760261 100644 --- a/program/archive.go +++ b/program/archive.go @@ -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 { @@ -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 { diff --git a/program/checksum.go b/program/checksum.go index 0ffd726..5b72160 100644 --- a/program/checksum.go +++ b/program/checksum.go @@ -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 diff --git a/program/url.go b/program/lock.go similarity index 75% rename from program/url.go rename to program/lock.go index 9689d68..aa4e405 100644 --- a/program/url.go +++ b/program/lock.go @@ -27,7 +27,9 @@ import ( "go.xargs.dev/bindl/internal" ) -type URLProgram struct { +// Lock is a configuration used by lockfile to explicitly state the +// expected validations of each program. +type Lock struct { Base Checksums map[string]*ArchiveChecksum `json:"checksums,omitempty"` @@ -35,10 +37,10 @@ type URLProgram struct { URLTemplate string `json:"url"` } -func NewURLProgram(c *Config) (*URLProgram, error) { - p := &URLProgram{ +func NewLock(c *Config) (*Lock, error) { + p := &Lock{ Base: Base{ - PName: c.PName, + Name: c.Name, Version: c.Version, Overlay: c.Overlay, }, @@ -57,7 +59,8 @@ func NewURLProgram(c *Config) (*URLProgram, error) { // TOFU: Trust on first use -- should only be run first time a program was added to // the lockfile. Collecting binary checksums by extracting archives. -func (p *URLProgram) collectBinaryChecksum(ctx context.Context, platforms map[string][]string) error { +// TODO: Use the values presented in SBOM when available. +func (p *Lock) collectBinaryChecksum(ctx context.Context, platforms map[string][]string) error { var wg sync.WaitGroup hasError := false @@ -70,16 +73,16 @@ func (p *URLProgram) collectBinaryChecksum(ctx context.Context, platforms map[st a, err := p.DownloadArchive(ctx, &download.HTTP{}, os, arch) if err != nil { - internal.ErrorMsg(fmt.Errorf("downloading archive for '%s' in %s/%s: %w", p.PName, os, arch, err)) + internal.ErrorMsg(fmt.Errorf("downloading archive for '%s' in %s/%s: %w", p.Name, os, arch, err)) return } - b, err := a.BinaryChecksum(p.PName) + b, err := a.BinaryChecksum(p.Name) if err != nil { - internal.ErrorMsg(fmt.Errorf("calculating binary checksum for '%s' in %s/%s: %w", p.PName, os, arch, err)) + internal.ErrorMsg(fmt.Errorf("calculating binary checksum for '%s' in %s/%s: %w", p.Name, os, arch, err)) return } - p.Checksums[a.Name].Binaries[p.PName] = string(b) + p.Checksums[a.Name].Binaries[p.Name] = string(b) }(os, arch) } } @@ -103,7 +106,10 @@ func (p *URLProgram) collectBinaryChecksum(ctx context.Context, platforms map[st return nil } -func (p *URLProgram) ArchiveName(os, arch string) (string, error) { +// ArchiveName returns the archive name with OS and Arch interpolated +// if necessary, i.e. someprogram-linux-amd64.tar.gz. +// This reads from URL and assumes that contains the archive name. +func (p *Lock) ArchiveName(os, arch string) (string, error) { url, err := p.URL(os, arch) if err != nil { return "", err @@ -112,7 +118,8 @@ func (p *URLProgram) ArchiveName(os, arch string) (string, error) { return filepath.Base(url), nil } -func (p *URLProgram) URL(goOS, goArch string) (string, error) { +// URL returns the download URL with variables interpolated as necessary. +func (p *Lock) URL(goOS, goArch string) (string, error) { t, err := template.New("url").Parse(p.URLTemplate) if err != nil { return "", err @@ -125,7 +132,9 @@ func (p *URLProgram) URL(goOS, goArch string) (string, error) { return buf.String(), nil } -func (p *URLProgram) DownloadArchive(ctx context.Context, d download.Downloader, goOS, goArch string) (*Archive, error) { +// DownloadArchive returns Archive which has the archive data in-memory, with guarantees +// on archive checksum. That is, if checksum fails, no data will be made available to caller. +func (p *Lock) DownloadArchive(ctx context.Context, d download.Downloader, goOS, goArch string) (*Archive, error) { url, err := p.URL(goOS, goArch) if err != nil { return nil, fmt.Errorf("generating URL for download: %w", err) diff --git a/program/url_online_test.go b/program/lock_online_test.go similarity index 97% rename from program/url_online_test.go rename to program/lock_online_test.go index 6c15f54..8fcc1c7 100644 --- a/program/url_online_test.go +++ b/program/lock_online_test.go @@ -29,7 +29,7 @@ func TestIntegrationDownloadArchy(t *testing.T) { if testing.Short() { t.Skipf("skipping integration test in short mode") } - p := program.URLProgram{} + p := program.Lock{} err := yaml.Unmarshal([]byte(rawArchyLockManifest), &p) if err != nil { t.Fatalf("failed when expecting pass: %v", err) diff --git a/program/url_test.go b/program/lock_test.go similarity index 95% rename from program/url_test.go rename to program/lock_test.go index 2b62596..2fcc592 100644 --- a/program/url_test.go +++ b/program/lock_test.go @@ -26,13 +26,13 @@ import ( ) func TestProgramChecksumsYAMLUnmarshalJSON(t *testing.T) { - p := program.URLProgram{} + p := program.Lock{} err := yaml.Unmarshal([]byte(rawProgramManifest), &p) if err != nil { t.Fatalf("failed when expecting pass: %v", err) } - assert(t, "myprogram", p.PName) + assert(t, "myprogram", p.Name) assert(t, "0.1.0-rc.2", p.Version) cs := p.Checksums["myprogram-Linux-x86_64.tar.gz"] @@ -41,7 +41,7 @@ func TestProgramChecksumsYAMLUnmarshalJSON(t *testing.T) { } func TestProgramURL(t *testing.T) { - p := &program.URLProgram{} + p := &program.Lock{} err := yaml.Unmarshal([]byte(rawProgramManifest), p) if err != nil { t.Fatalf("failed when expecting pass: %v", err) @@ -54,7 +54,7 @@ func TestProgramURL(t *testing.T) { } func TestDownloadChecksum(t *testing.T) { - p := &program.URLProgram{} + p := &program.Lock{} err := yaml.Unmarshal([]byte(rawProgramManifest), p) if err != nil { t.Fatalf("failed when expecting pass: %v", err) diff --git a/program/program.go b/program/program.go index 2da1d6f..f154a6e 100644 --- a/program/program.go +++ b/program/program.go @@ -24,19 +24,18 @@ import ( "go.xargs.dev/bindl/internal" ) +// Base is a minimal structure which exists in every program variations type Base struct { Overlay map[string]map[string]string `json:"overlay,omitempty"` - PName string `json:"name"` + Name string `json:"name"` Version string `json:"version"` } -func (b *Base) Name() string { - return b.PName -} - +// Vars returns a map of variables to be used in templates, +// with overlays applied. func (b *Base) Vars(goOS, goArch string) map[string]string { vars := map[string]string{ - "Name": b.PName, + "Name": b.Name, "Version": b.Version, "OS": goOS, @@ -54,6 +53,7 @@ func (b *Base) Vars(goOS, goArch string) map[string]string { return vars } +// Config is a program declaration in configuration file (default: bindl.yaml) type Config struct { Base @@ -62,15 +62,16 @@ type Config struct { Path string `json:"path"` } -func (c *Config) URLProgram(ctx context.Context, platforms map[string][]string) (*URLProgram, error) { +// Lock converts current configuration to Lock, which is the format used by lockfile. +func (c *Config) Lock(ctx context.Context, platforms map[string][]string) (*Lock, error) { if err := c.loadChecksum(platforms); err != nil { return nil, fmt.Errorf("loading checksums: %w", err) } - var p *URLProgram + var p *Lock var err error switch c.Provider { case "url": - p, err = NewURLProgram(c) + p, err = NewLock(c) if err != nil { return nil, err } @@ -105,7 +106,7 @@ func (c *Config) loadChecksum(platforms map[string][]string) error { return fmt.Errorf("retrieving checksum for %s/%s: %w", os, arch, err) } internal.Log().Debug(). - Str("program", c.PName). + Str("program", c.Name). Str("platform", os+"/"+arch). Str("url", buf.String()). Msg("generate checksum url") @@ -126,7 +127,7 @@ func (c *Config) loadChecksum(platforms map[string][]string) error { return fmt.Errorf("reading checksums: %w", err) } for f, cs := range data { - internal.Log().Debug().Str("program", c.PName).Str(f, cs).Msg("retrieved checksum") + internal.Log().Debug().Str("program", c.Name).Str(f, cs).Msg("retrieved checksum") checksums[f] = cs } } @@ -134,7 +135,7 @@ func (c *Config) loadChecksum(platforms map[string][]string) error { // Override the downloaded result with any explicitly specified checksum for f, cs := range c.Checksums { if f != "_src" { - internal.Log().Warn().Str("program", c.PName).Str(f, cs).Msg("overwrite retrieved checksum") + internal.Log().Warn().Str("program", c.Name).Str(f, cs).Msg("overwrite retrieved checksum") } checksums[f] = cs } diff --git a/program/program_online_test.go b/program/program_online_test.go index 58a20f2..8390b9b 100644 --- a/program/program_online_test.go +++ b/program/program_online_test.go @@ -33,7 +33,7 @@ func TestIntegrationConvertProgramConfigURLProviderToURL(t *testing.T) { t.Fatal(err) } - u, err := c.URLProgram(context.Background(), map[string][]string{ + u, err := c.Lock(context.Background(), map[string][]string{ "linux": []string{"amd64"}, "darwin": []string{"arm64"}, })