Skip to content

Commit 108073c

Browse files
authored
Merge pull request #6577 from jabellard/crd-proxy2
Proxy Support for Custom HTTP Source CRD Download Strategy in Karmada Operator
2 parents 64fbe75 + c35162b commit 108073c

File tree

8 files changed

+127
-91
lines changed

8 files changed

+127
-91
lines changed

charts/karmada-operator/crds/operator.karmada.io_karmadas.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5208,9 +5208,27 @@ spec:
52085208
description: HTTPSource specifies how to download the CRD tarball
52095209
via either HTTP or HTTPS protocol.
52105210
properties:
5211+
proxy:
5212+
description: |-
5213+
Proxy specifies the configuration of a proxy server to use when downloading the CRD tarball.
5214+
When set, the operator will use the configuration to determine how to establish a connection to the proxy to fetch the tarball from the URL specified above.
5215+
This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
5216+
If a proxy configuration is not set, the operator will attempt to download the tarball directly from the URL specified above without using a proxy.
5217+
properties:
5218+
proxyURL:
5219+
description: |-
5220+
ProxyURL specifies the HTTP/HTTPS proxy server URL to use when downloading the CRD tarball.
5221+
This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
5222+
The format should be a valid URL, e.g., "http://proxy.example.com:8080".
5223+
type: string
5224+
required:
5225+
- proxyURL
5226+
type: object
52115227
url:
52125228
description: URL specifies the URL of the CRD tarball resource.
52135229
type: string
5230+
required:
5231+
- url
52145232
type: object
52155233
type: object
52165234
customCertificate:

operator/config/crds/operator.karmada.io_karmadas.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5208,9 +5208,27 @@ spec:
52085208
description: HTTPSource specifies how to download the CRD tarball
52095209
via either HTTP or HTTPS protocol.
52105210
properties:
5211+
proxy:
5212+
description: |-
5213+
Proxy specifies the configuration of a proxy server to use when downloading the CRD tarball.
5214+
When set, the operator will use the configuration to determine how to establish a connection to the proxy to fetch the tarball from the URL specified above.
5215+
This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
5216+
If a proxy configuration is not set, the operator will attempt to download the tarball directly from the URL specified above without using a proxy.
5217+
properties:
5218+
proxyURL:
5219+
description: |-
5220+
ProxyURL specifies the HTTP/HTTPS proxy server URL to use when downloading the CRD tarball.
5221+
This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
5222+
The format should be a valid URL, e.g., "http://proxy.example.com:8080".
5223+
type: string
5224+
required:
5225+
- proxyURL
5226+
type: object
52115227
url:
52125228
description: URL specifies the URL of the CRD tarball resource.
52135229
type: string
5230+
required:
5231+
- url
52145232
type: object
52155233
type: object
52165234
customCertificate:

