Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

feat: add a proxy url field to kubefed clusters #1377

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions charts/kubefed/charts/controllermanager/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ spec:
items:
type: string
type: array
proxyURL:
description: ProxyURL allows to set proxy URL for the cluster.
type: string
secretRef:
description: Name of the secret containing the token required to access
the member cluster. The secret needs to exist in the same namespace
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
go.uber.org/zap v1.16.0 // indirect
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
k8s.io/api v0.20.2
k8s.io/apiextensions-apiserver v0.20.2
k8s.io/apimachinery v0.20.2
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/core/v1beta1/kubefedcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ type KubeFedClusterSpec struct {
// If * is specified, it is expected to be the only option in list.
// +optional
DisabledTLSValidations []TLSValidation `json:"disabledTLSValidations,omitempty"`

// ProxyURL allows to set proxy URL for the cluster.
// +optional
ProxyURL string `json:"proxyURL"`
}

// LocalSecretReference is a reference to a secret within the enclosing
Expand Down
97 changes: 97 additions & 0 deletions pkg/apis/core/v1beta1/kubefedcluster_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
jimmidyson marked this conversation as resolved.
Show resolved Hide resolved
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1beta1

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"sigs.k8s.io/controller-runtime/pkg/client"

"golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

var _ = Describe("KubefedCluster", func() {
var (
key types.NamespacedName
created, fetched *KubeFedCluster
)

Context("Create API", func() {
It("should create a kubefed cluster setting a proxy url", func() {

created = &KubeFedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: metav1.NamespaceDefault,
},
Spec: KubeFedClusterSpec{
APIEndpoint: "https://my.example.com:80/path/to/endpoint",
ProxyURL: "socks5://example.com",
SecretRef: LocalSecretReference{
Name: "foo",
},
},
}

key, _ = client.ObjectKeyFromObject(created)

By("creating an API obj")
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())

fetched = &KubeFedCluster{}
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

By("deleting the created object")
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())
})

It("should create a kubefed cluster without a proxy url and an empty secret", func() {

created = &KubeFedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: metav1.NamespaceDefault,
},
Spec: KubeFedClusterSpec{
APIEndpoint: "https://my.example.com:80/path/to/endpoint",
SecretRef: LocalSecretReference{
Name: "",
},
},
}

key, _ = client.ObjectKeyFromObject(created)

By("creating an API obj")
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())

fetched = &KubeFedCluster{}
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

By("deleting the created object")
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())
})

})

})
65 changes: 65 additions & 0 deletions pkg/apis/core/v1beta1/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package v1beta1
hectorj2f marked this conversation as resolved.
Show resolved Hide resolved

import (
"path/filepath"
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment

func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecsWithDefaultAndCustomReporters(t,
"v1beta1 Suite",
[]Reporter{printer.NewlineReporter{}})
}

var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.New(func(o *zap.Options) {
o.Development = true
o.DestWritter = GinkgoWriter
}))

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDInstallOptions: envtest.CRDInstallOptions{
ErrorIfPathMissing: true,
Paths: []string{
filepath.Join("..", "..", "..", "..", "charts", "kubefed", "charts", "controllermanager", "crds"),
},
},
}

err := SchemeBuilder.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())

close(done)
}, 60)

var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
19 changes: 19 additions & 0 deletions pkg/apis/core/v1beta1/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package validation

import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -163,6 +164,9 @@ func validateKubeFedClusterSpec(spec *v1beta1.KubeFedClusterSpec, path *field.Pa
allErrs := validateAPIEndpoint(spec.APIEndpoint, path.Child("apiEndpoint"))
allErrs = append(allErrs, validateLocalSecretReference(&spec.SecretRef, path.Child("secretRef"))...)
allErrs = append(allErrs, validateDisabledTLSValidations(spec.DisabledTLSValidations, path.Child("disabledTLSValidations"))...)
if spec.ProxyURL != "" {
allErrs = append(allErrs, validateProxyURL(spec.ProxyURL, path.Child("proxyURL"))...)
}
return allErrs
}

Expand All @@ -175,6 +179,21 @@ func validateKubeFedClusterStatus(status *v1beta1.KubeFedClusterStatus, path *fi
return allErrs
}

func validateProxyURL(proxyURL string, path *field.Path) field.ErrorList {
var allErrs field.ErrorList

u, err := url.Parse(proxyURL)
if err != nil {
allErrs = append(allErrs, field.Invalid(path, proxyURL, "error parsing the proxy URL"))
}
switch u.Scheme {
case "http", "https", "socks5":
default:
allErrs = append(allErrs, field.Invalid(path, proxyURL, "proxy URL scheme must be one of: [http, https, socks5]"))
}
return allErrs
}

