github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/client/client_test.go (about)

     1  package client
     2  
     3  import (
     4  	"io/ioutil"
     5  	"net/http"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/tickoalcantara12/micro/v3/service/runtime"
    10  
    11  	"github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/api"
    12  	"github.com/tickoalcantara12/micro/v3/test/fakes"
    13  
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  func TestCreate(t *testing.T) {
    18  	tcs := []struct {
    19  		name         string
    20  		namespace    string
    21  		resource     *Resource
    22  		expectedBody string
    23  		expectedURL  string
    24  	}{
    25  		{
    26  			name:      "deployment",
    27  			namespace: "foo-bar-baz",
    28  			resource: NewDeployment(&runtime.Service{
    29  				Name:     "svc1",
    30  				Version:  "latest",
    31  				Source:   "source",
    32  				Metadata: map[string]string{"foo": "bar", "hello": "world"},
    33  			}, &runtime.CreateOptions{
    34  				Command:   []string{"cmd", "arg"},
    35  				Args:      []string{"arg1", "arg2"},
    36  				Env:       []string{"FOO=BAR", "HELLO=WORLD"},
    37  				Type:      "service",
    38  				Image:     "DefaultImage",
    39  				Namespace: DefaultNamespace,
    40  				Resources: &runtime.Resources{
    41  					CPU:  200,
    42  					Mem:  200,
    43  					Disk: 2000,
    44  				},
    45  				ServiceAccount: "serviceAcc",
    46  			},
    47  			),
    48  
    49  			expectedBody: `
    50  apiVersion: apps/v1
    51  kind: Deployment
    52  metadata:
    53    name: "svc1-latest"
    54    namespace: "default"
    55    labels:
    56      micro: "service"
    57      name: "svc1"
    58      version: "latest"
    59    annotations:
    60      foo: "bar"
    61      hello: "world"
    62      name: "svc1"
    63      source: "source"
    64      version: "latest"
    65  spec:
    66    replicas: 1
    67    selector:
    68      matchLabels:
    69        micro: "service"
    70        name: "svc1"
    71        version: "latest"
    72    template:
    73      metadata:
    74        labels:
    75          micro: "service"
    76          name: "svc1"
    77          version: "latest"
    78        annotations:
    79          foo: "bar"
    80          hello: "world"
    81          name: "svc1"
    82          source: "source"
    83          version: "latest"
    84      spec: 
    85        runtimeClassName: 
    86        serviceAccountName: serviceAcc
    87        containers:
    88          - name: svc1
    89            env:
    90            - name: "FOO"
    91              value: "BAR"
    92            - name: "HELLO"
    93              value: "WORLD"
    94            args:
    95            - arg1
    96            - arg2
    97            command:
    98            - cmd
    99            - arg
   100            image: DefaultImage
   101            imagePullPolicy: IfNotPresent
   102            ports:
   103            - containerPort: 8080
   104              name: service-port
   105            readinessProbe:
   106              tcpSocket:
   107                port: 8080
   108              initialDelaySeconds: 10
   109              periodSeconds: 10
   110            resources:
   111              limits:
   112                memory: 200Mi
   113                cpu: 200m
   114                ephemeral-storage: 2000Mi
   115            volumeMounts: 
   116        volumes:`,
   117  			expectedURL: `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/`,
   118  		},
   119  		{
   120  			name:      "service",
   121  			namespace: "foo-bar-baz",
   122  			resource: NewService(&runtime.Service{
   123  				Name:     "svc1",
   124  				Version:  "latest",
   125  				Source:   "source",
   126  				Metadata: map[string]string{"foo": "bar", "hello": "world"},
   127  			}, &runtime.CreateOptions{
   128  				Command:   []string{"cmd", "arg"},
   129  				Args:      []string{"arg1", "arg2"},
   130  				Env:       []string{"FOO=BAR", "HELLO=WORLD"},
   131  				Type:      "service",
   132  				Image:     "DefaultImage",
   133  				Namespace: DefaultNamespace,
   134  				Resources: &runtime.Resources{
   135  					CPU:  200,
   136  					Mem:  200,
   137  					Disk: 2000,
   138  				},
   139  				ServiceAccount: "serviceAcc",
   140  			}),
   141  			expectedBody: `
   142  apiVersion: v1
   143  kind: Service
   144  metadata:
   145    name: "svc1"
   146    namespace: "default"
   147    labels:
   148      micro: "service"
   149      name: "svc1"
   150      version: "latest"
   151  spec:
   152    selector:
   153      micro: "service"
   154      name: "svc1"
   155      version: "latest"
   156    ports:
   157    - name: "service-port"
   158      port: 8080
   159      protocol:
   160  `,
   161  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/services/",
   162  		},
   163  		{
   164  			name:      "secrets",
   165  			namespace: "foo-bar-baz",
   166  			resource: &Resource{
   167  				Kind: "secret",
   168  				Name: "svc1",
   169  				Value: &Secret{
   170  					Type: "Opaque",
   171  					Data: map[string]string{
   172  						"key1": "val1",
   173  						"key2": "val2",
   174  					},
   175  					Metadata: &Metadata{
   176  						Name:      "svc1",
   177  						Namespace: "foo-bar-baz",
   178  					},
   179  				},
   180  			},
   181  			expectedBody: `
   182  apiVersion: v1
   183  kind: Secret
   184  type: "Opaque"
   185  metadata:
   186    name: "svc1"
   187    namespace: "foo-bar-baz"
   188    labels:
   189  data:
   190    key1: "val1"
   191    key2: "val2"
   192  `,
   193  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/secrets/",
   194  		},
   195  		{
   196  			name:      "serviceaccount",
   197  			namespace: "foo-bar-baz",
   198  			resource: &Resource{
   199  				Name: "svcacc",
   200  				Kind: "serviceaccount",
   201  				Value: &ServiceAccount{
   202  					Metadata: &Metadata{
   203  						Name:      "svcacc",
   204  						Namespace: "foo-bar-baz",
   205  					},
   206  					ImagePullSecrets: []ImagePullSecret{
   207  						{
   208  							Name: "pullme",
   209  						},
   210  					},
   211  				},
   212  			},
   213  			expectedBody: `
   214  apiVersion: v1
   215  kind: ServiceAccount
   216  metadata:
   217    name: "svcacc"
   218    labels:
   219  imagePullSecrets:
   220  - name: "pullme"
   221  `,
   222  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/",
   223  		},
   224  		{
   225  			name:      "networkpolicy",
   226  			namespace: "foo-bar-baz",
   227  			resource: &Resource{
   228  				Kind: "networkpolicy",
   229  				Value: NewNetworkPolicy("np1", "foo-bar-baz", map[string]string{
   230  					"foo":   "bar",
   231  					"hello": "world",
   232  				})},
   233  			expectedBody: `
   234  apiVersion: networking.k8s.io/v1
   235  kind: NetworkPolicy
   236  metadata:
   237    name: "np1"
   238    namespace: "foo-bar-baz"
   239    labels:
   240  spec:
   241    podSelector:
   242      matchLabels:
   243    ingress:
   244    - from: # Allow pods in this namespace to talk to each other
   245      - podSelector: {}
   246    - from: # Allow pods in the namespaces bearing the specified labels to talk to pods in this namespace:
   247      - namespaceSelector:
   248          matchLabels:
   249            foo: "bar"
   250            hello: "world"`,
   251  			expectedURL: "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/",
   252  		},
   253  		{
   254  			name:      "resourcequota",
   255  			namespace: "foo-bar-baz",
   256  			resource: &Resource{
   257  				Kind: "resourcequota",
   258  				Value: NewResourceQuota(&runtime.ResourceQuota{
   259  					Name:      "rq1",
   260  					Namespace: "foo-bar-baz",
   261  					Requests: &runtime.Resources{
   262  						CPU:  1000,
   263  						Mem:  2000,
   264  						Disk: 3000,
   265  					},
   266  					Limits: &runtime.Resources{
   267  						CPU:  4000,
   268  						Mem:  5000,
   269  						Disk: 6000,
   270  					},
   271  				}),
   272  			},
   273  			expectedBody: `
   274  apiVersion: v1
   275  kind: ResourceQuota
   276  metadata:
   277    name: "rq1"
   278    namespace: "foo-bar-baz"
   279    labels:
   280  spec:
   281    hard:
   282      limits.memory: 5000Mi
   283      limits.cpu: 4000m
   284      limits.ephemeral-storage: 6000Mi
   285      requests.memory: 2000Mi
   286      requests.cpu: 1000m
   287      requests.ephemeral-storage: 3000Mi
   288  `,
   289  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/",
   290  		},
   291  	}
   292  	for _, tc := range tcs {
   293  		t.Run(tc.name, func(t *testing.T) {
   294  			rt := fakes.FakeRoundTripper{}
   295  			httpClient := &http.Client{
   296  				Transport: &rt,
   297  			}
   298  			client := &client{
   299  				opts: &api.Options{
   300  					Host:      "example.com",
   301  					Namespace: DefaultNamespace,
   302  					Client:    httpClient,
   303  				},
   304  			}
   305  			rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil)
   306  
   307  			g := NewWithT(t)
   308  			err := client.Create(tc.resource, CreateNamespace(tc.namespace))
   309  			g.Expect(err).ToNot(HaveOccurred())
   310  			req := rt.RoundTripArgsForCall(0)
   311  			g.Expect(req.URL.String()).To(Equal(tc.expectedURL))
   312  			b, err := ioutil.ReadAll(req.Body)
   313  			g.Expect(err).ToNot(HaveOccurred())
   314  			g.Expect(strings.TrimSpace(string(b))).To(Equal(strings.TrimSpace(tc.expectedBody)))
   315  			g.Expect(req.Method).To(Equal(http.MethodPost))
   316  			g.Expect(req.Header.Get("Content-Type")).To(Equal("application/yaml"))
   317  		})
   318  	}
   319  
   320  }
   321  
   322  func TestUpdate(t *testing.T) {
   323  	tcs := []struct {
   324  		name         string
   325  		namespace    string
   326  		resource     *Resource
   327  		expectedBody string
   328  		expectedURL  string
   329  		expectedErr  bool
   330  	}{
   331  		{
   332  			name:      "deployment",
   333  			namespace: "foo-bar-baz",
   334  			resource: NewDeployment(&runtime.Service{
   335  				Name:     "svc1",
   336  				Version:  "latest",
   337  				Source:   "source",
   338  				Metadata: map[string]string{"foo": "bar", "hello": "world"},
   339  			}, &runtime.CreateOptions{
   340  				Command:   []string{"cmd", "arg"},
   341  				Args:      []string{"arg1", "arg2"},
   342  				Env:       []string{"FOO=BAR", "HELLO=WORLD"},
   343  				Type:      "service",
   344  				Image:     "DefaultImage",
   345  				Namespace: DefaultNamespace,
   346  				Resources: &runtime.Resources{
   347  					CPU:  200,
   348  					Mem:  200,
   349  					Disk: 2000,
   350  				},
   351  				ServiceAccount: "serviceAcc",
   352  			},
   353  			),
   354  
   355  			expectedBody: `{"metadata":{"name":"svc1-latest","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"},"annotations":{"foo":"bar","hello":"world","name":"svc1","source":"source","version":"latest"}},"spec":{"replicas":1,"selector":{"matchLabels":{"micro":"service","name":"svc1","version":"latest"}},"template":{"metadata":{"name":"svc1-latest","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"},"annotations":{"foo":"bar","hello":"world","name":"svc1","source":"source","version":"latest"}},"spec":{"containers":[{"name":"svc1","image":"DefaultImage","env":[{"name":"FOO","value":"BAR"},{"name":"HELLO","value":"WORLD"}],"command":["cmd","arg"],"args":["arg1","arg2"],"ports":[{"name":"service-port","containerPort":8080}],"readinessProbe":{"tcpSocket":{"port":8080},"periodSeconds":10,"initialDelaySeconds":10},"resources":{"limits":{"memory":"200Mi","cpu":"200m","ephemeral-storage":"2000Mi"}}}],"serviceAccountName":"serviceAcc"}}}}`,
   356  			expectedURL:  `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/svc1-latest`,
   357  		},
   358  		{
   359  			name:      "service",
   360  			namespace: "foo-bar-baz",
   361  			resource: NewService(&runtime.Service{
   362  				Name:     "svc1",
   363  				Version:  "latest",
   364  				Source:   "source",
   365  				Metadata: map[string]string{"foo": "bar", "hello": "world"},
   366  			}, &runtime.CreateOptions{
   367  				Command:   []string{"cmd", "arg"},
   368  				Args:      []string{"arg1", "arg2"},
   369  				Env:       []string{"FOO=BAR", "HELLO=WORLD"},
   370  				Type:      "service",
   371  				Image:     "DefaultImage",
   372  				Namespace: DefaultNamespace,
   373  				Resources: &runtime.Resources{
   374  					CPU:  200,
   375  					Mem:  200,
   376  					Disk: 2000,
   377  				},
   378  				ServiceAccount: "serviceAcc",
   379  			}),
   380  			expectedBody: `{"metadata":{"name":"svc1","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"}},"spec":{"clusterIP":"","type":"ClusterIP","selector":{"micro":"service","name":"svc1","version":"latest"},"ports":[{"name":"service-port","port":8080}]}}`,
   381  			expectedURL:  "example.com/api/v1/namespaces/foo-bar-baz/services/svc1",
   382  		},
   383  		{
   384  			name:      "secrets",
   385  			namespace: "foo-bar-baz",
   386  			resource: &Resource{
   387  				Kind: "secret",
   388  				Name: "svc1",
   389  				Value: &Secret{
   390  					Type: "Opaque",
   391  					Data: map[string]string{
   392  						"key1": "val1",
   393  						"key2": "val2",
   394  					},
   395  					Metadata: &Metadata{
   396  						Name:      "svc1",
   397  						Namespace: "foo-bar-baz",
   398  					},
   399  				},
   400  			},
   401  			expectedBody: ``,
   402  			expectedURL:  "example.com/api/v1/namespaces/foo-bar-baz/secrets/svc1",
   403  			expectedErr:  true,
   404  		},
   405  		{
   406  			name:      "serviceaccount",
   407  			namespace: "foo-bar-baz",
   408  			resource: &Resource{
   409  				Name: "svcacc",
   410  				Kind: "serviceaccount",
   411  				Value: &ServiceAccount{
   412  					Metadata: &Metadata{
   413  						Name:      "svcacc",
   414  						Namespace: "foo-bar-baz",
   415  					},
   416  					ImagePullSecrets: []ImagePullSecret{
   417  						{
   418  							Name: "pullme",
   419  						},
   420  					},
   421  				},
   422  			},
   423  			expectedBody: ``,
   424  			expectedURL:  "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/svcacc",
   425  			expectedErr:  true,
   426  		},
   427  		{
   428  			name:      "networkpolicy",
   429  			namespace: "foo-bar-baz",
   430  			resource: &Resource{
   431  				Name: "np1",
   432  				Kind: "networkpolicy",
   433  				Value: NewNetworkPolicy("np1", "foo-bar-baz", map[string]string{
   434  					"foo":   "bar",
   435  					"hello": "world",
   436  				})},
   437  			expectedBody: `{"metadata":{"name":"np1","namespace":"foo-bar-baz"},"spec":{"ingress":[{"from":[{"podSelector":{}}]},{"from":[{"namespaceSelector":{"matchLabels":{"foo":"bar","hello":"world"}}}]}],"podSelector":{},"policyTypes":["Ingress"]}}`,
   438  			expectedURL:  "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/np1",
   439  		},
   440  		{
   441  			name:      "resourcequota",
   442  			namespace: "foo-bar-baz",
   443  			resource: &Resource{
   444  				Name: "rq1",
   445  				Kind: "resourcequota",
   446  				Value: NewResourceQuota(&runtime.ResourceQuota{
   447  					Name:      "rq1",
   448  					Namespace: "foo-bar-baz",
   449  					Requests: &runtime.Resources{
   450  						CPU:  1000,
   451  						Mem:  2000,
   452  						Disk: 3000,
   453  					},
   454  					Limits: &runtime.Resources{
   455  						CPU:  4000,
   456  						Mem:  5000,
   457  						Disk: 6000,
   458  					},
   459  				}),
   460  			},
   461  			expectedBody: `{"metadata":{"name":"rq1","namespace":"foo-bar-baz"},"spec":{"hard":{"limits.cpu":"4000m","limits.ephemeral-storage":"6000Mi","limits.memory":"5000Mi","requests.cpu":"1000m","requests.ephemeral-storage":"3000Mi","requests.memory":"2000Mi"}}}`,
   462  			expectedURL:  "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/rq1",
   463  		},
   464  	}
   465  
   466  	for _, tc := range tcs {
   467  		t.Run(tc.name, func(t *testing.T) {
   468  			rt := fakes.FakeRoundTripper{}
   469  			httpClient := &http.Client{
   470  				Transport: &rt,
   471  			}
   472  			client := &client{
   473  				opts: &api.Options{
   474  					Host:      "example.com",
   475  					Namespace: DefaultNamespace,
   476  					Client:    httpClient,
   477  				},
   478  			}
   479  			rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil)
   480  
   481  			g := NewWithT(t)
   482  			err := client.Update(tc.resource, UpdateNamespace(tc.namespace))
   483  			if tc.expectedErr {
   484  				// these ones haven't been implemented yet
   485  				g.Expect(err).To(HaveOccurred())
   486  				return
   487  			}
   488  			g.Expect(err).ToNot(HaveOccurred())
   489  			req := rt.RoundTripArgsForCall(0)
   490  			g.Expect(req.URL.String()).To(Equal(tc.expectedURL))
   491  			b, err := ioutil.ReadAll(req.Body)
   492  			g.Expect(err).ToNot(HaveOccurred())
   493  			g.Expect(strings.TrimSpace(string(b))).To(Equal(strings.TrimSpace(tc.expectedBody)))
   494  			g.Expect(req.Method).To(Equal(http.MethodPatch))
   495  			g.Expect(req.Header.Get("Content-Type")).To(Equal("application/strategic-merge-patch+json"))
   496  		})
   497  	}
   498  }
   499  
   500  func TestDelete(t *testing.T) {
   501  	tcs := []struct {
   502  		name        string
   503  		namespace   string
   504  		resource    *Resource
   505  		expectedURL string
   506  	}{
   507  		{
   508  			name:      "deployment",
   509  			namespace: "foo-bar-baz",
   510  			resource: &Resource{
   511  				Name: "svc1-latest",
   512  				Kind: "deployment",
   513  			},
   514  			expectedURL: `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/svc1-latest`,
   515  		},
   516  		{
   517  			name:      "service",
   518  			namespace: "foo-bar-baz",
   519  			resource: &Resource{
   520  				Name: "svc1",
   521  				Kind: "service",
   522  			},
   523  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/services/svc1",
   524  		},
   525  		{
   526  			name:      "secrets",
   527  			namespace: "foo-bar-baz",
   528  			resource: &Resource{
   529  				Kind: "secret",
   530  				Name: "svc1",
   531  			},
   532  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/secrets/svc1",
   533  		},
   534  		{
   535  			name:      "serviceaccount",
   536  			namespace: "foo-bar-baz",
   537  			resource: &Resource{
   538  				Name: "svcacc",
   539  				Kind: "serviceaccount",
   540  			},
   541  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/svcacc",
   542  		},
   543  		{
   544  			name:      "networkpolicy",
   545  			namespace: "foo-bar-baz",
   546  			resource: &Resource{
   547  				Name: "np1",
   548  				Kind: "networkpolicy",
   549  			},
   550  			expectedURL: "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/np1",
   551  		},
   552  		{
   553  			name:      "resourcequota",
   554  			namespace: "foo-bar-baz",
   555  			resource: &Resource{
   556  				Name: "rq1",
   557  				Kind: "resourcequota",
   558  			},
   559  			expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/rq1",
   560  		},
   561  	}
   562  
   563  	for _, tc := range tcs {
   564  		t.Run(tc.name, func(t *testing.T) {
   565  			rt := fakes.FakeRoundTripper{}
   566  			httpClient := &http.Client{
   567  				Transport: &rt,
   568  			}
   569  			client := &client{
   570  				opts: &api.Options{
   571  					Host:      "example.com",
   572  					Namespace: DefaultNamespace,
   573  					Client:    httpClient,
   574  				},
   575  			}
   576  			rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil)
   577  
   578  			g := NewWithT(t)
   579  			err := client.Delete(tc.resource, DeleteNamespace(tc.namespace))
   580  			g.Expect(err).ToNot(HaveOccurred())
   581  			req := rt.RoundTripArgsForCall(0)
   582  			g.Expect(req.URL.String()).To(Equal(tc.expectedURL))
   583  			g.Expect(req.Method).To(Equal(http.MethodDelete))
   584  		})
   585  	}
   586  
   587  }