Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Introduce the same OpenAPI client as go-cli with small changes #225

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions api_client/apiclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package apiclient

import (
"context"
"strings"
"time"

"github.com/storageos/kubectl-storageos/api_client/openapi"
"github.com/storageos/kubectl-storageos/pkg/utils"
"github.com/storageos/kubectl-storageos/pkg/version"
"k8s.io/client-go/rest"
)

// Client wraps the openAPI client providing a collection of useful methods such
// as GetEntityByName, making use of the underlying client endpoints and
// extending their logic with filters, objects transformations, etc.
//
// It also decorates the underlying openAPI Authenticate() method with cached
// auth capabilities, reusing an in-memory token (session) if it exists. If that
// fails, it will continue as normal and then cache the new token.
// This cache is used until Authenticate is called again (manually or when the
// reauth wrapper is triggered).
type client struct {
*openapi.OpenAPI

cacheSessionToken string
cacheSessionExpiresAt time.Time
}

func GetAPIClient(restConfig *rest.Config) (*ClientWithReauth, error) {
userAgent := strings.Join([]string{"ondat kubectl plugin", version.PluginVersion}, "/")

endpoint, err := utils.GetFirstStorageOSAPIEndpoint(restConfig)
if err != nil {
return nil, err
}

basicOpenAPIClient, err := openapi.NewOpenAPI([]string{endpoint}, userAgent)
if err != nil {
return nil, err
}

// client wrapper adding some extra useful methods on top
internalClient := client{
OpenAPI: basicOpenAPIClient,
}

username, password, err := utils.GetAPICredentialsFromSecret(restConfig)
if err != nil {
return nil, err
}

// client wrapper adding reauth on first failure
clientWithReauth := NewClientWithReauth(internalClient, username, password)

return clientWithReauth, nil
}

func (c *client) Authenticate(ctx context.Context, username, password string) (string, time.Time, error) {
// TODO make configurable if we want to skip this feature of caching the session
if c.cacheSessionToken != "" && c.cacheSessionExpiresAt.After(time.Now()) {
err := c.UseAuthSession(ctx, c.cacheSessionToken)
if err == nil {
return c.cacheSessionToken, c.cacheSessionExpiresAt, nil
}
// failed to setup auth with in-memory session, proceed to create a new one
}

sessionToken, expiresBy, err := c.OpenAPI.Authenticate(ctx, username, password)
if err != nil {
return "", time.Now().Add(-time.Minute), err
}

// store new session in-memory
c.cacheSessionToken = sessionToken
c.cacheSessionExpiresAt = expiresBy

return sessionToken, expiresBy, nil
}
15 changes: 15 additions & 0 deletions api_client/diagnostics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package apiclient

import (
"context"
"io"
)

func (c *client) GetSingleNodeDiagnosticsByName(ctx context.Context, name string) (io.ReadCloser, string, error) {
node, err := c.GetNodeByName(ctx, name)
if err != nil {
return nil, "", err
}

return c.GetSingleNodeDiagnostics(ctx, node.ID)
}
26 changes: 26 additions & 0 deletions api_client/licence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package apiclient

import (
"context"

"github.com/storageos/kubectl-storageos/api_client/openapi"
"github.com/storageos/kubectl-storageos/model"
)

// UpdateLicence sends a new version of the licence to apply to the current
// cluster. It returns the new licence resource if correctly applied. It doesn't
// require a version but overwrite the licence using the last available version
// from the current licence.
func (c *client) UpdateLicence(ctx context.Context, licence []byte, params *openapi.UpdateLicenceRequestParams) (*model.License, error) {
if params == nil || params.CASVersion == "" {
l, err := c.GetLicence(ctx)
if err != nil {
return nil, err
}
params = &openapi.UpdateLicenceRequestParams{
CASVersion: l.Version,
}
}

return c.UpdateLicence(ctx, licence, params)
}
122 changes: 122 additions & 0 deletions api_client/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package apiclient

import (
"context"

"github.com/storageos/kubectl-storageos/api_client/openapi"
"github.com/storageos/kubectl-storageos/model"
)

// GetNamespaceByName requests basic information for the namespace resource
// which has the given name.
//
// The resource model for the API is build around using unique identifiers,
// so this operation is inherently more expensive than the corresponding
// GetNamespace() operation.
//
// Retrieving a namespace resource by name involves requesting a list of all
// namespaces from the StorageOS API and returning the first one where the
// name matches.
func (c *client) GetNamespaceByName(ctx context.Context, name string) (*model.Namespace, error) {
namespaces, err := c.ListNamespaces(ctx)
if err != nil {
return nil, err
}

for _, ns := range namespaces {
if ns.Name == name {
return ns, nil
}
}

return nil, openapi.NewNamespaceNameNotFoundError(name)
}

// GetListNamespacesByUID requests a list of namespace resources present in the
// cluster.
//
// The returned list is filtered using uids so that it contains only those
// namespace resources which have a matching ID. If no uids are given then
// all namespaces are returned.
func (c *client) GetListNamespacesByUID(ctx context.Context, uids ...string) ([]*model.Namespace, error) {
resources, err := c.ListNamespaces(ctx)
if err != nil {
return nil, err
}

return filterNamespacesForUIDs(resources, uids...)
}

