1// Copyright 2019 Istio Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package multicluster
16
17import (
18	"bytes"
19	"io/ioutil"
20	"reflect"
21	"testing"
22	"time"
23
24	"github.com/google/go-cmp/cmp"
25	"k8s.io/apimachinery/pkg/runtime"
26	"k8s.io/client-go/kubernetes"
27	"k8s.io/client-go/kubernetes/fake"
28	"k8s.io/client-go/tools/clientcmd/api"
29	"k8s.io/client-go/tools/clientcmd/api/latest"
30)
31
32var fakeKubeconfigData = `apiVersion: v1
33kind: Config
34clusters:
35- cluster:
36    certificate-authority-data: UEhPTlkK
37    server: https://192.168.1.1
38  name: prod0
39contexts:
40- context:
41    cluster: prod0
42    user: prod0
43  name: prod0
44current-context: prod0
45users:
46- name: prod0
47  user:
48    auth-provider:
49      name: gcp` // nolint:lll
50
51func createFakeKubeconfigFileOrDie(t *testing.T) (string, *api.Config) {
52	t.Helper()
53
54	f, err := ioutil.TempFile("", "fakeKubeconfigForEnvironment")
55	if err != nil {
56		t.Fatalf("could not create fake kubeconfig file: %v", err)
57	}
58	kubeconfigPath := f.Name()
59	defer func() {
60		if err := f.Close(); err != nil {
61			t.Errorf("couldn't close temp file %v: %v", kubeconfigPath, err)
62		}
63	}()
64
65	if _, err = f.WriteString(fakeKubeconfigData); err != nil {
66		t.Fatalf("could not write fake kubeconfig data to %v: %v", kubeconfigPath, err)
67	}
68
69	// Temporary workaround until https://github.com/kubernetes/kubernetes/pull/86414 merges
70	into := &api.Config{
71		Clusters:   map[string]*api.Cluster{},
72		AuthInfos:  map[string]*api.AuthInfo{},
73		Contexts:   map[string]*api.Context{},
74		Extensions: map[string]runtime.Object{},
75	}
76	out, _, err := latest.Codec.Decode([]byte(fakeKubeconfigData), nil, into)
77	if err != nil {
78		t.Fatalf("could not decode fake kubeconfig: %v", err)
79	}
80	config, ok := out.(*api.Config)
81	if !ok {
82		t.Fatalf("decoded kubeconfig is not a valid api.Config (%v)", reflect.TypeOf(out))
83	}
84
85	// fill in missing defaults
86	config.Contexts[config.CurrentContext].LocationOfOrigin = kubeconfigPath
87	config.Clusters[config.CurrentContext].LocationOfOrigin = kubeconfigPath
88	config.AuthInfos[config.CurrentContext].LocationOfOrigin = kubeconfigPath
89	config.Extensions = make(map[string]runtime.Object)
90	config.Preferences.Extensions = make(map[string]runtime.Object)
91
92	return kubeconfigPath, config
93}
94
95type fakeEnvironment struct {
96	KubeEnvironment
97
98	client                  *fake.Clientset
99	injectClientCreateError error
100	kubeconfig              string
101	wOut                    bytes.Buffer
102	wErr                    bytes.Buffer
103}
104
105func newFakeEnvironmentOrDie(t *testing.T, config *api.Config, objs ...runtime.Object) *fakeEnvironment {
106	t.Helper()
107
108	var wOut, wErr bytes.Buffer
109
110	f := &fakeEnvironment{
111		KubeEnvironment: KubeEnvironment{
112			config:     config,
113			stdout:     &wOut,
114			stderr:     &wErr,
115			kubeconfig: "unused",
116		},
117		client:     fake.NewSimpleClientset(objs...),
118		kubeconfig: "unused",
119		wOut:       wOut,
120		wErr:       wErr,
121	}
122
123	return f
124}
125
126func (f *fakeEnvironment) CreateClientSet(context string) (kubernetes.Interface, error) {
127	if f.injectClientCreateError != nil {
128		return nil, f.injectClientCreateError
129	}
130	return f.client, nil
131}
132
133func (f *fakeEnvironment) Poll(interval, timeout time.Duration, condition ConditionFunc) error {
134	// TODO - add hooks to inject fake timeouts
135	condition()
136	return nil
137}
138
139func TestNewEnvironment(t *testing.T) {
140	context := "" // empty, use current-Context
141	kubeconfig, wantConfig := createFakeKubeconfigFileOrDie(t)
142
143	var wOut, wErr bytes.Buffer
144
145	wantEnv := &KubeEnvironment{
146		config:     wantConfig,
147		stdout:     &wOut,
148		stderr:     &wErr,
149		kubeconfig: kubeconfig,
150	}
151
152	gotEnv, err := NewEnvironment(kubeconfig, context, &wOut, &wErr)
153	if err != nil {
154		t.Fatal(err)
155	}
156
157	if diff := cmp.Diff(*wantEnv.GetConfig(), *gotEnv.GetConfig()); diff != "" {
158		t.Errorf("bad config: \n got %v \nwant %v\ndiff %v", gotEnv, wantEnv, diff)
159	}
160
161	wantEnv.config = nil
162	gotEnv.config = nil
163	if !reflect.DeepEqual(wantEnv, gotEnv) {
164		t.Errorf("bad environment: \n got %v \nwant %v", *gotEnv, *wantEnv)
165	}
166
167	// verify interface
168	if gotEnv.Stderr() != &wErr {
169		t.Errorf("Stderr() returned wrong io.writer")
170	}
171	if gotEnv.Stdout() != &wOut {
172		t.Errorf("Stdout() returned wrong io.writer")
173	}
174	gotEnv.Printf("stdout %v", "test")
175	wantOut := "stdout test"
176	if gotOut := wOut.String(); gotOut != wantOut {
177		t.Errorf("Printf() printed wrong string: got %v want %v", gotOut, wantOut)
178	}
179	gotEnv.Errorf("stderr %v", "test")
180	wantErr := "stderr test"
181	if gotErr := wErr.String(); gotErr != wantErr {
182		t.Errorf("Errorf() printed wrong string: got %v want %v", gotErr, wantErr)
183	}
184}
185