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  }