Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/release client #7

Merged
merged 1 commit into from
May 3, 2024
Merged
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
6 changes: 4 additions & 2 deletions bonsai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,9 @@ type Client struct {
userAgent string

// Clients
Space SpaceClient
Plan PlanClient
Space SpaceClient
Plan PlanClient
Release ReleaseClient
}

func NewClient(options ...ClientOption) *Client {
Expand All @@ -343,6 +344,7 @@ func NewClient(options ...ClientOption) *Client {
// Configure child clients
client.Space = SpaceClient{client}
client.Plan = PlanClient{client}
client.Release = ReleaseClient{client}

return client
}
Expand Down
5 changes: 1 addition & 4 deletions bonsai/plan_impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package bonsai

import (
"encoding/json"

"github.com/google/go-cmp/cmp"
)

func (s *ClientImplTestSuite) TestPlanAllResponseJsonUnmarshal() {
Expand Down Expand Up @@ -64,8 +62,7 @@ func (s *ClientImplTestSuite) TestPlanAllResponseJsonUnmarshal() {
result := planAllResponse{}
err := json.Unmarshal([]byte(tc.received), &result)
s.NoError(err)
s.Equal(tc.expect, result)
s.Empty(cmp.Diff(result, tc.expect))
s.Equal(tc.expect, result, "expected struct matches unmarshaled result")
})
}
}
5 changes: 2 additions & 3 deletions bonsai/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"net/http"
"net/url"

"github.com/google/go-cmp/cmp"
"github.com/omc/bonsai-api-go/v1/bonsai"
)

Expand Down Expand Up @@ -114,7 +113,7 @@ func (s *ClientTestSuite) TestPlanClient_All() {
s.NoError(err, "successfully get all spaces")
s.Len(spaces, 2)

s.Empty(cmp.Diff(expect, spaces), "diff between received All response and expected should be empty")
s.ElementsMatch(expect, spaces, "elements expected match elements in received spaces")
}

func (s *ClientTestSuite) TestPlanClient_GetByPath() {
Expand Down Expand Up @@ -175,5 +174,5 @@ func (s *ClientTestSuite) TestPlanClient_GetByPath() {
resultResp, err := s.client.Plan.GetBySlug(context.Background(), "sandbox-aws-us-east-1")
s.NoError(err, "successfully get space by path")

s.Empty(cmp.Diff(expect, resultResp), "diff between received plan response and expected should be empty")
s.Equal(expect, resultResp, "expected struct matches unmarshaled result")
}
144 changes: 143 additions & 1 deletion bonsai/release.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,152 @@
package bonsai

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"reflect"
)

const ReleaseAPIBasePath = "/releases"

// Release is a placeholder for now.
type Release struct {
Name string `json:"name,omitempty"`
Slug string `json:"slug"`
ServiceType string `json:"service_type,omitempty"`
Version string `json:"version,omitempty"`
MultiTenant bool `json:"multi_tenant,omitempty"`
MultiTenant bool `json:"multitenant,omitempty"`
}

// ReleasesResultList is a wrapper around a slice of
// Releases for json unmarshaling.
type ReleasesResultList struct {
Releases []Release `json:"releases"`
}

// ReleaseClient is a client for the Releases API.
type ReleaseClient struct {
*Client
}

type releaseListOptions struct {
listOpts
}

func (o releaseListOptions) values() url.Values {
return o.listOpts.values()
}

// list returns a list of Releases for the page specified,
// by performing a GET request against [spaceAPIBasePath].
//
// Note: Pagination is not currently supported.
func (c *ReleaseClient) list(ctx context.Context, opt releaseListOptions) ([]Release, *Response, error) {
var (
req *http.Request
reqURL *url.URL
resp *Response
err error
)
// Let's make some initial capacity to reduce allocations
results := ReleasesResultList{
Releases: make([]Release, 0, defaultResponseCapacity),
}

reqURL, err = url.Parse(ReleaseAPIBasePath)
if err != nil {
return results.Releases, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ReleaseAPIBasePath, err)
}

// Conditionally set options if we received any
if !reflect.ValueOf(opt).IsZero() {
reqURL.RawQuery = opt.values().Encode()
}

req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil)
if err != nil {
return results.Releases, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err)
}

resp, err = c.Do(ctx, req)
if err != nil {
return results.Releases, resp, fmt.Errorf("client.do failed: %w", err)
}

if err = json.Unmarshal(resp.BodyBuf.Bytes(), &results); err != nil {
return results.Releases, resp, fmt.Errorf("json.Unmarshal failed: %w", err)
}

return results.Releases, resp, nil
}

