client

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: GPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package client is a thin wrapper over the Hamravesh Darkube REST API.

Authentication is a two-part scheme discovered against the live API:

Authorization: Api-key <account-token>
X-Organization: <tenant-slug>

The account token identifies the user; X-Organization scopes every request to one tenant (organization). Requests without a valid X-Organization are rejected with 403 permission_denied even though the token itself is valid.

Index

Constants

View Source
const (
	// CodeSameHelmReleaseName means the name is taken by a Helm release in the
	// target namespace. Deleting an app drops its record immediately but can
	// leave the release behind, so this also fires for names with no app.
	CodeSameHelmReleaseName = "SameHelmReleaseNameExists"
	// CodeTerminatingApp means an app of this name is still being deleted.
	CodeTerminatingApp = "TerminatingAppException"
	// CodeDuplicateReleaseAndNamespace means a live app of this name already
	// exists in the namespace. The API distinguishes this from
	// CodeSameHelmReleaseName, which is the orphaned-release case.
	CodeDuplicateReleaseAndNamespace = "DuplicateReleaseAndNamespaceException"
	// CodeGithubAuth and CodeGitlabAuth mean the git provider has not been
	// connected to this Hamravesh account, so Darkube cannot read the repository
	// it is being asked to build. Creating a git-backed app has an account-level
	// prerequisite that creating an image-backed one does not.
	CodeGithubAuth = "GithubAuthException"
	CodeGitlabAuth = "GitlabAuthException"
)

API error codes worth handling by name. The `detail` that accompanies them is Persian prose, so matching the code is both more robust and more readable.

View Source
const (
	CreationMethodDockerImage = "docker_image"
	CreationMethodGitRepoURL  = "git_repo_url"

	ProviderGithub = "Github"
	ProviderGitlab = "Gitlab"

	BuilderDockerfile      = "dockerfile"
	BuilderHerokuBuildpack = "heroku_buildpacks"

	BuildMethodGitlabCI = "gitlabci"
	BuildMethodWebhook  = "webhook"
)

Valid values for the enumerated build fields, as advertised by OPTIONS on /api/v1/darkube/apps/ (confirmed 2026-08-28).

View Source
const DefaultBaseURL = "https://api.hamravesh.com"

DefaultBaseURL is the public Hamravesh API host.

Variables

View Source
var (
	ErrAppNotFound  = errors.New("no app named or with that id")
	ErrAppAmbiguous = errors.New("app name is ambiguous")
)

Sentinel errors returned by ResolveApp, comparable with errors.Is.

View Source
var ErrNoOrganizationID = errors.New("could not determine the numeric organization id")

ErrNoOrganizationID is returned when the numeric org id can't be derived.

View Source
var ErrNoSuchEnv = errors.New("no such environment variable")

ErrNoSuchEnv is returned when removing an environment variable that is absent.

View Source
var ErrNoSuchHost = errors.New("no such domain")

ErrNoSuchHost is returned when removing a domain the app does not serve.

Functions

func ErrorCode added in v0.2.0

func ErrorCode(err error) string

ErrorCode returns the API error code carried by err, or "" if err is not an *APIError.

func ExternalHosts added in v0.4.0

func ExternalHosts(app map[string]any) []string

ExternalHosts reads the domains routed to an app. This is where custom domains actually live; custom_domain_address is a separate, usually empty field and is not the ingress host list.

func IsNotFound added in v0.5.0

func IsNotFound(err error) bool

IsNotFound reports whether err is an API 404. This is the distinction `wait --for deleted` turns on: "the app is gone" versus "the request failed".

func IsTransient added in v0.5.0

func IsTransient(err error) bool

IsTransient reports whether err is worth retrying: a transport failure, or a 5xx from the API. Polling loops use it so that a flaky minute — which this API does produce — does not abort a wait that would otherwise have succeeded.

func SecretEnvNames added in v0.4.0

func SecretEnvNames(app map[string]any) []string

SecretEnvNames reads the names of an app's secret environment variables.

Only names are ever available: the API stores the values in a vault and returns them empty on every read, so there is nothing to unmask.

func SetEnvVars added in v0.4.0

