internal/providercache: Installation from HTTP URLs and local archives
When a provider source produces an HTTP URL location we'll expect it to resolve to a zip file, which we'll first download to a temporary directory and then treat it like a local archive. When a provider source produces a local archive path we'll expect it to be a zip file and extract it into the target directory. This does not yet include an implementation of installing from an already-unpacked local directory. That will follow in a subsequent commit, likely following a similar principle as in Dir.LinkFromOtherCache.
This commit is contained in:
parent
754b7ebb65
commit
807267d1b5
|
@ -1,6 +1,7 @@
|
||||||
package providercache
|
package providercache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -12,9 +13,26 @@ import (
|
||||||
// InstallPackage takes a metadata object describing a package available for
|
// InstallPackage takes a metadata object describing a package available for
|
||||||
// installation, retrieves that package, and installs it into the receiving
|
// installation, retrieves that package, and installs it into the receiving
|
||||||
// cache directory.
|
// cache directory.
|
||||||
func (d *Dir) InstallPackage(meta getproviders.PackageMeta) error {
|
func (d *Dir) InstallPackage(ctx context.Context, meta getproviders.PackageMeta) error {
|
||||||
// TODO: Implement this
|
if meta.TargetPlatform != d.targetPlatform {
|
||||||
return fmt.Errorf("InstallPackage is not yet implemented")
|
return fmt.Errorf("can't install %s package into cache directory expecting %s", meta.TargetPlatform, d.targetPlatform)
|
||||||
|
}
|
||||||
|
newPath := getproviders.UnpackedDirectoryPathForPackage(
|
||||||
|
d.baseDir, meta.Provider, meta.Version, d.targetPlatform,
|
||||||
|
)
|
||||||
|
|
||||||
|
switch location := meta.Location.(type) {
|
||||||
|
case getproviders.PackageHTTPURL:
|
||||||
|
return installFromHTTPURL(ctx, string(location), newPath)
|
||||||
|
case getproviders.PackageLocalArchive:
|
||||||
|
return installFromLocalArchive(ctx, string(location), newPath)
|
||||||
|
case getproviders.PackageLocalDir:
|
||||||
|
return installFromLocalDir(ctx, string(location), newPath)
|
||||||
|
default:
|
||||||
|
// Should not get here, because the above should be exhaustive for
|
||||||
|
// all implementations of getproviders.Location.
|
||||||
|
return fmt.Errorf("don't know how to install from a %T location", location)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinkFromOtherCache takes a CachedProvider value produced from another Dir
|
// LinkFromOtherCache takes a CachedProvider value produced from another Dir
|
||||||
|
|
|
@ -89,12 +89,10 @@ func (i *Installer) SetGlobalCacheDir(cacheDir *Dir) {
|
||||||
// in the final returned error value so callers should show either one or the
|
// in the final returned error value so callers should show either one or the
|
||||||
// other, and not both.
|
// other, and not both.
|
||||||
func (i *Installer) EnsureProviderVersions(ctx context.Context, reqs map[addrs.Provider]getproviders.VersionConstraints, mode InstallMode) (map[addrs.Provider]getproviders.Version, error) {
|
func (i *Installer) EnsureProviderVersions(ctx context.Context, reqs map[addrs.Provider]getproviders.VersionConstraints, mode InstallMode) (map[addrs.Provider]getproviders.Version, error) {
|
||||||
// FIXME: Currently the context isn't actually propagated into the
|
// FIXME: Currently the context isn't actually propagated into all of the
|
||||||
// other functions we call here, because they are not context-aware.
|
// other functions we call here, because they are not context-aware.
|
||||||
// Right now the context is used only for the InstallerEvents object.
|
// Anything that could be making network requests here should take a
|
||||||
// Before considering this "finished" we should update the functions
|
// context and ideally respond to the cancellation of that context.
|
||||||
// we're calling below that might perform external network requests
|
|
||||||
// and make them also take a context and respect cancellation of it.
|
|
||||||
|
|
||||||
errs := map[addrs.Provider]error{}
|
errs := map[addrs.Provider]error{}
|
||||||
evts := installerEventsForContext(ctx)
|
evts := installerEventsForContext(ctx)
|
||||||
|
@ -256,7 +254,7 @@ NeedProvider:
|
||||||
installTo = i.targetDir
|
installTo = i.targetDir
|
||||||
linkTo = nil // no linking needed
|
linkTo = nil // no linking needed
|
||||||
}
|
}
|
||||||
err = installTo.InstallPackage(meta)
|
err = installTo.InstallPackage(ctx, meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: Consider retrying for certain kinds of error that seem
|
// TODO: Consider retrying for certain kinds of error that seem
|
||||||
// likely to be transient. For now, we just treat all errors equally.
|
// likely to be transient. For now, we just treat all errors equally.
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package providercache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
getter "github.com/hashicorp/go-getter"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/httpclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We borrow the "unpack a zip file into a target directory" logic from
|
||||||
|
// go-getter, even though we're not otherwise using go-getter here.
|
||||||
|
// (We don't need the same flexibility as we have for modules, because
|
||||||
|
// providers _always_ come from provider registries, which have a very
|
||||||
|
// specific protocol and set of expectations.)
|
||||||
|
var unzip = getter.ZipDecompressor{}
|
||||||
|
|
||||||
|
func installFromHTTPURL(ctx context.Context, url string, targetDir string) error {
|
||||||
|
// When we're installing from an HTTP URL we expect the URL to refer to
|
||||||
|
// a zip file. We'll fetch that into a temporary file here and then
|
||||||
|
// delegate to installFromLocalArchive below to actually extract it.
|
||||||
|
// (We're not using go-getter here because its HTTP getter has a bunch
|
||||||
|
// of extraneous functionality we don't need or want, like indirection
|
||||||
|
// through X-Terraform-Get header, attempting partial fetches for
|
||||||
|
// files that already exist, etc.)
|
||||||
|
|
||||||
|
httpClient := httpclient.New()
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider download request: %s", err)
|
||||||
|
}
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unsuccessful request to %s: %s", url, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "terraform-provider")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open temporary file to download from %s", url)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// We'll borrow go-getter's "cancelable copy" implementation here so that
|
||||||
|
// the download can potentially be interrupted partway through.
|
||||||
|
n, err := getter.Copy(ctx, f, resp.Body)
|
||||||
|
if err == nil && n < resp.ContentLength {
|
||||||
|
err = fmt.Errorf("incorrect response size: expected %d bytes, but got %d bytes", resp.ContentLength, n)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we managed to download successfully then we can now delegate to
|
||||||
|
// installFromLocalArchive for extraction.
|
||||||
|
archiveFilename := f.Name()
|
||||||
|
return installFromLocalArchive(ctx, archiveFilename, targetDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installFromLocalArchive(ctx context.Context, filename string, targetDir string) error {
|
||||||
|
return unzip.Decompress(targetDir, filename, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installFromLocalDir(ctx context.Context, sourceDir string, targetDir string) error {
|
||||||
|
return fmt.Errorf("installFromLocalDir not yet implemented")
|
||||||
|
}
|
Loading…
Reference in New Issue