operator/pkg/apis/operator/v1alpha1/type.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@ const (
5858
// HTTPSource specifies how to download the CRD tarball via either HTTP or HTTPS protocol.
5959
type HTTPSource struct {
6060
// URL specifies the URL of the CRD tarball resource.
61+
// +required
6162
URL string `json:"url,omitempty"`
63+
64+
// Proxy specifies the configuration of a proxy server to use when downloading the CRD tarball.
65+
// When set, the operator will use the configuration to determine how to establish a connection to the proxy to fetch the tarball from the URL specified above.
66+
// This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
67+
// If a proxy configuration is not set, the operator will attempt to download the tarball directly from the URL specified above without using a proxy.
68+
// +optional
69+
Proxy *ProxyConfig `json:"proxy,omitempty"`
70+
}
71+
72+
// ProxyConfig defines the configuration for a proxy server to use when downloading a CRD tarball.
73+
type ProxyConfig struct {
74+
// ProxyURL specifies the HTTP/HTTPS proxy server URL to use when downloading the CRD tarball.
75+
// This is useful in environments where direct access to the server hosting the CRD tarball is restricted and a proxy must be used to reach that server.
76+
// The format should be a valid URL, e.g., "http://proxy.example.com:8080".
77+
// +required
78+
ProxyURL string `json:"proxyURL"`
6279
}
6380

6481
// CRDTarball specifies the source from which the Karmada CRD tarball should be downloaded, along with the download policy to use.

operator/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

operator/pkg/controller/karmada/validating.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,24 @@ import (
3333
)
3434

3535
func validateCRDTarball(crdTarball *operatorv1alpha1.CRDTarball, fldPath *field.Path) (errs field.ErrorList) {
36+
// A custom CRD tarball download config is optional, and given that an HTTP source is the only possible option, we
37+
// only have to validate the config if an HTTP source is set.
3638
if crdTarball == nil || crdTarball.HTTPSource == nil {
3739
return nil
3840
}
3941

42+
// Since the server URL is required when an HTTP source is set, we'll verify that the URL is valid.
4043
if _, err := url.ParseRequestURI(crdTarball.HTTPSource.URL); err != nil {
4144
errs = append(errs, field.Invalid(fldPath.Child("httpSource").Child("url"), crdTarball.HTTPSource.URL, "invalid CRDs remote URL"))
4245
}
4346

47+
// Since the Proxy URL is required when a proxy config is set, we'll verify that the URL is valid.
48+
if crdTarball.HTTPSource.Proxy != nil {
49+
if _, err := url.ParseRequestURI(crdTarball.HTTPSource.Proxy.ProxyURL); err != nil {
50+
errs = append(errs, field.Invalid(fldPath.Child("httpSource").Child("proxy").Child("proxyURL"), crdTarball.HTTPSource.Proxy.ProxyURL, "invalid CRDs proxy URL"))
51+
}
52+
}
53+
4454
return errs
4555
}
4656

operator/pkg/tasks/init/crd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func runCrdsDownload(r workflow.RunData) error {
136136
}
137137

138138
crdTarball := data.CrdTarball()
139-
if err := util.DownloadFile(crdTarball.HTTPSource.URL, crdsTarPath); err != nil {
139+
if err := util.DownloadFile(crdTarball.HTTPSource.URL, crdsTarPath, crdTarball.HTTPSource.Proxy); err != nil {
140140
return fmt.Errorf("failed to download CRD tar, err: %w", err)
141141
}
142142

operator/pkg/util/util.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"io"
2525
"net/http"
26+
urlpkg "net/url"
2627
"os"
2728
"path/filepath"
2829
"reflect"
@@ -75,13 +76,22 @@ func (d *Downloader) Read(p []byte) (n int, err error) {
7576
return
7677
}
7778

78-
var httpClient = http.Client{
79-
Timeout: 60 * time.Second,
80-
}
79+
// DownloadFile downloads files via URL, optionally using a proxy if provided.
80+
func DownloadFile(url, filePath string, proxyConfig *operatorv1alpha1.ProxyConfig) error {
81+
client := &http.Client{
82+
Timeout: 60 * time.Second,
83+
}
84+
if proxyConfig != nil {
85+
parsedProxyURL, err := urlpkg.ParseRequestURI(proxyConfig.ProxyURL)
86+
if err != nil {
87+
return fmt.Errorf("invalid proxy URL: %w", err)
88+
}
89+
client.Transport = &http.Transport{
90+
Proxy: http.ProxyURL(parsedProxyURL),
91+
}
92+
}
8193

82-
// DownloadFile Download files via URL
83-
func DownloadFile(url, filePath string) error {
84-
resp, err := httpClient.Get(url)
94+
resp, err := client.Get(url)
8595
if err != nil {
8696
return err
8797
}
@@ -105,7 +115,6 @@ func DownloadFile(url, filePath string) error {
105115
if _, err := io.Copy(file, downloader); err != nil {
106116
return err
107117
}
108-
109118
return nil
110119
}
111120

operator/pkg/util/util_test.go

Lines changed: 25 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,21 @@ package util
1818

1919
import (
2020
"archive/tar"
21-
"bytes"
2221
"compress/gzip"
2322
"errors"
2423
"fmt"
2524
"io"
26-
"net/http"
2725
"os"
2826
"path/filepath"
2927
"regexp"
3028
"strings"
3129
"testing"
32-
"time"
3330

3431
"github.com/stretchr/testify/assert"
3532
"k8s.io/utils/ptr"
3633
"sigs.k8s.io/yaml"
34+
35+
"github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
3736
)
3837

3938
// mockReader is a simple io.Reader that returns an error after being called.
@@ -51,18 +50,6 @@ func (m *mockReader) Read(p []byte) (n int, err error) {
5150
return n, m.err
5251
}
5352

54-
type mockRoundTripper struct {
55-
response *http.Response
56-
err error
57-
}
58-
59-
func (m *mockRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
60-
if m.err != nil {
61-
return nil, m.err
62-
}
63-
return m.response, nil
64-
}
65-
6653
func TestRead(t *testing.T) {
6754
tests := []struct {
6855
name string
@@ -137,85 +124,47 @@ func TestRead(t *testing.T) {
137124

138125
func TestDownloadFile(t *testing.T) {
139126
tests := []struct {
140-
name string
141-
url string
142-
filePath string
143-
prep func(url, filePath string) error
144-
verify func(filePath string) error
145-
wantErr bool
146-
errMsg string
127+
name string
128+
url string
129+
proxyConfig *v1alpha1.ProxyConfig
130+
filePath string
131+
verify func(filePath string) error
132+
wantErr bool
133+
errMsg string
147134
}{
148135
{
149-
name: "DownloadFile_UrlIsNotFound_FailedToGetResponse",
150-
url: "not-found-url",
151-
prep: func(url, _ string) error {
152-
httpClient = http.Client{
153-
Transport: &mockRoundTripper{
154-
err: fmt.Errorf("failed to get url %s, url is not found", url),
155-
},
156-
Timeout: time.Second,
157-
}
158-
return nil
159-
},
160-
verify: func(string) error { return nil },
161-
wantErr: true,
162-
errMsg: "failed to get url not-found-url, url is not found",
136+
name: "DownloadFile_UrlIsNotFound_FailedToGetResponse",
137+
url: "not-found-url",
138+
proxyConfig: nil,
139+
verify: func(string) error { return nil },
140+
wantErr: true,
141+
errMsg: "failed to get url not-found-url, url is not found",
163142
},
164143
{
165144
name: "DownloadFile_ServiceIsUnavailable_FailedToReachTheService",
166145
url: "https://www.example.com/test-file",
167-
prep: func(_, _ string) error {
168-
httpClient = http.Client{
169-
Transport: &mockRoundTripper{
170-
response: &http.Response{
171-
StatusCode: http.StatusServiceUnavailable,
172-
},
173-
},
174-
Timeout: time.Second,
175-
}
176-
return nil
146+
proxyConfig: &v1alpha1.ProxyConfig{
147+
ProxyURL: "http://www.dummy-proxy.com",
177148
},
178149
verify: func(string) error { return nil },
179150
wantErr: true,
180151
errMsg: "failed to download file",
181152
},
182153
{
183-
name: "DownloadFile_FileDownloaded_",
184-
url: "https://www.example.com/test-file",
185-
filePath: filepath.Join(os.TempDir(), "temp-download-file.txt"),
186-
prep: func(_, filePath string) error {
187-
// Create temp download filepath.
188-
tempFile, err := os.Create(filePath)
189-
if err != nil {
190-
return fmt.Errorf("failed to create temp download file: %w", err)
191-
}
192-
defer tempFile.Close()
193-
194-
// Create HTTP client.
195-
httpClient = http.Client{
196-
Transport: &mockRoundTripper{
197-
response: &http.Response{
198-
StatusCode: http.StatusOK,
199-
Body: io.NopCloser(bytes.NewReader([]byte("Hello, World!"))),
200-
ContentLength: int64(len("Hello, World!")),
201-
},
202-
},
203-
Timeout: time.Second,
204-
}
205-
206-
return nil
207-
},
154+
name: "DownloadFile_FileDownloaded_",
155+
url: "https://www.example.com",
156+
proxyConfig: nil,
157+
filePath: filepath.Join(os.TempDir(), "temp-download-file.txt"),
208158
verify: func(filePath string) error {
209159
// Read the content of the downloaded file.
210160
content, err := os.ReadFile(filePath)
211161
if err != nil {
212162
return fmt.Errorf("failed to read file: %w", err)
213163
}
214164

215-
// Verify the content of the file.
216-
expected := "Hello, World!"
217-
if string(content) != expected {
218-
return fmt.Errorf("unexpected file content: got %q, want %q", string(content), expected)
165+
// Verify that content has been downloaded and saved to the file
166+
if len(content) == 0 {
167+
return fmt.Errorf("file is empty: %s", filePath)
219168
}
220169

221170
if err := os.Remove(filePath); err != nil {
@@ -229,19 +178,13 @@ func TestDownloadFile(t *testing.T) {
229178
}
230179
for _, test := range tests {
231180
t.Run(test.name, func(t *testing.T) {
232-
if err := test.prep(test.url, test.filePath); err != nil {
233-
t.Fatalf("failed to prep before downloading the file, got: %v", err)
234-
}
235-
err := DownloadFile(test.url, test.filePath)
181+
err := DownloadFile(test.url, test.filePath, test.proxyConfig)
236182
if err == nil && test.wantErr {
237183
t.Fatal("expected an error, but got none")
238184
}
239185
if err != nil && !test.wantErr {
240186
t.Errorf("unexpected error, got: %v", err)
241187
}
242-
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
243-
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
244-
}
245188
if err := test.verify(test.filePath); err != nil {
246189
t.Errorf("failed to verify the actual of download of file: %v", err)
247190
}

0 commit comments

Comments
 (0)