refactor: replace fmt.Errorf and errors.New with coreerr.E from go-log
Replace all remaining fmt.Errorf and errors.New calls in production code with structured coreerr.E(op, msg, err) from forge.lthn.ai/core/go-log. Covers 10 files across the infra package and cmd/prod and cmd/monitor. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
6beb06686a
commit
9a24df3d5f
10 changed files with 55 additions and 50 deletions
24
client.go
24
client.go
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetryConfig controls exponential backoff retry behaviour.
|
// RetryConfig controls exponential backoff retry behaviour.
|
||||||
|
|
@ -105,7 +107,7 @@ func (a *APIClient) Do(req *http.Request, result any) error {
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
resp, err := a.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("%s: %w", a.prefix, err)
|
lastErr = coreerr.E(a.prefix, "request failed", err)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +117,7 @@ func (a *APIClient) Do(req *http.Request, result any) error {
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("read response: %w", err)
|
lastErr = coreerr.E("client.Do", "read response", err)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +131,7 @@ func (a *APIClient) Do(req *http.Request, result any) error {
|
||||||
a.blockedUntil = time.Now().Add(retryAfter)
|
a.blockedUntil = time.Now().Add(retryAfter)
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
lastErr = fmt.Errorf("%s %d: rate limited", a.prefix, resp.StatusCode)
|
lastErr = coreerr.E(a.prefix, fmt.Sprintf("rate limited: HTTP %d", resp.StatusCode), nil)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
|
|
@ -142,7 +144,7 @@ func (a *APIClient) Do(req *http.Request, result any) error {
|
||||||
|
|
||||||
// Server errors are retryable.
|
// Server errors are retryable.
|
||||||
if resp.StatusCode >= 500 {
|
if resp.StatusCode >= 500 {
|
||||||
lastErr = fmt.Errorf("%s %d: %s", a.prefix, resp.StatusCode, string(data))
|
lastErr = coreerr.E(a.prefix, fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(data)), nil)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -151,13 +153,13 @@ func (a *APIClient) Do(req *http.Request, result any) error {
|
||||||
|
|
||||||
// Client errors (4xx, except 429 handled above) are not retried.
|
// Client errors (4xx, except 429 handled above) are not retried.
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
return fmt.Errorf("%s %d: %s", a.prefix, resp.StatusCode, string(data))
|
return coreerr.E(a.prefix, fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(data)), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success — decode if requested.
|
// Success — decode if requested.
|
||||||
if result != nil {
|
if result != nil {
|
||||||
if err := json.Unmarshal(data, result); err != nil {
|
if err := json.Unmarshal(data, result); err != nil {
|
||||||
return fmt.Errorf("decode response: %w", err)
|
return coreerr.E("client.Do", "decode response", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -191,7 +193,7 @@ func (a *APIClient) DoRaw(req *http.Request) ([]byte, error) {
|
||||||
|
|
||||||
resp, err := a.client.Do(req)
|
resp, err := a.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("%s: %w", a.prefix, err)
|
lastErr = coreerr.E(a.prefix, "request failed", err)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +203,7 @@ func (a *APIClient) DoRaw(req *http.Request) ([]byte, error) {
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("read response: %w", err)
|
lastErr = coreerr.E("client.DoRaw", "read response", err)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +216,7 @@ func (a *APIClient) DoRaw(req *http.Request) ([]byte, error) {
|
||||||
a.blockedUntil = time.Now().Add(retryAfter)
|
a.blockedUntil = time.Now().Add(retryAfter)
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
lastErr = fmt.Errorf("%s %d: rate limited", a.prefix, resp.StatusCode)
|
lastErr = coreerr.E(a.prefix, fmt.Sprintf("rate limited: HTTP %d", resp.StatusCode), nil)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
|
|
@ -226,7 +228,7 @@ func (a *APIClient) DoRaw(req *http.Request) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode >= 500 {
|
if resp.StatusCode >= 500 {
|
||||||
lastErr = fmt.Errorf("%s %d: %s", a.prefix, resp.StatusCode, string(data))
|
lastErr = coreerr.E(a.prefix, fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(data)), nil)
|
||||||
if attempt < attempts-1 {
|
if attempt < attempts-1 {
|
||||||
a.backoff(attempt, req)
|
a.backoff(attempt, req)
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +236,7 @@ func (a *APIClient) DoRaw(req *http.Request) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
return nil, fmt.Errorf("%s %d: %s", a.prefix, resp.StatusCode, string(data))
|
return nil, coreerr.E(a.prefix, fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(data)), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|
|
||||||
23
cloudns.go
23
cloudns.go
|
|
@ -3,10 +3,11 @@ package infra
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cloudnsBaseURL = "https://api.cloudns.net"
|
const cloudnsBaseURL = "https://api.cloudns.net"
|
||||||
|
|
@ -81,7 +82,7 @@ func (c *CloudNSClient) ListRecords(ctx context.Context, domain string) (map[str
|
||||||
|
|
||||||
var records map[string]CloudNSRecord
|
var records map[string]CloudNSRecord
|
||||||
if err := json.Unmarshal(data, &records); err != nil {
|
if err := json.Unmarshal(data, &records); err != nil {
|
||||||
return nil, fmt.Errorf("parse records: %w", err)
|
return nil, coreerr.E("CloudNSClient.ListRecords", "parse records", err)
|
||||||
}
|
}
|
||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
@ -108,11 +109,11 @@ func (c *CloudNSClient) CreateRecord(ctx context.Context, domain, host, recordTy
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &result); err != nil {
|
if err := json.Unmarshal(data, &result); err != nil {
|
||||||
return "", fmt.Errorf("parse response: %w", err)
|
return "", coreerr.E("CloudNSClient.CreateRecord", "parse response", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Status != "Success" {
|
if result.Status != "Success" {
|
||||||
return "", fmt.Errorf("cloudns: %s", result.StatusDescription)
|
return "", coreerr.E("CloudNSClient.CreateRecord", result.StatusDescription, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return strconv.Itoa(result.Data.ID), nil
|
return strconv.Itoa(result.Data.ID), nil
|
||||||
|
|
@ -138,11 +139,11 @@ func (c *CloudNSClient) UpdateRecord(ctx context.Context, domain, recordID, host
|
||||||
StatusDescription string `json:"statusDescription"`
|
StatusDescription string `json:"statusDescription"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &result); err != nil {
|
if err := json.Unmarshal(data, &result); err != nil {
|
||||||
return fmt.Errorf("parse response: %w", err)
|
return coreerr.E("CloudNSClient.UpdateRecord", "parse response", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Status != "Success" {
|
if result.Status != "Success" {
|
||||||
return fmt.Errorf("cloudns: %s", result.StatusDescription)
|
return coreerr.E("CloudNSClient.UpdateRecord", result.StatusDescription, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -164,11 +165,11 @@ func (c *CloudNSClient) DeleteRecord(ctx context.Context, domain, recordID strin
|
||||||
StatusDescription string `json:"statusDescription"`
|
StatusDescription string `json:"statusDescription"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &result); err != nil {
|
if err := json.Unmarshal(data, &result); err != nil {
|
||||||
return fmt.Errorf("parse response: %w", err)
|
return coreerr.E("CloudNSClient.DeleteRecord", "parse response", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Status != "Success" {
|
if result.Status != "Success" {
|
||||||
return fmt.Errorf("cloudns: %s", result.StatusDescription)
|
return coreerr.E("CloudNSClient.DeleteRecord", result.StatusDescription, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -179,7 +180,7 @@ func (c *CloudNSClient) DeleteRecord(ctx context.Context, domain, recordID strin
|
||||||
func (c *CloudNSClient) EnsureRecord(ctx context.Context, domain, host, recordType, value string, ttl int) (bool, error) {
|
func (c *CloudNSClient) EnsureRecord(ctx context.Context, domain, host, recordType, value string, ttl int) (bool, error) {
|
||||||
records, err := c.ListRecords(ctx, domain)
|
records, err := c.ListRecords(ctx, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("list records: %w", err)
|
return false, coreerr.E("CloudNSClient.EnsureRecord", "list records", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if record already exists
|
// Check if record already exists
|
||||||
|
|
@ -190,7 +191,7 @@ func (c *CloudNSClient) EnsureRecord(ctx context.Context, domain, host, recordTy
|
||||||
}
|
}
|
||||||
// Update existing record
|
// Update existing record
|
||||||
if err := c.UpdateRecord(ctx, domain, id, host, recordType, value, ttl); err != nil {
|
if err := c.UpdateRecord(ctx, domain, id, host, recordType, value, ttl); err != nil {
|
||||||
return false, fmt.Errorf("update record: %w", err)
|
return false, coreerr.E("CloudNSClient.EnsureRecord", "update record", err)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +199,7 @@ func (c *CloudNSClient) EnsureRecord(ctx context.Context, domain, host, recordTy
|
||||||
|
|
||||||
// Create new record
|
// Create new record
|
||||||
if _, err := c.CreateRecord(ctx, domain, host, recordType, value, ttl); err != nil {
|
if _, err := c.CreateRecord(ctx, domain, host, recordType, value, ttl); err != nil {
|
||||||
return false, fmt.Errorf("create record: %w", err)
|
return false, coreerr.E("CloudNSClient.EnsureRecord", "create record", err)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -582,5 +582,5 @@ func parseGitHubRepo(url string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("could not parse GitHub repo from URL: %s", url)
|
return "", log.E("monitor.parseGitHubRepo", "could not parse GitHub repo from URL: "+url, nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ package prod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"forge.lthn.ai/core/go-infra"
|
"forge.lthn.ai/core/go-infra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -56,7 +55,7 @@ func getDNSClient() (*infra.CloudNSClient, error) {
|
||||||
authID := os.Getenv("CLOUDNS_AUTH_ID")
|
authID := os.Getenv("CLOUDNS_AUTH_ID")
|
||||||
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
|
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
|
||||||
if authID == "" || authPass == "" {
|
if authID == "" || authPass == "" {
|
||||||
return nil, errors.New("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
|
return nil, coreerr.E("prod.getDNSClient", "CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required", nil)
|
||||||
}
|
}
|
||||||
return infra.NewCloudNSClient(authID, authPass), nil
|
return infra.NewCloudNSClient(authID, authPass), nil
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +76,7 @@ func runDNSList(cmd *cli.Command, args []string) error {
|
||||||
|
|
||||||
records, err := dns.ListRecords(ctx, zone)
|
records, err := dns.ListRecords(ctx, zone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list records: %w", err)
|
return coreerr.E("prod.runDNSList", "list records", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Print("%s DNS records for %s\n\n", cli.BoldStyle.Render("▶"), cli.TitleStyle.Render(zone))
|
cli.Print("%s DNS records for %s\n\n", cli.BoldStyle.Render("▶"), cli.TitleStyle.Render(zone))
|
||||||
|
|
@ -114,7 +113,7 @@ func runDNSSet(cmd *cli.Command, args []string) error {
|
||||||
|
|
||||||
changed, err := dns.EnsureRecord(ctx, dnsZone, host, recordType, value, dnsTTL)
|
changed, err := dns.EnsureRecord(ctx, dnsZone, host, recordType, value, dnsTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("set record: %w", err)
|
return coreerr.E("prod.runDNSSet", "set record", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package prod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"forge.lthn.ai/core/go-infra"
|
"forge.lthn.ai/core/go-infra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ func init() {
|
||||||
func getHCloudClient() (*infra.HCloudClient, error) {
|
func getHCloudClient() (*infra.HCloudClient, error) {
|
||||||
token := os.Getenv("HCLOUD_TOKEN")
|
token := os.Getenv("HCLOUD_TOKEN")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return nil, errors.New("HCLOUD_TOKEN environment variable required")
|
return nil, coreerr.E("prod.getHCloudClient", "HCLOUD_TOKEN environment variable required", nil)
|
||||||
}
|
}
|
||||||
return infra.NewHCloudClient(token), nil
|
return infra.NewHCloudClient(token), nil
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ func runLBStatus(cmd *cli.Command, args []string) error {
|
||||||
|
|
||||||
lbs, err := hc.ListLoadBalancers(ctx)
|
lbs, err := hc.ListLoadBalancers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list load balancers: %w", err)
|
return coreerr.E("prod.runLBStatus", "list load balancers", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(lbs) == 0 {
|
if len(lbs) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ package prod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"forge.lthn.ai/core/go-infra"
|
"forge.lthn.ai/core/go-infra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,7 +70,7 @@ func runSetup(cmd *cli.Command, args []string) error {
|
||||||
|
|
||||||
if err := step.fn(ctx, cfg); err != nil {
|
if err := step.fn(ctx, cfg); err != nil {
|
||||||
cli.Print(" %s %s: %s\n", cli.ErrorStyle.Render("✗"), step.name, err)
|
cli.Print(" %s %s: %s\n", cli.ErrorStyle.Render("✗"), step.name, err)
|
||||||
return fmt.Errorf("step %s failed: %w", step.name, err)
|
return coreerr.E("prod.setup", "step "+step.name+" failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Print(" %s %s complete\n", cli.SuccessStyle.Render("✓"), step.name)
|
cli.Print(" %s %s complete\n", cli.SuccessStyle.Render("✓"), step.name)
|
||||||
|
|
@ -90,7 +89,7 @@ func stepDiscover(ctx context.Context, cfg *infra.Config) error {
|
||||||
hc := infra.NewHCloudClient(hcloudToken)
|
hc := infra.NewHCloudClient(hcloudToken)
|
||||||
servers, err := hc.ListServers(ctx)
|
servers, err := hc.ListServers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list HCloud servers: %w", err)
|
return coreerr.E("prod.stepDiscover", "list HCloud servers", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
|
|
@ -115,7 +114,7 @@ func stepDiscover(ctx context.Context, cfg *infra.Config) error {
|
||||||
hr := infra.NewHRobotClient(robotUser, robotPass)
|
hr := infra.NewHRobotClient(robotUser, robotPass)
|
||||||
servers, err := hr.ListServers(ctx)
|
servers, err := hr.ListServers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list Robot servers: %w", err)
|
return coreerr.E("prod.stepDiscover", "list Robot servers", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
|
|
@ -141,7 +140,7 @@ func stepDiscover(ctx context.Context, cfg *infra.Config) error {
|
||||||
func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
|
func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
|
||||||
hcloudToken := os.Getenv("HCLOUD_TOKEN")
|
hcloudToken := os.Getenv("HCLOUD_TOKEN")
|
||||||
if hcloudToken == "" {
|
if hcloudToken == "" {
|
||||||
return errors.New("HCLOUD_TOKEN required for load balancer management")
|
return coreerr.E("prod.stepLoadBalancer", "HCLOUD_TOKEN required for load balancer management", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
hc := infra.NewHCloudClient(hcloudToken)
|
hc := infra.NewHCloudClient(hcloudToken)
|
||||||
|
|
@ -149,7 +148,7 @@ func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
|
||||||
// Check if LB already exists
|
// Check if LB already exists
|
||||||
lbs, err := hc.ListLoadBalancers(ctx)
|
lbs, err := hc.ListLoadBalancers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list load balancers: %w", err)
|
return coreerr.E("prod.stepLoadBalancer", "list load balancers", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lb := range lbs {
|
for _, lb := range lbs {
|
||||||
|
|
@ -176,7 +175,7 @@ func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
|
||||||
for _, b := range cfg.LoadBalancer.Backends {
|
for _, b := range cfg.LoadBalancer.Backends {
|
||||||
host, ok := cfg.Hosts[b.Host]
|
host, ok := cfg.Hosts[b.Host]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("backend host '%s' not found in config", b.Host)
|
return coreerr.E("prod.stepLoadBalancer", "backend host '"+b.Host+"' not found in config", nil)
|
||||||
}
|
}
|
||||||
targets = append(targets, infra.HCloudLBCreateTarget{
|
targets = append(targets, infra.HCloudLBCreateTarget{
|
||||||
Type: "ip",
|
Type: "ip",
|
||||||
|
|
@ -224,7 +223,7 @@ func stepLoadBalancer(ctx context.Context, cfg *infra.Config) error {
|
||||||
|
|
||||||
lb, err := hc.CreateLoadBalancer(ctx, req)
|
lb, err := hc.CreateLoadBalancer(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create load balancer: %w", err)
|
return coreerr.E("prod.stepLoadBalancer", "create load balancer", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Print(" Created: %s (ID: %d, IP: %s)\n",
|
cli.Print(" Created: %s (ID: %d, IP: %s)\n",
|
||||||
|
|
@ -237,7 +236,7 @@ func stepDNS(ctx context.Context, cfg *infra.Config) error {
|
||||||
authID := os.Getenv("CLOUDNS_AUTH_ID")
|
authID := os.Getenv("CLOUDNS_AUTH_ID")
|
||||||
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
|
authPass := os.Getenv("CLOUDNS_AUTH_PASSWORD")
|
||||||
if authID == "" || authPass == "" {
|
if authID == "" || authPass == "" {
|
||||||
return errors.New("CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required")
|
return coreerr.E("prod.stepDNS", "CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD required", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := infra.NewCloudNSClient(authID, authPass)
|
dns := infra.NewCloudNSClient(authID, authPass)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sshCmd = &cli.Command{
|
var sshCmd = &cli.Command{
|
||||||
|
|
@ -37,7 +38,7 @@ func runSSH(cmd *cli.Command, args []string) error {
|
||||||
for n, h := range cfg.Hosts {
|
for n, h := range cfg.Hosts {
|
||||||
cli.Print(" %s %s (%s)\n", cli.BoldStyle.Render(n), h.IP, h.Role)
|
cli.Print(" %s %s (%s)\n", cli.BoldStyle.Render(n), h.IP, h.Role)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("host '%s' not found in infra.yaml", name)
|
return coreerr.E("prod.ssh", "host '"+name+"' not found in infra.yaml", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
sshArgs := []string{
|
sshArgs := []string{
|
||||||
|
|
@ -55,7 +56,7 @@ func runSSH(cmd *cli.Command, args []string) error {
|
||||||
|
|
||||||
sshPath, err := exec.LookPath("ssh")
|
sshPath, err := exec.LookPath("ssh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ssh not found: %w", err)
|
return coreerr.E("prod.ssh", "ssh not found", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace current process with SSH
|
// Replace current process with SSH
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"forge.lthn.ai/core/go-ansible"
|
"forge.lthn.ai/core/go-ansible"
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"forge.lthn.ai/core/go-infra"
|
"forge.lthn.ai/core/go-infra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -114,14 +115,14 @@ func checkHost(ctx context.Context, name string, host *infra.Host) hostStatus {
|
||||||
|
|
||||||
client, err := ansible.NewSSHClient(sshCfg)
|
client, err := ansible.NewSSHClient(sshCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Error = fmt.Errorf("create SSH client: %w", err)
|
s.Error = coreerr.E("prod.checkHost", "create SSH client", err)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
defer func() { _ = client.Close() }()
|
defer func() { _ = client.Close() }()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := client.Connect(ctx); err != nil {
|
if err := client.Connect(ctx); err != nil {
|
||||||
s.Error = fmt.Errorf("SSH connect: %w", err)
|
s.Error = coreerr.E("prod.checkHost", "SSH connect", err)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
s.Connected = true
|
s.Connected = true
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
package infra
|
package infra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
coreio "forge.lthn.ai/core/go-io"
|
coreio "forge.lthn.ai/core/go-io"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
@ -232,12 +232,12 @@ type BackupJob struct {
|
||||||
func Load(path string) (*Config, error) {
|
func Load(path string) (*Config, error) {
|
||||||
data, err := coreio.Local.Read(path)
|
data, err := coreio.Local.Read(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read infra config: %w", err)
|
return nil, coreerr.E("infra.Load", "read infra config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg Config
|
var cfg Config
|
||||||
if err := yaml.Unmarshal([]byte(data), &cfg); err != nil {
|
if err := yaml.Unmarshal([]byte(data), &cfg); err != nil {
|
||||||
return nil, fmt.Errorf("parse infra config: %w", err)
|
return nil, coreerr.E("infra.Load", "parse infra config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand SSH key paths
|
// Expand SSH key paths
|
||||||
|
|
@ -269,7 +269,7 @@ func Discover(startDir string) (*Config, string, error) {
|
||||||
}
|
}
|
||||||
dir = parent
|
dir = parent
|
||||||
}
|
}
|
||||||
return nil, "", fmt.Errorf("infra.yaml not found (searched from %s)", startDir)
|
return nil, "", coreerr.E("infra.Discover", "infra.yaml not found (searched from "+startDir+")", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostsByRole returns all hosts matching the given role.
|
// HostsByRole returns all hosts matching the given role.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -210,7 +212,7 @@ func (c *HCloudClient) GetLoadBalancer(ctx context.Context, id int) (*HCloudLoad
|
||||||
func (c *HCloudClient) CreateLoadBalancer(ctx context.Context, req HCloudLBCreateRequest) (*HCloudLoadBalancer, error) {
|
func (c *HCloudClient) CreateLoadBalancer(ctx context.Context, req HCloudLBCreateRequest) (*HCloudLoadBalancer, error) {
|
||||||
body, err := json.Marshal(req)
|
body, err := json.Marshal(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal request: %w", err)
|
return nil, coreerr.E("HCloudClient.CreateLoadBalancer", "marshal request", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue