Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
23be3f9
fix(vscodeweb): retry VS Code CLI download and report truncation clearly
skevetter Jul 23, 2026
bba1494
refactor(http): extract shared DownloadToFile helper
skevetter Jul 23, 2026
bc6ae5f
fix(http): verify TLS by default for the shared HTTP client
skevetter Jul 23, 2026
d6028a2
refactor(ide): route all IDE binary downloads through DownloadToFile
skevetter Jul 23, 2026
7482176
refactor(ide): add ide.DownloadAndExtract for download+extract compos…
skevetter Jul 23, 2026
0e7d147
fix: touchup comments
skevetter Jul 23, 2026
c993030
Merge branch 'main' into fix/vscode-cli-download-retry
skevetter Jul 23, 2026
b895695
fix(download): write downloads atomically and stage extractions
skevetter Jul 23, 2026
d438c88
fix(download): abort stalled downloads via inactivity watchdog
skevetter Jul 23, 2026
fb672bc
refactor(download): satisfy cyclop/revive/gosec lint
skevetter Jul 23, 2026
74ec75e
fix(download): retry 429 and preserve install dir permissions
skevetter Jul 23, 2026
ea0782f
refactor(http): move stall timeout into a RoundTripper decorator
skevetter Jul 23, 2026
d0be3db
feat(http): composable client with retry, user-agent, header timeout
skevetter Jul 23, 2026
5641859
docs(http): trim comments to match self-documenting style
skevetter Jul 23, 2026
9e8fe96
fix(http): guard retry against unreplayable request bodies
skevetter Jul 23, 2026
297d417
fix(download): harden install swap and retry backoff
skevetter Jul 23, 2026
11fa6e3
fix(http): propagate cancellation and guard shared client body reads
skevetter Jul 24, 2026
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
4 changes: 4 additions & 0 deletions pkg/config/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const (
// EnvDebug enables debug logging.
EnvDebug = "DEVSY_DEBUG"

// EnvInsecureTLS, when truthy, disables TLS certificate verification for the
// shared HTTP client. Enabled by default.
EnvInsecureTLS = "DEVSY_INSECURE_TLS"

// EnvDisableTelemetry disables telemetry collection.
EnvDisableTelemetry = "DEVSY_DISABLE_TELEMETRY"

Expand Down
66 changes: 6 additions & 60 deletions pkg/devcontainer/feature/features.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package feature

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"

pkgconfig "github.com/devsy-org/devsy/pkg/config"
"github.com/devsy-org/devsy/pkg/devcontainer/config"
Expand Down Expand Up @@ -546,66 +545,13 @@ func downloadFeatureFromURL(
) error {
log.Debugf("starting feature download: url=%s, destFile=%s", url, destFile)

// #nosec G301 -- TODO Consider using a more secure permission setting and ownership if needed.
err := os.MkdirAll(filepath.Dir(destFile), 0o755)
if err != nil {
return fmt.Errorf("create feature folder: %w", err)
}

attempt := 0
for range 3 {
if attempt > 0 {
delay := time.Duration(1<<uint(attempt-1)) * time.Second
log.Debugf("retrying download: delay=%v, attempt=%v", delay, attempt)
time.Sleep(delay)
}

log.Debugf("download feature: url=%s", url)
if err := tryDownload(url, destFile, httpHeaders); err != nil {
if attempt == 2 {
return err
}
log.Debugf("download attempt failed: error=%v, attempt=%v", err, attempt)
attempt++
continue
}
log.Infof("Feature download completed successfully: url=%s, destFile=%s", url, destFile)
return nil
}

return fmt.Errorf("download failed")
}

func tryDownload(url, destFile string, httpHeaders map[string]string) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return fmt.Errorf("make request: %w", err)
}
for key, value := range httpHeaders {
req.Header.Set(key, value)
}

resp, err := devsyhttp.GetHTTPClient().Do(req)
if err != nil {
return fmt.Errorf("make request: %w", err)
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode >= 400 {
return fmt.Errorf("GET request failed, status code is %d", resp.StatusCode)
}

file, err := os.Create(filepath.Clean(destFile)) //nolint:gosec // path from internal resolution
if err != nil {
return fmt.Errorf("create download file: %w", err)
}
defer func() { _ = file.Close() }()

_, err = io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("download feature: %w", err)
if err := devsyhttp.DownloadToFile(
context.Background(), url, destFile, devsyhttp.WithHeaders(httpHeaders),
); err != nil {
return err
}

log.Infof("Feature download completed successfully: url=%s, destFile=%s", url, destFile)
return nil
}

Expand Down
Loading
Loading