Initial commit: stalwart-migrator design and scaffolding
In-place upgrade tool for Stalwart Mail Server (0.15.5 -> latest) with checkpointed rollback and post-migration validation. Design stage; see ARCHITECTURE.md.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user