Skip to content

Commit

Permalink
Add ConintuableInstallError for continuing installation on version mi…
Browse files Browse the repository at this point in the history
…smatches
  • Loading branch information
nywilken authored and lbajolet-hashicorp committed May 8, 2024
1 parent 2e8bdb5 commit 11dc684
Showing 1 changed file with 55 additions and 35 deletions.
90 changes: 55 additions & 35 deletions packer/plugin-getter/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
Expand Down Expand Up @@ -100,19 +101,28 @@ type PrereleaseInstallError struct {
Source string
}

func (pe *PrereleaseInstallError) Error() string {
func (e *PrereleaseInstallError) Error() string {
s := strings.Builder{}
s.WriteString("error:\n")
fmt.Fprintf(&s, "Remote installation of the plugin version %s is unsupported.\n", pe.ReportedVersion)
fmt.Fprintf(&s, "Error: Remote installation of the plugin version %s is unsupported.\n", e.ReportedVersion)

if pe.RequestedVersion != pe.ReportedVersion {
fmt.Fprintf(&s, "This is likely an upstream issue with the %s release, which should be reported.\n", pe.RequestedVersion)
if e.RequestedVersion != e.ReportedVersion {
fmt.Fprintf(&s, "This is likely an upstream issue with the %s release, which should be reported.\n", e.RequestedVersion)
}
s.WriteString("If you require this specific version of the plugin, download the binary and install it manually.\n")
fmt.Fprintf(&s, "\npacker plugins install --path '<plugin_binary>' %s\n", pe.Source)
fmt.Fprintf(&s, "\npacker plugins install --path '<plugin_binary>' %s\n", e.Source)
return s.String()
}

// ContinuableInstallError describe a failed getter install that is
// capable of falling back to next available version.
type ContinuableInstallError struct {
Err error
}

func (e *ContinuableInstallError) Error() string {
return fmt.Sprintf("Continuing to next available version: %s", e.Err)
}

func (pr Requirement) FilenamePrefix() string {
if pr.Identifier == nil {
return "packer-plugin-"
Expand Down Expand Up @@ -876,36 +886,13 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
}
tmpOutputFile.Close()

desc, err := GetPluginDescription(tmpBinFileName)
if err != nil {
err := fmt.Errorf("failed to describe plugin binary %q: %s", tmpBinFileName, err)
errs = multierror.Append(errs, err)
continue
}
descVersion, err := goversion.NewSemver(desc.Version)
if err != nil {
err := fmt.Errorf("invalid self-reported version %q: %s", desc.Version, err)
if err := checkVersion(tmpBinFileName, pr.Identifier.String(), version); err != nil {
errs = multierror.Append(errs, err)
continue
}
if descVersion.Core().Compare(version.Core()) != 0 {
err := fmt.Errorf("binary reported version (%q) is different from the expected %q, skipping", desc.Version, version.String())
errs = multierror.Append(errs, err)
continue
}
// Since only final releases can be installed remotely, a non-empty prerelease version
// means something's not right on the release, as it should report a final version.
//
// Therefore to avoid surprises (and avoid being able to install a version that
// cannot be loaded), we error here, and advise users to manually install the plugin if they
// need it.
if descVersion.Prerelease() != "" {
err := PrereleaseInstallError{
Source: pr.Identifier.String(),
RequestedVersion: version.String(),
ReportedVersion: desc.Version,
var continuableError *ContinuableInstallError
if errors.As(err, &continuableError) {
continue
}
return nil, &err
return nil, errs
}

// create directories if need be
Expand All @@ -915,7 +902,6 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
log.Printf("[TRACE] %s", err.Error())
return nil, errs
}

outputFile, err := os.OpenFile(outputFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
err = fmt.Errorf("could not create final plugin binary file: %w", err)
Expand Down Expand Up @@ -974,6 +960,40 @@ func GetPluginDescription(pluginPath string) (pluginsdk.SetDescription, error) {
return desc, err
}

// checkVersion checks the described version of a plugin binary against the requested version constriant.
// A ContinuableInstallError is returned upon a version mismatch to indicate that the caller should try the next
// available version. A PrereleaseInstallError is returned to indicate an unsupported version install.
func checkVersion(binPath string, identifier string, version *goversion.Version) error {
desc, err := GetPluginDescription(binPath)
if err != nil {
err := fmt.Errorf("failed to describe plugin binary %q: %s", binPath, err)
return &ContinuableInstallError{Err: err}
}
descVersion, err := goversion.NewSemver(desc.Version)
if err != nil {
err := fmt.Errorf("invalid self-reported version %q: %s", desc.Version, err)
return &ContinuableInstallError{Err: err}
}
if descVersion.Core().Compare(version.Core()) != 0 {
err := fmt.Errorf("binary reported version (%q) is different from the expected %q, skipping", desc.Version, version.String())
return &ContinuableInstallError{Err: err}
}
// Since only final releases can be installed remotely, a non-empty prerelease version
// means something's not right on the release, as it should report a final version.
//
// Therefore to avoid surprises (and avoid being able to install a version that
// cannot be loaded), we error here, and advise users to manually install the plugin if they
// need it.
if descVersion.Prerelease() != "" {
return &PrereleaseInstallError{
Source: identifier,
RequestedVersion: version.String(),
ReportedVersion: desc.Version,
}
}
return nil
}

func init() {
var err error
// Should never error if both components are set
Expand Down

0 comments on commit 11dc684

Please sign in to comment.