clonepack/client/client.go
GnomeZworc c4776d81dd
add apt gestion
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-04-25 19:03:26 +02:00

417 lines
12 KiB
Go

package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)
var ErrNotFound = errors.New("repo not found")
type Client struct {
baseURL string
httpClient *http.Client
}
func New(baseURL string) *Client {
return &Client{
baseURL: baseURL,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
}
type CreateRepoInput struct {
Name string `json:"name"`
Type string `json:"type"`
SourceURL string `json:"source_url"`
SyncMode string `json:"sync_mode,omitempty"`
AptSuite string `json:"apt_suite,omitempty"`
AptComponents []string `json:"apt_components,omitempty"`
AptArchitectures []string `json:"apt_architectures,omitempty"`
}
type Repo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
SourceURL string `json:"source_url"`
Frozen bool `json:"frozen"`
SyncMode string `json:"sync_mode"`
AptSuite string `json:"apt_suite,omitempty"`
AptComponents []string `json:"apt_components,omitempty"`
AptArchitectures []string `json:"apt_architectures,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type listReposResponse struct {
Items []Repo `json:"items"`
Total int `json:"total"`
}
func (c *Client) CreateRepo(ctx context.Context, in CreateRepoInput) (*Repo, error) {
body, _ := json.Marshal(in)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/v1/repos", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
var repo Repo
if err := c.do(req, &repo); err != nil {
return nil, err
}
return &repo, nil
}
func (c *Client) ListRepos(ctx context.Context) ([]Repo, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/v1/repos", nil)
if err != nil {
return nil, err
}
var resp listReposResponse
if err := c.do(req, &resp); err != nil {
return nil, err
}
return resp.Items, nil
}
func (c *Client) GetRepo(ctx context.Context, id int64) (*Repo, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/repos/%d", c.baseURL, id), nil)
if err != nil {
return nil, err
}
var repo Repo
if err := c.do(req, &repo); err != nil {
return nil, err
}
return &repo, nil
}
type UpdateRepoInput struct {
SourceURL *string `json:"source_url,omitempty"`
SyncMode *string `json:"sync_mode,omitempty"`
AptSuite *string `json:"apt_suite,omitempty"`
AptComponents []string `json:"apt_components,omitempty"`
AptArchitectures []string `json:"apt_architectures,omitempty"`
}
func (c *Client) UpdateRepo(ctx context.Context, id int64, in UpdateRepoInput) (*Repo, error) {
body, _ := json.Marshal(in)
req, err := http.NewRequestWithContext(ctx, http.MethodPatch,
fmt.Sprintf("%s/api/v1/repos/%d", c.baseURL, id), bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
var repo Repo
if err := c.do(req, &repo); err != nil {
return nil, err
}
return &repo, nil
}
func (c *Client) DeleteRepo(ctx context.Context, id int64) error {
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/api/v1/repos/%d", c.baseURL, id), nil)
if err != nil {
return err
}
return c.do(req, nil)
}
type StartCloneResponse struct {
JobID int64 `json:"job_id"`
Status string `json:"status"`
}
type CloneStatus struct {
JobID int64 `json:"job_id"`
RepoID int64 `json:"repo_id"`
Status string `json:"status"`
StartedAt *string `json:"started_at,omitempty"`
FinishedAt *string `json:"finished_at,omitempty"`
Error *string `json:"error,omitempty"`
CreatedAt string `json:"created_at"`
}
func (c *Client) StartClone(ctx context.Context, repoID int64) (*StartCloneResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/clone", c.baseURL, repoID), nil)
if err != nil {
return nil, err
}
var resp StartCloneResponse
if err := c.do(req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (c *Client) GetCloneStatus(ctx context.Context, repoID int64) (*CloneStatus, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/clone/status", c.baseURL, repoID), nil)
if err != nil {
return nil, err
}
var status CloneStatus
if err := c.do(req, &status); err != nil {
return nil, err
}
return &status, nil
}
type PendingPackage struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
Version string `json:"version"`
Arch string `json:"arch"`
Location string `json:"location"`
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"created_at"`
}
type listPendingResponse struct {
Items []PendingPackage `json:"items"`
Total int `json:"total"`
}
type syncSelectionBody struct {
IDs []int64 `json:"ids"`
}
func (c *Client) TriggerSync(ctx context.Context, repoID int64) error {
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/sync/trigger", c.baseURL, repoID), nil)
if err != nil {
return err
}
return c.do(req, nil)
}
func (c *Client) ListPending(ctx context.Context, repoID int64) ([]PendingPackage, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/sync/pending", c.baseURL, repoID), nil)
if err != nil {
return nil, err
}
var resp listPendingResponse
if err := c.do(req, &resp); err != nil {
return nil, err
}
return resp.Items, nil
}
func (c *Client) ApprovePending(ctx context.Context, repoID int64, ids []int64) error {
body, _ := json.Marshal(syncSelectionBody{IDs: ids})
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/sync/approve", c.baseURL, repoID),
bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return c.do(req, nil)
}
func (c *Client) RejectPending(ctx context.Context, repoID int64, ids []int64) error {
body, _ := json.Marshal(syncSelectionBody{IDs: ids})
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/sync/reject", c.baseURL, repoID),
bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return c.do(req, nil)
}
type Snapshot struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Label string `json:"label"`
CreatedAt time.Time `json:"created_at"`
}
type SnapshotPackage struct {
ID int64 `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Arch string `json:"arch"`
Location string `json:"location"`
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
}
type SnapshotDetail struct {
Snapshot
Packages []SnapshotPackage `json:"packages"`
}
type listSnapshotsResponse struct {
Items []Snapshot `json:"items"`
Total int `json:"total"`
}
type SnapshotDiff struct {
From Snapshot `json:"from"`
To Snapshot `json:"to"`
Added []SnapshotPackage `json:"added"`
Removed []SnapshotPackage `json:"removed"`
Unchanged int `json:"unchanged"`
}
func (c *Client) CreateSnapshot(ctx context.Context, repoID int64, label string) (*Snapshot, error) {
body, _ := json.Marshal(map[string]string{"label": label})
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots", c.baseURL, repoID), bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
var snap Snapshot
if err := c.do(req, &snap); err != nil {
return nil, err
}
return &snap, nil
}
func (c *Client) ListSnapshots(ctx context.Context, repoID int64) ([]Snapshot, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots", c.baseURL, repoID), nil)
if err != nil {
return nil, err
}
var resp listSnapshotsResponse
if err := c.do(req, &resp); err != nil {
return nil, err
}
return resp.Items, nil
}
func (c *Client) GetSnapshot(ctx context.Context, repoID, snapID int64) (*SnapshotDetail, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots/%d", c.baseURL, repoID, snapID), nil)
if err != nil {
return nil, err
}
var detail SnapshotDetail
if err := c.do(req, &detail); err != nil {
return nil, err
}
return &detail, nil
}
func (c *Client) DeleteSnapshot(ctx context.Context, repoID, snapID int64) error {
req, err := http.NewRequestWithContext(ctx, http.MethodDelete,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots/%d", c.baseURL, repoID, snapID), nil)
if err != nil {
return err
}
return c.do(req, nil)
}
func (c *Client) DiffSnapshots(ctx context.Context, repoID, fromID, toID int64) (*SnapshotDiff, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots/diff?from=%d&to=%d", c.baseURL, repoID, fromID, toID), nil)
if err != nil {
return nil, err
}
var diff SnapshotDiff
if err := c.do(req, &diff); err != nil {
return nil, err
}
return &diff, nil
}
func (c *Client) RollbackSnapshot(ctx context.Context, repoID, snapID int64) error {
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/snapshots/%d/rollback", c.baseURL, repoID, snapID), nil)
if err != nil {
return err
}
return c.do(req, nil)
}
type BlockedPackage struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
Location string `json:"location"`
CreatedAt time.Time `json:"created_at"`
}
type listBlockedResponse struct {
Items []BlockedPackage `json:"items"`
Total int `json:"total"`
}
func (c *Client) BlockPackages(ctx context.Context, repoID int64, pendingIDs []int64) error {
body, _ := json.Marshal(syncSelectionBody{IDs: pendingIDs})
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/sync/block", c.baseURL, repoID),
bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return c.do(req, nil)
}
func (c *Client) UnblockPackages(ctx context.Context, repoID int64, blockedIDs []int64) error {
body, _ := json.Marshal(syncSelectionBody{IDs: blockedIDs})
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
fmt.Sprintf("%s/api/v1/repos/%d/sync/unblock", c.baseURL, repoID),
bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
return c.do(req, nil)
}
func (c *Client) ListBlocked(ctx context.Context, repoID int64) ([]BlockedPackage, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
fmt.Sprintf("%s/api/v1/repos/%d/sync/blocked", c.baseURL, repoID), nil)
if err != nil {
return nil, err
}
var resp listBlockedResponse
if err := c.do(req, &resp); err != nil {
return nil, err
}
return resp.Items, nil
}
type errorResponse struct {
Error string `json:"error"`
}
func (c *Client) do(req *http.Request, out any) error {
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return ErrNotFound
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
var errResp errorResponse
json.NewDecoder(resp.Body).Decode(&errResp)
if errResp.Error != "" {
return fmt.Errorf("%s", errResp.Error)
}
return fmt.Errorf("unexpected status %d", resp.StatusCode)
}
if out != nil {
return json.NewDecoder(resp.Body).Decode(out)
}
return nil
}