Skip to content

Commit

Permalink
feat: Link to latest version on 404 hub downloads (#423)
Browse files Browse the repository at this point in the history

<!--
Explain what problem this PR addresses
-->

---
  • Loading branch information
dcelasun authored Oct 23, 2024
1 parent e4a3e0b commit b4ca098
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 9 deletions.
27 changes: 23 additions & 4 deletions managedplugin/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ type DownloaderOptions struct {
NoProgress bool
}

func DownloadPluginFromHub(ctx context.Context, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) (AssetSource, error) {
func DownloadPluginFromHub(ctx context.Context, logger zerolog.Logger, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) (AssetSource, error) {
if _, err := os.Stat(ops.LocalPath); err == nil {
return AssetSourceCached, nil
}
return AssetSourceRemote, doDownloadPluginFromHub(ctx, c, ops, dops)
return AssetSourceRemote, doDownloadPluginFromHub(ctx, logger, c, ops, dops)
}

func doDownloadPluginFromHub(ctx context.Context, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) error {
func doDownloadPluginFromHub(ctx context.Context, logger zerolog.Logger, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) error {
downloadDir := filepath.Dir(ops.LocalPath)
if err := os.MkdirAll(downloadDir, 0755); err != nil {
return fmt.Errorf("failed to create plugin directory %s: %w", downloadDir, err)
Expand All @@ -148,13 +148,32 @@ func doDownloadPluginFromHub(ctx context.Context, c *cloudquery_api.ClientWithRe
if err != nil {
return fmt.Errorf("failed to get plugin metadata from hub: %w", err)
}

switch statusCode {
case http.StatusOK:
// we allow this status code
case http.StatusUnauthorized:
return fmt.Errorf("unauthorized. Try logging in via `cloudquery login`")
case http.StatusNotFound:
return fmt.Errorf("failed to download plugin %v %v/%v@%v: plugin version not found. If you're trying to use a private plugin you'll need to run `cloudquery login` first", ops.PluginKind, ops.PluginTeam, ops.PluginName, ops.PluginVersion)
var errRetryWithLogin = fmt.Errorf("failed to download plugin %v %v/%v@%v: plugin version not found. If you're trying to use a private plugin you'll need to run `cloudquery login` first", ops.PluginKind, ops.PluginTeam, ops.PluginName, ops.PluginVersion)

// See if the plugin exists, but not the version.
pvw, err := NewPluginVersionWarner(logger, ops.AuthToken)
if err != nil {
return errRetryWithLogin
}

ver, err := pvw.getLatestVersion(ctx, ops.PluginTeam, ops.PluginName, ops.PluginKind)
if err != nil {
return errRetryWithLogin
}

if ver != nil {
return fmt.Errorf("version %s does not exist, consider using the latest version at %s", ops.PluginVersion,
fmt.Sprintf("https://hub.cloudquery.io/plugins/%s/%s/%s/v%s", ops.PluginKind, ops.PluginTeam, ops.PluginName, ver.String()))
}

return errRetryWithLogin
case http.StatusTooManyRequests:
return fmt.Errorf("too many download requests. Try logging in via `cloudquery login` to increase rate limits")
default:
Expand Down
53 changes: 52 additions & 1 deletion managedplugin/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package managedplugin
import (
"context"
"path"
"strings"
"testing"

cloudquery_api "github.com/cloudquery/cloudquery-api-go"
Expand Down Expand Up @@ -59,7 +60,7 @@ func TestDownloadPluginFromCloudQueryHub(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.testName, func(t *testing.T) {
assetSource, err := DownloadPluginFromHub(context.Background(), c, HubDownloadOptions{
assetSource, err := DownloadPluginFromHub(context.Background(), zerolog.Nop(), c, HubDownloadOptions{
LocalPath: path.Join(tmp, tc.testName),
AuthToken: "",
TeamName: "",
Expand All @@ -80,3 +81,53 @@ func TestDownloadPluginFromCloudQueryHub(t *testing.T) {
})
}
}

func TestDownloadPluginNonExistentVersionFromCloudQueryHub(t *testing.T) {
tmp := t.TempDir()
cases := []struct {
testName string
team string
plugin string
typ PluginType
version string
wantErr bool
errStr string
}{
{
testName: "should download test plugin from cloudquery registry with non-existent version",
team: "cloudquery", plugin: "aws", version: "v9000.0.0", typ: PluginSource, wantErr: true,
// This is only a prefix as the latest version won't be fixed in an integration test
errStr: "version v9000.0.0 does not exist, consider using the latest version at https://hub.cloudquery.io/plugins/source/cloudquery/aws/v",
},
}
c, err := cloudquery_api.NewClientWithResponses(APIBaseURL())
if err != nil {
t.Fatalf("failed to create Hub API client: %v", err)
}
for _, tc := range cases {
t.Run(tc.testName, func(t *testing.T) {
assetSource, err := DownloadPluginFromHub(context.Background(), zerolog.Nop(), c, HubDownloadOptions{
LocalPath: path.Join(tmp, tc.testName),
AuthToken: "",
TeamName: "",
PluginTeam: tc.team,
PluginKind: tc.typ.String(),
PluginName: tc.plugin,
PluginVersion: tc.version,
},
DownloaderOptions{},
)
if assetSource != AssetSourceRemote {
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() got = %v, want %v", assetSource, AssetSourceRemote)
}
if (err != nil) != tc.wantErr {
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() error = %v, wantErr %v", err, tc.wantErr)
return
}

if tc.wantErr && !strings.HasPrefix(err.Error(), tc.errStr) {
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() got error = %v, want %s", err, tc.errStr)
}
})
}
}
24 changes: 22 additions & 2 deletions managedplugin/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,30 @@ func getHubClient(logger zerolog.Logger, ops HubDownloadOptions) (*cloudquery_ap
return c, nil
}

func isDockerPlugin(ctx context.Context, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions) (bool, error) {
// validateDockerPlugin checks if the plugin has PackageType=docker and ops.PluginVersion exists
func validateDockerPlugin(ctx context.Context, logger zerolog.Logger, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions) (bool, error) {
var errFailed = fmt.Sprintf("failed to get %s plugin (name: %s/%s@%s) information", cloudquery_api.PluginKind(ops.PluginKind), ops.PluginTeam, ops.PluginName, ops.PluginVersion)

p, err := c.GetPluginVersionWithResponse(ctx, ops.PluginTeam, cloudquery_api.PluginKind(ops.PluginKind), ops.PluginName, ops.PluginVersion)
if err != nil {
return false, fmt.Errorf("failed to get %s plugin (name: %s/%s@%s) information: %w", cloudquery_api.PluginKind(ops.PluginKind), ops.PluginTeam, ops.PluginName, ops.PluginVersion, err)
return false, fmt.Errorf(errFailed+": %w", err)
}
if p.StatusCode() == http.StatusNotFound {
// See if the plugin exists, but not the version.
pvw, err := NewPluginVersionWarner(logger, ops.AuthToken)
if err != nil {
return false, fmt.Errorf(errFailed+": %w", err)
}

ver, err := pvw.getLatestVersion(ctx, ops.PluginTeam, ops.PluginName, ops.PluginKind)
if err != nil {
return false, fmt.Errorf(errFailed+": %w", err)
}

if ver != nil {
return false, fmt.Errorf("version %s does not exist, consider using the latest version at %s", ops.PluginVersion,
fmt.Sprintf("https://hub.cloudquery.io/plugins/%s/%s/%s/v%s", ops.PluginKind, ops.PluginTeam, ops.PluginName, ver.String()))
}
}
if p.StatusCode() != http.StatusOK {
return false, fmt.Errorf("failed to get %s plugin (name: %s/%s@%s) information: %s", cloudquery_api.PluginKind(ops.PluginKind), ops.PluginTeam, ops.PluginName, ops.PluginVersion, p.Status())
Expand Down
4 changes: 2 additions & 2 deletions managedplugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (c *Client) downloadPlugin(ctx context.Context, typ PluginType) (AssetSourc
if err != nil {
return AssetSourceUnknown, err
}
isDocker, err := isDockerPlugin(ctx, hubClient, ops)
isDocker, err := validateDockerPlugin(ctx, c.logger, hubClient, ops)
if err != nil {
return AssetSourceUnknown, err
}
Expand All @@ -234,7 +234,7 @@ func (c *Client) downloadPlugin(ctx context.Context, typ PluginType) (AssetSourc
}
return AssetSourceCached, nil
}
return DownloadPluginFromHub(ctx, hubClient, ops, dops)
return DownloadPluginFromHub(ctx, c.logger, hubClient, ops, dops)
default:
return AssetSourceUnknown, fmt.Errorf("unknown registry %s", c.config.Registry.String())
}
Expand Down

0 comments on commit b4ca098

Please sign in to comment.