388 lines
11 KiB
Go
388 lines
11 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"`
|
|
}
|
|
|
|
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"`
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|