feat: move client to public package
All checks were successful
arcad/emissary/pipeline/head This commit looks good

This commit is contained in:
2023-06-25 11:45:43 -06:00
parent 4217850c30
commit 42d49eb090
23 changed files with 45 additions and 25 deletions

23
pkg/client/alias.go Normal file
View File

@ -0,0 +1,23 @@
package client
import (
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
)
type (
Spec = spec.Spec
SpecName = spec.Name
)
type (
AgentID = datastore.AgentID
Agent = datastore.Agent
AgentStatus = datastore.AgentStatus
)
type MetadataTuple = metadata.Tuple
type Key = jwk.Key

144
pkg/client/client.go Normal file
View File

@ -0,0 +1,144 @@
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/api"
"gitlab.com/wpetit/goweb/logger"
)
type Client struct {
http *http.Client
defaultOpts Options
serverURL string
}
func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error {
if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *Client) apiPost(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
if err := c.apiDo(ctx, http.MethodPost, path, payload, result, funcs...); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *Client) apiPut(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
if err := c.apiDo(ctx, http.MethodPut, path, payload, result, funcs...); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *Client) apiDelete(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
if err := c.apiDo(ctx, http.MethodDelete, path, payload, result, funcs...); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *Client) apiDo(ctx context.Context, method string, path string, payload any, response any, funcs ...OptionFunc) error {
opts := c.defaultOptions()
for _, fn := range funcs {
fn(opts)
}
url := c.serverURL + path
logger.Debug(
ctx, "new http request",
logger.F("method", method),
logger.F("url", url),
logger.F("payload", payload),
)
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
if err := encoder.Encode(payload); err != nil {
return errors.WithStack(err)
}
req, err := http.NewRequest(method, url, &buf)
if err != nil {
return errors.WithStack(err)
}
for key, values := range opts.Headers {
for _, v := range values {
req.Header.Add(key, v)
}
}
res, err := c.http.Do(req)
if err != nil {
return errors.WithStack(err)
}
defer res.Body.Close()
decoder := json.NewDecoder(res.Body)
if err := decoder.Decode(&response); err != nil {
return errors.WithStack(err)
}
return nil
}
func (c *Client) defaultOptions() *Options {
return &Options{
Headers: c.defaultOpts.Headers,
}
}
func withResponse[T any]() struct {
Data T
Error *api.Error
} {
return struct {
Data T
Error *api.Error
}{}
}
func joinSlice[T any](items []T) string {
str := ""
for idx, item := range items {
if idx != 0 {
str += ","
}
str += fmt.Sprintf("%v", item)
}
return str
}
func New(serverURL string, funcs ...OptionFunc) *Client {
opts := Options{}
for _, fn := range funcs {
fn(&opts)
}
return &Client{
serverURL: serverURL,
http: &http.Client{},
defaultOpts: opts,
}
}

View File

@ -0,0 +1,27 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"github.com/pkg/errors"
)
func (c *Client) DeleteAgent(ctx context.Context, agentID AgentID, funcs ...OptionFunc) (AgentID, error) {
response := withResponse[struct {
AgentID int64 `json:"agentId"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
if err := c.apiDelete(ctx, path, nil, &response, funcs...); err != nil {
return 0, errors.WithStack(err)
}
if response.Error != nil {
return 0, errors.WithStack(response.Error)
}
return datastore.AgentID(response.Data.AgentID), nil
}

View File

@ -0,0 +1,33 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
func (c *Client) DeleteAgentSpec(ctx context.Context, agentID AgentID, name SpecName, funcs ...OptionFunc) (SpecName, error) {
payload := struct {
Name spec.Name `json:"name"`
}{
Name: name,
}
response := withResponse[struct {
Name spec.Name `json:"name"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
if err := c.apiDelete(ctx, path, payload, &response, funcs...); err != nil {
return "", errors.WithStack(err)
}
if response.Error != nil {
return "", errors.WithStack(response.Error)
}
return response.Data.Name, nil
}

26
pkg/client/get_agent.go Normal file
View File

@ -0,0 +1,26 @@
package client
import (
"context"
"fmt"
"github.com/pkg/errors"
)
func (c *Client) GetAgent(ctx context.Context, agentID AgentID, funcs ...OptionFunc) (*Agent, error) {
response := withResponse[struct {
Agent *Agent `json:"agent"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
return nil, errors.WithStack(err)
}
if response.Error != nil {
return nil, errors.WithStack(response.Error)
}
return response.Data.Agent, nil
}

View File

@ -0,0 +1,32 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
func (c *Client) GetAgentSpecs(ctx context.Context, agentID AgentID, funcs ...OptionFunc) ([]Spec, error) {
response := withResponse[struct {
Specs []*spec.RawSpec `json:"specs"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
return nil, errors.WithStack(err)
}
if response.Error != nil {
return nil, errors.WithStack(response.Error)
}
specs := make([]spec.Spec, 0, len(response.Data.Specs))
for _, s := range response.Data.Specs {
specs = append(specs, spec.Spec(s))
}
return specs, nil
}

24
pkg/client/options.go Normal file
View File

@ -0,0 +1,24 @@
package client
import "net/http"
type Options struct {
Headers http.Header
}
type OptionFunc func(*Options)
func WithToken(token string) OptionFunc {
return func(o *Options) {
if o.Headers == nil {
o.Headers = http.Header{}
}
o.Headers.Set("Authorization", "Bearer "+token)
}
}
func WithHeaders(headers http.Header) OptionFunc {
return func(o *Options) {
o.Headers = headers
}
}

View File

@ -0,0 +1,99 @@
package client
import (
"context"
"fmt"
"net/url"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"github.com/pkg/errors"
)
type QueryAgentsOptionFunc func(*QueryAgentsOptions)
type QueryAgentsOptions struct {
Options []OptionFunc
Limit *int
Offset *int
Thumbprints []string
IDs []AgentID
Statuses []AgentStatus
}
func WithQueryAgentsOptions(funcs ...OptionFunc) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.Options = funcs
}
}
func WithQueryAgentsLimit(limit int) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.Limit = &limit
}
}
func WithQueryAgentsOffset(offset int) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.Offset = &offset
}
}
func WithQueryAgentsThumbprints(thumbprints ...string) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.Thumbprints = thumbprints
}
}
func WithQueryAgentsID(ids ...datastore.AgentID) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.IDs = ids
}
}
func WithQueryAgentsStatus(statuses ...datastore.AgentStatus) QueryAgentsOptionFunc {
return func(opts *QueryAgentsOptions) {
opts.Statuses = statuses
}
}
func (c *Client) QueryAgents(ctx context.Context, funcs ...QueryAgentsOptionFunc) ([]*Agent, int, error) {
options := &QueryAgentsOptions{}
for _, fn := range funcs {
fn(options)
}
query := url.Values{}
if options.IDs != nil && len(options.IDs) > 0 {
query.Set("ids", joinSlice(options.IDs))
}
if options.Thumbprints != nil && len(options.Thumbprints) > 0 {
query.Set("thumbprints", joinSlice(options.Thumbprints))
}
if options.Statuses != nil && len(options.Statuses) > 0 {
query.Set("statuses", joinSlice(options.Statuses))
}
path := fmt.Sprintf("/api/v1/agents?%s", query.Encode())
response := withResponse[struct {
Agents []*datastore.Agent `json:"agents"`
Total int `json:"total"`
}]()
if options.Options == nil {
options.Options = make([]OptionFunc, 0)
}
if err := c.apiGet(ctx, path, &response, options.Options...); err != nil {
return nil, 0, errors.WithStack(err)
}
if response.Error != nil {
return nil, 0, errors.WithStack(response.Error)
}
return response.Data.Agents, response.Data.Total, nil
}

