Files
vim-ale/internal/releases/hashicorp/hashicorp.go
AJ ONeal 631147901a feat: add Go release cache daemon (webicached)
Rewrites the Node.js release classification pipeline in Go. webicached
fetches upstream releases (GitHub, Gitea, GitLab, HashiCorp, custom
sources), classifies assets by OS/arch/variant, and writes legacy-format
JSON caches compatible with the existing webinstall.dev API.

Git-clone packages emit git_tag and git_commit_hash from real repo
clones — no fabricated refs.
2026-05-16 21:22:38 -06:00

80 lines
2.4 KiB
Go

// Package hashicorp fetches release data from the HashiCorp releases API.
//
// HashiCorp publishes release indexes at:
//
// https://releases.hashicorp.com/{product}/index.json
//
// The response is a JSON object with a "versions" key mapping version strings
// to objects containing build arrays with url, os, arch, and filename.
package hashicorp
import (
"context"
"encoding/json"
"fmt"
"iter"
"net/http"
)
// Index is the top-level response from the HashiCorp releases API.
type Index struct {
Versions map[string]Version `json:"versions"`
}
// Version is one release version with its builds.
type Version struct {
Name string `json:"name"` // "terraform"
Version string `json:"version"` // "1.12.0"
SHASUMS string `json:"shasums,omitempty"` // URL to SHA256SUMS file
SHASUMSSig string `json:"shasums_signature"` // URL to signature
Builds []Build `json:"builds"`
TimestampCreated string `json:"timestamp_created,omitempty"`
TimestampUpdated string `json:"timestamp_updated,omitempty"`
}
// Build is one downloadable artifact.
type Build struct {
Name string `json:"name"` // "terraform"
Version string `json:"version"` // "1.12.0"
OS string `json:"os"` // "linux", "darwin", "windows"
Arch string `json:"arch"` // "amd64", "arm64", "386"
Filename string `json:"filename"` // "terraform_1.12.0_linux_amd64.zip"
URL string `json:"url"` // full download URL
}
// Fetch retrieves the HashiCorp release index for a product.
//
// Yields one batch containing all versions.
func Fetch(ctx context.Context, client *http.Client, product string) iter.Seq2[*Index, error] {
return func(yield func(*Index, error) bool) {
url := fmt.Sprintf("https://releases.hashicorp.com/%s/index.json", product)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
yield(nil, fmt.Errorf("hashicorp: %w", err))
return
}
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
yield(nil, fmt.Errorf("hashicorp: fetch %s: %w", product, err))
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
yield(nil, fmt.Errorf("hashicorp: fetch %s: %s", product, resp.Status))
return
}
var idx Index
if err := json.NewDecoder(resp.Body).Decode(&idx); err != nil {
yield(nil, fmt.Errorf("hashicorp: decode %s: %w", product, err))
return
}
yield(&idx, nil)
}
}