diff --git a/integration/autoupdate/tools/main_test.go b/integration/autoupdate/tools/main_test.go index 06f36894217d8..a9f60a7737d8d 100644 --- a/integration/autoupdate/tools/main_test.go +++ b/integration/autoupdate/tools/main_test.go @@ -101,6 +101,10 @@ func serve256File(w http.ResponseWriter, _ *http.Request, filePath string) { w.Header().Set("Content-Type", "plain/text") file, err := os.Open(filePath) + if errors.Is(err, os.ErrNotExist) { + http.Error(w, "file not found", http.StatusNotFound) + return + } if err != nil { http.Error(w, "failed to open file", http.StatusInternalServerError) return @@ -160,7 +164,7 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers } switch runtime.GOOS { case "darwin": - archivePath := filepath.Join(path, fmt.Sprintf("tsh-%s.pkg", version)) + archivePath := filepath.Join(path, fmt.Sprintf("teleport-%s.pkg", version)) return trace.Wrap(helpers.CompressDirToPkgFile(ctx, versionPath, archivePath, "com.example.pkgtest")) case "windows": archivePath := filepath.Join(path, fmt.Sprintf("teleport-v%s-windows-amd64-bin.zip", version)) diff --git a/lib/autoupdate/client_update.go b/lib/autoupdate/client_update.go index a82972f571ee3..6ef8be89db1ed 100644 --- a/lib/autoupdate/client_update.go +++ b/lib/autoupdate/client_update.go @@ -225,7 +225,7 @@ func (u *ClientUpdater) UpdateWithLock(ctx context.Context, toolsVersion string) // with defined updater directory suffix. func (u *ClientUpdater) Update(ctx context.Context, toolsVersion string) error { // Get platform specific download URLs. - archiveURL, hashURL, err := urls(u.baseUrl, toolsVersion) + packages, err := teleportPackageURLs(u.baseUrl, toolsVersion) if err != nil { return trace.Wrap(err) } @@ -233,16 +233,32 @@ func (u *ClientUpdater) Update(ctx context.Context, toolsVersion string) error { signalCtx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) defer cancel() - // Download the archive and validate against the hash. Download to a - // temporary path within tools directory. - hash, err := u.downloadHash(signalCtx, hashURL) + for _, pkg := range packages { + if err := u.update(signalCtx, pkg); err != nil { + return trace.Wrap(err) + } + } + + return nil +} + +// update downloads the archive and validate against the hash. Download to a +// temporary path within tools directory. +func (u *ClientUpdater) update(ctx context.Context, pkg packageURL) error { + hash, err := u.downloadHash(ctx, pkg.Hash) + if pkg.Optional && trace.IsNotFound(err) { + return nil + } if err != nil { return trace.Wrap(err) } - archivePath, archiveHash, err := u.downloadArchive(signalCtx, u.toolsDir, archiveURL) + archivePath, archiveHash, err := u.downloadArchive(ctx, u.toolsDir, pkg.Archive) if err != nil { return trace.Wrap(err) } + if pkg.Optional && trace.IsNotFound(err) { + return nil + } defer func() { if err := os.Remove(archivePath); err != nil { slog.WarnContext(ctx, "failed to remove archive", "error", err) @@ -311,6 +327,9 @@ func (u *ClientUpdater) downloadHash(ctx context.Context, url string) (string, e return "", trace.Wrap(err) } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return "", trace.NotFound("hash file is not found: %v", resp.StatusCode) + } if resp.StatusCode != http.StatusOK { return "", trace.BadParameter("bad status when downloading archive hash: %v", resp.StatusCode) } @@ -337,6 +356,9 @@ func (u *ClientUpdater) downloadArchive(ctx context.Context, downloadDir string, return "", "", trace.Wrap(err) } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return "", "", trace.NotFound("archive file is not found: %v", resp.StatusCode) + } if resp.StatusCode != http.StatusOK { return "", "", trace.BadParameter("bad status when downloading archive: %v", resp.StatusCode) } diff --git a/lib/autoupdate/utils.go b/lib/autoupdate/utils.go index 04733018e7490..db7bdb21e3c72 100644 --- a/lib/autoupdate/utils.go +++ b/lib/autoupdate/utils.go @@ -114,15 +114,31 @@ func checkClientToolVersion(toolsDir string) (string, error) { return "", trace.BadParameter("unable to determine version") } -// urls returns the URL for the Teleport archive to download. The format is: +// packageURL defines URLs to the archive and their archive sha256 hash file, and marks +// if this package is optional, for such case download needs to be ignored if package +// not found in CDN. +type packageURL struct { + Archive string + Hash string + Optional bool +} + +// teleportPackageURLs returns the URL for the Teleport archive to download. The format is: // https://cdn.teleport.dev/teleport-{, ent-}v15.3.0-{linux, darwin, windows}-{amd64,arm64,arm,386}-{fips-}bin.tar.gz -func urls(baseUrl, toolsVersion string) (string, string, error) { - var archive string +func teleportPackageURLs(baseUrl, toolsVersion string) ([]packageURL, error) { switch runtime.GOOS { case "darwin": - archive = baseUrl + "/tsh-" + toolsVersion + ".pkg" + tsh := baseUrl + "/tsh-" + toolsVersion + ".pkg" + teleport := baseUrl + "/teleport-" + toolsVersion + ".pkg" + return []packageURL{ + {Archive: teleport, Hash: teleport + ".sha256"}, + {Archive: tsh, Hash: tsh + ".sha256", Optional: true}, + }, nil case "windows": - archive = baseUrl + "/teleport-v" + toolsVersion + "-windows-amd64-bin.zip" + archive := baseUrl + "/teleport-v" + toolsVersion + "-windows-amd64-bin.zip" + return []packageURL{ + {Archive: archive, Hash: archive + ".sha256"}, + }, nil case "linux": var b strings.Builder b.WriteString(baseUrl + "/teleport-") @@ -134,12 +150,13 @@ func urls(baseUrl, toolsVersion string) (string, string, error) { b.WriteString("fips-") } b.WriteString("bin.tar.gz") - archive = b.String() + archive := b.String() + return []packageURL{ + {Archive: archive, Hash: archive + ".sha256"}, + }, nil default: - return "", "", trace.BadParameter("unsupported runtime: %v", runtime.GOOS) + return nil, trace.BadParameter("unsupported runtime: %v", runtime.GOOS) } - - return archive, archive + ".sha256", nil } // toolName returns the path to {tsh, tctl} for the executable that started