1
package main
1
package main
2
2
3
import "encoding/json"
3
import "encoding/json"
4
import "fmt"
4
import "fmt"
5
import "io"
5
import "io"
6
import "net/http"
6
import "net/http"
7
import "os"
7
import "os"
8
import "os/exec"
8
import "os/exec"
9
import "regexp"
9
import "regexp"
10
import "strings"
10
import "strings"
11
11
12
var repoRegex = regexp.MustCompile(
12
var repoRegex = regexp.MustCompile(
13
`code\.oscarkilo\.com/([a-z][-a-z0-9]*)\.git`)
13
`code\.oscarkilo\.com/([a-z][-a-z0-9]*)\.git`)
14
14
15
// detectRepo parses the git remote URL for the klee repo name.
15
// detectRepo parses the git remote URL for the klee repo name.
16
func detectRepo() (string, error) {
16
func detectRepo() (string, error) {
17
cmd := exec.Command("git", "remote", "get-url", "origin")
17
cmd := exec.Command("git", "remote", "get-url", "origin")
18
out, err := cmd.Output()
18
out, err := cmd.Output()
19
if err != nil {
19
if err != nil {
20
return "", fmt.Errorf(
20
return "", fmt.Errorf(
21
"not a git repo or no remote 'origin': %v", err)
21
"not a git repo or no remote 'origin': %v", err)
22
}
22
}
23
url := strings.TrimSpace(string(out))
23
url := strings.TrimSpace(string(out))
24
m := repoRegex.FindStringSubmatch(url)
24
m := repoRegex.FindStringSubmatch(url)
25
if m == nil {
25
if m == nil {
26
return "", fmt.Errorf(
26
return "", fmt.Errorf(
27
"remote URL %q is not a klee repo", url)
27
"remote URL %q is not a klee repo", url)
28
}
28
}
29
return m[1], nil
29
return m[1], nil
30
}
30
}
31
31
32
// resolveRepo returns the repo name from --repo flag,
32
// resolveRepo returns the repo name from --repo flag,
33
// OKG_REPO env var, or git remote detection.
33
// OKG_REPO env var, or git remote detection.
34
func resolveRepo(flagRepo string) (string, error) {
34
func resolveRepo(flagRepo string) (string, error) {
35
if flagRepo != "" {
35
if flagRepo != "" {
36
return flagRepo, nil
36
return flagRepo, nil
37
}
37
}
38
if v := os.Getenv("OKG_REPO"); v != "" {
38
if v := os.Getenv("OKG_REPO"); v != "" {
39
return v, nil
39
return v, nil
40
}
40
}
41
return detectRepo()
41
return detectRepo()
42
}
42
}
43
43
44
type Client struct {
44
type Client struct {
45
Host string
45
Host string
46
ApiKey string
46
ApiKey string
47
http *http.Client
47
http *http.Client
48
}
48
}
49
49
50
func newClient(cfg *Config) *Client {
50
func newClient(cfg *Config) *Client {
51
return &Client{
51
return &Client{
52
Host: cfg.Host,
52
Host: cfg.Host,
53
ApiKey: cfg.ApiKey,
53
ApiKey: cfg.ApiKey,
54
http: &http.Client{},
54
http: &http.Client{},
55
}
55
}
56
}
56
}
57
57
58
// do performs an HTTP request and returns the response.
58
// do performs an HTTP request and returns the response.
59
func (c *Client) do(
59
func (c *Client) do(
60
method, path string, body io.Reader,
60
method, path string, body io.Reader,
61
) (*http.Response, error) {
61
) (*http.Response, error) {
62
url := c.Host + path
62
url := c.Host + path
63
req, err := http.NewRequest(method, url, body)
63
req, err := http.NewRequest(method, url, body)
64
if err != nil {
64
if err != nil {
65
return nil, err
65
return nil, err
66
}
66
}
67
req.Header.Set("Accept", "application/json")
67
req.Header.Set("Accept", "application/json")
68
if c.ApiKey != "" {
68
if c.ApiKey != "" {
69
req.Header.Set(
69
req.Header.Set(
70
"Authorization", "Bearer "+c.ApiKey)
70
"Authorization", "Bearer "+c.ApiKey)
71
}
71
}
72
if body != nil {
72
if body != nil {
73
req.Header.Set("Content-Type", "application/json")
73
req.Header.Set("Content-Type", "application/json")
74
}
74
}
75
return c.http.Do(req)
75
return c.http.Do(req)
76
}
76
}
77
77
78
// getJSON performs a GET and decodes JSON into dst.
78
// getJSON performs a GET and decodes JSON into dst.
79
func (c *Client) getJSON(path string, dst interface{}) error {
79
func (c *Client) getJSON(path string, dst interface{}) error {
80
resp, err := c.do("GET", path, nil)
80
resp, err := c.do("GET", path, nil)
81
if err != nil {
81
if err != nil {
82
return err
82
return err
83
}
83
}
84
defer resp.Body.Close()
84
defer resp.Body.Close()
85
if resp.StatusCode >= 400 {
85
if resp.StatusCode >= 400 {
86
body, _ := io.ReadAll(resp.Body)
86
body, _ := io.ReadAll(resp.Body)
87
return fmt.Errorf(
87
return fmt.Errorf(
88
"HTTP %d: %s", resp.StatusCode, string(body))
88
"HTTP %d: %s", resp.StatusCode, string(body))
89
}
89
}
90
return json.NewDecoder(resp.Body).Decode(dst)
90
return json.NewDecoder(resp.Body).Decode(dst)
91
}
91
}
92
92
93
// postJSON performs a POST with a JSON body,
93
// postJSON performs a POST with a JSON body,
94
// decodes the response into dst.
94
// decodes the response into dst.
95
func (c *Client) postJSON(
95
func (c *Client) postJSON(
96
path string, payload interface{}, dst interface{},
96
path string, payload interface{}, dst interface{},
97
) error {
97
) error {
98
body, err := jsonBody(payload)
98
body, err := jsonBody(payload)
99
if err != nil {
99
if err != nil {
100
return err
100
return err
101
}
101
}
102
resp, err := c.do("POST", path, body)
102
resp, err := c.do("POST", path, body)
103
if err != nil {
103
if err != nil {
104
return err
104
return err
105
}
105
}
106
defer resp.Body.Close()
106
defer resp.Body.Close()
107
if resp.StatusCode >= 400 {
107
if resp.StatusCode >= 400 {
108
b, _ := io.ReadAll(resp.Body)
108
b, _ := io.ReadAll(resp.Body)
109
return fmt.Errorf(
109
return fmt.Errorf(
110
"HTTP %d: %s", resp.StatusCode, string(b))
110
"HTTP %d: %s", resp.StatusCode, string(b))
111
}
111
}
112
if dst != nil {
112
if dst != nil {
113
return json.NewDecoder(resp.Body).Decode(dst)
113
return json.NewDecoder(resp.Body).Decode(dst)
114
}
114
}
115
return nil
115
return nil
116
}
116
}
117
117
118
// patchJSON performs a PATCH with a JSON body,
118
// patchJSON performs a PATCH with a JSON body,
119
// decodes the response into dst.
119
// decodes the response into dst.
120
func (c *Client) patchJSON(
120
func (c *Client) patchJSON(
121
path string, payload interface{}, dst interface{},
121
path string, payload interface{}, dst interface{},
122
) error {
122
) error {
123
body, err := jsonBody(payload)
123
body, err := jsonBody(payload)
124
if err != nil {
124
if err != nil {
125
return err
125
return err
126
}
126
}
127
resp, err := c.do("PATCH", path, body)
127
resp, err := c.do("PATCH", path, body)
128
if err != nil {
128
if err != nil {
129
return err
129
return err
130
}
130
}
131
defer resp.Body.Close()
131
defer resp.Body.Close()
132
if resp.StatusCode >= 400 {
132
if resp.StatusCode >= 400 {
133
b, _ := io.ReadAll(resp.Body)
133
b, _ := io.ReadAll(resp.Body)
134
return fmt.Errorf(
134
return fmt.Errorf(
135
"HTTP %d: %s", resp.StatusCode, string(b))
135
"HTTP %d: %s", resp.StatusCode, string(b))
136
}
136
}
137
if dst != nil {
137
if dst != nil {
138
return json.NewDecoder(resp.Body).Decode(dst)
138
return json.NewDecoder(resp.Body).Decode(dst)
139
}
139
}
140
return nil
140
return nil
141
}
141
}
142
142
143
func jsonBody(v interface{}) (io.Reader, error) {
143
func jsonBody(v interface{}) (io.Reader, error) {
144
data, err := json.Marshal(v)
144
data, err := json.Marshal(v)
145
if err != nil {
145
if err != nil {
146
return nil, err
146
return nil, err
147
}
147
}
148
return strings.NewReader(string(data)), nil
148
return strings.NewReader(string(data)), nil
149
}
149
}