getproviders: add a registry-specific error and modify output when a
provider is not found. Previously a user would see the following error even if terraform was only searching the local filesystem: "provider registry registry.terraform.io does not have a provider named ...." This PR adds a registry-specific error type and modifies the MultiSource installer to check for registry errors. It will return the registry-specific error message if there is one, but if not the error message will list all locations searched.
This commit is contained in:
parent
a33a613703
commit
8d28d73de3
|
@ -576,13 +576,12 @@ func (c *ZeroThirteenUpgradeCommand) detectProviderSources(requiredProviders map
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rp.Type = p
|
rp.Type = p
|
||||||
} else {
|
} else {
|
||||||
if _, ok := err.(getproviders.ErrProviderNotKnown); ok {
|
|
||||||
// Setting the provider address to a zero value struct
|
// Setting the provider address to a zero value struct
|
||||||
// indicates that there is no known FQN for this provider,
|
// indicates that there is no known FQN for this provider,
|
||||||
// which will cause us to write an explanatory comment in the
|
// which will cause us to write an explanatory comment in the
|
||||||
// HCL output advising the user what to do about this.
|
// HCL output advising the user what to do about this.
|
||||||
rp.Type = addrs.Provider{}
|
rp.Type = addrs.Provider{}
|
||||||
}
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Warning,
|
tfdiags.Warning,
|
||||||
"Could not detect provider source",
|
"Could not detect provider source",
|
||||||
|
|
|
@ -453,7 +453,7 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
|
||||||
|
|
||||||
// The default (or configured) search paths are logged earlier, in provider_source.go
|
// The default (or configured) search paths are logged earlier, in provider_source.go
|
||||||
// Log that those are being overridden by the `-plugin-dir` command line options
|
// Log that those are being overridden by the `-plugin-dir` command line options
|
||||||
log.Printf("[DEBUG] init: overriding provider plugin search paths")
|
log.Println("[DEBUG] init: overriding provider plugin search paths")
|
||||||
log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs)
|
log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,11 +495,30 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
|
||||||
c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version))
|
c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version))
|
||||||
},
|
},
|
||||||
QueryPackagesFailure: func(provider addrs.Provider, err error) {
|
QueryPackagesFailure: func(provider addrs.Provider, err error) {
|
||||||
|
switch errorTy := err.(type) {
|
||||||
|
case getproviders.ErrProviderNotFound:
|
||||||
|
sources := errorTy.Sources
|
||||||
|
displaySources := make([]string, len(sources))
|
||||||
|
for i, source := range sources {
|
||||||
|
displaySources[i] = fmt.Sprintf("- %s", source)
|
||||||
|
}
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"Failed to query available provider packages",
|
"Failed to query available provider packages",
|
||||||
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s.", provider.ForDisplay(), err),
|
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s ",
|
||||||
|
provider.ForDisplay(), err, strings.Join(displaySources, "\n"),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
|
default:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to query available provider packages",
|
||||||
|
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s ",
|
||||||
|
provider.ForDisplay(), err,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
|
LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
|
|
@ -73,7 +73,22 @@ func (err ErrUnauthorized) Error() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrProviderNotKnown is an error type used to indicate that the hostname
|
// ErrProviderNotFound is an error type used to indicate that requested provider
|
||||||
|
// was not found in the source(s) included in the Description field. This can be
|
||||||
|
// used to produce user-friendly error messages.
|
||||||
|
type ErrProviderNotFound struct {
|
||||||
|
Provider addrs.Provider
|
||||||
|
Sources []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrProviderNotFound) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"provider %s was not found in any of the search locations",
|
||||||
|
err.Provider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRegistryProviderNotKnown is an error type used to indicate that the hostname
|
||||||
// given in a provider address does appear to be a provider registry but that
|
// given in a provider address does appear to be a provider registry but that
|
||||||
// registry does not know about the given provider namespace or type.
|
// registry does not know about the given provider namespace or type.
|
||||||
//
|
//
|
||||||
|
@ -85,11 +100,11 @@ func (err ErrUnauthorized) Error() string {
|
||||||
// because we expect that the caller will have better context to decide what
|
// because we expect that the caller will have better context to decide what
|
||||||
// hints are appropriate, e.g. by looking at the configuration given by the
|
// hints are appropriate, e.g. by looking at the configuration given by the
|
||||||
// user.
|
// user.
|
||||||
type ErrProviderNotKnown struct {
|
type ErrRegistryProviderNotKnown struct {
|
||||||
Provider addrs.Provider
|
Provider addrs.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrProviderNotKnown) Error() string {
|
func (err ErrRegistryProviderNotKnown) Error() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"provider registry %s does not have a provider named %s",
|
"provider registry %s does not have a provider named %s",
|
||||||
err.Provider.Hostname.ForDisplay(),
|
err.Provider.Hostname.ForDisplay(),
|
||||||
|
@ -181,7 +196,7 @@ func (err ErrQueryFailed) Unwrap() error {
|
||||||
// grow in future.
|
// grow in future.
|
||||||
func ErrIsNotExist(err error) bool {
|
func ErrIsNotExist(err error) bool {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ErrProviderNotKnown, ErrPlatformNotSupported:
|
case ErrProviderNotFound, ErrPlatformNotSupported:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -120,3 +120,8 @@ func (s *FilesystemMirrorSource) scanAllVersions() error {
|
||||||
s.allPackages = ret
|
s.allPackages = ret
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FilesystemMirrorSource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
// TODO: Since we have the provider, this could show the entire search path
|
||||||
|
return s.baseDir
|
||||||
|
}
|
||||||
|
|
|
@ -35,3 +35,8 @@ func (s *HTTPMirrorSource) AvailableVersions(provider addrs.Provider) (VersionLi
|
||||||
func (s *HTTPMirrorSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
func (s *HTTPMirrorSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
||||||
return PackageMeta{}, fmt.Errorf("Network-based provider mirrors are not supported in this version of Terraform")
|
return PackageMeta{}, fmt.Errorf("Network-based provider mirrors are not supported in this version of Terraform")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForDisplay returns a string description of the source for user-facing output.
|
||||||
|
func (s *HTTPMirrorSource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
return "Network-based provider mirrors are not supported in this version of Terraform"
|
||||||
|
}
|
||||||
|
|
|
@ -94,3 +94,7 @@ func (s *MemoizeSource) PackageMeta(provider addrs.Provider, version Version, ta
|
||||||
}
|
}
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MemoizeSource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
return s.underlying.ForDisplay(provider)
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestMemoizeSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = source.AvailableVersions(nonexistProvider)
|
_, err = source.AvailableVersions(nonexistProvider)
|
||||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||||
t.Fatalf("wrong error type from nonexist call:\ngot: %T\nwant: %T", err, want)
|
t.Fatalf("wrong error type from nonexist call:\ngot: %T\nwant: %T", err, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,11 +122,11 @@ func TestMemoizeSource(t *testing.T) {
|
||||||
source := NewMemoizeSource(mock)
|
source := NewMemoizeSource(mock)
|
||||||
|
|
||||||
_, err := source.AvailableVersions(nonexistProvider)
|
_, err := source.AvailableVersions(nonexistProvider)
|
||||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||||
t.Fatalf("wrong error type from first call:\ngot: %T\nwant: %T", err, want)
|
t.Fatalf("wrong error type from first call:\ngot: %T\nwant: %T", err, want)
|
||||||
}
|
}
|
||||||
_, err = source.AvailableVersions(nonexistProvider)
|
_, err = source.AvailableVersions(nonexistProvider)
|
||||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||||
t.Fatalf("wrong error type from second call:\ngot: %T\nwant: %T", err, want)
|
t.Fatalf("wrong error type from second call:\ngot: %T\nwant: %T", err, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (s *MockSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
// In this case, we'll behave like a registry that doesn't know about
|
// In this case, we'll behave like a registry that doesn't know about
|
||||||
// this provider at all, rather than just returning an empty result.
|
// this provider at all, rather than just returning an empty result.
|
||||||
return nil, ErrProviderNotKnown{provider}
|
return nil, ErrProviderNotFound{provider, []string{"mock source"}}
|
||||||
}
|
}
|
||||||
ret.Sort()
|
ret.Sort()
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
@ -198,3 +198,7 @@ func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protoc
|
||||||
}
|
}
|
||||||
return meta, close, nil
|
return meta, close, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MockSource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
return "mock source"
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
||||||
// We will return the union of all versions reported by the nested
|
// We will return the union of all versions reported by the nested
|
||||||
// sources that have matching patterns that accept the given provider.
|
// sources that have matching patterns that accept the given provider.
|
||||||
vs := make(map[Version]struct{})
|
vs := make(map[Version]struct{})
|
||||||
|
var registryError bool
|
||||||
for _, selector := range s {
|
for _, selector := range s {
|
||||||
if !selector.CanHandleProvider(provider) {
|
if !selector.CanHandleProvider(provider) {
|
||||||
continue // doesn't match the given patterns
|
continue // doesn't match the given patterns
|
||||||
|
@ -44,7 +45,10 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// okay
|
// okay
|
||||||
case ErrProviderNotKnown:
|
case ErrRegistryProviderNotKnown:
|
||||||
|
registryError = true
|
||||||
|
continue // ignore, then
|
||||||
|
case ErrProviderNotFound:
|
||||||
continue // ignore, then
|
continue // ignore, then
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -55,7 +59,11 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vs) == 0 {
|
if len(vs) == 0 {
|
||||||
return nil, ErrProviderNotKnown{provider}
|
if registryError {
|
||||||
|
return nil, ErrRegistryProviderNotKnown{provider}
|
||||||
|
} else {
|
||||||
|
return nil, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret := make(VersionList, 0, len(vs))
|
ret := make(VersionList, 0, len(vs))
|
||||||
for v := range vs {
|
for v := range vs {
|
||||||
|
@ -70,7 +78,7 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
||||||
// from the first selector that indicates availability of it.
|
// from the first selector that indicates availability of it.
|
||||||
func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
||||||
if len(s) == 0 { // Easy case: no providers exist at all
|
if len(s) == 0 { // Easy case: no providers exist at all
|
||||||
return PackageMeta{}, ErrProviderNotKnown{provider}
|
return PackageMeta{}, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, selector := range s {
|
for _, selector := range s {
|
||||||
|
@ -81,7 +89,7 @@ func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, targe
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return meta, nil
|
return meta, nil
|
||||||
case ErrProviderNotKnown, ErrPlatformNotSupported:
|
case ErrProviderNotFound, ErrRegistryProviderNotKnown, ErrPlatformNotSupported:
|
||||||
continue // ignore, then
|
continue // ignore, then
|
||||||
default:
|
default:
|
||||||
return PackageMeta{}, err
|
return PackageMeta{}, err
|
||||||
|
@ -224,3 +232,20 @@ func normalizeProviderNameOrWildcard(s string) (string, error) {
|
||||||
}
|
}
|
||||||
return addrs.ParseProviderPart(s)
|
return addrs.ParseProviderPart(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s MultiSource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
return strings.Join(s.sourcesForProvider(provider), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourcesForProvider returns a list of source display strings configured for a
|
||||||
|
// given provider, taking into account any `Exclude` statements.
|
||||||
|
func (s MultiSource) sourcesForProvider(provider addrs.Provider) []string {
|
||||||
|
ret := make([]string, 0)
|
||||||
|
for _, selector := range s {
|
||||||
|
if !selector.CanHandleProvider(provider) {
|
||||||
|
continue // doesn't match the given patterns
|
||||||
|
}
|
||||||
|
ret = append(ret, selector.Source.ForDisplay(provider))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ func TestMultiSourceAvailableVersions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
||||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||||
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -147,10 +147,32 @@ func TestMultiSourceAvailableVersions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
||||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||||
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("provider not found", func(t *testing.T) {
|
||||||
|
s1 := NewMockSource(nil)
|
||||||
|
s2 := NewMockSource(nil)
|
||||||
|
multi := MultiSource{
|
||||||
|
{Source: s1},
|
||||||
|
{Source: s2},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := multi.AvailableVersions(addrs.NewDefaultProvider("foo"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got success")
|
||||||
|
}
|
||||||
|
|
||||||
|
wantErr := `provider registry.terraform.io/hashicorp/foo was not found in any of the search locations`
|
||||||
|
_ = []string{"mock source", "mock source"}
|
||||||
|
|
||||||
|
if err.Error() != wantErr {
|
||||||
|
t.Fatalf("wrong error.\ngot: %s\nwant: %s\n", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiSourcePackageMeta(t *testing.T) {
|
func TestMultiSourcePackageMeta(t *testing.T) {
|
||||||
|
|
|
@ -93,7 +93,7 @@ func newRegistryClient(baseURL *url.URL, creds svcauth.HostCredentials) *registr
|
||||||
// ProviderVersions returns the raw version and protocol strings produced by the
|
// ProviderVersions returns the raw version and protocol strings produced by the
|
||||||
// registry for the given provider.
|
// registry for the given provider.
|
||||||
//
|
//
|
||||||
// The returned error will be ErrProviderNotKnown if the registry responds with
|
// The returned error will be ErrRegistryProviderNotKnown if the registry responds with
|
||||||
// 404 Not Found to indicate that the namespace or provider type are not known,
|
// 404 Not Found to indicate that the namespace or provider type are not known,
|
||||||
// ErrUnauthorized if the registry responds with 401 or 403 status codes, or
|
// ErrUnauthorized if the registry responds with 401 or 403 status codes, or
|
||||||
// ErrQueryFailed for any other protocol or operational problem.
|
// ErrQueryFailed for any other protocol or operational problem.
|
||||||
|
@ -122,7 +122,7 @@ func (c *registryClient) ProviderVersions(addr addrs.Provider) (map[string][]str
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
// Great!
|
// Great!
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
return nil, ErrProviderNotKnown{
|
return nil, ErrRegistryProviderNotKnown{
|
||||||
Provider: addr,
|
Provider: addr,
|
||||||
}
|
}
|
||||||
case http.StatusUnauthorized, http.StatusForbidden:
|
case http.StatusUnauthorized, http.StatusForbidden:
|
||||||
|
@ -357,7 +357,6 @@ func (c *registryClient) PackageMeta(provider addrs.Provider, version Version, t
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyProviderDefaultNamespace returns the raw address strings produced by
|
|
||||||
// findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match.
|
// findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match.
|
||||||
func (c *registryClient) findClosestProtocolCompatibleVersion(provider addrs.Provider, version Version) (Version, error) {
|
func (c *registryClient) findClosestProtocolCompatibleVersion(provider addrs.Provider, version Version) (Version, error) {
|
||||||
var match Version
|
var match Version
|
||||||
|
@ -401,7 +400,7 @@ FindMatch:
|
||||||
return match, nil
|
return match, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyProviderCanonicalAddress returns the raw address strings produced by
|
// LegacyProviderDefaultNamespace returns the raw address strings produced by
|
||||||
// the registry when asked about the given unqualified provider type name.
|
// the registry when asked about the given unqualified provider type name.
|
||||||
// The returned namespace string is taken verbatim from the registry's response.
|
// The returned namespace string is taken verbatim from the registry's response.
|
||||||
//
|
//
|
||||||
|
@ -437,7 +436,7 @@ func (c *registryClient) LegacyProviderDefaultNamespace(typeName string) (string
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
// Great!
|
// Great!
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
return "", ErrProviderNotKnown{
|
return "", ErrProviderNotFound{
|
||||||
Provider: placeholderProviderAddr,
|
Provider: placeholderProviderAddr,
|
||||||
}
|
}
|
||||||
case http.StatusUnauthorized, http.StatusForbidden:
|
case http.StatusUnauthorized, http.StatusForbidden:
|
||||||
|
|
|
@ -167,3 +167,7 @@ func (s *RegistrySource) registryClient(hostname svchost.Hostname) (*registryCli
|
||||||
|
|
||||||
return newRegistryClient(url, creds), nil
|
return newRegistryClient(url, creds), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RegistrySource) ForDisplay(provider addrs.Provider) string {
|
||||||
|
return fmt.Sprintf("registry %s", provider.Hostname.ForDisplay())
|
||||||
|
}
|
||||||
|
|
|
@ -9,4 +9,5 @@ import (
|
||||||
type Source interface {
|
type Source interface {
|
||||||
AvailableVersions(provider addrs.Provider) (VersionList, error)
|
AvailableVersions(provider addrs.Provider) (VersionList, error)
|
||||||
PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error)
|
PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error)
|
||||||
|
ForDisplay(provider addrs.Provider) string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue