Skip to content

Commit

Permalink
Add support for outputting kube config credentials from AKS (#953)
Browse files Browse the repository at this point in the history
* Add kubeconfig to aks resource output

* Added kube config to output variables

* Updated docs

* Fixed naming in docs

* Rebased onto origin master

* Refactored out kubernetes helper

* Made kubeconfig embedded structs private

* Loaded test configs from testdata directory

* Added additional unit tests

* Checking the outputs are set

* Added raw kube config as output

* Changes required for sdkv15

* Parsing the Kube Config as a raw string, since it's decoded via string(bytes)

* Ensuring the Kubernetes Config is set & tested
  • Loading branch information
jjcollinge authored and tombuildsstuff committed Apr 26, 2018
1 parent ad13561 commit 587c815
Show file tree
Hide file tree
Showing 33 changed files with 10,411 additions and 3 deletions.
74 changes: 74 additions & 0 deletions azurerm/helpers/kubernetes/kube_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package kubernetes

import (
"fmt"

yaml "gopkg.in/yaml.v2"
)

type clusterItem struct {
Name string `yaml:"name"`
Cluster cluster `yaml:"cluster"`
}

type cluster struct {
ClusterAuthorityData string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
}

type userItem struct {
Name string `yaml:"name"`
User user `yaml:"user"`
}

type user struct {
ClientCertificteData string `yaml:"client-certificate-data"`
Token string `yaml:"token"`
ClientKeyData string `yaml:"client-key-data"`
}

type contextItem struct {
Name string `yaml:"name"`
Context context `yaml:"context"`
}

type context struct {
Cluster string `yaml:"cluster"`
User string `yaml:"user"`
Namespace string `yaml:"namespace,omitempty"`
}

type KubeConfig struct {
APIVersion string `yaml:"apiVersion"`
Clusters []clusterItem `yaml:"clusters"`
Users []userItem `yaml:"users"`
Contexts []contextItem `yaml:"contexts,omitempty"`
CurrentContext string `yaml:"current-context,omitempty"`
Kind string `yaml:"kind,omitempty"`
Preferences map[string]interface{} `yaml:"preferences,omitempty"`
}

func ParseKubeConfig(config string) (*KubeConfig, error) {
if config == "" {
return nil, fmt.Errorf("Cannot parse empty config")
}

var kubeConfig KubeConfig
err := yaml.Unmarshal([]byte(config), &kubeConfig)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal YAML config with error %+v", err)
}
if len(kubeConfig.Clusters) <= 0 || len(kubeConfig.Users) <= 0 {
return nil, fmt.Errorf("Config %+v contains no valid clusters or users", kubeConfig)
}
user := kubeConfig.Users[0].User
if user.Token == "" && (user.ClientCertificteData == "" || user.ClientKeyData == "") {
return nil, fmt.Errorf("Config requires either token or certificate auth for user %+v", user)
}
cluster := kubeConfig.Clusters[0].Cluster
if cluster.Server == "" {
return nil, fmt.Errorf("Config has invalid or non existent server for cluster %+v", cluster)
}

return &kubeConfig, nil
}
189 changes: 189 additions & 0 deletions azurerm/helpers/kubernetes/kube_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package kubernetes

import (
"fmt"
"io/ioutil"
"path/filepath"
"reflect"
"testing"
)