func SetEnvVars(app map[string]any, envs []EnvVar)

SetEnvVars replaces an app's plain environment variables.

func SetExternalHosts added in v0.4.0

func SetExternalHosts(app map[string]any, hosts []string)

SetExternalHosts replaces the domains routed to an app.

Types

type APIError

type APIError struct {
	StatusCode int    `json:"-"`
	Detail     string `json:"detail"`
	Code       string `json:"code"`
}

APIError is a structured error returned by the API's DRF backend.

func (*APIError) Error

func (e *APIError) Error() string

type Alert added in v0.4.0

type Alert struct {
	ID               string `json:"id"`
	AlertName        string `json:"alertname"`
	Status           string `json:"status"`
	Severity         string `json:"severity"`
	Instance         string `json:"instance"`
	ServiceOwner     string `json:"service_owner"`
	Condition        string `json:"condition"`
	AlertDescription string `json:"alert_description"`
	DescriptionFA    string `json:"description_fa"`
	TimeWindow       string `json:"timewindow"`
	StartsAt         string `json:"starts_at"`
	EndsAt           string `json:"ends_at"`
	ActionLink       string `json:"action_link"`
	ForMe            bool   `json:"for_me"`
}

Alert is one monitoring alert raised against the tenant's resources.

func (Alert) IsFiring added in v0.4.0

func (a Alert) IsFiring() bool

IsFiring reports whether the alert is currently active rather than resolved.

type App

type App struct {
	ID                  string    `json:"id"`
	Name                string    `json:"name"`
	Namespace           Namespace `json:"namespace"`
	State               State     `json:"state"`
	Plan                *Plan     `json:"plan"`
	Replicas            int       `json:"replicas"`
	IsEnabled           bool      `json:"is_enabled"`
	IsDeployable        bool      `json:"is_deployable"`
	IsHPAEnabled        bool      `json:"is_hpa_enabled"`
	RAMLimit            string    `json:"ram_limit"`
	CPURequest          string    `json:"cpu_request"`
	CustomDomainAddress string    `json:"custom_domain_address"`
	EnableSSL           bool      `json:"enable_SSL"`
	ImageRepo           string    `json:"image_repo"`
	ImageTag            string    `json:"image_tag"`
	CreationTime        string    `json:"creation_time"`
	UpdatedAt           string    `json:"updated_at"`

	// CreationMethod is how the app came to exist, and it is the field that
	// separates a plain workload from a managed service. There is no separate
	// "managed services" API: a oneclick Redis, Postgres, Grafana or Prometheus is
	// an ordinary app in this same list, carrying creation_method "redisnew",
	// "postgresqlnew", "grafana", "prometheus" and so on. Without surfacing it
	// there is no way to tell the two apart, which is why `get apps -o wide`
	// prints it and `--type` filters on it.
	CreationMethod string `json:"creation_method"`
}

App is a Darkube application (maps to a Kubernetes workload).

Only the commonly used fields are typed; `darkubectl describe`/`-o json` read the raw object so no data is lost to this partial view.

func (App) Image added in v0.5.0

func (a App) Image() string

Image is the container image the app runs, as repo:tag. The v2 list route returns the whole object, so this costs no extra request — which is what makes "which build is each app on?" answerable for a whole namespace at once.

func (App) IsManagedService added in v0.5.0

func (a App) IsManagedService() bool

IsManagedService reports whether the app is a marketplace/oneclick service (Redis, Postgres, Grafana, …) rather than a workload someone built.

type Auth

type Auth string

Auth is an HTTP Authorization header value. The Darkube API accepts two schemes: an account Api-key, or a Console JWT (Bearer) obtained from login.

func APIKey

func APIKey(token string) Auth

APIKey builds Api-key–scheme authorization from an account token.

func BearerToken

func BearerToken(jwt string) Auth

BearerToken builds Bearer-scheme (JWT) authorization from a login access token.

type Certificate

type Certificate struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	CommonName string `json:"common_name"`
	State      string `json:"state"`
	Domain     string `json:"domain"`
}

Certificate is a TLS certificate entry. The certificates endpoint uses a different envelope than the paginated ones: {"data":{"items":[...]}}.

