Code Monkey home page Code Monkey logo

go-katapult's Introduction

logo

go-katapult

Go client library for Katapult.

Actions Status Coverage GitHub last commit GitHub issues GitHub pull requests License Status


WARNING: Work in progress; features are missing, there will be breaking changes.


Documentation:

Experimental "next" Client

A more feature complete client is being generated in the next package. The aim for this client is to be generated from an openapi spec and should offer access to everything that is documented / exposed in our API documentation.

Usage Guidance

Each endpoint has multiple functions for calling it. Typically FunctionName and FunctionNameWithResponse are provided.

It is recommended to use the FunctionNameWithResponse functions as they return a response object that contains the response data and the HTTP response object.

The FunctionName functions are provided for convenience and return only the response data.

Example

res, err := client.GetDataCenterDefaultNetworkWithResponse(ctx,
	&katapult.GetDataCenterDefaultNetworkParams{
		DataCenterPermalink: "perma-link",
	},
)

go-katapult's People

Contributors

andrewjtait avatar github-actions[bot] avatar ikadix avatar jimeh avatar jimehk avatar replease[bot] avatar strideynet avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

jt-krystal

go-katapult's Issues

Use easyjson to improve JSON marshal/unmarshal performance

easyjson improves the performance of marshaling and unmarshaling JSON by
generating custom code for each of your structs. As the generated code is based
on your structs, it avoids using reflection.

  • Add easyjson, generating code via go generate for all structs which are
    used for JSON requests/responses.
  • Add CI step to ensure generated code is up to date, failing if it is not

Simplify/improve katapult.Client methods

I'd like to propose a change to the katapult.Client type with a goal of
simplifying it, and making it easier to mock with an interface.

Currently the client has two methods:

  • NewRequestWithContext: Create a new *http.Request object and populate
    headers as needed, and marshal body to JSON.
  • Do: Performs a *http.Request against the Katapult API, under the
    assumption that the given *http.Request has come from
    NewRequestWithContext so it has all the right headers set.

This means to mock the Client in other packages, you need to create an
interface with both methods, and also implement both in your fakeClient, ideally
in such a way that it can verify if the request object is one that came from the
client.

Instead, I would like to propose the Client type only has one method:

func (c *Client) Do(r *Request, v interface {}) (*Response, error)

This requires the introduction of a new katapult.Request type, which would
contain a few pieces of additional Katapult-specific metadata, as well as what a
http.Request struct already contains. And then the Do method becomes
responsible for setting various headers, like user-agent, authorization, etc.

This means a fakeClient in other packages only needs to implement the Do
method, and simply verify that the correct attributes are set on the given
*katapult.Request argument.

I would expect the Request struct to be something like this:

type Request struct {
	http.Request

	// Authenticated determines if the "Authorization" header should be sent.
	// Set to false for un-authenticated API endpoints.
	Authenticated bool

	// Body is any object which can be marshaled to JSON with json.Marshal().
	Body interface{}
}

func NewRequestWithContext(
	ctx context.Context,
	method string,
	url string,
	body interface{},
) *Request {
	return &Request{
		Request: http.NewRequestWithContext(ctx, method, url, nil),
		Body:    body,
	}
}

(I'm not sure embedding http.Request is the best approach, but good enough as
a demo here)

Prepare OpenAPI-generated go-katapult client in `next` branch

Now that Katapult exposes a experimental OpenAPI schema, we should start looking at generating a new Go client for Katapult's API from the OpenAPI schema.

Initially I believe it would best to keep this new client in a branch called next, as it will be complete departure from the current codebase.

One thing to keep in mind, is the buildspec package, as the OpenAPI schema simply uses a String type for the XML spec, the buildspec package would still be very useful for programmatically building VM build specs.

Improve error handling

Handling of error responses from Katapult's API isn't great right now, as we
don't actually parse the detail attribute due to the type being different
depending on the type of error.

To address this, we need to create a whole series of error structs that match up
against all of the error types the Katapult API can return, which are capable of
parsing the detail property according to the the type of that error. These
errors should also conform to the error interface, where the Error() method
returns a sensible string version of itself, taking code, description, and
detail values into account. These custom error structs should also implement
Unwrap() and Is() methods, and act as if they've wrapped a standard error
var.

The existing ResponseError structs will then be used to initially parse the
returned JSON response, and based on the Code field, we initialize the
relevant error struct, having it parse the Detail json.RawMessage field.

All API methods which currently return a stuct, Response, and error should
then be changed, so the error is actually the parsed error struct.

As an example, right now if you get a error back, but want custom logic if it is
a 404 not found error, something like this must be done:

ip, resp, err := client.IPAddresses.GetByID(ctx, "ip_r3NoJOsbEtZOwT89")
if err != nil {
	if resp != nil && resp.Response && resp.StatusCode != 404 {
		// do something for not found errors
	} else {
		// do something for any other error
	}
}

The above is rather cumbersome and annoying, instead I would like to be able to
do something like this:

ip, resp, err := client.IPAddresses.GetByID(ctx, "ip_r3NoJOsbEtZOwT89")
if err != nil {
	if errors.Is(err, katapult.ErrNotFound) {
		// do something for not found errors
	} else {
		// do something for any other error
	}
}

Errors should also have a multiple levels of wrapping, so in the above example,
err is all of the following when checked with errors.Is():

  • katapult.ErrIPAddressNotFound
  • katapult.ErrNotFound
  • katapult.Err

As a final note, once errors are actually returned as the error return value,
the Response return value should probably be refactored into some form of
"meta" object, as it'll mostly just be needed for pagination details, rate
limits, and other such "meta" about the response.