func TestParseKubeConfig(t *testing.T) {
testCases := []struct {
sourceFile string
expected KubeConfig
checkFunc func(expected KubeConfig, config string) (bool, error)
}{
{
"user_with_token.yml",
KubeConfig{
APIVersion: "v1",
Clusters: []clusterItem{
{
Name: "test-cluster",
Cluster: cluster{
Server: "https://testcluster.net:8080",
},
},
},
Users: []userItem{
{
Name: "test-user",
User: user{
Token: "test-token",
},
},
},
Kind: "Config",
},
isValidConfig,
},
{
"user_with_cert.yml",
KubeConfig{
APIVersion: "v1",
Clusters: []clusterItem{
{
Name: "test-cluster",
Cluster: cluster{
ClusterAuthorityData: "test-cluster-authority-data",
Server: "https://testcluster.org:443",
},
},
},
Users: []userItem{
{
Name: "test-user",
User: user{
ClientCertificteData: "test-client-certificate-data",
ClientKeyData: "test-client-key-data",
},
},
},
Contexts: []contextItem{
{
Name: "test-cluster",
Context: context{
Cluster: "test-cluster",
User: "test-user",
Namespace: "test-namespace",
},
},
},
CurrentContext: "test-cluster",
Kind: "Config",
Preferences: nil,
},
isValidConfig,
},
{
"user_with_cert_token.yml",
KubeConfig{
APIVersion: "v1",
Clusters: []clusterItem{
{
Name: "test-cluster",
Cluster: cluster{
ClusterAuthorityData: "test-cluster-authority-data",
Server: "https://testcluster.org:443",
},
},
},
Users: []userItem{
{
Name: "test-user",
User: user{
ClientCertificteData: "test-client-certificate-data",
ClientKeyData: "test-client-key-data",
Token: "test-token",
},
},
},
Contexts: []contextItem{
{
Name: "test-cluster",
Context: context{
Cluster: "test-cluster",
User: "test-user",
Namespace: "test-namespace",
},
},
},
CurrentContext: "test-cluster",
Kind: "Config",
Preferences: map[string]interface{}{
"colors": true,
},
},
isValidConfig,
},
{
"user_with_no_auth.yml",
KubeConfig{},
isInvalidConfig,
},
{
"no_cluster.yml",
KubeConfig{},
isInvalidConfig,
},
{
"no_user.yml",
KubeConfig{},
isInvalidConfig,
},
{
"user_with_partial_auth.yml",
KubeConfig{},
isInvalidConfig,
},
{
"cluster_with_no_server.yml",
KubeConfig{},
isInvalidConfig,
},
}

for i, test := range testCases {
encodedConfig := LoadConfig(test.sourceFile)
if len(encodedConfig) <= 0 {
t.Fatalf("Test case [%d]: Failed to read config from file '%+v' \n",
i, test.sourceFile)
}
if success, err := test.checkFunc(test.expected, encodedConfig); !success {
t.Fatalf("Test case [%d]: Failed, config '%+v' with error: '%+v'",
i, test.sourceFile, err)
}
}
}

func isValidConfig(expected KubeConfig, encodedConfig string) (bool, error) {
result, err := ParseKubeConfig(encodedConfig)
if err != nil {
return false, err
}

if !reflect.DeepEqual(expected, *result) {
return false, fmt.Errorf("expected '%+v but got '%+v' with encoded config '%+v'",
expected, *result, encodedConfig)
}
return true, nil
}

func isInvalidConfig(expected KubeConfig, encodedConfig string) (bool, error) {
_, err := ParseKubeConfig(encodedConfig)
if err == nil {
return false, fmt.Errorf("expected test to throw error but didn't")
}
return true, nil
}

func LoadConfig(fileName string) string {
filePath := filepath.Join("testdata", fileName)
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return ""
}

return string(bytes)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
clusters:
- cluster:
name: test-cluster
users:
- name: test-user
user:
token: test-token
kind: Config
7 changes: 7 additions & 0 deletions azurerm/helpers/kubernetes/testdata/no_cluster.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
clusters:
users:
- name: test-user
user:
token: test-token
kind: Config
7 changes: 7 additions & 0 deletions azurerm/helpers/kubernetes/testdata/no_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: test-cluster-authority-data
server: https://testcluster.org:443
name: test-cluster
users:
19 changes: 19 additions & 0 deletions azurerm/helpers/kubernetes/testdata/user_with_cert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: test-cluster-authority-data
server: https://testcluster.org:443
name: test-cluster
contexts:
- context:
cluster: test-cluster
user: test-user
namespace: test-namespace
name: test-cluster
current-context: test-cluster
users:
- name: test-user
user:
client-certificate-data: test-client-certificate-data
client-key-data: test-client-key-data
kind: Config
22 changes: 22 additions & 0 deletions azurerm/helpers/kubernetes/testdata/user_with_cert_token.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: test-cluster-authority-data
server: https://testcluster.org:443
name: test-cluster
contexts:
- context:
cluster: test-cluster
user: test-user
namespace: test-namespace
name: test-cluster
current-context: test-cluster
users:
- name: test-user
user:
client-certificate-data: test-client-certificate-data
client-key-data: test-client-key-data
token: test-token
kind: Config
preferences:
colors: true
9 changes: 9 additions & 0 deletions azurerm/helpers/kubernetes/testdata/user_with_no_auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: test-cluster-authority-data
server: https://testcluster.org:443
name: test-cluster
users:
- name: test-user
user:
20 changes: 20 additions & 0 deletions azurerm/helpers/kubernetes/testdata/user_with_partial_auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: test-cluster-authority-data
server: https://testcluster.org:443
name: test-cluster
contexts:
- context:
cluster: test-cluster
user: test-user
namespace: test-namespace
name: test-cluster
current-context: test-cluster
users:
- name: test-user
user:
client-certificate-data: test-client-certificate-data
kind: Config
preferences:
colors: true
10 changes: 10 additions & 0 deletions azurerm/helpers/kubernetes/testdata/user_with_token.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
clusters:
- cluster:
server: https://testcluster.net:8080
name: test-cluster
users:
- name: test-user
user:
token: test-token
kind: Config
Loading

0 comments on commit 587c815

Please sign in to comment.