173 lines
4.7 KiB
Go
173 lines
4.7 KiB
Go
package talos
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultPlatform = "nocloud"
|
|
defaultTarget = "cloud"
|
|
)
|
|
|
|
var defaultExtensions = []string{
|
|
"siderolabs/iscsi-tools",
|
|
"siderolabs/nfs-utils",
|
|
"siderolabs/qemu-guest-agent",
|
|
"siderolabs/util-linux-tools",
|
|
}
|
|
|
|
type releaseResponse struct {
|
|
TagName string `json:"tag_name"`
|
|
}
|
|
|
|
type schematicRequest struct {
|
|
Customization struct {
|
|
SystemExtensions struct {
|
|
OfficialExtensions []string `json:"officialExtensions"`
|
|
} `json:"systemExtensions"`
|
|
} `json:"customization"`
|
|
}
|
|
|
|
type schematicResponse struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
func LatestVersion() string {
|
|
client := http.Client{Timeout: 10 * time.Second}
|
|
request, err := http.NewRequest(http.MethodGet, "https://api.github.com/repos/siderolabs/talos/releases/latest", nil)
|
|
if err != nil {
|
|
return "v1.13.6"
|
|
}
|
|
request.Header.Set("Accept", "application/vnd.github+json")
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return "v1.13.6"
|
|
}
|
|
defer response.Body.Close()
|
|
if response.StatusCode != http.StatusOK {
|
|
return "v1.13.6"
|
|
}
|
|
var release releaseResponse
|
|
if err := json.NewDecoder(response.Body).Decode(&release); err != nil || release.TagName == "" {
|
|
return "v1.13.6"
|
|
}
|
|
return release.TagName
|
|
}
|
|
|
|
func NormalizeVersion(version string) string {
|
|
if strings.HasPrefix(version, "v") {
|
|
return version
|
|
}
|
|
return "v" + version
|
|
}
|
|
|
|
func RequireCLICompatibility(version string) error {
|
|
version = NormalizeVersion(version)
|
|
if err := requireTalhelper(version); err != nil {
|
|
return err
|
|
}
|
|
return requireTalosctl(version)
|
|
}
|
|
|
|
func requireTalhelper(version string) error {
|
|
output, err := exec.Command("talhelper", "--version").Output()
|
|
if err != nil {
|
|
return fmt.Errorf("talhelper is required for Talos %s; install a compatible version and try again", version)
|
|
}
|
|
major, minor, ok := parseToolVersion(string(output))
|
|
if !ok || major < 3 || (major == 3 && minor < 1) {
|
|
return fmt.Errorf("talhelper %s may be incompatible with Talos %s; check that you have the right talhelper version", strings.TrimSpace(string(output)), version)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func requireTalosctl(version string) error {
|
|
output, err := exec.Command("talosctl", "version", "--client", "--short").Output()
|
|
if err != nil {
|
|
return fmt.Errorf("talosctl is required for Talos %s; install a compatible version and try again", version)
|
|
}
|
|
major, minor, ok := parseToolVersion(string(output))
|
|
if !ok {
|
|
return fmt.Errorf("unable to parse talosctl version %q; check that it matches Talos %s", strings.TrimSpace(string(output)), version)
|
|
}
|
|
talosMajor, talosMinor, ok := parseToolVersion(version)
|
|
if !ok || major != talosMajor || minor != talosMinor {
|
|
return fmt.Errorf("talosctl %s may be incompatible with Talos %s; check that you have the right talosctl version", strings.TrimSpace(string(output)), version)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseToolVersion(raw string) (int, int, bool) {
|
|
raw = strings.TrimSpace(raw)
|
|
fields := strings.Fields(raw)
|
|
if len(fields) > 0 {
|
|
raw = fields[len(fields)-1]
|
|
}
|
|
raw = strings.TrimPrefix(raw, "v")
|
|
parts := strings.Split(raw, ".")
|
|
if len(parts) < 2 {
|
|
return 0, 0, false
|
|
}
|
|
major, err := strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return 0, 0, false
|
|
}
|
|
minor, err := strconv.Atoi(parts[1])
|
|
if err != nil {
|
|
return 0, 0, false
|
|
}
|
|
return major, minor, true
|
|
}
|
|
|
|
func FactoryURL(version, arch string) string {
|
|
version = NormalizeVersion(version)
|
|
return fmt.Sprintf("https://factory.talos.dev/?arch=%s&extensions=-&extensions=%s&extensions=%s&extensions=%s&extensions=%s&platform=%s&target=%s&version=%s",
|
|
arch,
|
|
"siderolabs%2Fiscsi-tools",
|
|
"siderolabs%2Fnfs-utils",
|
|
"siderolabs%2Fqemu-guest-agent",
|
|
"siderolabs%2Futil-linux-tools",
|
|
defaultPlatform,
|
|
defaultTarget,
|
|
strings.TrimPrefix(version, "v"),
|
|
)
|
|
}
|
|
|
|
func ResolveSchematicID() (string, error) {
|
|
requestBody := schematicRequest{}
|
|
requestBody.Customization.SystemExtensions.OfficialExtensions = defaultExtensions
|
|
payload, err := json.Marshal(requestBody)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
client := http.Client{Timeout: 15 * time.Second}
|
|
request, err := http.NewRequest(http.MethodPost, "https://factory.talos.dev/schematics", bytes.NewReader(payload))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer response.Body.Close()
|
|
if response.StatusCode >= 300 {
|
|
return "", fmt.Errorf("talos factory returned %s", response.Status)
|
|
}
|
|
var schematic schematicResponse
|
|
if err := json.NewDecoder(response.Body).Decode(&schematic); err != nil {
|
|
return "", err
|
|
}
|
|
if schematic.ID == "" {
|
|
return "", fmt.Errorf("talos factory returned empty schematic id")
|
|
}
|
|
return schematic.ID, nil
|
|
}
|