Files
admin f02ec9977c
Container-Image veröffentlichen / container (push) Has been cancelled
feat: add resilient updates and access controls
2026-08-28 14:02:07 +02:00

85 lines
2.3 KiB
Go

package appupdate
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/updateprotocol"
)
type AgentClient struct {
socket string
}
func NewAgentClient(socket string) *AgentClient {
return &AgentClient{socket: socket}
}
func (c *AgentClient) Start(ctx context.Context, request updateprotocol.Request) error {
encoded, err := json.Marshal(request)
if err != nil {
return err
}
response, err := c.do(ctx, http.MethodPost, "/v1/update", bytes.NewReader(encoded))
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode == http.StatusAccepted {
return nil
}
if response.StatusCode == http.StatusConflict {
return errors.New("ein Software-Update läuft bereits")
}
return fmt.Errorf("Update-Agent antwortet mit HTTP %d", response.StatusCode)
}
func (c *AgentClient) Status(ctx context.Context) (updateprotocol.Status, error) {
response, err := c.do(ctx, http.MethodGet, "/v1/status", nil)
if err != nil {
return updateprotocol.Status{}, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return updateprotocol.Status{}, fmt.Errorf("Update-Agent antwortet mit HTTP %d", response.StatusCode)
}
var status updateprotocol.Status
if err := json.NewDecoder(io.LimitReader(response.Body, 64<<10)).Decode(&status); err != nil {
return updateprotocol.Status{}, fmt.Errorf("Status des Update-Agents lesen: %w", err)
}
return status, nil
}
func (c *AgentClient) do(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
if c == nil || c.socket == "" {
return nil, errors.New("Update-Agent ist nicht konfiguriert")
}
transport := &http.Transport{
DisableKeepAlives: true,
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
dialer := net.Dialer{Timeout: time.Second}
return dialer.DialContext(ctx, "unix", c.socket)
},
}
client := &http.Client{Transport: transport, Timeout: 3 * time.Second}
request, err := http.NewRequestWithContext(ctx, method, "http://update-agent"+path, body)
if err != nil {
return nil, err
}
if body != nil {
request.Header.Set("Content-Type", "application/json")
}
response, err := client.Do(request)
if err != nil {
return nil, errors.New("Update-Agent ist nicht erreichbar")
}
return response, nil
}