Skip to content

Commit b4ca098

Browse files
authored
feat: Link to latest version on 404 hub downloads (#423)
<!-- Explain what problem this PR addresses --> ---
1 parent e4a3e0b commit b4ca098

File tree

4 files changed

+99
-9
lines changed

4 files changed

+99
-9
lines changed

managedplugin/download.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ type DownloaderOptions struct {
131131
NoProgress bool
132132
}
133133

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

141-
func doDownloadPluginFromHub(ctx context.Context, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) error {
141+
func doDownloadPluginFromHub(ctx context.Context, logger zerolog.Logger, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions, dops DownloaderOptions) error {
142142
downloadDir := filepath.Dir(ops.LocalPath)
143143
if err := os.MkdirAll(downloadDir, 0755); err != nil {
144144
return fmt.Errorf("failed to create plugin directory %s: %w", downloadDir, err)
@@ -148,13 +148,32 @@ func doDownloadPluginFromHub(ctx context.Context, c *cloudquery_api.ClientWithRe
148148
if err != nil {
149149
return fmt.Errorf("failed to get plugin metadata from hub: %w", err)
150150
}
151+
151152
switch statusCode {
152153
case http.StatusOK:
153154
// we allow this status code
154155
case http.StatusUnauthorized:
155156
return fmt.Errorf("unauthorized. Try logging in via `cloudquery login`")
156157
case http.StatusNotFound:
157-
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)
158+
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)
159+
160+
// See if the plugin exists, but not the version.
161+
pvw, err := NewPluginVersionWarner(logger, ops.AuthToken)
162+
if err != nil {
163+
return errRetryWithLogin
164+
}
165+
166+
ver, err := pvw.getLatestVersion(ctx, ops.PluginTeam, ops.PluginName, ops.PluginKind)
167+
if err != nil {
168+
return errRetryWithLogin
169+
}
170+
171+
if ver != nil {
172+
return fmt.Errorf("version %s does not exist, consider using the latest version at %s", ops.PluginVersion,
173+
fmt.Sprintf("https://hub.cloudquery.io/plugins/%s/%s/%s/v%s", ops.PluginKind, ops.PluginTeam, ops.PluginName, ver.String()))
174+
}
175+
176+
return errRetryWithLogin
158177
case http.StatusTooManyRequests:
159178
return fmt.Errorf("too many download requests. Try logging in via `cloudquery login` to increase rate limits")
160179
default:

managedplugin/download_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package managedplugin
33
import (
44
"context"
55
"path"
6+
"strings"
67
"testing"
78

89
cloudquery_api "github.com/cloudquery/cloudquery-api-go"
@@ -59,7 +60,7 @@ func TestDownloadPluginFromCloudQueryHub(t *testing.T) {
5960
}
6061
for _, tc := range cases {
6162
t.Run(tc.testName, func(t *testing.T) {
62-
assetSource, err := DownloadPluginFromHub(context.Background(), c, HubDownloadOptions{
63+
assetSource, err := DownloadPluginFromHub(context.Background(), zerolog.Nop(), c, HubDownloadOptions{
6364
LocalPath: path.Join(tmp, tc.testName),
6465
AuthToken: "",
6566
TeamName: "",
@@ -80,3 +81,53 @@ func TestDownloadPluginFromCloudQueryHub(t *testing.T) {
8081
})
8182
}
8283
}
84+
85+
func TestDownloadPluginNonExistentVersionFromCloudQueryHub(t *testing.T) {
86+
tmp := t.TempDir()
87+
cases := []struct {
88+
testName string
89+
team string
90+
plugin string
91+
typ PluginType
92+
version string
93+
wantErr bool
94+
errStr string
95+
}{
96+
{
97+
testName: "should download test plugin from cloudquery registry with non-existent version",
98+
team: "cloudquery", plugin: "aws", version: "v9000.0.0", typ: PluginSource, wantErr: true,
99+
// This is only a prefix as the latest version won't be fixed in an integration test
100+
errStr: "version v9000.0.0 does not exist, consider using the latest version at https://hub.cloudquery.io/plugins/source/cloudquery/aws/v",
101+
},
102+
}
103+
c, err := cloudquery_api.NewClientWithResponses(APIBaseURL())
104+
if err != nil {
105+
t.Fatalf("failed to create Hub API client: %v", err)
106+
}
107+
for _, tc := range cases {
108+
t.Run(tc.testName, func(t *testing.T) {
109+
assetSource, err := DownloadPluginFromHub(context.Background(), zerolog.Nop(), c, HubDownloadOptions{
110+
LocalPath: path.Join(tmp, tc.testName),
111+
AuthToken: "",
112+
TeamName: "",
113+
PluginTeam: tc.team,
114+
PluginKind: tc.typ.String(),
115+
PluginName: tc.plugin,
116+
PluginVersion: tc.version,
117+
},
118+
DownloaderOptions{},
119+
)
120+
if assetSource != AssetSourceRemote {
121+
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() got = %v, want %v", assetSource, AssetSourceRemote)
122+
}
123+
if (err != nil) != tc.wantErr {
124+
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() error = %v, wantErr %v", err, tc.wantErr)
125+
return
126+
}
127+
128+
if tc.wantErr && !strings.HasPrefix(err.Error(), tc.errStr) {
129+
t.Errorf("TestDownloadPluginFromCloudQueryIntegration() got error = %v, want %s", err, tc.errStr)
130+
}
131+
})
132+
}
133+
}

managedplugin/hub.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,30 @@ func getHubClient(logger zerolog.Logger, ops HubDownloadOptions) (*cloudquery_ap
2424
return c, nil
2525
}
2626

27-
func isDockerPlugin(ctx context.Context, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions) (bool, error) {
27+
// validateDockerPlugin checks if the plugin has PackageType=docker and ops.PluginVersion exists
28+
func validateDockerPlugin(ctx context.Context, logger zerolog.Logger, c *cloudquery_api.ClientWithResponses, ops HubDownloadOptions) (bool, error) {
29+
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)
30+
2831
p, err := c.GetPluginVersionWithResponse(ctx, ops.PluginTeam, cloudquery_api.PluginKind(ops.PluginKind), ops.PluginName, ops.PluginVersion)
2932
if err != nil {
30-
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)
33+
return false, fmt.Errorf(errFailed+": %w", err)
34+
}
35+
if p.StatusCode() == http.StatusNotFound {
36+
// See if the plugin exists, but not the version.
37+
pvw, err := NewPluginVersionWarner(logger, ops.AuthToken)
38+
if err != nil {
39+
return false, fmt.Errorf(errFailed+": %w", err)
40+
}
41+
42+
ver, err := pvw.getLatestVersion(ctx, ops.PluginTeam, ops.PluginName, ops.PluginKind)
43+
if err != nil {
44+
return false, fmt.Errorf(errFailed+": %w", err)
45+
}
46+
47+
if ver != nil {
48+
return false, fmt.Errorf("version %s does not exist, consider using the latest version at %s", ops.PluginVersion,
49+
fmt.Sprintf("https://hub.cloudquery.io/plugins/%s/%s/%s/v%s", ops.PluginKind, ops.PluginTeam, ops.PluginName, ver.String()))
50+
}
3151
}
3252
if p.StatusCode() != http.StatusOK {
3353
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())

managedplugin/plugin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (c *Client) downloadPlugin(ctx context.Context, typ PluginType) (AssetSourc
219219
if err != nil {
220220
return AssetSourceUnknown, err
221221
}
222-
isDocker, err := isDockerPlugin(ctx, hubClient, ops)
222+
isDocker, err := validateDockerPlugin(ctx, c.logger, hubClient, ops)
223223
if err != nil {
224224
return AssetSourceUnknown, err
225225
}
@@ -234,7 +234,7 @@ func (c *Client) downloadPlugin(ctx context.Context, typ PluginType) (AssetSourc
234234
}
235235
return AssetSourceCached, nil
236236
}
237-
return DownloadPluginFromHub(ctx, hubClient, ops, dops)
237+
return DownloadPluginFromHub(ctx, c.logger, hubClient, ops, dops)
238238
default:
239239
return AssetSourceUnknown, fmt.Errorf("unknown registry %s", c.config.Registry.String())
240240
}

0 commit comments

Comments
 (0)