File: do_options.go

package info (click to toggle)
gitlab-agent 16.1.3-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid, trixie
  • size: 6,324 kB
  • sloc: makefile: 175; sh: 52; ruby: 3
file content (182 lines) | stat: -rw-r--r-- 4,468 bytes parent folder | download
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
	}
}