More intuitive support for referencing orgs/lbs etc

At the moment, when interacting with or referring to resources we tend to require the consumer to pass the entire object in that they want to refer to. This is pretty inconvenient as it will force the user to either

  1. Work out which fields are actually being used, and fill those from known values (e.g ID or subdomain)
  2. Fetch the entire object before interacting with/referencing it

I'd like to suggest probably a cleaner interface along the lines of:

type OrgReferencer func(qs *url.Values)

func ByOrgID(orgId string) OrgReferencer {
	return func(qs *url.Values) {
		qs.Set("organization[id]", orgId)
	}
}
func ByOrgSubdomain(subdomain string) OrgReferencer {
	return func(qs *url.Values) {
		qs.Set("organization[sub_domain]", subdomain)
	}
}

func (o Organization) Ref() OrgReferencer {
	return func(qs *url.Values) {
		qs.Set("organization[id]", o.ID)
	}
}

// Lists the VMs for an org
func (s *VirtualMachinesClient) List(
	ctx context.Context,
	orgRef OrgReferencer,
	opts *ListOptions,
) ([]*VirtualMachine, *katapult.Response, error) {
	qs := queryValues(opts)
	orgRef(qs)
	u := &url.URL{
		Path:     "organizations/_/virtual_machines",
		RawQuery: qs.Encode(),
	}
	body, resp, err := s.doRequest(ctx, "GET", u, nil)
	resp.Pagination = body.Pagination
	return body.VirtualMachines, resp, err
}

This gives a consumer experience along the lines of:

c.LoadBalancers.List(ctx, ByOrgId(orgID})
c.LoadBalancers.List(ctx, ByOrgSubdomain(orgSubDomain))
org := &Organization{}
c.LoadBalancers.List(ctx, org.Ref())

I'm more certain of how I want the consumer experience to be than the actually underlying implementation, which I feel could equally be implemented using aliased function type (as shown in the example) or by returning a type that implements the queryable interface.

Add support for File Storage Volumes

Support for File Storage Volumes is coming shortly in Katapult's Core API. We know what the API endpoints will look like already, so we can start implementing support for them already.

Any potential changes that might occur to the API between now and production deployment, are likely to be very minor, and would not have a big impact on the go-katapult implementation.

API Docs โ€” Screenshots

Below are screenshots from the development version of Katapult's Core API documentation, illustrating how the upcoming FSV API endpoints will work.

Overview:

Screenshot 2023-03-16 at 18 14 10

Endpoints

List Endpoint:

Screenshot 2023-03-16 at 18 14 26

Get Endpoint:

Screenshot 2023-03-16 at 18 14 39

Create Endpoint:

Screenshot 2023-03-16 at 18 14 55

Update Endpoint:

Screenshot 2023-03-16 at 18 15 06

Delete Endpoint:

Screenshot 2023-03-16 at 18 15 23

Objects

Lookup Object:

Screenshot 2023-03-16 at 18 15 41

Arguments Object:

Screenshot 2023-03-16 at 18 15 55

Move buildspec package to core/buildspec

The buildspec is specific to the Core API and creating virtual machines. So I
think it makes more sense to let the package be nested within the core package.

Add support for Disk Template Version list/get operations

Add support to the existing DiskTemplatesClient for fetching list of versions,
and details for a specific version.

API documentation:
https://developers.katapult.io/api/docs/latest/groups/disk_template_versions/

I would suggest adding the following methods to DiskTemplatesClient:

GetVersions(ctx context.Context, dt *DiskTemplate) ([]*DiskTemplateVersion, *Response, error)
GetVersion(ctx, id string) (*DiskTemplateVersion, *Response, error)

Refactor package names/structure to match Katapult API structure

Currently only the "Core" API from katapult is supported via a Go package called
github.com/krystal/go-katapult/pkg/katapult.

Current Katapult APIs:

More APIs will be coming soon, so to prepare for that, this project should be
split into Go packages for each supported API.

Roughly, the following refactors/changes should be implemented:

  • Make the root of the repository a katapult package, which exposes a Client
    struct (the current apiClient struct, cleaned up a little).
  • Rename katapult (/pkg/katapult) package to core and remove pkg folder,
    making it's import path github.com/krystal/katapult/core.
  • Expose all new*Client functions within the core package, allowing users to
    initialize only sub-sets of the whole core API.
  • Move buildspec and namegenerator package folders out of pkg folder and
    into the root of the repo, simplifying their import paths. This should be fine
    as nearly every directory in this repo is a importable Go package.
  • Leave the internal folder alone for now.

This means initialization for talking to the Core API would look something like
this:

import (
	"github.com/krystal/go-katapult"
	"github.com/krystal/go-katapult/core"
)

func main() {
	// Create a *katapult.Client instance
	c := katapult.New(katapult.Config{APIKey: "<API_KEY>"})

	// Create a core client for the whole suite of API endpoints
	coreClient := core.New(c)

	vms, _, _ := coreClient.VirtualMachines.List(
		context.TODO(), &Organization{ID: "org_4uqYndJSN45bdSz0"}, nil,
	)

	fmt.Printf("vms: %+v\n", vms)

	// Create a subset client for a specific resource
	ipsClient := core.NewIPAddressesClient(c)

	ips, _, _ := ipsClient.List(
		context.TODO(), &Organization{ID: "org_4uqYndJSN45bdSz0"}, nil,
	)

	fmt.Printf("ips: %+v\n", ips)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.