Skip to content

Commit

Permalink
Merge pull request #176 from sapcc/liquid-client
Browse files Browse the repository at this point in the history
liquidapi: add type Client
  • Loading branch information
majewsky authored Nov 5, 2024
2 parents e859150 + 562b902 commit 3d84e0c
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
130 changes: 130 additions & 0 deletions liquidapi/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*******************************************************************************
*
* Copyright 2024 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, you may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*******************************************************************************/

package liquidapi

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/gophercloud/gophercloud/v2"
"github.com/sapcc/go-api-declarations/liquid"
)

// Client provides structured access to a LIQUID API.
type Client struct {
gophercloud.ServiceClient
}

// ClientOpts contains additional options for NewClient().
type ClientOpts struct {
// The service type of the liquid in the Keystone catalog.
// Required if EndpointOverride is not given.
ServiceType string

// Skips inspecting the Keystone catalog and assumes that the liquid's API is
// located at this base URL. Required if ServiceType is not given.
EndpointOverride string
}

// NewClient creates a Client for interacting with a liquid.
func NewClient(client *gophercloud.ProviderClient, endpointOpts gophercloud.EndpointOpts, opts ClientOpts) (*Client, error) {
if opts.ServiceType == "" || opts.EndpointOverride == "" {
return nil, errors.New("either ServiceType or EndpointOverride needs to be given in liquidapi.NewClient()")
}

endpoint := opts.EndpointOverride
if endpoint == "" {
endpointOpts.ApplyDefaults(opts.ServiceType)
var err error
endpoint, err = client.EndpointLocator(endpointOpts)
if err != nil {
return nil, err
}
}

if opts.ServiceType == "" {
opts.ServiceType = "liquid"
}
return &Client{
ServiceClient: gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
Type: opts.ServiceType,
},
}, nil
}

// GetInfo executes GET /v1/info.
func (c *Client) GetInfo(ctx context.Context) (result liquid.ServiceInfo, err error) {
url := c.ServiceURL("v1", "info")
opts := gophercloud.RequestOpts{KeepResponseBody: true}
resp, err := c.Get(ctx, url, nil, &opts)
if err == nil {
err = parseLiquidResponse(resp, &result)
}
return
}

// GetCapacityReport executes POST /v1/report-capacity.
func (c *Client) GetCapacityReport(ctx context.Context, req liquid.ServiceCapacityRequest) (result liquid.ServiceCapacityReport, err error) {
url := c.ServiceURL("v1", "report-capacity")
opts := gophercloud.RequestOpts{KeepResponseBody: true, OkCodes: []int{http.StatusOK}}
resp, err := c.Post(ctx, url, req, nil, &opts)
if err == nil {
err = parseLiquidResponse(resp, &result)
}
return
}

// GetUsageReport executes POST /v1/projects/:uuid/report-usage.
func (c *Client) GetUsageReport(ctx context.Context, projectUUID string, req liquid.ServiceUsageRequest) (result liquid.ServiceUsageReport, err error) {
url := c.ServiceURL("v1", "projects", projectUUID, "report-usage")
opts := gophercloud.RequestOpts{KeepResponseBody: true, OkCodes: []int{http.StatusOK}}
resp, err := c.Post(ctx, url, req, nil, &opts)
if err == nil {
err = parseLiquidResponse(resp, &result)
}
return
}

// PutQuota executes PUT /v1/projects/:uuid/quota.
func (c *Client) PutQuota(ctx context.Context, projectUUID string, req liquid.ServiceQuotaRequest) (err error) {
url := c.ServiceURL("v1", "projects", projectUUID, "quota")
opts := gophercloud.RequestOpts{KeepResponseBody: true, OkCodes: []int{http.StatusNoContent}}
_, err = c.Put(ctx, url, req, nil, &opts) //nolint:bodyclose // either the response is 204 and does not have a body, or it's an error and Gophercloud does a ReadAll() internally
return
}

// We do not use the standard response body parsing from Gophercloud
// because we want to be strict and DisallowUnknownFields().
func parseLiquidResponse(resp *http.Response, result any) error {
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
dec.DisallowUnknownFields()
err := dec.Decode(&result)
if err != nil {
return fmt.Errorf("could not parse response body from %s %s: %w",
resp.Request.Method, resp.Request.URL.String(), err)
}
return nil
}
5 changes: 5 additions & 0 deletions liquidapi/liquidapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
// API and nothing else. The application will only have to provide a type that
// implements the Logic interface. Then it can call Run() on an instance of it
// to parse configuration, connect to OpenStack and run the HTTP server.
//
// Ref: <https://pkg.go.dev/github.com/sapcc/go-api-declarations/liquid>
//
// This package also provides a Gophercloud-based Client for talking to the
// LIQUID API. Realistically, only Limes and limesctl will need this though. :)
package liquidapi

import (
Expand Down

0 comments on commit 3d84e0c

Please sign in to comment.