type Client

type Client struct {
	BaseURL string
	Org     string
	// contains filtered or unexported fields
}

Client talks to the Darkube API for a single tenant.

func New

func New(baseURL string, auth Auth, org string) *Client

New builds a Client. baseURL may be empty to use DefaultBaseURL.

func (*Client) Alerts added in v0.4.0

func (c *Client) Alerts(ctx context.Context) ([]Alert, error)

Alerts returns the tenant's monitoring alerts, both firing and resolved.

func (*Client) AppLogs added in v0.2.0

func (c *Client) AppLogs(ctx context.Context, appID string, opts LogOptions) ([]LogEntry, int, error)

AppLogs returns the tail of one container's log, oldest entry first.

The endpoint is index-based rather than time-based: from_index/to_index bound a window and reference_index anchors it. Reading the tail means asking for a window past the end and letting the server clamp, which is what the console does.

func (*Client) Close

func (c *Client) Close() error

Close releases the underlying transport (resty v3 clients are closable).

func (*Client) CreateApp

func (c *Client) CreateApp(ctx context.Context, in CreateAppInput) (map[string]any, error)

CreateApp creates a Docker-image app and returns the created object. The payload mirrors the console's confirmed POST /api/v1/darkube/apps/ request.

func (*Client) DeleteApp

func (c *Client) DeleteApp(ctx context.Context, id string) error

DeleteApp deletes an app by UUID.

func (*Client) DeployToken added in v0.3.0

func (c *Client) DeployToken(ctx context.Context, id string) (string, error)

DeployToken returns an app's CI trigger deploy token.

This is the credential `darkube deploy --token` wants in a pipeline, paired with the app's own id as --app-id. The console shows it on the app's CI/CD page and it is stored nowhere in the cluster, so the API is the only way to wire up CI without the web UI.

It is deliberately not a field on App: `get apps -o json` would then print every app's deploy token, which is not what asking for a list of apps means.

func (*Client) GetApp

func (c *Client) GetApp(ctx context.Context, id string) (map[string]any, error)

GetApp returns the full raw app object by UUID. The result is a generic map so every field is preserved for `describe` / `-o json|yaml`.

func (*Client) GetAppTyped

func (c *Client) GetAppTyped(ctx context.Context, id string) (*App, error)

GetAppTyped returns a single app decoded into the typed App struct.

func (*Client) ListApps

func (c *Client) ListApps(ctx context.Context) ([]App, error)

ListApps returns all apps in the current tenant, following pagination.

func (*Client) ListCertificates

func (c *Client) ListCertificates(ctx context.Context) ([]Certificate, error)

ListCertificates returns TLS certificates for the current tenant.

func (*Client) ListNamespaces added in v0.2.0

func (c *Client) ListNamespaces(ctx context.Context) ([]Namespace, error)

ListNamespaces returns every namespace (project) in the current tenant, including ones that hold no apps yet. Requires a JWT.

func (*Client) ListPlans

func (c *Client) ListPlans(ctx context.Context) ([]Plan, error)

ListPlans returns all resource/pricing plans (paginated DRF envelope).

func (*Client) Namespaces added in v0.2.0

func (c *Client) Namespaces(ctx context.Context) ([]Namespace, error)

Namespaces returns the tenant's namespaces, preferring the dedicated endpoint and falling back to deriving them from apps.

The distinction matters: the derived list can only contain namespaces that already hold at least one app, so a freshly created, still-empty project is invisible to it — which is exactly when you need to look one up in order to create the first app in it.

func (*Client) NamespacesFromApps

func (c *Client) NamespacesFromApps(ctx context.Context) ([]Namespace, error)

NamespacesFromApps derives the set of namespaces (projects) visible in the current tenant from the app list. This is the fallback for credentials that cannot reach the dedicated endpoint; it omits namespaces with no apps.

func (*Client) Notifications added in v0.4.0

func (c *Client) Notifications(ctx context.Context, limit int) ([]Notification, error)

Notifications returns the most recent notifications, newest first, following pagination up to limit entries.

func (*Client) Organization added in v0.4.0