View File

@ -0,0 +1,48 @@
package client
import (
"context"
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"github.com/pkg/errors"
)
func (c *Client) RegisterAgent(ctx context.Context, key Key, thumbprint string, meta []MetadataTuple, funcs ...OptionFunc) (*Agent, error) {
keySet, err := jwk.PublicKeySet(key)
if err != nil {
return nil, errors.WithStack(err)
}
signature, err := jwk.Sign(key, thumbprint, meta)
if err != nil {
return nil, errors.WithStack(err)
}
payload := struct {
KeySet jwk.Set `json:"keySet"`
Thumbprint string `json:"thumbprint"`
Metadata []metadata.Tuple `json:"metadata"`
Signature string `json:"signature"`
}{
Thumbprint: thumbprint,
Metadata: meta,
Signature: signature,
KeySet: keySet,
}
response := withResponse[struct {
Agent *datastore.Agent `json:"agent"`
}]()
if err := c.apiPost(ctx, "/api/v1/register", payload, &response, funcs...); err != nil {
return nil, errors.WithStack(err)
}
if response.Error != nil {
return nil, errors.WithStack(response.Error)
}
return response.Data.Agent, nil
}

View File

@ -0,0 +1,72 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"github.com/pkg/errors"
)
type UpdateAgentOptions struct {
Status *int
Label *string
Options []OptionFunc
}
type UpdateAgentOptionFunc func(*UpdateAgentOptions)
func WithAgentStatus(status int) UpdateAgentOptionFunc {
return func(opts *UpdateAgentOptions) {
opts.Status = &status
}
}
func WithAgentLabel(label string) UpdateAgentOptionFunc {
return func(opts *UpdateAgentOptions) {
opts.Label = &label
}
}
func WithUpdateAgentsOptions(funcs ...OptionFunc) UpdateAgentOptionFunc {
return func(opts *UpdateAgentOptions) {
opts.Options = funcs
}
}
func (c *Client) UpdateAgent(ctx context.Context, agentID datastore.AgentID, funcs ...UpdateAgentOptionFunc) (*datastore.Agent, error) {
opts := &UpdateAgentOptions{}
for _, fn := range funcs {
fn(opts)
}
payload := map[string]any{}
if opts.Status != nil {
payload["status"] = *opts.Status
}
if opts.Label != nil {
payload["label"] = *opts.Label
}
response := withResponse[struct {
Agent *datastore.Agent `json:"agent"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
if opts.Options == nil {
opts.Options = make([]OptionFunc, 0)
}
if err := c.apiPut(ctx, path, payload, &response, opts.Options...); err != nil {
return nil, errors.WithStack(err)
}
if response.Error != nil {
return nil, errors.WithStack(response.Error)
}
return response.Data.Agent, nil
}

View File

@ -0,0 +1,39 @@
package client
import (
"context"
"fmt"
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
"forge.cadoles.com/Cadoles/emissary/internal/spec"
"github.com/pkg/errors"
)
func (c *Client) UpdateAgentSpec(ctx context.Context, agentID AgentID, spc Spec, funcs ...OptionFunc) (Spec, error) {
payload := struct {
Name spec.Name `json:"name"`
Revision int `json:"revision"`
Data metadata.Metadata `json:"data"`
}{
Name: spc.SpecName(),
Revision: spc.SpecRevision(),
Data: spc.SpecData(),
}
response := withResponse[struct {
Spec *datastore.Spec `json:"spec"`
}]()
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
if err := c.apiPost(ctx, path, payload, &response, funcs...); err != nil {
return nil, errors.WithStack(err)
}
if response.Error != nil {
return nil, errors.WithStack(response.Error)
}
return response.Data.Spec, nil
}