diff --git a/packer/plugin-getter/plugins.go b/packer/plugin-getter/plugins.go index c36b9ede4e6..138bb084d6d 100644 --- a/packer/plugin-getter/plugins.go +++ b/packer/plugin-getter/plugins.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "io/fs" @@ -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 '' %s\n", pe.Source) + fmt.Fprintf(&s, "\npacker plugins install --path '' %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-" @@ -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 @@ -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) @@ -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