diff --git a/internal/download/downloader.go b/internal/download/downloader.go index 4a4d1fea..07c1eeb0 100644 --- a/internal/download/downloader.go +++ b/internal/download/downloader.go @@ -34,7 +34,7 @@ import ( func download(url string, verifier Verifier, fetcher Fetcher) (io.ReaderAt, int64, error) { body, err := fetcher.Get(url) if err != nil { - return nil, 0, errors.Wrapf(err, "failed to obtain plugin archive") + return nil, 0, errors.Wrapf(err, "failed to obtain plugin") } defer body.Close() @@ -161,6 +161,25 @@ func extractTARGZ(targetDir string, at io.ReaderAt, size int64) error { return nil } +// Dowloads the given binary to the target directory +func downloadBinary(targetDir string, read io.ReaderAt, size int64) error { + klog.V(4).Infof("Downloading binary to %q", targetDir) + + in := io.NewSectionReader(read, 0, size) + buf := make([]byte, size) + + if _, err := in.Read(buf); err != nil { + return errors.Wrap(err, "failed to read binary") + } + + if err := os.WriteFile(filepath.Join(targetDir, "binary"), buf, 0o755); err != nil { + return errors.Wrap(err, "failed to write binary") + } + + klog.V(4).Infof("download of binary complete to %s complete", targetDir) + return nil +} + func suspiciousPath(path string) error { if strings.Contains(path, "..") { return errors.Errorf("refusing to unpack archive with suspicious entry %q", path) @@ -193,6 +212,7 @@ type extractor func(targetDir string, read io.ReaderAt, size int64) error var defaultExtractors = map[string]extractor{ "application/zip": extractZIP, "application/x-gzip": extractTARGZ, + "application/octet-stream": downloadBinary, } func extractArchive(dst string, at io.ReaderAt, size int64) error { @@ -203,7 +223,7 @@ func extractArchive(dst string, at io.ReaderAt, size int64) error { klog.V(4).Infof("detected %q file type", t) exf, ok := defaultExtractors[t] if !ok { - return errors.Errorf("mime type %q for archive file is not a supported archive format", t) + return errors.Errorf("mime type %q for file is not a supported format", t) } return errors.Wrap(exf(dst, at, size), "failed to extract file") } diff --git a/internal/installation/install.go b/internal/installation/install.go index 03b6b68b..8c5af2b8 100644 --- a/internal/installation/install.go +++ b/internal/installation/install.go @@ -15,6 +15,7 @@ package installation import ( + "io" "os" "path/filepath" "runtime" @@ -108,6 +109,16 @@ func install(op installOperation, opts InstallOpts) error { return errors.Wrap(err, "failed to unpack into staging dir") } + // Download Liscense file from given URI and rename downloaded binary to plugin name + if op.platform.License != "" { + if err:= downloadLicenseFile(downloadStagingDir, op.platform.License); err != nil { + return errors.Wrap(err, "failed downloading license file to installation directory") + } + if err := renameBinary(downloadStagingDir, op.platform.Bin); err != nil { + return errors.Wrap(err, "failed renaming binary in installation directory") + } + } + applyDefaults(&op.platform) if err := moveToInstallDir(downloadStagingDir, op.installDir, op.platform.Files); err != nil { return errors.Wrap(err, "failed while moving files to the installation directory") @@ -137,7 +148,7 @@ func applyDefaults(platform *index.Platform) { } // downloadAndExtract downloads the specified archive uri (or uses the provided overrideFile, if a non-empty value) -// while validating its checksum with the provided sha256sum, and extracts its contents to extractDir that must be. +// while validating its checksum with the provided sha256sum, and extracts its contents to extractDir that must be // created. func downloadAndExtract(extractDir, uri, sha256sum, overrideFile string) error { var fetcher download.Fetcher = download.HTTPFetcher{} @@ -150,6 +161,33 @@ func downloadAndExtract(extractDir, uri, sha256sum, overrideFile string) error { return errors.Wrap(err, "failed to unpack the plugin archive") } +// downloadLicenseFile will download the license file from the given URI and save it to the installation directory +// of the plugin. +func downloadLicenseFile(extractDir, uri string) error { + var fetcher download.Fetcher = download.HTTPFetcher{} + + body, err := fetcher.Get(uri) + if err != nil { + return errors.Wrap(err, "failed to download the license file") + } + defer body.Close() + + out, err := os.Create(filepath.Join(extractDir, "LICENSE")) + if err != nil { + return errors.Wrap(err, "failed to create LICENSE file") + } + defer out.Close() + + _, err = io.Copy(out, body) + + return err +} + +// renames the binary in the installation directory to the plugin name if the binary is given temporary "binary" name +func renameBinary(extractDir, plugin string) error { + return os.Rename(filepath.Join(extractDir, "binary"), filepath.Join(extractDir, plugin)) +} + // Uninstall will uninstall a plugin. func Uninstall(p environment.Paths, name string) error { if name == constants.KrewPluginName { diff --git a/pkg/index/types.go b/pkg/index/types.go index e2b19325..451d34a0 100644 --- a/pkg/index/types.go +++ b/pkg/index/types.go @@ -46,6 +46,8 @@ type Platform struct { Selector *metav1.LabelSelector `json:"selector,omitempty"` Files []FileOperation `json:"files"` + License string `json:"license,omitempty"` + // Bin specifies the path to the plugin executable. // The path is relative to the root of the installation folder. // The binary will be linked after all FileOperations are executed.