Skip to content

Commit

Permalink
adds first service to the gostash client
Browse files Browse the repository at this point in the history
UserService permits retrieving indivudual users and listing them.

Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Sep 10, 2021
1 parent 8339630 commit 75b64aa
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 17 deletions.
38 changes: 27 additions & 11 deletions http/client.go → go-stash/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package http
package gostash

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"math/rand"
"net/http"
"net/url"
Expand Down Expand Up @@ -79,6 +80,9 @@ type Client struct {
HeaderFields *http.Header
// Logger is the logger used to log the request and response.
Logger *logr.Logger

// Services are used to communicate with the differents stash endpoints
Users *UsersService
}

// RateLimiter is the interface that wraps the basic Wait method.
Expand Down Expand Up @@ -149,6 +153,11 @@ func NewClient(httpClient *http.Client, host string, header *http.Header, logger

c.HeaderFields = header

c.Users = &UsersService{
Client: c,
log: *c.Logger,
}

return c, nil
}

Expand Down Expand Up @@ -281,12 +290,12 @@ func (c *Client) configureLimiter() error {
return nil
}

// NewRequest creates a request, and returns an retryablehttp.Request and an error,
// NewRequest creates a request, and returns an http.Request and an error,
// given a path and optional method, query, body, and header.
// A relative URL path can be provided in path, in which case it is resolved relative to the base URL of the Client.
// Relative URL paths should always be specified without a preceding slash.
// If specified, the value pointed to by body is JSON encoded and included as the request body.
func (c *Client) NewRequest(ctx context.Context, method string, path string, query url.Values, body interface{}, header http.Header) (*retryablehttp.Request, error) { // nolint:funlen,gocognit,gocyclo // ok
func (c *Client) NewRequest(ctx context.Context, method string, path string, query url.Values, body interface{}, header http.Header) (*http.Request, error) {
u := *c.BaseURL
unescaped, err := url.PathUnescape(path)
if err != nil {
Expand All @@ -301,13 +310,15 @@ func (c *Client) NewRequest(ctx context.Context, method string, path string, que
method = http.MethodGet
}

var jsonBody interface{}
var bodyReader io.ReadCloser
if (method == http.MethodPost || method == http.MethodPut) && body != nil {
jsonBody, e := json.Marshal(body)
if e != nil {
return nil, fmt.Errorf("failed to marshall request body, %w", e)
}

bodyReader = io.NopCloser(bytes.NewReader(jsonBody))

c.Logger.V(2).Info("request", "body", string(jsonBody))
}

Expand All @@ -317,7 +328,7 @@ func (c *Client) NewRequest(ctx context.Context, method string, path string, que

u.RawQuery = query.Encode()

req, err := retryablehttp.NewRequest(method, u.String(), jsonBody)
req, err := http.NewRequest(method, u.String(), bodyReader)
if err != nil {
return req, fmt.Errorf("failed create request for %s %s, %w", method, u.String(), err)
}
Expand All @@ -343,11 +354,11 @@ func (c *Client) NewRequest(ctx context.Context, method string, path string, que
return req, nil
}

// Do performs a request, and returns an http.Response and an error given an retryablehttp.Request.
// Do performs a request, and returns an http.Response and an error given an http.Request.
// For an outgoing Client request, the context controls the entire lifetime of a reques:
// obtaining a connection, sending the request, checking errors and retrying.
// The response body is not closed.
func (c *Client) Do(request *retryablehttp.Request) ([]byte, *http.Response, error) {
func (c *Client) Do(request *http.Request) ([]byte, *http.Response, error) {
// If not yet configured, try to configure the rate limiter. Fail
// silently as the limiter will be disabled in case of an error.
c.configureLimiterOnce.Do(func() { c.configureLimiter() })
Expand All @@ -360,7 +371,12 @@ func (c *Client) Do(request *retryablehttp.Request) ([]byte, *http.Response, err

c.Logger.V(2).Info("request", "method", request.Method, "url", request.URL)

resp, err := c.Client.Do(request)
req, err := retryablehttp.FromRequest(request)
if err != nil {
return nil, nil, err
}

resp, err := c.Client.Do(req)
if err != nil {
return nil, nil, err
}
Expand All @@ -382,9 +398,9 @@ func (c *Client) Do(request *retryablehttp.Request) ([]byte, *http.Response, err
return nil, resp, fmt.Errorf("request %s %s returned status code: %s, %w", request.Method, request.URL, resp.Status, ErrorUnexpectedStatusCode)
}

// getRespBody is used to obtain the response body as a string.
// getRespBody is used to obtain the response body as a []byte.
func getRespBody(resp *http.Response) ([]byte, error) {
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
Expand Down
10 changes: 4 additions & 6 deletions http/client_test.go → go-stash/client_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package http
package gostash

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -222,9 +222,7 @@ func Test_Do(t *testing.T) {

if tt.method == http.MethodGet {
user := user{}
// Restore the io.ReadCloser to its original state
resp.Body = ioutil.NopCloser(bytes.NewBuffer(res))
err := json.NewDecoder(resp.Body).Decode(&user)
err := json.Unmarshal(res, &user)
if err != nil {
t.Fatalf("%s users failed, unable to obtain response body: %v", tt.method, err)
}
Expand Down Expand Up @@ -277,7 +275,7 @@ func Test_DoWithRetry(t *testing.T) {
}
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(fmt.Sprint(retries))),
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprint(retries))),
Header: make(http.Header),
}, nil
}, func(c *Client) {
Expand Down
107 changes: 107 additions & 0 deletions go-stash/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 gostash

import (
"net/http"
)

const (
contextKey = "context"
filterKey = "filter"
)

var (
stashURIprefix = "/rest/api/1.0"
stashURIkeys = "/rest/keys/1.0"
)

// Session keeps a record of a request for a given user.
type Session struct {
// UserID is the ID of the user making the request.
UserID string `json:"userID,omitempty"`
// UserName is the name of the user making the request.
UserName string `json:"userName,omitempty"`
// SessionID is the ID of the session.
SessionID string `json:"sessionID,omitempty"`
// RequestID is the ID of the request.
RequestID string `json:"requestID,omitempty"`
}

func (s *Session) set(resp *http.Response) {
s.UserID = resp.Header.Get("X-Auserid")
s.UserName = resp.Header.Get("X-Ausername")
s.SessionID = resp.Header.Get("X-Asessionid")
s.RequestID = resp.Header.Get("X-Arequestid")
}

func (s *Session) copy(p *Session) {
s.UserID = p.UserID
s.UserName = p.UserName
s.SessionID = p.SessionID
s.RequestID = p.RequestID
}

// Paging is the paging information.
type Paging struct {
// IsLastPage indicates whether another page of items exists.
IsLastPage bool `json:"isLastPage,omitempty"`
// Limit indicates how many results to return per page.
Limit int64 `json:"limit,omitempty"`
// Size indicates the total number of results..
Size int64 `json:"size,omitempty"`
// Start indicates which item should be used as the first item in the page of results.
Start int64 `json:"start,omitempty"`
// NexPageStart must be used by the client as the start parameter on the next request.
// Identifiers of adjacent objects in a page may not be contiguous,
// so the start of the next page is not necessarily the start of the last page plus the last page's size.
// Always use nextPageStart to avoid unexpected results from a paged API.
NextPageStart int64 `json:"nextPageStart,omitempty"`
}

// IsLast() returns true if the paging information indicates that there are no more pages.
func (p *Paging) IsLast() bool {
return p.IsLastPage
}

// PagingOptions is the options for paging.
type PagingOptions struct {
// Start indicates which item should be used as the first item in the page of results.
Start int64
// Limit indicates how many results to return per page.
Limit int64
}

// Self indicates the hyperlink to a REST resource.
type Self struct {
Href string `json:"href,omitempty"`
}

// Clone is a hyperlink to another REST resource.
type Clone struct {
// Href is the hyperlink to the resource.
Href string `json:"href,omitempty"`
// Name is the name of the resource.
Name string `json:"name,omitempty"`
}

// Links is a set of hyperlinks that link to other related resources.
type Links struct {
// Self is the hyperlink to the resource.
Self []Self `json:"self,omitempty"`
// Clone is a set of hyperlinks to other REST resources.
Clone []Clone `json:"clone,omitempty"`
}
Loading

0 comments on commit 75b64aa

Please sign in to comment.