Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 32 additions & 24 deletions pkg/tools/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,34 +131,52 @@ func (d *Downloader) downloadExact(ctx context.Context, name, version string) er
return nil
}

// githubRelease represents a GitHub release
type githubRelease struct {
TagName string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
// replicatedPingResponse represents the response from replicated.app/ping
type replicatedPingResponse struct {
ClientIP string `json:"client_ip"`
ClientVersions map[string]string `json:"client_versions"`
}

// getLatestStableVersion fetches the latest non-prerelease version from GitHub
func getLatestStableVersion(repo string) (string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo)
// getLatestStableVersion fetches the latest version from replicated.app/ping
func getLatestStableVersion(toolName string) (string, error) {
url := "https://replicated.app/ping"

client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
return "", fmt.Errorf("fetching latest release: %w", err)
return "", fmt.Errorf("fetching versions from replicated.app: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
return "", fmt.Errorf("GitHub API returned HTTP %d", resp.StatusCode)
return "", fmt.Errorf("replicated.app/ping returned HTTP %d", resp.StatusCode)
}

var release githubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return "", fmt.Errorf("parsing release JSON: %w", err)
var pingResp replicatedPingResponse
if err := json.NewDecoder(resp.Body).Decode(&pingResp); err != nil {
return "", fmt.Errorf("parsing ping response JSON: %w", err)
}

// Map tool names to client_versions keys
var versionKey string
switch toolName {
case ToolHelm:
versionKey = "helm"
case ToolPreflight:
versionKey = "preflight"
case ToolSupportBundle:
versionKey = "support_bundle"
default:
return "", fmt.Errorf("unknown tool: %s", toolName)
}

version, ok := pingResp.ClientVersions[versionKey]
if !ok {
return "", fmt.Errorf("version not found for tool %s in ping response", toolName)
}

// Remove 'v' prefix if present
version := strings.TrimPrefix(release.TagName, "v")
version = strings.TrimPrefix(version, "v")

return version, nil
}
Expand All @@ -175,17 +193,7 @@ func (d *Downloader) DownloadWithFallback(ctx context.Context, name, version str
fmt.Printf("⚠️ Version %s failed: %v\n", version, err)
fmt.Printf("Attempting to download latest stable version...\n")

var repo string
switch name {
case ToolHelm:
repo = "helm/helm"
case ToolPreflight, ToolSupportBundle:
repo = "replicatedhq/troubleshoot"
default:
return "", fmt.Errorf("unknown tool: %s", name)
}

latestVersion, err := getLatestStableVersion(repo)
latestVersion, err := getLatestStableVersion(name)
if err != nil {
return "", fmt.Errorf("could not get latest version: %w", err)
}
Expand Down
28 changes: 4 additions & 24 deletions pkg/tools/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,10 @@ func NewResolver() *Resolver {
}
}

// ResolveLatestVersion fetches the latest stable version for a tool from GitHub
// ResolveLatestVersion fetches the latest stable version for a tool from replicated.app/ping
// without downloading it. Useful for displaying version information.
func (r *Resolver) ResolveLatestVersion(ctx context.Context, name string) (string, error) {
var repo string
switch name {
case ToolHelm:
repo = "helm/helm"
case ToolPreflight, ToolSupportBundle:
repo = "replicatedhq/troubleshoot"
default:
return "", fmt.Errorf("unknown tool: %s", name)
}

latestVersion, err := getLatestStableVersion(repo)
latestVersion, err := getLatestStableVersion(name)
if err != nil {
return "", fmt.Errorf("failed to get latest version for %s: %w", name, err)
}
Expand All @@ -41,19 +31,9 @@ func (r *Resolver) ResolveLatestVersion(ctx context.Context, name string) (strin

// Resolve returns the path to a tool binary, downloading if not cached
func (r *Resolver) Resolve(ctx context.Context, name, version string) (string, error) {
// If version is "latest" or empty, fetch the latest stable version from GitHub
// If version is "latest" or empty, fetch the latest stable version from replicated.app/ping
if version == "latest" || version == "" {
var repo string
switch name {
case ToolHelm:
repo = "helm/helm"
case ToolPreflight, ToolSupportBundle:
repo = "replicatedhq/troubleshoot"
default:
return "", fmt.Errorf("unknown tool: %s", name)
}

latestVersion, err := getLatestStableVersion(repo)
latestVersion, err := getLatestStableVersion(name)
if err != nil {
return "", fmt.Errorf("failed to get latest version for %s: %w", name, err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/tools/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestResolveLatestVersion(t *testing.T) {
name: "unknown tool should error",
tool: "unknown-tool",
wantErr: true,
errMessage: "unknown tool: unknown-tool",
errMessage: "failed to get latest version for unknown-tool: unknown tool: unknown-tool",
},
}

Expand Down