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 172 173 174 175 176 177 178 179 180 181 182
|
package gitlab
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/api"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/httpz"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
type ResponseHandler interface {
// Handle is invoked with HTTP client's response and error values.
Handle(*http.Response, error) error
// Accept returns the value to send in the Accept HTTP header.
// Empty string means no value should be sent.
Accept() string
}
type ValidatableMessage interface {
proto.Message
ValidateAll() error
}
// doConfig holds configuration for the Do call.
type doConfig struct {
method string
path string
query url.Values
header http.Header
body io.Reader
responseHandler ResponseHandler
withJWT bool
noRetry bool
}
func (c *doConfig) ensureHeaderNotNil() {
if c.header == nil {
c.header = make(http.Header)
}
}
// DoOption to configure the Do call of the client.
type DoOption func(*doConfig) error
func applyDoOptions(opts []DoOption) (doConfig, error) {
config := doConfig{
method: http.MethodGet,
}
for _, v := range opts {
err := v(&config)
if err != nil {
return doConfig{}, err
}
}
if config.responseHandler == nil {
return doConfig{}, errors.New("missing response handler")
}
return config, nil
}
func WithMethod(method string) DoOption {
return func(config *doConfig) error {
config.method = method
return nil
}
}
func WithPath(path string) DoOption {
return func(config *doConfig) error {
config.path = path
return nil
}
}
func WithQuery(query url.Values) DoOption {
return func(config *doConfig) error {
config.query = query
return nil
}
}
func WithHeader(header http.Header) DoOption {
return func(config *doConfig) error {
clone := header.Clone()
if config.header == nil {
config.header = clone
} else {
for k, v := range clone {
config.header[k] = v // overwrite
}
}
return nil
}
}
func WithJWT(withJWT bool) DoOption {
return func(config *doConfig) error {
config.withJWT = withJWT
return nil
}
}
func WithAgentToken(agentToken api.AgentToken) DoOption {
return func(config *doConfig) error {
config.ensureHeaderNotNil()
config.header[httpz.AuthorizationHeader] = []string{"Bearer " + string(agentToken)}
return nil
}
}
func WithJobToken(jobToken string) DoOption {
return func(config *doConfig) error {
config.ensureHeaderNotNil()
config.header["Job-Token"] = []string{jobToken}
return nil
}
}
// WithRequestBody sets the request body and HTTP Content-Type header if contentType is not empty.
func WithRequestBody(body io.Reader, contentType string) DoOption {
return func(config *doConfig) error {
config.body = body
if contentType != "" {
config.ensureHeaderNotNil()
config.header[httpz.ContentTypeHeader] = []string{contentType}
}
return nil
}
}
// WithProtoJsonRequestBody specifies the object to marshal to JSON and use as request body.
// Use this method with proto messages.
func WithProtoJsonRequestBody(body ValidatableMessage) DoOption {
return func(config *doConfig) error {
if err := body.ValidateAll(); err != nil {
return fmt.Errorf("WithProtoJsonRequestBody: %w", err)
}
bodyBytes, err := protojson.Marshal(body)
if err != nil {
return fmt.Errorf("WithProtoJsonRequestBody: %w", err)
}
return WithRequestBody(bytes.NewReader(bodyBytes), "application/json")(config)
}
}
// WithJsonRequestBody specifies the object to marshal to JSON and use as request body.
// Do NOT use this method with proto messages, use WithProtoJsonRequestBody instead.
func WithJsonRequestBody(body interface{}) DoOption {
return func(config *doConfig) error {
bodyBytes, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("WithJsonRequestBody: %w", err)
}
return WithRequestBody(bytes.NewReader(bodyBytes), "application/json")(config)
}
}
func WithResponseHandler(handler ResponseHandler) DoOption {
return func(config *doConfig) error {
config.responseHandler = handler
accept := handler.Accept()
if accept != "" {
config.ensureHeaderNotNil()
config.header[httpz.AcceptHeader] = []string{accept}
}
return nil
}
}
func WithoutRetries() DoOption {
return func(config *doConfig) error {
config.noRetry = true
return nil
}
}
|