func (c *Client) Organization(ctx context.Context) (*Organization, error)

Organization returns the current tenant's entry from the user's profile, matching on the slug carried in the X-Organization header.

func (*Client) OrganizationID

func (c *Client) OrganizationID(ctx context.Context) (int, error)

OrganizationID returns the current tenant's numeric organization id, which the create payload requires.

The user profile is the authoritative source and, crucially, works for a tenant that holds no apps yet — the case the app-detail fallback below cannot serve, which used to make it impossible to create the *first* app in a new organization.

func (*Client) PrepareAppUpdate added in v0.5.0

func (c *Client) PrepareAppUpdate(
	ctx context.Context, id string, mutate func(app map[string]any) error,
) (map[string]any, map[string]any, error)

PrepareAppUpdate performs every step of UpdateApp except the write, returning the normalized object as it stands now and as it would be sent.

This is what makes a dry run possible, and it matters more here than it would on an API with a real partial update: every write is a full-object PUT rebuilt from a read, so a mistake in the mutate step can rewrite fields nobody meant to touch, and the request body alone does not show which of its seventy fields actually changed.

func (*Client) Profile added in v0.4.0

func (c *Client) Profile(ctx context.Context) (*Profile, error)

Profile returns the signed-in user and the tenants they belong to.

func (*Client) ResolveApp

func (c *Client) ResolveApp(ctx context.Context, nameOrID string) (*App, error)

ResolveApp finds an app by UUID or by exact name within the current tenant. Names are not guaranteed unique across namespaces; an ambiguous name is an error.

func (*Client) UpdateApp added in v0.4.0

func (c *Client) UpdateApp(ctx context.Context, id string, mutate func(app map[string]any) error) (map[string]any, error)

UpdateApp applies mutate to an app's current state and writes it back.

The API has no partial update, so this is a read-modify-write: the current object is fetched, normalized into the shape the write serializer accepts, handed to mutate, and PUT in full. Callers therefore change one field without having to reconstruct the other seventy.

type Cluster

type Cluster struct {
	ID              int    `json:"id"`
	Name            string `json:"name"`
	LocationCountry string `json:"location_country"`
	IsOnPremise     bool   `json:"is_on_premise"`
}

Cluster is the physical cluster an app's namespace lives on.

type CreateAppInput

type CreateAppInput struct {
	Name           string
	NamespaceID    int
	OrganizationID int
	PlanID         string
	ImageRepo      string
	ImageTag       string
	Command        string
	Args           string
	Replicas       int

	SvcType    string
	Ports      map[string]Port
	Disk       *Disk
	Envs       []EnvVar
	SecretEnvs []EnvVar

	// Git, when non-nil, switches the app from pulling a prebuilt image to being
	// built by Darkube from a repository. ImageRepo/ImageTag are then chosen by
	// the platform (it pushes to registry.hamdocker.ir/<tenant>/<app>) and must
	// be left empty.
	Git *GitSource
}

CreateAppInput describes a Docker-image app to create. Names have already been resolved to ids by the caller.

Everything below Replicas is optional. It can also be changed afterwards through UpdateApp, which PUTs the whole object back — the API has no partial update, but it is not read-only either.

type Disk added in v0.2.0

type Disk struct {
	Partitions       []Partition `json:"partitions"                   yaml:"partitions"`
	SizeInGi         int         `json:"size_in_Gi"                   yaml:"sizeInGi"`
	StorageClassName string      `json:"storage_class_name,omitempty" yaml:"storageClassName"`
	SetFSGroup       bool        `json:"set_fsgroup"                  yaml:"setFsGroup"`
}

Disk is the app's persistent volume. SizeInGi of 0 means "no disk".

type EnvVar added in v0.2.0

type EnvVar struct {
	Name  string `json:"name"  yaml:"name"`
	Value string `json:"value" yaml:"value"`
}

EnvVar is one entry of envs or secret_envs. The API calls the key "name".

func EnvVars added in v0.4.0

func EnvVars(app map[string]any) []EnvVar

EnvVars reads an app's plain (non-secret) environment variables.

type GitSource added in v0.5.0

