-
Notifications
You must be signed in to change notification settings - Fork 5
/
client.go
148 lines (123 loc) · 4.59 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package se2
import (
"encoding/base64"
"encoding/json"
"net/http"
"time"
"github.com/pkg/errors"
)
const (
modeUnset ServerMode = iota
ModeStaging
ModeProduction
hostProduction string = "https://api.suborbital.network"
hostStaging string = "https://stg.api.suborbital.network"
hostExecProduction string = "https://edge.suborbital.network"
hostExecStaging string = "https://stg.edge.suborbital.network"
minAccessKeyLength = 60
defaultTimeout = 60 * time.Second
emptyString string = ""
zeroLength int = 0
httpResponseCodeErrorFormat = "%s: expected http response code to be %d, got %d"
)
var (
ErrNoAccessKey = errors.New("no access key provided, or it's likely malformed")
ErrUnknownMode = errors.New("unknown client mode set. Use one of the ModeStaging or ModeProduction constants")
)
// ServerMode is an alias type to help ensure that only the options we declared here can be used.
type ServerMode int
// accessKey is a transitive type that helps the NewClient constructor make sure that the passed in token is of the
// correct form and structure.
type accessKey struct {
Key int `json:"key"`
Secret string `json:"secret"`
}
// Client holds our configured http client, its methods, and all the functionality necessary so users can interact with
// our API without having to call actual http requests.
type Client struct {
httpClient *http.Client
host string
execHost string
token string
}
// ClientOption is a function signature users can use to configure different parts of the client. They are run at the
// very end of initialization, once we know that the mode and the access key are correct.
type ClientOption func(*Client)
// NewClient returns a configured instance of a configured client for SE2. Required parameters are the mode to specify
// whether it's the production or the staging environment, and an access key you can grab from the SE2 admin area for
// an environment.
//
// By default, the underlying http client has a 60-second timeout. Otherwise, you can use the
// WithHTTPClient(*http.Client) function to use your own configured version for it.
func NewClient(mode ServerMode, token string, options ...ClientOption) (*Client, error) {
// Create zero value client with default http client.
nc := Client{
httpClient: defaultHTTPClient(),
}
// Set hosts based on mode, if somehow it's an unknown mode, return error.
switch mode {
case ModeStaging:
nc.host = hostStaging
nc.execHost = hostExecStaging
case ModeProduction:
nc.host = hostProduction
nc.execHost = hostExecProduction
default:
return nil, ErrUnknownMode
}
// If access key is too short after modifiers, return error.
if len(token) < minAccessKeyLength {
return nil, ErrNoAccessKey
}
// If access key is not a base64 encoded string, return error.
decoded, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return nil, ErrNoAccessKey
}
// If access key is not a valid JSON base64 encoded, return error.
var akUnmarshaled accessKey
err = json.Unmarshal(decoded, &akUnmarshaled)
if err != nil {
return nil, ErrNoAccessKey
}
// Save the good token to the client.
nc.token = token
// Apply all the modifiers.
for _, o := range options {
o(&nc)
}
return &nc, nil
}
// defaultHTTPClient returns an http.Client with a 60-second timeout that's used until the users decide to change it by
// use the WithHTTPClient function.
func defaultHTTPClient() *http.Client {
return &http.Client{
Timeout: defaultTimeout,
}
}
// WithHTTPClient allows you to configure the http.Client used in the SE2 client.
func WithHTTPClient(client *http.Client) func(*Client) {
return func(c *Client) {
c.httpClient = client
}
}
// do is the meat of the client, every other admin level exported method uses this. Its main job is to add the
// authorization header with the access key to outgoing requests.
func (c *Client) do(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+c.token)
res, err := c.httpClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "c.httpClient.do")
}
return res, nil
}
// sessionDo is a common method to work with requests against the builder where a session token is needed instead of the
// environment token that the do method uses.
func (c *Client) sessionDo(req *http.Request, token CreateSessionResponse) (*http.Response, error) {
req.Header.Add("Authorization", "Bearer "+token.Token)
res, err := c.httpClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "c.httpClient.do")
}
return res, nil
}