-
Notifications
You must be signed in to change notification settings - Fork 880
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for Istio multicluster (#1274)
Signed-off-by: Shakti <[email protected]>
- Loading branch information
1 parent
503c520
commit ba21c6c
Showing
9 changed files
with
275 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package istio | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
log "github.com/sirupsen/logrus" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/labels" | ||
"k8s.io/apimachinery/pkg/selection" | ||
"k8s.io/client-go/dynamic" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/tools/clientcmd" | ||
) | ||
|
||
const ( | ||
PrimaryClusterSecretLabel = "istio.argoproj.io/primary-cluster" | ||
) | ||
|
||
func GetPrimaryClusterDynamicClient(kubeClient kubernetes.Interface, namespace string) (string, dynamic.Interface) { | ||
primaryClusterSecret := getPrimaryClusterSecret(kubeClient, namespace) | ||
if primaryClusterSecret != nil { | ||
clusterId, clientConfig, err := getKubeClientConfig(primaryClusterSecret) | ||
if err != nil { | ||
return clusterId, nil | ||
} | ||
|
||
config, err := clientConfig.ClientConfig() | ||
if err != nil { | ||
log.Errorf("Error fetching primary ClientConfig: %v", err) | ||
return clusterId, nil | ||
} | ||
|
||
dynamicClient, err := dynamic.NewForConfig(config) | ||
if err != nil { | ||
log.Errorf("Error building dynamic client from config: %v", err) | ||
return clusterId, nil | ||
} | ||
|
||
return clusterId, dynamicClient | ||
} | ||
|
||
return "", nil | ||
} | ||
|
||
func getPrimaryClusterSecret(kubeClient kubernetes.Interface, namespace string) *corev1.Secret { | ||
req, err := labels.NewRequirement(PrimaryClusterSecretLabel, selection.Equals, []string{"true"}) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
secrets, err := kubeClient.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{Limit: 1, LabelSelector: req.String()}) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
if secrets != nil && len(secrets.Items) > 0 { | ||
return &secrets.Items[0] | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getKubeClientConfig(secret *corev1.Secret) (string, clientcmd.ClientConfig, error) { | ||
for clusterId, kubeConfig := range secret.Data { | ||
primaryClusterConfig, err := buildKubeClientConfig(kubeConfig) | ||
if err != nil { | ||
log.Errorf("Error building kubeconfig for primary cluster %s: %v", clusterId, err) | ||
return clusterId, nil, fmt.Errorf("error building primary cluster client %s: %v", clusterId, err) | ||
} | ||
log.Infof("Istio primary/config cluster is %s", clusterId) | ||
return clusterId, primaryClusterConfig, err | ||
} | ||
return "", nil, nil | ||
} | ||
|
||
func buildKubeClientConfig(kubeConfig []byte) (clientcmd.ClientConfig, error) { | ||
if len(kubeConfig) == 0 { | ||
return nil, errors.New("kubeconfig is empty") | ||
} | ||
|
||
rawConfig, err := clientcmd.Load(kubeConfig) | ||
if err != nil { | ||
return nil, fmt.Errorf("kubeconfig cannot be loaded: %v", err) | ||
} | ||
|
||
if err := clientcmd.Validate(*rawConfig); err != nil { | ||
return nil, fmt.Errorf("kubeconfig is not valid: %v", err) | ||
} | ||
|
||
clientConfig := clientcmd.NewDefaultClientConfig(*rawConfig, &clientcmd.ConfigOverrides{}) | ||
return clientConfig, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package istio | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
var ( | ||
secret0 = makeSecret("secret0", "namespace0", "primary0", []byte("kubeconfig0-0")) | ||
secret1 = makeSecret("secret1", "namespace1", "primary1", []byte("kubeconfig1-1")) | ||
rolloutControllerNamespace = "argo-rollout-ns" | ||
) | ||
|
||
func TestGetPrimaryClusterDynamicClient(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
namespace string | ||
existingSecrets []*v1.Secret | ||
expectedClusterId string | ||
}{ | ||
{ | ||
"TestNoPrimaryClusterSecret", | ||
metav1.NamespaceAll, | ||
nil, | ||
"", | ||
}, | ||
{ | ||
"TestPrimaryClusterSingleSecret", | ||
metav1.NamespaceAll, | ||
[]*v1.Secret{ | ||
secret0, | ||
}, | ||
"primary0", | ||
}, | ||
{ | ||
"TestPrimaryClusterMultipleSecrets", | ||
metav1.NamespaceAll, | ||
[]*v1.Secret{ | ||
secret0, | ||
secret1, | ||
}, | ||
"primary0", | ||
}, | ||
{ | ||
"TestPrimaryClusterNoSecretInNamespaceForNamespacedController", | ||
rolloutControllerNamespace, | ||
[]*v1.Secret{ | ||
secret0, | ||
}, | ||
"", | ||
}, | ||
{ | ||
"TestPrimaryClusterSingleSecretInNamespaceForNamespacedController", | ||
rolloutControllerNamespace, | ||
[]*v1.Secret{ | ||
makeSecret("secret0", rolloutControllerNamespace, "primary0", []byte("kubeconfig0-0")), | ||
}, | ||
"primary0", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
var existingObjs []runtime.Object | ||
for _, s := range tc.existingSecrets { | ||
existingObjs = append(existingObjs, s) | ||
} | ||
|
||
client := fake.NewSimpleClientset(existingObjs...) | ||
clusterId, _ := GetPrimaryClusterDynamicClient(client, tc.namespace) | ||
assert.Equal(t, tc.expectedClusterId, clusterId) | ||
}) | ||
} | ||
} | ||
|
||
func makeSecret(secret, namespace, clusterID string, kubeconfig []byte) *v1.Secret { | ||
return &v1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: secret, | ||
Namespace: namespace, | ||
Labels: map[string]string{ | ||
PrimaryClusterSecretLabel: "true", | ||
}, | ||
}, | ||
Data: map[string][]byte{ | ||
clusterID: kubeconfig, | ||
}, | ||
} | ||
} |