Skip to content

Commit 886c15f

Browse files
authored
refactor pkg/kind (#482)
Signed-off-by: Caleb Boylan <[email protected]>
1 parent 64d93ac commit 886c15f

File tree

4 files changed

+313
-68
lines changed

4 files changed

+313
-68
lines changed

pkg/kind/cluster.go

Lines changed: 10 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ package kind
22

33
import (
44
"context"
5-
"embed"
65
"errors"
76
"fmt"
87
"github.com/cnoe-io/idpbuilder/pkg/util"
98
"github.com/cnoe-io/idpbuilder/pkg/util/files"
10-
"io"
11-
"io/fs"
12-
"os"
9+
"net/http"
1310
"strconv"
14-
"strings"
1511

1612
"github.com/cnoe-io/idpbuilder/api/v1alpha1"
1713
"github.com/go-logr/logr"
@@ -32,8 +28,13 @@ var (
3228
setupLog = log.Log.WithName("setup")
3329
)
3430

31+
type HttpClient interface {
32+
Get(url string) (resp *http.Response, err error)
33+
}
34+
3535
type Cluster struct {
3636
provider IProvider
37+
httpClient HttpClient
3738
name string
3839
kubeVersion string
3940
kubeConfigPath string
@@ -43,11 +44,6 @@ type Cluster struct {
4344
cfg v1alpha1.BuildCustomizationSpec
4445
}
4546

46-
type PortMapping struct {
47-
HostPort string
48-
ContainerPort string
49-
}
50-
5147
type IProvider interface {
5248
List() ([]string, error)
5349
ListNodes(string) ([]nodes.Node, error)
@@ -57,67 +53,12 @@ type IProvider interface {
5753
ExportKubeConfig(string, string, bool) error
5854
}
5955

60-
type TemplateConfig struct {
61-
v1alpha1.BuildCustomizationSpec
62-
KubernetesVersion string
63-
ExtraPortsMapping []PortMapping
64-
RegistryConfig string
65-
}
66-
67-
//go:embed resources/*
68-
var configFS embed.FS
69-
7056
func (c *Cluster) getConfig() ([]byte, error) {
57+
rawConfigTempl, err := loadConfig(c.kindConfigPath, c.httpClient)
7158

72-
var rawConfigTempl []byte
73-
var err error
59+
portMappingPairs := parsePortMappings(c.extraPortsMapping)
7460

75-
if c.kindConfigPath != "" {
76-
if strings.HasPrefix(c.kindConfigPath, "https://") || strings.HasPrefix(c.kindConfigPath, "http://") {
77-
httpClient := util.GetHttpClient()
78-
resp, err := httpClient.Get(c.kindConfigPath)
79-
if err != nil {
80-
return nil, fmt.Errorf("fetching remote kind config: %w", err)
81-
}
82-
defer resp.Body.Close()
83-
rawConfigTempl, err = io.ReadAll(resp.Body)
84-
if err != nil {
85-
return nil, fmt.Errorf("reading remote kind config body: %w", err)
86-
}
87-
} else {
88-
rawConfigTempl, err = os.ReadFile(c.kindConfigPath)
89-
}
90-
} else {
91-
rawConfigTempl, err = fs.ReadFile(configFS, "resources/kind.yaml.tmpl")
92-
}
93-
94-
if err != nil {
95-
return nil, fmt.Errorf("reading kind config: %w", err)
96-
}
97-
98-
var portMappingPairs []PortMapping
99-
if len(c.extraPortsMapping) > 0 {
100-
// Split pairs of ports "11=1111","22=2222",etc
101-
pairs := strings.Split(c.extraPortsMapping, ",")
102-
// Create a slice to store PortMapping pairs.
103-
portMappingPairs = make([]PortMapping, len(pairs))
104-
// Parse each pair into PortPair objects.
105-
for i, pair := range pairs {
106-
parts := strings.Split(pair, ":")
107-
if len(parts) == 2 {
108-
portMappingPairs[i] = PortMapping{parts[0], parts[1]}
109-
}
110-
}
111-
}
112-
113-
registryConfig := ""
114-
for _, s := range c.registryConfig {
115-
path := os.ExpandEnv(s)
116-
if _, err := os.Stat(path); err == nil {
117-
registryConfig = path
118-
break
119-
}
120-
}
61+
registryConfig := findRegistryConfig(c.registryConfig)
12162

12263
if len(c.registryConfig) > 0 && registryConfig == "" {
12364
return nil, errors.New("--registry-config flag used but no registry config was found")
@@ -159,6 +100,7 @@ func NewCluster(name, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMap
159100

160101
return &Cluster{
161102
provider: provider,
103+
httpClient: util.GetHttpClient(),
162104
name: name,
163105
kindConfigPath: kindConfigPath,
164106
kubeVersion: kubeVersion,

pkg/kind/config.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package kind
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"io"
7+
"io/fs"
8+
"os"
9+
"strings"
10+
11+
"github.com/cnoe-io/idpbuilder/api/v1alpha1"
12+
)
13+
14+
type PortMapping struct {
15+
HostPort string
16+
ContainerPort string
17+
}
18+
19+
type TemplateConfig struct {
20+
v1alpha1.BuildCustomizationSpec
21+
KubernetesVersion string
22+
ExtraPortsMapping []PortMapping
23+
RegistryConfig string
24+
}
25+
26+
//go:embed resources/* testdata/custom-kind.yaml.tmpl
27+
var configFS embed.FS
28+
29+
func loadConfig(path string, httpClient HttpClient) ([]byte, error) {
30+
var rawConfigTempl []byte
31+
var err error
32+
if path != "" {
33+
if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
34+
resp, err := httpClient.Get(path)
35+
if err != nil {
36+
return nil, fmt.Errorf("fetching remote kind config: %w", err)
37+
}
38+
defer resp.Body.Close()
39+
if !(resp.StatusCode < 300 && resp.StatusCode >= 200) {
40+
return nil, fmt.Errorf("got %d status code when fetching kind config", resp.StatusCode)
41+
}
42+
rawConfigTempl, err = io.ReadAll(resp.Body)
43+
if err != nil {
44+
return nil, fmt.Errorf("reading remote kind config body: %w", err)
45+
}
46+
} else {
47+
rawConfigTempl, err = os.ReadFile(path)
48+
}
49+
} else {
50+
rawConfigTempl, err = fs.ReadFile(configFS, "resources/kind.yaml.tmpl")
51+
}
52+
53+
if err != nil {
54+
return nil, fmt.Errorf("reading kind config: %w", err)
55+
}
56+
return rawConfigTempl, nil
57+
}
58+
59+
func parsePortMappings(extraPortsMapping string) []PortMapping {
60+
var portMappingPairs []PortMapping
61+
if len(extraPortsMapping) > 0 {
62+
// Split pairs of ports "11=1111","22=2222",etc
63+
pairs := strings.Split(extraPortsMapping, ",")
64+
// Create a slice to store PortMapping pairs.
65+
portMappingPairs = make([]PortMapping, len(pairs))
66+
// Parse each pair into PortPair objects.
67+
for i, pair := range pairs {
68+
parts := strings.Split(pair, ":")
69+
if len(parts) == 2 {
70+
portMappingPairs[i] = PortMapping{parts[0], parts[1]}
71+
}
72+
}
73+
}
74+
return portMappingPairs
75+
}
76+
77+
func findRegistryConfig(registryConfigPaths []string) string {
78+
for _, s := range registryConfigPaths {
79+
path := os.ExpandEnv(s)
80+
if _, err := os.Stat(path); err == nil {
81+
return path
82+
}
83+
}
84+
return ""
85+
}

pkg/kind/config_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package kind
2+
3+
import (
4+
"errors"
5+
"io"
6+
"io/fs"
7+
"net/http"
8+
"reflect"
9+
"strings"
10+
"testing"
11+
)
12+
13+
type MockHttpClient struct{}
14+
15+
func (o *MockHttpClient) Get(url string) (resp *http.Response, err error) {
16+
if url == "https://doesnotexist" || url == "http://doesnotexist" {
17+
return nil, errors.New("connection error")
18+
} else if url == "https://404" {
19+
body := io.NopCloser(strings.NewReader(""))
20+
r := http.Response{
21+
Status: "404 NotFound",
22+
StatusCode: 404,
23+
Body: body,
24+
}
25+
return &r, nil
26+
}
27+
28+
body := io.NopCloser(strings.NewReader("foo: bar"))
29+
r := http.Response{
30+
Status: "200 OK",
31+
StatusCode: 200,
32+
Body: body,
33+
}
34+
35+
return &r, nil
36+
}
37+
38+
func TestLoadConfig(t *testing.T) {
39+
httpClient := MockHttpClient{}
40+
defaultTemplate, err := fs.ReadFile(configFS, "resources/kind.yaml.tmpl")
41+
if err != nil {
42+
t.Fatalf("failed to load default kind template: %v", err)
43+
}
44+
45+
customTemplate, err := fs.ReadFile(configFS, "testdata/custom-kind.yaml.tmpl")
46+
if err != nil {
47+
t.Fatalf("failed to load custom kind template: %v", err)
48+
}
49+
50+
httpsTemplate := []byte("foo: bar")
51+
52+
connectionErr := "fetching remote kind config: connection error"
53+
notFoundErr := "got 404 status code when fetching kind config"
54+
55+
type test struct {
56+
path string
57+
expected []byte
58+
err *string
59+
}
60+
tests := []test{
61+
{
62+
path: "",
63+
expected: defaultTemplate,
64+
err: nil,
65+
},
66+
{
67+
path: "testdata/custom-kind.yaml.tmpl",
68+
expected: customTemplate,
69+
err: nil,
70+
},
71+
{
72+
path: "https://doesnotexist",
73+
expected: defaultTemplate,
74+
err: &connectionErr,
75+
},
76+
{
77+
path: "http://doesnotexist",
78+
expected: customTemplate,
79+
err: &connectionErr,
80+
},
81+
{
82+
path: "https://404",
83+
expected: defaultTemplate,
84+
err: &notFoundErr,
85+
},
86+
{
87+
path: "https://anyurlworks",
88+
expected: httpsTemplate,
89+
err: nil,
90+
},
91+
}
92+
93+
for _, tc := range tests {
94+
out, err := loadConfig(tc.path, &httpClient)
95+
if tc.err != nil {
96+
if err != nil {
97+
if err.Error() != *tc.err {
98+
t.Errorf("expected error: %v\nfound error: %v", *tc.err, err.Error())
99+
}
100+
} else {
101+
t.Errorf("expected error: %v\ndidnt find an error", *tc.err)
102+
}
103+
} else {
104+
if err != nil {
105+
t.Errorf("failed to load kind config: %v", err)
106+
}
107+
if !reflect.DeepEqual(tc.expected, out) {
108+
t.Errorf("expected:\n%v\ngot:\n%v", string(tc.expected), string(out))
109+
}
110+
}
111+
}
112+
}
113+
114+
func TestExtraPortMappingsUtilFunc(t *testing.T) {
115+
type test struct {
116+
extraPortMappings string
117+
expected []PortMapping
118+
}
119+
tests := []test{
120+
{
121+
extraPortMappings: "",
122+
expected: []PortMapping(nil),
123+
},
124+
{
125+
extraPortMappings: "22:32222",
126+
expected: []PortMapping{
127+
{
128+
HostPort: "22",
129+
ContainerPort: "32222",
130+
},
131+
},
132+
},
133+
{
134+
extraPortMappings: "11:1111,33:3333,4444:4444",
135+
expected: []PortMapping{
136+
{
137+
HostPort: "11",
138+
ContainerPort: "1111",
139+
},
140+
{
141+
HostPort: "33",
142+
ContainerPort: "3333",
143+
},
144+
{
145+
HostPort: "4444",
146+
ContainerPort: "4444",
147+
},
148+
},
149+
},
150+
}
151+
152+
for _, tc := range tests {
153+
pmOutput := parsePortMappings(tc.extraPortMappings)
154+
if !reflect.DeepEqual(tc.expected, pmOutput) {
155+
t.Errorf("expected: %v, got: %v", tc.expected, pmOutput)
156+
}
157+
}
158+
}
159+
160+
func TestFindRegistryConfig(t *testing.T) {
161+
type test struct {
162+
paths []string
163+
expected string
164+
}
165+
tests := []test{
166+
{
167+
paths: []string{"testdata/empty.json"},
168+
expected: "testdata/empty.json",
169+
},
170+
{
171+
paths: []string{"doesntexist"},
172+
expected: "",
173+
},
174+
{
175+
paths: []string{"doesntexist", "testdata/empty.json"},
176+
expected: "testdata/empty.json",
177+
},
178+
}
179+
180+
for _, tc := range tests {
181+
out := findRegistryConfig(tc.paths)
182+
if !reflect.DeepEqual(tc.expected, out) {
183+
t.Errorf("expected:\n%v\ngot:\n%v", tc.expected, out)
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)