blob: 6cfe0ebdd0a4fb8ab47b0b85f2c284c8731f3aec [file]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 cloudstack
import (
"encoding/base64"
"fmt"
"strings"
"testing"
)
func TestParseKubernetesClusterConfig(t *testing.T) {
tests := []struct {
name string
configData string
expected kubernetesClusterCredentials
expectErr string
}{
{
name: "kubeadm admin.conf as returned by CloudStack",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
users:
- name: kubernetes-admin
user:
client-certificate-data: %s
client-key-data: %s
`, testBase64("ca-certificate"), testBase64("client-certificate"), testBase64("client-key")),
expected: kubernetesClusterCredentials{
Endpoint: "https://10.1.1.1:6443",
ClusterCACertificate: "ca-certificate",
ClientCertificate: "client-certificate",
ClientKey: "client-key",
},
},
{
// Guards against blindly taking clusters[0] / users[0].
name: "current context selects the second cluster and user",
configData: testKubeConfigTwoClusters("second@second"),
expected: kubernetesClusterCredentials{
Endpoint: "https://10.2.2.2:6443",
ClusterCACertificate: "second-ca-certificate",
ClientCertificate: "second-client-certificate",
ClientKey: "second-client-key",
},
},
{
name: "absent current context falls back to the first entry",
configData: testKubeConfigTwoClusters(""),
expected: kubernetesClusterCredentials{
Endpoint: "https://10.1.1.1:6443",
ClusterCACertificate: "first-ca-certificate",
ClientCertificate: "first-client-certificate",
ClientKey: "first-client-key",
},
},
{
// Ambiguous among several contexts: error rather than guess.
name: "unknown current context among several contexts is an error",
configData: testKubeConfigTwoClusters("missing@missing"),
expectErr: "does not define",
},
{
// Only one candidate exists, so this falls back instead of erroring.
name: "current context naming a missing cluster falls back to the only entry",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
current-context: third@third
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: first
contexts:
- context:
cluster: third
user: third
name: third@third
users:
- name: first
user:
client-certificate-data: %s
client-key-data: %s
`, testBase64("first-ca-certificate"),
testBase64("first-client-certificate"), testBase64("first-client-key")),
expected: kubernetesClusterCredentials{
Endpoint: "https://10.1.1.1:6443",
ClusterCACertificate: "first-ca-certificate",
ClientCertificate: "first-client-certificate",
ClientKey: "first-client-key",
},
},
{
// Ambiguous among several clusters: error rather than guess.
// No users section needed; the cluster error returns first.
name: "current context naming an undefined cluster among several is an error",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
current-context: third@third
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: first
- cluster:
certificate-authority-data: %s
server: https://10.2.2.2:6443
name: second
contexts:
- context:
cluster: third
user: third
name: third@third
`, testBase64("first-ca-certificate"), testBase64("second-ca-certificate")),
expectErr: "does not define",
},
{
// Same error, but via the user branch instead of the cluster branch.
name: "current context naming an undefined user among several is an error",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
current-context: kubernetes@kubernetes
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: third
name: kubernetes@kubernetes
users:
- name: first
user:
client-certificate-data: %s
client-key-data: %s
- name: second
user:
client-certificate-data: %s
client-key-data: %s
`, testBase64("ca-certificate"),
testBase64("first-client-certificate"), testBase64("first-client-key"),
testBase64("second-client-certificate"), testBase64("second-client-key")),
expectErr: "does not define",
},
{
// A kubeconfig without client certificates must not fail the data
// source, so that config_data stays usable.
name: "no users leaves the client credentials empty",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: kubernetes
`, testBase64("ca-certificate")),
expected: kubernetesClusterCredentials{
Endpoint: "https://10.1.1.1:6443",
ClusterCACertificate: "ca-certificate",
},
},
{
name: "no clusters leaves the endpoint empty",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
users:
- name: kubernetes-admin
user:
client-certificate-data: %s
client-key-data: %s
`, testBase64("client-certificate"), testBase64("client-key")),
expected: kubernetesClusterCredentials{
ClientCertificate: "client-certificate",
ClientKey: "client-key",
},
},
{
// The empty-config guard lives in the read function, not here.
name: "empty document yields no credentials and no error",
configData: "",
expected: kubernetesClusterCredentials{},
},
{
name: "invalid yaml",
configData: "\tthis is not a kubeconfig",
expectErr: "Invalid kubeconfig",
},
{
name: "certificate authority data is not base64",
configData: `apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: "@@@ not base64 @@@"
server: https://10.1.1.1:6443
name: kubernetes
`,
expectErr: "Failed to decode certificate-authority-data",
},
{
name: "client key data is not base64",
configData: fmt.Sprintf(`apiVersion: v1
kind: Config
users:
- name: kubernetes-admin
user:
client-certificate-data: %s
client-key-data: "@@@ not base64 @@@"
`, testBase64("client-certificate")),
expectErr: "Failed to decode client-key-data",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
credentials, err := parseKubernetesClusterConfig(tt.configData)
if tt.expectErr != "" {
if err == nil {
t.Fatalf("Expected an error containing %q, got none", tt.expectErr)
}
if !strings.Contains(err.Error(), tt.expectErr) {
t.Fatalf("Expected an error containing %q, got: %s", tt.expectErr, err)
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if *credentials != tt.expected {
t.Errorf("Expected %+v, got %+v", tt.expected, *credentials)
}
})
}
}
func testBase64(value string) string {
return base64.StdEncoding.EncodeToString([]byte(value))
}
// testKubeConfigTwoClusters renders a two-cluster, two-user kubeconfig;
// currentContext picks the context ("" omits current-context). Args are
// listed in template order so the substitution can be eyeballed.
func testKubeConfigTwoClusters(currentContext string) string {
return fmt.Sprintf(`apiVersion: v1
kind: Config
current-context: %s
clusters:
- cluster:
certificate-authority-data: %s
server: https://10.1.1.1:6443
name: first
- cluster:
certificate-authority-data: %s
server: https://10.2.2.2:6443
name: second
contexts:
- context:
cluster: first
user: first
name: first@first
- context:
cluster: second
user: second
name: second@second
users:
- name: first
user:
client-certificate-data: %s
client-key-data: %s
- name: second
user:
client-certificate-data: %s
client-key-data: %s
`,
currentContext,
testBase64("first-ca-certificate"),
testBase64("second-ca-certificate"),
testBase64("first-client-certificate"),
testBase64("first-client-key"),
testBase64("second-client-certificate"),
testBase64("second-client-key"))
}