// All lists all Releases from the Releases API.
func (c *ReleaseClient) All(ctx context.Context) ([]Release, error) {
var (
err error
resp *Response
)

allResults := make([]Release, 0, defaultListResultSize)
// No pagination support as yet, but support it for future use

err = c.all(ctx, newEmptyListOpts(), func(opt listOpts) (*Response, error) {
var listResults []Release

listResults, resp, err = c.list(ctx, releaseListOptions{listOpts: opt})
if err != nil {
return resp, fmt.Errorf("client.list failed: %w", err)
}

allResults = append(allResults, listResults...)
if len(allResults) >= resp.PageSize {
resp.MarkPaginationComplete()
}
return resp, err
})

if err != nil {
return allResults, fmt.Errorf("client.all failed: %w", err)
}

return allResults, err
}

// GetBySlug gets a Release from the Releases API by its slug.
//
//nolint:dupl // Allow duplicated code blocks in code paths that may change
func (c *ReleaseClient) GetBySlug(ctx context.Context, slug string) (Release, error) {
var (
req *http.Request
reqURL *url.URL
resp *Response
err error
result Release
)

reqURL, err = url.Parse(ReleaseAPIBasePath)
if err != nil {
return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ReleaseAPIBasePath, err)
}

reqURL.Path = path.Join(reqURL.Path, slug)

req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil)
if err != nil {
return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err)
}

resp, err = c.Do(ctx, req)
if err != nil {
return result, fmt.Errorf("client.do failed: %w", err)
}

if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil {
return result, fmt.Errorf("json.Unmarshal failed: %w", err)
}

return result, nil
}
118 changes: 118 additions & 0 deletions bonsai/release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package bonsai_test

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

"github.com/omc/bonsai-api-go/v1/bonsai"
)

func (s *ClientTestSuite) TestReleaseClient_All() {
s.serveMux.HandleFunc(bonsai.ReleaseAPIBasePath, func(w http.ResponseWriter, _ *http.Request) {
respStr := `
{
"releases": [
{
"name": "Elasticsearch 5.6.16",
"slug": "elasticsearch-5.6.16",
"service_type": "elasticsearch",
"version": "5.6.16",
"multitenant": true
},
{
"name": "Elasticsearch 6.5.4",
"slug": "elasticsearch-6.5.4",
"service_type": "elasticsearch",
"version": "6.5.4",
"multitenant": true
},
{
"name": "Elasticsearch 7.2.0",
"slug": "elasticsearch-7.2.0",
"service_type": "elasticsearch",
"version": "7.2.0",
"multitenant": true
}
]
}
`

resp := &bonsai.ReleasesResultList{Releases: make([]bonsai.Release, 0, 3)}
err := json.Unmarshal([]byte(respStr), resp)
s.NoError(err, "unmarshal json into bonsai.ReleasesResultList")

err = json.NewEncoder(w).Encode(resp)
s.NoError(err, "encode bonsai.ReleasesResultList into json")
})

expect := []bonsai.Release{
{
Name: "Elasticsearch 5.6.16",
Slug: "elasticsearch-5.6.16",
ServiceType: "elasticsearch",
Version: "5.6.16",
MultiTenant: true,
},
{
Name: "Elasticsearch 6.5.4",
Slug: "elasticsearch-6.5.4",
ServiceType: "elasticsearch",
Version: "6.5.4",
MultiTenant: true,
},
{
Name: "Elasticsearch 7.2.0",
Slug: "elasticsearch-7.2.0",
ServiceType: "elasticsearch",
Version: "7.2.0",
MultiTenant: true,
},
}
releases, err := s.client.Release.All(context.Background())
s.NoError(err, "successfully get all releases")
s.Len(releases, 3)

s.ElementsMatch(expect, releases, "elements in expect match elements in received releases")
}

func (s *ClientTestSuite) TestReleaseClient_GetBySlug() {
const targetReleaseSlug = "elasticsearch-7.2.0"

urlPath, err := url.JoinPath(bonsai.ReleaseAPIBasePath, targetReleaseSlug)
s.NoError(err, "successfully resolved path")

s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) {
respStr := fmt.Sprintf(`
{
"name": "Elasticsearch 7.2.0",
"slug": "%s",
"service_type": "elasticsearch",
"version": "7.2.0",
"multitenant": true
}
`, targetReleaseSlug)

resp := &bonsai.Release{}
err = json.Unmarshal([]byte(respStr), resp)
s.NoError(err, "unmarshals json into bonsai.Space")

err = json.NewEncoder(w).Encode(resp)
s.NoError(err, "encodes bonsai.Space into json on the writer")
})

expect := bonsai.Release{
Slug: "elasticsearch-7.2.0",
Name: "Elasticsearch 7.2.0",
ServiceType: "elasticsearch",
Version: "7.2.0",
MultiTenant: true,
}

resultResp, err := s.client.Release.GetBySlug(context.Background(), targetReleaseSlug)
s.NoError(err, "successfully get release by path")

s.Equal(expect, resultResp, "elements in expect match elements in received release response")
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/omc/bonsai-api-go/v1
go 1.22

require (
github.com/google/go-cmp v0.6.0
github.com/hetznercloud/hcloud-go/v2 v2.7.2
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.24.0
Expand Down
Loading