-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathclient.go
More file actions
171 lines (155 loc) · 4.52 KB
/
client.go
File metadata and controls
171 lines (155 loc) · 4.52 KB
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Package gitinfo provides a client to interact with the git information service
// to retrieve metadata about git repositories.
package gitinfo
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
apps "github.com/ninech/apis/apps/v1alpha1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
)
// Client is a client for the git information service.
type Client struct {
token string
url *url.URL
client *http.Client
logRetryFunc func(err error)
retryBackoff wait.Backoff
}
// New returns a client which can be used to retrieve
// metadata information about a given git repository
func New(address string, token string) (*Client, error) {
u, err := url.Parse(address)
if err != nil {
return nil, fmt.Errorf("can not parse git information service URL: %w", err)
}
return defaultGitInformationClient(setURLDefaults(u), token), nil
}
func setURLDefaults(u *url.URL) *url.URL {
// the git information service just responds to messages on /explore
newURL := u.JoinPath("explore")
if u.Scheme == "" {
newURL.Scheme = "https"
}
return newURL
}
func defaultGitInformationClient(url *url.URL, token string) *Client {
g := &Client{
token: token,
url: url,
client: http.DefaultClient,
retryBackoff: wait.Backoff{
Steps: 5,
Duration: 200 * time.Millisecond,
Factor: 2.0,
Jitter: 0.1,
Cap: 2 * time.Second,
},
}
g.logRetryFunc = func(err error) {
g.logError("Retrying because of error: %v\n", err)
}
return g
}
func (g *Client) logError(format string, v ...any) {
fmt.Fprintf(os.Stderr, format, v...)
}
// SetLogRetryFunc allows to set the function which logs retries when doing
// requests to the git information service
func (g *Client) SetLogRetryFunc(f func(err error)) {
g.logRetryFunc = f
}
// SetRetryBackoffs sets the backoff properties for retries
func (g *Client) SetRetryBackoffs(backoff wait.Backoff) {
g.retryBackoff = backoff
}
func (g *Client) repositoryInformation(ctx context.Context, git apps.GitTarget, auth Auth) (*apps.GitExploreResponse, error) {
req := apps.GitExploreRequest{
Repository: git.URL,
Revision: git.Revision,
}
if auth.Enabled() {
req.Auth = &apps.Auth{}
if auth.HasBasicAuth() {
req.Auth.BasicAuth = &apps.BasicAuth{
Username: *auth.Username,
Password: *auth.Password,
}
}
if auth.HasPrivateKey() {
req.Auth.PrivateKey = []byte(*auth.SSHPrivateKey)
}
}
return g.sendRequest(ctx, req)
}
// RepositoryInformation returns information about a given git repository and
// optionally checks if a given revision can be found in the repo. It retries
// on client connection issues.
func (g *Client) RepositoryInformation(ctx context.Context, git apps.GitTarget, auth Auth) (*apps.GitExploreResponse, error) {
var repoInfo *apps.GitExploreResponse
err := retry.OnError(
g.retryBackoff,
func(err error) bool {
if g.logRetryFunc != nil {
g.logRetryFunc(err)
}
// retry regardless of the error
return true
},
func() error {
var err error
repoInfo, err = g.repositoryInformation(ctx, git, auth)
return err
})
return repoInfo, err
}
func (g *Client) sendRequest(ctx context.Context, req apps.GitExploreRequest) (*apps.GitExploreResponse, error) {
data, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("can not JSON marshal request: %w", err)
}
httpReq, err := http.NewRequest(http.MethodPost, g.url.String(), bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("can not create HTTP request: %w", err)
}
if g.token != "" {
httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.token))
}
resp, err := g.client.Do(httpReq.WithContext(ctx))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("can not read response body: %w", err)
}
exploreResponse := &apps.GitExploreResponse{}
if err := json.Unmarshal(body, exploreResponse); err != nil {
return nil, fmt.Errorf(
"can not unmarshal response %q with status code %d: %w",
string(body),
resp.StatusCode,
err,
)
}
return exploreResponse, nil
}
// RetryLogFunc returns a retry log function depending on the given showErrors
// parameter. If it is set to true, exact errors are shown when retrying to
// connect to the git information service. Otherwise they are not shown.
func RetryLogFunc(showErrors bool) func(err error) {
return func(err error) {
if showErrors {
fmt.Fprintf(os.Stderr, "got error: %v\n", err)
}
fmt.Fprintln(os.Stderr, "Retrying...")
}
}