Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

feat: reuse http.Client #248

Merged
merged 4 commits into from
Feb 23, 2021
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
16 changes: 11 additions & 5 deletions ci/integration/envs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@ import (
"cdr.dev/slog/sloggers/slogtest"
"cdr.dev/slog/sloggers/slogtest/assert"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"

"cdr.dev/coder-cli/coder-sdk"
"cdr.dev/coder-cli/pkg/tcli"
)

func cleanupClient(ctx context.Context, t *testing.T) *coder.Client {
func cleanupClient(ctx context.Context, t *testing.T) coder.Client {
creds := login(ctx, t)

u, err := url.Parse(creds.url)
assert.Success(t, "parse base url", err)

return &coder.Client{BaseURL: u, Token: creds.token}
client, err := coder.NewClient(coder.ClientOptions{
BaseURL: u,
Token: creds.token,
})
require.NoError(t, err, "failed to create coder.Client")
return client
}

func cleanupEnv(t *testing.T, client *coder.Client, envID string) func() {
func cleanupEnv(t *testing.T, client coder.Client, envID string) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
Expand All @@ -39,9 +45,9 @@ func cleanupEnv(t *testing.T, client *coder.Client, envID string) func() {
}

// this is a stopgap until we have support for a `coder images` subcommand
// until then, we want can use the *coder.Client to ensure our integration tests
// until then, we want can use the coder.Client to ensure our integration tests
// work on fresh deployments.
func ensureImageImported(ctx context.Context, t *testing.T, client *coder.Client, img string) {
func ensureImageImported(ctx context.Context, t *testing.T, client coder.Client, img string) {
orgs, err := client.Organizations(ctx)
assert.Success(t, "get orgs", err)

Expand Down
2 changes: 1 addition & 1 deletion coder-sdk/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type activityRequest struct {
}

// PushActivity pushes CLI activity to Coder.
func (c Client) PushActivity(ctx context.Context, source, envID string) error {
func (c *DefaultClient) PushActivity(ctx context.Context, source, envID string) error {
resp, err := c.request(ctx, http.MethodPost, "/api/private/metrics/usage/push", activityRequest{
Source: source,
EnvironmentID: envID,
Expand Down
79 changes: 50 additions & 29 deletions coder-sdk/client.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,67 @@
package coder

import (
"errors"
"net/http"
"net/http/cookiejar"
"net/url"
)

// ensure that DefaultClient implements Client.
var _ = Client(&DefaultClient{})

// Me is the user ID of the authenticated user.
const Me = "me"

// Client wraps the Coder HTTP API.
type Client struct {
// ClientOptions contains options for the Coder SDK Client.
type ClientOptions struct {
// BaseURL is the root URL of the Coder installation.
BaseURL *url.URL
Token string

// Client is the http.Client to use for requests (optional).
// If omitted, the http.DefaultClient will be used.
HTTPClient *http.Client

// Token is the API Token used to authenticate
Token string
}

// newHTTPClient creates a default underlying http client and sets the auth cookie.
//
// NOTE:
// As we do not specify a custom transport, the default one from the stdlib will be used,
// resulting in a persistent connection pool.
// We do not set a timeout here as it could cause issue with the websocket.
// The caller is expected to set it when needed.
//
// WARNING:
// If the caller sets a custom transport to set TLS settings or a custom CA the default
// pool will not be used and it might result in a new dns lookup/tls session/socket begin
// established each time.
func (c Client) newHTTPClient() (*http.Client, error) {
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
// NewClient creates a new default Coder SDK client.
func NewClient(opts ClientOptions) (*DefaultClient, error) {
httpClient := opts.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}

if opts.BaseURL == nil {
return nil, errors.New("the BaseURL parameter is required")
}

jar.SetCookies(c.BaseURL, []*http.Cookie{{
Name: "session_token",
Value: c.Token,
MaxAge: 86400,
Path: "/",
HttpOnly: true,
Secure: c.BaseURL.Scheme == "https",
}})
if opts.Token == "" {
return nil, errors.New("an API token is required")
}

client := &DefaultClient{
baseURL: opts.BaseURL,
httpClient: httpClient,
token: opts.Token,
}

return client, nil
}

// DefaultClient is the default implementation of the coder.Client
// interface.
//
// The empty value is meaningless and the fields are unexported;
// use NewClient to create an instance.
type DefaultClient struct {
// baseURL is the URL (scheme, hostname/IP address, port,
// path prefix of the Coder installation)
baseURL *url.URL

// httpClient is the http.Client used to issue requests.
httpClient *http.Client

return &http.Client{Jar: jar}, nil
// token is the API Token credential.
token string
}
38 changes: 25 additions & 13 deletions coder-sdk/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ func TestPushActivity(t *testing.T) {
u, err := url.Parse(server.URL)
require.NoError(t, err, "failed to parse test server URL")

client := coder.Client{
client, err := coder.NewClient(coder.ClientOptions{
BaseURL: u,
}
Token: "SwdcSoq5Jc-0C1r8wfwm7h6h9i0RDk7JT",
})
require.NoError(t, err, "failed to create coder.Client")

err = client.PushActivity(context.Background(), source, envID)
require.NoError(t, err)
}
Expand Down Expand Up @@ -81,9 +84,12 @@ func TestUsers(t *testing.T) {
u, err := url.Parse(server.URL)
require.NoError(t, err, "failed to parse test server URL")

client := coder.Client{
client, err := coder.NewClient(coder.ClientOptions{
BaseURL: u,
}
Token: "JcmErkJjju-KSrztst0IJX7xGJhKQPtfv",
})
require.NoError(t, err, "failed to create coder.Client")

users, err := client.Users(context.Background())
require.NoError(t, err, "error getting Users")
require.Len(t, users, 1, "users should return a single user")
Expand All @@ -96,9 +102,8 @@ func TestAuthentication(t *testing.T) {

const token = "g4mtIPUaKt-pPl9Q0xmgKs7acSypHt4Jf"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_token")
require.NoError(t, err, "error extracting session token")
require.Equal(t, token, cookie.Value, "token does not match")
gotToken := r.Header.Get("Session-Token")
require.Equal(t, token, gotToken, "token does not match")

w.WriteHeader(http.StatusServiceUnavailable)
}))
Expand All @@ -109,11 +114,14 @@ func TestAuthentication(t *testing.T) {
u, err := url.Parse(server.URL)
require.NoError(t, err, "failed to parse test server URL")

client := coder.Client{
client, err := coder.NewClient(coder.ClientOptions{
BaseURL: u,
Token: token,
}
_, _ = client.APIVersion(context.Background())
})
require.NoError(t, err, "failed to create coder.Client")

_, err = client.APIVersion(context.Background())
require.NoError(t, err, "failed to get API version information")
}

func TestContextRoot(t *testing.T) {
Expand All @@ -140,9 +148,13 @@ func TestContextRoot(t *testing.T) {
for _, prefix := range contextRoots {
u.Path = prefix

client := coder.Client{
client, err := coder.NewClient(coder.ClientOptions{
BaseURL: u,
}
_, _ = client.Users(context.Background())
Token: "FrOgA6xhpM-p5nTfsupmvzYJA6DJSOUoE",
})
require.NoError(t, err, "failed to create coder.Client")

_, err = client.Users(context.Background())
require.Error(t, err, "expected 503 error")
}
}
14 changes: 7 additions & 7 deletions coder-sdk/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type ConfigOAuth struct {
}

// SiteConfigAuth fetches the sitewide authentication configuration.
func (c Client) SiteConfigAuth(ctx context.Context) (*ConfigAuth, error) {
func (c *DefaultClient) SiteConfigAuth(ctx context.Context) (*ConfigAuth, error) {
var conf ConfigAuth
if err := c.requestBody(ctx, http.MethodGet, "/api/private/auth/config", nil, &conf); err != nil {
return nil, err
Expand All @@ -74,12 +74,12 @@ func (c Client) SiteConfigAuth(ctx context.Context) (*ConfigAuth, error) {
}

// PutSiteConfigAuth sets the sitewide authentication configuration.
func (c Client) PutSiteConfigAuth(ctx context.Context, req ConfigAuth) error {
func (c *DefaultClient) PutSiteConfigAuth(ctx context.Context, req ConfigAuth) error {
return c.requestBody(ctx, http.MethodPut, "/api/private/auth/config", req, nil)
}

// SiteConfigOAuth fetches the sitewide git provider OAuth configuration.
func (c Client) SiteConfigOAuth(ctx context.Context) (*ConfigOAuth, error) {
func (c *DefaultClient) SiteConfigOAuth(ctx context.Context) (*ConfigOAuth, error) {
var conf ConfigOAuth
if err := c.requestBody(ctx, http.MethodGet, "/api/private/oauth/config", nil, &conf); err != nil {
return nil, err
Expand All @@ -88,7 +88,7 @@ func (c Client) SiteConfigOAuth(ctx context.Context) (*ConfigOAuth, error) {
}

// PutSiteConfigOAuth sets the sitewide git provider OAuth configuration.
func (c Client) PutSiteConfigOAuth(ctx context.Context, req ConfigOAuth) error {
func (c *DefaultClient) PutSiteConfigOAuth(ctx context.Context, req ConfigOAuth) error {
return c.requestBody(ctx, http.MethodPut, "/api/private/oauth/config", req, nil)
}

Expand All @@ -97,7 +97,7 @@ type configSetupMode struct {
}

// SiteSetupModeEnabled fetches the current setup_mode state of a Coder Enterprise deployment.
func (c Client) SiteSetupModeEnabled(ctx context.Context) (bool, error) {
func (c *DefaultClient) SiteSetupModeEnabled(ctx context.Context) (bool, error) {
var conf configSetupMode
if err := c.requestBody(ctx, http.MethodGet, "/api/private/config/setup-mode", nil, &conf); err != nil {
return false, err
Expand Down Expand Up @@ -125,7 +125,7 @@ type ConfigExtensionMarketplace struct {
}

// SiteConfigExtensionMarketplace fetches the extension marketplace configuration.
func (c Client) SiteConfigExtensionMarketplace(ctx context.Context) (*ConfigExtensionMarketplace, error) {
func (c *DefaultClient) SiteConfigExtensionMarketplace(ctx context.Context) (*ConfigExtensionMarketplace, error) {
var conf ConfigExtensionMarketplace
if err := c.requestBody(ctx, http.MethodGet, "/api/private/extensions/config", nil, &conf); err != nil {
return nil, err
Expand All @@ -134,6 +134,6 @@ func (c Client) SiteConfigExtensionMarketplace(ctx context.Context) (*ConfigExte
}

// PutSiteConfigExtensionMarketplace sets the extension marketplace configuration.
func (c Client) PutSiteConfigExtensionMarketplace(ctx context.Context, req ConfigExtensionMarketplace) error {
func (c *DefaultClient) PutSiteConfigExtensionMarketplace(ctx context.Context, req ConfigExtensionMarketplace) error {
return c.requestBody(ctx, http.MethodPut, "/api/private/extensions/config", req, nil)
}
8 changes: 4 additions & 4 deletions coder-sdk/devurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type delDevURLRequest struct {
}

// DeleteDevURL deletes the specified devurl.
func (c Client) DeleteDevURL(ctx context.Context, envID, urlID string) error {
func (c *DefaultClient) DeleteDevURL(ctx context.Context, envID, urlID string) error {
reqURL := fmt.Sprintf("/api/v0/environments/%s/devurls/%s", envID, urlID)

return c.requestBody(ctx, http.MethodDelete, reqURL, delDevURLRequest{
Expand All @@ -41,12 +41,12 @@ type CreateDevURLReq struct {
}

// CreateDevURL inserts a new devurl for the authenticated user.
func (c Client) CreateDevURL(ctx context.Context, envID string, req CreateDevURLReq) error {
func (c *DefaultClient) CreateDevURL(ctx context.Context, envID string, req CreateDevURLReq) error {
return c.requestBody(ctx, http.MethodPost, "/api/v0/environments/"+envID+"/devurls", req, nil)
}

// DevURLs fetches the Dev URLs for a given environment.
func (c Client) DevURLs(ctx context.Context, envID string) ([]DevURL, error) {
func (c *DefaultClient) DevURLs(ctx context.Context, envID string) ([]DevURL, error) {
var devurls []DevURL
if err := c.requestBody(ctx, http.MethodGet, "/api/v0/environments/"+envID+"/devurls", nil, &devurls); err != nil {
return nil, err
Expand All @@ -58,6 +58,6 @@ func (c Client) DevURLs(ctx context.Context, envID string) ([]DevURL, error) {
type PutDevURLReq CreateDevURLReq

// PutDevURL updates an existing devurl for the authenticated user.
func (c Client) PutDevURL(ctx context.Context, envID, urlID string, req PutDevURLReq) error {
func (c *DefaultClient) PutDevURL(ctx context.Context, envID, urlID string, req PutDevURLReq) error {
return c.requestBody(ctx, http.MethodPut, "/api/v0/environments/"+envID+"/devurls/"+urlID, req, nil)
}
Loading