// GetListNamespacesByName requests a list of namespace resources present in
// the cluster.
//
// The returned list is filtered using names so that it contains only those
// namespaces resources which have a matching name. If no names are given then
// all namespaces are returned.
func (c *client) GetListNamespacesByName(ctx context.Context, names ...string) ([]*model.Namespace, error) {
resources, err := c.ListNamespaces(ctx)
if err != nil {
return nil, err
}

return filterNamespacesForNames(resources, names...)
}

// filterNamespacesForNames will return a subset of namespaces containing
// resources which have one of the provided names. If names is not provided,
// namespaces is returned as is.
//
// If there is no resource for a given name then an error is returned, thus
// this is a strict helper.
func filterNamespacesForNames(namespaces []*model.Namespace, names ...string) ([]*model.Namespace, error) {
if len(names) == 0 {
return namespaces, nil
}

retrieved := map[string]*model.Namespace{}

for _, ns := range namespaces {
retrieved[ns.Name] = ns
}

filtered := make([]*model.Namespace, 0, len(names))
for _, name := range names {
ns, ok := retrieved[name]
if !ok {
return nil, openapi.NewNamespaceNameNotFoundError(name)
}
filtered = append(filtered, ns)
}

return filtered, nil
}

// filterNamespacesForUIDS will return a subset of namespaces containing
// resources which have one of the provided uids. If uids is not provided,
// namespaces is returned as is.
//
// If there is no resource for a given uid then an error is returned, thus
// this is a strict helper.
func filterNamespacesForUIDs(namespaces []*model.Namespace, uids ...string) ([]*model.Namespace, error) {
if len(uids) == 0 {
return namespaces, nil
}

retrieved := map[string]*model.Namespace{}

for _, ns := range namespaces {
retrieved[ns.ID] = ns
}

filtered := make([]*model.Namespace, 0, len(uids))

for _, idVar := range uids {
ns, ok := retrieved[idVar]
if !ok {
return nil, openapi.NewNamespaceNotFoundError(idVar)
}
filtered = append(filtered, ns)
}

return filtered, nil
}
123 changes: 123 additions & 0 deletions api_client/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package apiclient

import (
"context"

"github.com/storageos/kubectl-storageos/api_client/openapi"
"github.com/storageos/kubectl-storageos/model"
)

// GetNodeByName requests basic information for the node resource which has
// name.
//
// The resource model for the API is build around using unique identifiers,
// so this operation is inherently more expensive than the corresponding
// GetNode() operation.
//
// Retrieving a node resource by name involves requesting a list of all nodes
// in the cluster from the StorageOS API and returning the first node where the
// name matches.
func (c *client) GetNodeByName(ctx context.Context, name string) (*model.Node, error) {
nodes, err := c.ListNodes(ctx)
if err != nil {
return nil, err
}

for _, n := range nodes {
if n.Name == name {
return n, nil
}
}

return nil, openapi.NewNodeNameNotFoundError(name)
}

// GetListNodesByUID requests a list containing basic information on each
// node resource in the cluster.
//
// The returned list is filtered using uids so that it contains only those
// resources which have a matching ID. Omitting uids will skip the filtering.
func (c *client) GetListNodesByUID(ctx context.Context, uids ...string) ([]*model.Node, error) {
nodes, err := c.ListNodes(ctx)
if err != nil {
return nil, err
}

return filterNodesForUIDs(nodes, uids...)
}

// GetListNodesByName requests a list containing basic information on each
// node resource in the cluster.
//
// The returned list is filtered using names so that it contains only those
// resources which have a matching name. Omitting names will skip the filtering.
func (c *client) GetListNodesByName(ctx context.Context, names ...string) ([]*model.Node, error) {
nodes, err := c.ListNodes(ctx)
if err != nil {
return nil, err
}

return filterNodesForNames(nodes, names...)
}

// filterNodesForNames will return a subset of nodes containing resources
// which have one of the provided names. If names is not provided, nodes is
// returned as is.
//
// If there is no resource for a given name then an error is returned, thus
// this is a strict helper.
func filterNodesForNames(nodes []*model.Node, names ...string) ([]*model.Node, error) {
// return everything if no filter names given
if len(names) == 0 {
return nodes, nil
}

retrieved := map[string]*model.Node{}

for _, n := range nodes {
retrieved[n.Name] = n
}

filtered := make([]*model.Node, 0, len(names))

for _, name := range names {
n, ok := retrieved[name]
if !ok {
return nil, openapi.NewNodeNameNotFoundError(name)
}
filtered = append(filtered, n)
}

return filtered, nil
}

// filterNodesForUIDs will return a subset of nodes containing resources
// which have one of the provided uids. If uids is not provided, nodes is
// returned as is.
//
// If there is no resource for a given uid then an error is returned, thus
// this is a strict helper.
func filterNodesForUIDs(nodes []*model.Node, uids ...string) ([]*model.Node, error) {
// return everything if no filter uids given
if len(uids) == 0 {
return nodes, nil
}

retrieved := map[string]*model.Node{}

for _, n := range nodes {
retrieved[n.ID] = n
}

filtered := make([]*model.Node, 0, len(uids))

for _, idVar := range uids {
n, ok := retrieved[idVar]
if !ok {
return nil, openapi.NewNodeNotFoundError(idVar)
}
filtered = append(filtered, n)
}

return filtered, nil
}
Loading