In-place upgrade tool for Stalwart Mail Server (0.15.5 -> latest) with checkpointed rollback and post-migration validation. Design stage; see ARCHITECTURE.md.
75 lines
2.2 KiB
Go
75 lines
2.2 KiB
Go
package preflight
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// githubAPIBase is a var, not a const, so tests can point it at an
|
|
// httptest server instead of hitting the real GitHub API.
|
|
var githubAPIBase = "https://api.github.com/repos/stalwartlabs/stalwart/releases"
|
|
|
|
type ReleaseAsset struct {
|
|
Name string `json:"name"`
|
|
DownloadURL string `json:"browser_download_url"`
|
|
SizeBytes int64 `json:"size"`
|
|
}
|
|
|
|
type Release struct {
|
|
TagName string `json:"tag_name"`
|
|
Assets []ReleaseAsset `json:"assets"`
|
|
}
|
|
|
|
// ResolveRelease looks up a Stalwart release from the public GitHub API.
|
|
// version is either an exact tag like "0.16.14" (a "v" prefix is added if
|
|
// missing) or "latest".
|
|
func ResolveRelease(ctx context.Context, httpClient *http.Client, version string) (*Release, error) {
|
|
url := githubAPIBase + "/latest"
|
|
if version != "" && version != "latest" {
|
|
tag := version
|
|
if !strings.HasPrefix(tag, "v") {
|
|
tag = "v" + tag
|
|
}
|
|
url = githubAPIBase + "/tags/" + tag
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Accept", "application/vnd.github+json")
|
|
if httpClient == nil {
|
|
httpClient = &http.Client{Timeout: 30 * time.Second}
|
|
}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("preflight: fetch release %s: %w", url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("preflight: fetch release %s: unexpected status %s", url, resp.Status)
|
|
}
|
|
var rel Release
|
|
if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
|
|
return nil, fmt.Errorf("preflight: parse release response from %s: %w", url, err)
|
|
}
|
|
return &rel, nil
|
|
}
|
|
|
|
// ChecksumAsset returns the release asset that looks like a published
|
|
// checksum manifest, if any. Its presence isn't guaranteed by Stalwart's
|
|
// release process, so callers must treat a nil result as "no independently
|
|
// published checksum to verify the download against", not an error.
|
|
func ChecksumAsset(rel *Release) *ReleaseAsset {
|
|
for i := range rel.Assets {
|
|
name := strings.ToLower(rel.Assets[i].Name)
|
|
if strings.Contains(name, "sha256") || strings.Contains(name, "checksum") {
|
|
return &rel.Assets[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|