144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client"
|
|
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/service"
|
|
"go.yaml.in/yaml/v3"
|
|
)
|
|
|
|
const (
|
|
repoURL = "https://tangled.org/evan.jarrett.net/at-container-registry"
|
|
repoBranch = "main"
|
|
privateNetworkCIDR = "10.0.1.0/24"
|
|
)
|
|
|
|
// InfraConfig holds infrastructure configuration.
|
|
type InfraConfig struct {
|
|
Zone string
|
|
Plan string
|
|
SSHPublicKey string
|
|
S3SecretKey string
|
|
|
|
// Infrastructure naming — derived from configs/appview.yaml.tmpl.
|
|
// Edit that template to rebrand.
|
|
ClientName string
|
|
BaseDomain string
|
|
RegistryDomains []string
|
|
RepoURL string
|
|
RepoBranch string
|
|
}
|
|
|
|
// Naming returns a Naming helper derived from ClientName.
|
|
func (c *InfraConfig) Naming() Naming {
|
|
return Naming{ClientName: c.ClientName}
|
|
}
|
|
|
|
func loadConfig(zone, plan, sshKeyPath, s3Secret string) (*InfraConfig, error) {
|
|
sshKey, err := readSSHPublicKey(sshKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientName, baseDomain, registryDomains, err := extractFromAppviewTemplate()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("extract config from template: %w", err)
|
|
}
|
|
|
|
return &InfraConfig{
|
|
Zone: zone,
|
|
Plan: plan,
|
|
SSHPublicKey: sshKey,
|
|
S3SecretKey: s3Secret,
|
|
ClientName: clientName,
|
|
BaseDomain: baseDomain,
|
|
RegistryDomains: registryDomains,
|
|
RepoURL: repoURL,
|
|
RepoBranch: repoBranch,
|
|
}, nil
|
|
}
|
|
|
|
// extractFromAppviewTemplate renders the appview config template with
|
|
// zero-value ConfigValues and parses the resulting YAML to extract
|
|
// deployment-specific values. The template is the single source of truth.
|
|
func extractFromAppviewTemplate() (clientName, baseDomain string, registryDomains []string, err error) {
|
|
rendered, err := renderConfig(appviewConfigTmpl, &ConfigValues{})
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("render appview template: %w", err)
|
|
}
|
|
|
|
var cfg struct {
|
|
Server struct {
|
|
BaseURL string `yaml:"base_url"`
|
|
ClientName string `yaml:"client_name"`
|
|
RegistryDomains []string `yaml:"registry_domains"`
|
|
} `yaml:"server"`
|
|
}
|
|
if err := yaml.Unmarshal([]byte(rendered), &cfg); err != nil {
|
|
return "", "", nil, fmt.Errorf("parse appview template YAML: %w", err)
|
|
}
|
|
|
|
clientName = strings.ToLower(cfg.Server.ClientName)
|
|
baseDomain = strings.TrimPrefix(cfg.Server.BaseURL, "https://")
|
|
registryDomains = cfg.Server.RegistryDomains
|
|
|
|
return clientName, baseDomain, registryDomains, nil
|
|
}
|
|
|
|
// readSSHPublicKey reads an SSH public key from a file path.
|
|
func readSSHPublicKey(path string) (string, error) {
|
|
if path == "" {
|
|
return "", fmt.Errorf("--ssh-key is required (path to SSH public key file)")
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("read SSH public key %s: %w", path, err)
|
|
}
|
|
key := strings.TrimSpace(string(data))
|
|
if key == "" {
|
|
return "", fmt.Errorf("SSH public key file %s is empty", path)
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// resolveInteractive fills in any empty Zone/Plan fields by launching
|
|
// interactive TUI pickers that query the UpCloud API.
|
|
func resolveInteractive(ctx context.Context, svc *service.Service, cfg *InfraConfig) error {
|
|
if cfg.Zone == "" {
|
|
z, err := pickZone(ctx, svc)
|
|
if err != nil {
|
|
return fmt.Errorf("zone picker: %w", err)
|
|
}
|
|
cfg.Zone = z
|
|
}
|
|
if cfg.Plan == "" {
|
|
p, err := pickPlan(ctx, svc)
|
|
if err != nil {
|
|
return fmt.Errorf("plan picker: %w", err)
|
|
}
|
|
cfg.Plan = p
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// newService creates an UpCloud API client. If token is non-empty it's used
|
|
// directly; otherwise credentials are read from UPCLOUD_TOKEN env var.
|
|
func newService(token string) (*service.Service, error) {
|
|
var c *client.Client
|
|
var err error
|
|
if token != "" {
|
|
c = client.New("", "", client.WithBearerAuth(token), client.WithTimeout(120*time.Second))
|
|
} else {
|
|
c, err = client.NewFromEnv(client.WithTimeout(120 * time.Second))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create UpCloud client: %w\n\nPass --token or set UPCLOUD_TOKEN", err)
|
|
}
|
|
}
|
|
return service.New(c), nil
|
|
}
|