211 lines
6.8 KiB
Go
211 lines
6.8 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/iceBear67/simplepages/api"
|
|
)
|
|
|
|
// ---------------------------------------------------------------- identity
|
|
|
|
// WhoAmI describes the credential the client is using.
|
|
func (c *Client) WhoAmI(ctx context.Context) (api.WhoAmI, error) {
|
|
var out api.WhoAmI
|
|
err := c.do(ctx, http.MethodGet, api.PathWhoAmI(), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// SystemInfo returns server-wide counters. Admin scope only.
|
|
func (c *Client) SystemInfo(ctx context.Context) (api.SystemInfo, error) {
|
|
var out api.SystemInfo
|
|
err := c.do(ctx, http.MethodGet, api.PathSystemInfo(), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ---------------------------------------------------------------- projects
|
|
|
|
// CreateProject creates a project. Admin scope only.
|
|
func (c *Client) CreateProject(ctx context.Context, req api.CreateProjectRequest) (api.Project, error) {
|
|
var out api.Project
|
|
err := c.do(ctx, http.MethodPost, api.PathProjects(), req, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ListOptions pages a listing endpoint. A zero Limit takes the server default.
|
|
type ListOptions struct {
|
|
Limit int
|
|
Cursor string
|
|
}
|
|
|
|
func (o ListOptions) query() string {
|
|
q := url.Values{}
|
|
if o.Limit > 0 {
|
|
q.Set("limit", strconv.Itoa(o.Limit))
|
|
}
|
|
if o.Cursor != "" {
|
|
q.Set("cursor", o.Cursor)
|
|
}
|
|
if len(q) == 0 {
|
|
return ""
|
|
}
|
|
return "?" + q.Encode()
|
|
}
|
|
|
|
// ListProjects returns one page of projects. Admin scope only.
|
|
func (c *Client) ListProjects(ctx context.Context, opts ListOptions) (api.ProjectList, error) {
|
|
var out api.ProjectList
|
|
err := c.do(ctx, http.MethodGet, api.PathProjects()+opts.query(), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ListAllProjects follows the cursor to the end.
|
|
//
|
|
// The CLI pages on the user's behalf because "pages project list" that silently
|
|
// showed the first hundred of three hundred projects would be a lie; a caller
|
|
// that wants one page asks for ListProjects.
|
|
func (c *Client) ListAllProjects(ctx context.Context) ([]api.Project, error) {
|
|
var all []api.Project
|
|
opts := ListOptions{Limit: 500}
|
|
for {
|
|
page, err := c.ListProjects(ctx, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
all = append(all, page.Projects...)
|
|
if page.NextCursor == "" || len(page.Projects) == 0 {
|
|
return all, nil
|
|
}
|
|
opts.Cursor = page.NextCursor
|
|
}
|
|
}
|
|
|
|
// GetProject reads one project. A project-scoped key may read only its own.
|
|
func (c *Client) GetProject(ctx context.Context, name string) (api.Project, error) {
|
|
var out api.Project
|
|
err := c.do(ctx, http.MethodGet, api.PathProject(name), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// PatchProject applies a partial update. Admin scope only.
|
|
func (c *Client) PatchProject(ctx context.Context, name string, patch api.ProjectPatch) (api.Project, error) {
|
|
var out api.Project
|
|
err := c.do(ctx, http.MethodPatch, api.PathProject(name), patch, &out)
|
|
return out, err
|
|
}
|
|
|
|
// DeleteProject removes a project and everything under it. Admin scope only.
|
|
func (c *Client) DeleteProject(ctx context.Context, name string) error {
|
|
return c.do(ctx, http.MethodDelete, api.PathProject(name), nil, nil)
|
|
}
|
|
|
|
// -------------------------------------------------------------------- keys
|
|
|
|
// CreateAdminKey mints an admin key. Admin scope only.
|
|
//
|
|
// The returned token is the only copy that will ever exist; the caller shows it
|
|
// once and must not log it.
|
|
func (c *Client) CreateAdminKey(ctx context.Context, req api.CreateKeyRequest) (api.CreateKeyResponse, error) {
|
|
var out api.CreateKeyResponse
|
|
err := c.do(ctx, http.MethodPost, api.PathKeys(), req, &out)
|
|
return out, err
|
|
}
|
|
|
|
// CreateProjectKey mints a key scoped to one project. Admin scope only.
|
|
func (c *Client) CreateProjectKey(ctx context.Context, project string, req api.CreateKeyRequest) (api.CreateKeyResponse, error) {
|
|
var out api.CreateKeyResponse
|
|
err := c.do(ctx, http.MethodPost, api.PathProjectKeys(project), req, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ListKeys returns every key on the server. Admin scope only.
|
|
func (c *Client) ListKeys(ctx context.Context) (api.KeyList, error) {
|
|
var out api.KeyList
|
|
err := c.do(ctx, http.MethodGet, api.PathKeys(), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ListProjectKeys returns the keys of one project.
|
|
func (c *Client) ListProjectKeys(ctx context.Context, project string) (api.KeyList, error) {
|
|
var out api.KeyList
|
|
err := c.do(ctx, http.MethodGet, api.PathProjectKeys(project), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// RevokeKey revokes a key by id. It takes effect immediately, and is
|
|
// idempotent: revoking an already-revoked key succeeds.
|
|
func (c *Client) RevokeKey(ctx context.Context, keyID string) error {
|
|
return c.do(ctx, http.MethodDelete, api.PathKey(keyID), nil, nil)
|
|
}
|
|
|
|
// ------------------------------------------------------------- deployments
|
|
|
|
// DeploymentListOptions selects and pages a project's deployments. A zero State
|
|
// lists every state.
|
|
type DeploymentListOptions struct {
|
|
ListOptions
|
|
State string
|
|
}
|
|
|
|
func (o DeploymentListOptions) query() string {
|
|
q := url.Values{}
|
|
if o.Limit > 0 {
|
|
q.Set("limit", strconv.Itoa(o.Limit))
|
|
}
|
|
if o.Cursor != "" {
|
|
q.Set("cursor", o.Cursor)
|
|
}
|
|
if o.State != "" {
|
|
q.Set("state", o.State)
|
|
}
|
|
if len(q) == 0 {
|
|
return ""
|
|
}
|
|
return "?" + q.Encode()
|
|
}
|
|
|
|
// ListDeployments returns one page of a project's deployments, newest first.
|
|
func (c *Client) ListDeployments(ctx context.Context, project string, opts DeploymentListOptions) (api.DeploymentList, error) {
|
|
var out api.DeploymentList
|
|
err := c.do(ctx, http.MethodGet, api.PathDeployments(project)+opts.query(), nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// GetDeployment reads one deployment. withFiles asks for its manifest too,
|
|
// which for a large site is a great deal more response than the rest of it.
|
|
func (c *Client) GetDeployment(ctx context.Context, project, id string, withFiles bool) (api.Deployment, error) {
|
|
var out api.Deployment
|
|
path := api.PathDeployment(project, id)
|
|
if withFiles {
|
|
path += "?files=true"
|
|
}
|
|
err := c.do(ctx, http.MethodGet, path, nil, &out)
|
|
return out, err
|
|
}
|
|
|
|
// DeleteDeployment removes a deployment and its files. The active one cannot be
|
|
// deleted; the server answers deployment_active until something else is
|
|
// activated.
|
|
func (c *Client) DeleteDeployment(ctx context.Context, project, id string) error {
|
|
return c.do(ctx, http.MethodDelete, api.PathDeployment(project, id), nil, nil)
|
|
}
|
|
|
|
// ---------------------------------------------------------------- upkeep
|
|
|
|
// Collect runs a garbage collection pass. Admin scope only.
|
|
func (c *Client) Collect(ctx context.Context, dryRun bool) (api.GCStats, error) {
|
|
var out api.GCStats
|
|
err := c.do(ctx, http.MethodPost, api.PathGC(), api.GCRequest{DryRun: dryRun}, &out)
|
|
return out, err
|
|
}
|
|
|
|
// Fsck checks the blob reference counts against the manifests, optionally
|
|
// correcting them. Admin scope only.
|
|
func (c *Client) Fsck(ctx context.Context, repair bool) (api.FsckReport, error) {
|
|
var out api.FsckReport
|
|
err := c.do(ctx, http.MethodPost, api.PathFsck(), api.FsckRequest{Repair: repair}, &out)
|
|
return out, err
|
|
}
|