type GitSource struct {
	RepoURL    string
	Branch     string
	Provider   string // Github | Gitlab
	Dockerfile string
	Context    string
	Workdir    string
	Builder    string // dockerfile | heroku_buildpacks
	// BuildMethod is gitlabci or webhook. Autodeploy has a pointer type so an
	// explicit false is distinguishable from "unset".
	BuildMethod string
	Autodeploy  *bool
}

GitSource describes a repository Darkube builds the app from, producing creation_method "git_repo_url" rather than "docker_image".

Every field maps to one the API returns on such an app; the zero values are filled from the defaults the console uses, so RepoURL alone is enough.

type LogEntry added in v0.2.0

type LogEntry struct {
	Timestamp string // RFC3339 with nanoseconds, as sent by the API
	Text      string
}

LogEntry is one log record: the container's stdout/stderr at a point in time. The API returns a timestamp-keyed object, so entries carry their own key.

type LogOptions added in v0.2.0

type LogOptions struct {
	PodName   string
	Container string
	Tail      int  // number of lines back from the newest
	Previous  bool // the previous container instance, i.e. what a crashloop left behind
}

LogOptions selects which slice of an app's log to read.

type Namespace

type Namespace struct {
	ID      int     `json:"id"`
	Name    string  `json:"name"`
	Cluster Cluster `json:"cluster"`
}

Namespace maps to a Darkube "project" (a Kubernetes namespace).

type Notification added in v0.4.0

type Notification struct {
	Slug        int    `json:"slug"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Timestamp   string `json:"timestamp"`
	TargetType  string `json:"target_type"`
	ActionURL   string `json:"action_url"`
	ActionLabel string `json:"action_label"`
	Unread      bool   `json:"unread"`
	Public      bool   `json:"public"`
}

Notification is one entry of the account's notification feed. Titles and descriptions are Persian prose, and the description carries HTML.

type Organization added in v0.4.0

type Organization struct {
	ID    int      `json:"id"`
	Name  string   `json:"name"`
	Roles []string `json:"current_user_roles"`
}

Organization is a tenant the signed-in user belongs to.

ID is the numeric primary key the app create/update payload calls "organization"; Name is the slug sent as the X-Organization header.

type Partition added in v0.2.0

type Partition struct {
	DisplayName string `json:"display_name" yaml:"name"`
	MountPath   string `json:"mount_path"   yaml:"mountPath"`
	SubPath     string `json:"sub_path"     yaml:"subPath"`
}

Partition is one mount inside a Disk.

type Plan

type Plan struct {
	ID              string     `json:"id"`
	Name            string     `json:"name"`
	CodeName        string     `json:"code_name"`
	PlanType        string     `json:"plan_type"`
	CostType        string     `json:"cost_type"`
	ShowInCreateApp bool       `json:"show_in_create_app"`
	Detail          PlanDetail `json:"detail"`
	Cluster         *Cluster   `json:"cluster"`
}

Plan is a resource/pricing plan.

func (Plan) IsCreatable

func (p Plan) IsCreatable() bool

IsCreatable reports whether a plan can be picked when creating an app.

type PlanDetail

type PlanDetail struct {
	RAMLimit   int `json:"ram_limit"`
	CPURequest int `json:"cpu_request"`
}

PlanDetail holds an app plan's resource sizing (megabytes / millicores).

type Port added in v0.2.0

type Port struct {
	ContainerPort int    `json:"containerPort" yaml:"containerPort"`
	ServicePort   int    `json:"servicePort"   yaml:"servicePort"`
	Protocol      string `json:"protocol"      yaml:"protocol"`
}

Port is one entry of svc.ports, keyed by name in the API ("main", "amqp", …).

type Profile added in v0.4.0

type Profile struct {
	ID            int            `json:"id"`
	Email         string         `json:"email"`
	FullName      string         `json:"full_name"`
	Organizations []Organization `json:"organizations"`
}

Profile is the signed-in user's account.

type State

type State struct {
	StateType   string `json:"state_type"`
	Text        string `json:"text"`
	Description string `json:"description"`
}

State is an app's live health, as reported by the platform.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL