github.com/verrazzano/verrazzano@v1.7.0/pkg/k8s/resource/resource_util_test.go (about) 1 // Copyright (c) 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package resource 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "net/http/httptest" 12 "os" 13 "testing" 14 15 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 16 17 "github.com/stretchr/testify/assert" 18 "github.com/verrazzano/verrazzano/pkg/k8sutil" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/runtime/schema" 21 ) 22 23 const ( 24 Secret = "testdata/secret.yaml" 25 SecretBadNamespace = "testdata/secret_bad_namespace.yaml" 26 SecretInvalid = "testdata/secret_invalid.yaml" 27 SecretNoNamespace = "testdata/secret_no_namespace.yaml" 28 ) 29 30 // TestCreateOrUpdateResourceFromFile tests the CreateOrUpdateResourceFromFile function 31 // Given a yaml file, create the resource 32 func TestCreateOrUpdateResourceFromFile(t *testing.T) { 33 asserts := assert.New(t) 34 file := Secret 35 36 server := newServer() 37 defer server.Close() 38 39 err := createFakeKubeConfig(server.URL) 40 defer deleteFakeKubeConfig() 41 asserts.NoError(err) 42 43 kubeConfigPath, err := getFakeKubeConfigPath() 44 asserts.NoError(err) 45 46 // Preserve previous env var value 47 prevEnvVar := os.Getenv(k8sutil.EnvVarTestKubeConfig) 48 defer os.Setenv(k8sutil.EnvVarTestKubeConfig, prevEnvVar) 49 50 // Test using environment variable 51 err = os.Setenv(k8sutil.EnvVarTestKubeConfig, kubeConfigPath) 52 asserts.NoError(err) 53 54 logger := vzlog.DefaultLogger().GetZapLogger() 55 err = CreateOrUpdateResourceFromFile(file, logger) 56 asserts.NoError(err) 57 } 58 59 // TestCreateOrUpdateResourceFromBytes tests the CreateOrUpdateResourceFromBytes function 60 // Given a stream of bytes, create the resource 61 func TestCreateOrUpdateResourceFromBytes(t *testing.T) { 62 asserts := assert.New(t) 63 file := Secret 64 65 bytes, err := os.ReadFile(file) 66 asserts.NoError(err) 67 68 server := newServer() 69 defer server.Close() 70 71 err = createFakeKubeConfig(server.URL) 72 defer deleteFakeKubeConfig() 73 asserts.NoError(err) 74 75 kubeConfigPath, err := getFakeKubeConfigPath() 76 asserts.NoError(err) 77 78 // Preserve previous env var value 79 prevEnvVar := os.Getenv(k8sutil.EnvVarTestKubeConfig) 80 defer os.Setenv(k8sutil.EnvVarTestKubeConfig, prevEnvVar) 81 82 // Test using environment variable 83 err = os.Setenv(k8sutil.EnvVarTestKubeConfig, kubeConfigPath) 84 asserts.NoError(err) 85 86 err = CreateOrUpdateResourceFromBytes(bytes, vzlog.DefaultLogger().GetZapLogger()) 87 asserts.NoError(err) 88 } 89 90 // TestCreateOrUpdateResourceFromFileInCluster tests the CreateOrUpdateResourceFromFileInCluster function 91 // Given a yaml file and the kubeconfig path, create the resource in the namespace 92 // Given a yaml file with bad namespace and the kubeconfig path, return an error 93 // Given a yaml file with invalid namespace and the kubeconfig path, return an error 94 func TestCreateOrUpdateResourceFromFileInCluster(t *testing.T) { 95 asserts := assert.New(t) 96 file := Secret 97 98 server := newServer() 99 defer server.Close() 100 101 err := createFakeKubeConfig(server.URL) 102 defer deleteFakeKubeConfig() 103 asserts.NoError(err) 104 105 kubeConfigPath, err := getFakeKubeConfigPath() 106 asserts.NoError(err) 107 108 // Creating a resource with a valid yaml file 109 // and in a namespace that exists 110 // should not return an error 111 err = CreateOrUpdateResourceFromFileInCluster(file, kubeConfigPath) 112 asserts.NoError(err) 113 114 // Creating a resource in a namespace that doesn't exist 115 // should return an error 116 file = SecretBadNamespace 117 err = CreateOrUpdateResourceFromFileInCluster(file, kubeConfigPath) 118 asserts.Error(err) 119 120 // Passing a yaml file with no specified namespace 121 // should return an error 122 file = SecretNoNamespace 123 err = CreateOrUpdateResourceFromFileInCluster(file, kubeConfigPath) 124 asserts.Error(err) 125 126 // Passing an invalid yaml file to create a resource 127 // should return an error 128 file = SecretInvalid 129 err = CreateOrUpdateResourceFromFileInCluster(file, kubeConfigPath) 130 asserts.Error(err) 131 } 132 133 // TestCreateOrUpdateResourceFromFileInGeneratedNamespace tests the 134 // CreateOrUpdateResourceFromFileInGeneratedNamespace 135 // Given a yaml file, create the resource in the provided namespace 136 func TestCreateOrUpdateResourceFromFileInGeneratedNamespace(t *testing.T) { 137 asserts := assert.New(t) 138 file := Secret 139 140 server := newServer() 141 defer server.Close() 142 143 err := createFakeKubeConfig(server.URL) 144 defer deleteFakeKubeConfig() 145 asserts.NoError(err) 146 147 kubeConfigPath, err := getFakeKubeConfigPath() 148 asserts.NoError(err) 149 150 // Preserve previous env var value 151 prevEnvVar := os.Getenv(k8sutil.EnvVarTestKubeConfig) 152 defer os.Setenv(k8sutil.EnvVarTestKubeConfig, prevEnvVar) 153 154 // Test using environment variable 155 err = os.Setenv(k8sutil.EnvVarTestKubeConfig, kubeConfigPath) 156 asserts.NoError(err) 157 158 err = CreateOrUpdateResourceFromFileInGeneratedNamespace(file, "default") 159 asserts.NoError(err) 160 } 161 162 // TestCreateOrUpdateResourceFromFileInClusterInGeneratedNamespace tests 163 // the CreateOrUpdateResourceFromFileInClusterInGeneratedNamespace function 164 // Given a yaml file with no namespace and the kubeconfig path, create the resource in the provided namespace 165 // When provided with a bad namespace, return an error 166 // Given an invalid yaml file and the kubeconfig path, return an error 167 func TestCreateOrUpdateResourceFromFileInClusterInGeneratedNamespace(t *testing.T) { 168 asserts := assert.New(t) 169 file := SecretNoNamespace 170 171 server := newServer() 172 defer server.Close() 173 174 err := createFakeKubeConfig(server.URL) 175 defer deleteFakeKubeConfig() 176 asserts.NoError(err) 177 178 kubeConfigPath, err := getFakeKubeConfigPath() 179 asserts.NoError(err) 180 181 err = CreateOrUpdateResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "default") 182 asserts.NoError(err) 183 184 // Namespace doesn't exist, should return an error 185 err = CreateOrUpdateResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "test") 186 asserts.Error(err) 187 188 file = SecretInvalid 189 err = CreateOrUpdateResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "default") 190 asserts.Error(err) 191 } 192 193 // TestDeleteResourceFromFile tests the DeleteResourceFromFile 194 // Given a yaml file, delete the resource 195 func TestDeleteResourceFromFile(t *testing.T) { 196 asserts := assert.New(t) 197 file := Secret 198 199 server := newServer() 200 defer server.Close() 201 202 err := createFakeKubeConfig(server.URL) 203 defer deleteFakeKubeConfig() 204 asserts.NoError(err) 205 206 kubeConfigPath, err := getFakeKubeConfigPath() 207 asserts.NoError(err) 208 209 // Preserve previous env var value 210 prevEnvVar := os.Getenv(k8sutil.EnvVarTestKubeConfig) 211 defer os.Setenv(k8sutil.EnvVarTestKubeConfig, prevEnvVar) 212 213 // Test using environment variable 214 err = os.Setenv(k8sutil.EnvVarTestKubeConfig, kubeConfigPath) 215 asserts.NoError(err) 216 217 err = DeleteResourceFromFile(file, vzlog.DefaultLogger().GetZapLogger()) 218 asserts.NoError(err) 219 } 220 221 // TestDeleteResourceFromFileInCluster tests the DeleteResourceFromFileInCluster function 222 // Given a yaml and the kubeconfig path, delete the resource 223 // Given a yaml with bad namespace and the kubeconfig path, return an error 224 // Given an invalid yaml and the kubeconfig path, return an error 225 func TestDeleteResourceFromFileInCluster(t *testing.T) { 226 asserts := assert.New(t) 227 file := Secret 228 229 server := newServer() 230 defer server.Close() 231 232 err := createFakeKubeConfig(server.URL) 233 defer deleteFakeKubeConfig() 234 asserts.NoError(err) 235 236 kubeConfigPath, err := getFakeKubeConfigPath() 237 asserts.NoError(err) 238 239 // Resource not found error is not returned, so 240 // check for no error 241 err = DeleteResourceFromFileInCluster(file, kubeConfigPath) 242 asserts.NoError(err) 243 244 file = SecretBadNamespace 245 err = DeleteResourceFromFileInCluster(file, kubeConfigPath) 246 asserts.Error(err) 247 248 file = SecretInvalid 249 err = DeleteResourceFromFileInCluster(file, kubeConfigPath) 250 asserts.Error(err) 251 } 252 253 // TestDeleteResourceFromFileInGeneratedNamespace tests 254 // the DeleteResourceFromFileInGeneratedNamespace 255 // Given a yaml with no namespace, delete the resource 256 func TestDeleteResourceFromFileInGeneratedNamespace(t *testing.T) { 257 asserts := assert.New(t) 258 file := SecretNoNamespace 259 260 server := newServer() 261 defer server.Close() 262 263 err := createFakeKubeConfig(server.URL) 264 defer deleteFakeKubeConfig() 265 asserts.NoError(err) 266 267 kubeConfigPath, err := getFakeKubeConfigPath() 268 asserts.NoError(err) 269 270 // Preserve previous env var value 271 prevEnvVar := os.Getenv(k8sutil.EnvVarTestKubeConfig) 272 defer os.Setenv(k8sutil.EnvVarTestKubeConfig, prevEnvVar) 273 274 // Test using environment variable 275 err = os.Setenv(k8sutil.EnvVarTestKubeConfig, kubeConfigPath) 276 asserts.NoError(err) 277 278 err = DeleteResourceFromFileInGeneratedNamespace(file, "default") 279 asserts.NoError(err) 280 } 281 282 // TestDeleteResourceFromFileInClusterInGeneratedNamespace tests 283 // the DeleteResourceFromFileInClusterInGeneratedNamespace function 284 // Given a yaml with no namespace, delete the resource in the provided namespace 285 // When provided with a bad namespace, return an error 286 // Given an invalid yaml file, return an error 287 func TestDeleteResourceFromFileInClusterInGeneratedNamespace(t *testing.T) { 288 asserts := assert.New(t) 289 file := SecretNoNamespace 290 291 server := newServer() 292 defer server.Close() 293 294 err := createFakeKubeConfig(server.URL) 295 defer deleteFakeKubeConfig() 296 asserts.NoError(err) 297 298 kubeConfigPath, err := getFakeKubeConfigPath() 299 asserts.NoError(err) 300 301 err = DeleteResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "default") 302 asserts.NoError(err) 303 304 // Namespace doesn't exist, expect an error 305 err = DeleteResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "test") 306 asserts.Error(err) 307 308 file = SecretInvalid 309 err = DeleteResourceFromFileInClusterInGeneratedNamespace(file, kubeConfigPath, "default") 310 asserts.Error(err) 311 } 312 313 // TestPatchResourceFromFileInCluster tests PatchResourceFromFileInCluster function 314 // Given a yaml file, patch the resource if it exists 315 // Given an invalid yaml file, return an error 316 func TestPatchResourceFromFileInCluster(t *testing.T) { 317 asserts := assert.New(t) 318 file := Secret 319 320 server := newServer() 321 defer server.Close() 322 323 err := createFakeKubeConfig(server.URL) 324 defer deleteFakeKubeConfig() 325 asserts.NoError(err) 326 327 kubeConfigPath, err := getFakeKubeConfigPath() 328 asserts.NoError(err) 329 330 gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: ""} 331 332 // Patching a resource that doesn't exist should return an error 333 err = PatchResourceFromFileInCluster(gvr, "default", "test-secret", file, kubeConfigPath) 334 asserts.Error(err) 335 336 file = SecretInvalid 337 err = PatchResourceFromFileInCluster(gvr, "default", "test-secret", file, kubeConfigPath) 338 asserts.Error(err) 339 } 340 341 // newServer returns a httptest server which the 342 // dynamic client and discovery client can send 343 // GET/POST/DELETE requests to instead of a real cluster 344 func newServer() *httptest.Server { 345 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 346 var obj interface{} 347 switch req.URL.Path { 348 case "/api/v1/namespaces/default": 349 obj = &metav1.APIVersions{ 350 TypeMeta: metav1.TypeMeta{ 351 Kind: "APIVersions", 352 }, 353 Versions: []string{ 354 "v1", 355 }, 356 } 357 case "/api": 358 obj = &metav1.APIVersions{ 359 Versions: []string{ 360 "v1", 361 }, 362 } 363 case "/api/v1": 364 obj = &metav1.APIResourceList{ 365 GroupVersion: "v1", 366 APIResources: []metav1.APIResource{ 367 {Name: "secrets", Namespaced: true, Kind: "Secret"}, 368 }, 369 } 370 case "/api/v1/namespaces/default/secrets": 371 // POST request, return the raw request body 372 body, _ := io.ReadAll(req.Body) 373 w.Write(body) 374 return 375 default: 376 w.WriteHeader(http.StatusNotFound) 377 return 378 } 379 output, err := json.Marshal(obj) 380 if err != nil { 381 return 382 } 383 w.Header().Set("Content-Type", "application/json") 384 w.WriteHeader(http.StatusOK) 385 w.Write(output) 386 })) 387 return server 388 } 389 390 // createFakeKubeConfig creates a fake kubeconfig 391 // in the pwd with the url of the httptest server 392 func createFakeKubeConfig(url string) error { 393 fakeKubeConfig, err := os.Create("dummy-kubeconfig") 394 defer fakeKubeConfig.Close() 395 396 if err != nil { 397 return err 398 } 399 400 _, err = fmt.Fprintf(fakeKubeConfig, `apiVersion: v1 401 clusters: 402 - cluster: 403 # This is dummy data 404 certificate-authority-data: RFVNTVkgQ0VSVElGSUNBVEU= 405 server: %s 406 name: user-test 407 users: 408 - name: user-test 409 contexts: 410 - context: 411 cluster: user-test 412 user: user-test 413 name: user-test 414 current-context: user-test`, url) 415 416 return err 417 } 418 419 func getFakeKubeConfigPath() (string, error) { 420 pwd, err := os.Getwd() 421 422 if err != nil { 423 return pwd, err 424 } 425 426 pwd = pwd + "/dummy-kubeconfig" 427 return pwd, nil 428 } 429 430 func deleteFakeKubeConfig() error { 431 err := os.Remove("dummy-kubeconfig") 432 return err 433 }