Skip to content

Commit

Permalink
Merge pull request #7 from omc/feat/release-client
Browse files Browse the repository at this point in the history
Feat/release client
  • Loading branch information
momer authored May 3, 2024
2 parents 07ee910 + b0c00b9 commit e063e61
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 11 deletions.
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

0 comments on commit e063e61

Please sign in to comment.