func validateAPIEndpoint(endpoint string, path *field.Path) field.ErrorList {
if endpoint == "" {
return field.ErrorList{field.Required(path, "")}
Expand Down
50 changes: 50 additions & 0 deletions pkg/apis/core/v1beta1/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,56 @@ func TestValidateAPIEndpoint(t *testing.T) {
}
}

func TestProxyURL(t *testing.T) {
tests := []struct {
proxyURL string
expectedErrMsg string
}{
{
proxyURL: "socks5://example.com",
},
{
proxyURL: "https://example.com",
},
{
proxyURL: "http://example.com",
},
{
proxyURL: "socks6://example.com",
expectedErrMsg: "proxyURL: Invalid value: \"socks6://example.com\": proxy URL scheme must be one of: [http, https, socks5]",
},
{
proxyURL: "example.com",
expectedErrMsg: "proxyURL: Invalid value: \"example.com\": proxy URL scheme must be one of: [http, https, socks5]",
},
{
proxyURL: "chewbacca@example.com",
expectedErrMsg: "proxyURL: Invalid value: \"chewbacca@example.com\": proxy URL scheme must be one of: [http, https, socks5]",
},
}

for _, test := range tests {
errs := validateProxyURL(test.proxyURL, field.NewPath("proxyURL"))
if len(errs) == 0 && test.expectedErrMsg == "" {
continue
}
if len(errs) == 0 && test.expectedErrMsg != "" {
t.Errorf("[%s] expected failure", test.expectedErrMsg)
} else {
matchedErr := false
for _, err := range errs {
if strings.Contains(err.Error(), test.expectedErrMsg) {
matchedErr = true
break
}
}
if !matchedErr {
t.Errorf("unexpected error: %v, expected: %q", errs, test.expectedErrMsg)
}
}
}
}

func TestValidateLocalSecretReference(t *testing.T) {
testCases := []struct {
secretName string
Expand Down
8 changes: 8 additions & 0 deletions pkg/controller/util/cluster_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ func BuildClusterConfig(fedCluster *fedv1b1.KubeFedCluster, client generic.Clien
clusterConfig.QPS = KubeAPIQPS
clusterConfig.Burst = KubeAPIBurst

if fedCluster.Spec.ProxyURL != "" {
proxyURL, err := url.Parse(fedCluster.Spec.ProxyURL)
if err != nil {
return nil, errors.Errorf("Failed to parse provided proxy URL %s: %v", fedCluster.Spec.ProxyURL, err)
}
clusterConfig.Proxy = http.ProxyURL(proxyURL)
}

if len(fedCluster.Spec.DisabledTLSValidations) != 0 {
klog.V(1).Infof("Cluster %s will use a custom transport for TLS certificate validation", fedCluster.Name)
if err = CustomizeTLSTransport(fedCluster, clusterConfig); err != nil {
Expand Down
17 changes: 15 additions & 2 deletions pkg/kubefedctl/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,20 @@ func joinClusterForNamespace(hostConfig, clusterConfig *rest.Config, kubefedName
caBundle = clusterConfig.CAData
}

var proxyURL string
if clusterConfig.Proxy != nil {
url, err := clusterConfig.Proxy(nil)
if err != nil {
klog.V(2).Infof("Error getting proxy URL for host %s: %w", clusterConfig.Host, err)
return nil, errors.Errorf("failed to create proxy URL request for kubefed cluster: %v", err)
}
if url != nil {
proxyURL = url.String()
}
}

kubefedCluster, err := createKubeFedCluster(client, joiningClusterName, clusterConfig.Host,
secret.Name, kubefedNamespace, caBundle, disabledTLSValidations, dryRun, errorOnExisting)
secret.Name, kubefedNamespace, caBundle, disabledTLSValidations, proxyURL, dryRun, errorOnExisting)
if err != nil {
klog.V(2).Infof("Failed to create federated cluster resource: %v", err)
return nil, err
Expand Down Expand Up @@ -319,7 +331,7 @@ func performPreflightChecks(clusterClientset kubeclient.Interface, name, hostClu
// the cluster and secret.
func createKubeFedCluster(client genericclient.Client, joiningClusterName, apiEndpoint,
secretName, kubefedNamespace string, caBundle []byte, disabledTLSValidations []fedv1b1.TLSValidation,
dryRun, errorOnExisting bool) (*fedv1b1.KubeFedCluster, error) {
proxyURL string, dryRun, errorOnExisting bool) (*fedv1b1.KubeFedCluster, error) {
fedCluster := &fedv1b1.KubeFedCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: kubefedNamespace,
Expand All @@ -332,6 +344,7 @@ func createKubeFedCluster(client genericclient.Client, joiningClusterName, apiEn
Name: secretName,
},
DisabledTLSValidations: disabledTLSValidations,
ProxyURL: proxyURL,
},
}

Expand Down
Loading