github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/step/expose/step_expose_test.go (about)

     1  // +build unit
     2  
     3  package expose_test
     4  
     5  import (
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    10  	"github.com/olli-ai/jx/v2/pkg/cmd/step/expose"
    11  	"github.com/olli-ai/jx/v2/pkg/cmd/testhelpers"
    12  	"github.com/olli-ai/jx/v2/pkg/config"
    13  	"k8s.io/api/extensions/v1beta1"
    14  	"k8s.io/client-go/kubernetes"
    15  
    16  	"github.com/olli-ai/jx/v2/pkg/gits"
    17  	"github.com/olli-ai/jx/v2/pkg/helm"
    18  	resources_test "github.com/olli-ai/jx/v2/pkg/kube/resources/mocks"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	corev1 "k8s.io/api/core/v1"
    22  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  )
    26  
    27  func TestStepExpose(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	type testData struct {
    31  		Dir, Domain, NamespaceSubDomain string
    32  		TLSEnabled                      bool
    33  	}
    34  
    35  	ns := "jx-staging"
    36  	subDomain := "-" + ns + "."
    37  
    38  	tests := []testData{
    39  		{
    40  			Dir:                "test_data/http",
    41  			Domain:             "35.195.192.178.nip.io",
    42  			NamespaceSubDomain: subDomain,
    43  			TLSEnabled:         false,
    44  		},
    45  		{
    46  			Dir:                "test_data/https",
    47  			Domain:             "my-domain.com",
    48  			NamespaceSubDomain: subDomain,
    49  			TLSEnabled:         true,
    50  		},
    51  	}
    52  
    53  	for _, td := range tests {
    54  		commonOpts := opts.NewCommonOptionsWithFactory(nil)
    55  		o := &expose.StepExposeOptions{
    56  			Dir: td.Dir,
    57  		}
    58  		o.CommonOptions = &commonOpts
    59  		o.Namespace = ns
    60  
    61  		testhelpers.ConfigureTestOptionsWithResources(o.CommonOptions,
    62  			[]runtime.Object{},
    63  			[]runtime.Object{},
    64  			gits.NewGitCLI(),
    65  			nil,
    66  			helm.NewHelmCLI("helm", helm.V2, "", true),
    67  			resources_test.NewMockInstaller(),
    68  		)
    69  
    70  		kubeClient, err := o.KubeClient()
    71  		require.NoError(t, err)
    72  
    73  		expectedRequirements := &config.RequirementsConfig{
    74  			Ingress: config.IngressConfig{
    75  				Domain:             td.Domain,
    76  				NamespaceSubDomain: td.NamespaceSubDomain,
    77  				TLS: config.TLSConfig{
    78  					Enabled: td.TLSEnabled,
    79  				},
    80  			},
    81  		}
    82  		ings := []*v1beta1.Ingress{
    83  			{
    84  				ObjectMeta: metav1.ObjectMeta{
    85  					Name: "update-my-ingress",
    86  				},
    87  			},
    88  		}
    89  		svcs := []*corev1.Service{
    90  			{
    91  				ObjectMeta: metav1.ObjectMeta{
    92  					Name: "svc1",
    93  					Labels: map[string]string{
    94  						"fabric8.io/expose": "true",
    95  					},
    96  				},
    97  				Spec: corev1.ServiceSpec{
    98  					Ports: []corev1.ServicePort{
    99  						{
   100  							Name:     "http",
   101  							Protocol: "TCP",
   102  							Port:     80,
   103  						},
   104  					},
   105  				},
   106  			},
   107  			{
   108  				ObjectMeta: metav1.ObjectMeta{
   109  					Name: "update-my-ingress",
   110  					Labels: map[string]string{
   111  						"fabric8.io/expose": "true",
   112  					},
   113  				},
   114  				Spec: corev1.ServiceSpec{
   115  					Ports: []corev1.ServicePort{
   116  						{
   117  							Name:     "http",
   118  							Protocol: "TCP",
   119  							Port:     8080,
   120  						},
   121  					},
   122  				},
   123  			},
   124  			{
   125  				ObjectMeta: metav1.ObjectMeta{
   126  					Name: "dont-expose",
   127  					Labels: map[string]string{
   128  						"fabric8.io/expose": "false",
   129  					},
   130  				},
   131  				Spec: corev1.ServiceSpec{
   132  					Ports: []corev1.ServicePort{
   133  						{
   134  							Name:     "http",
   135  							Protocol: "TCP",
   136  							Port:     80,
   137  						},
   138  					},
   139  				},
   140  			},
   141  			{
   142  				ObjectMeta: metav1.ObjectMeta{
   143  					Name: "no-expose-label",
   144  					Labels: map[string]string{
   145  						"fabric8.io/expose": "false",
   146  					},
   147  				},
   148  				Spec: corev1.ServiceSpec{
   149  					Ports: []corev1.ServicePort{
   150  						{
   151  							Name:     "http",
   152  							Protocol: "TCP",
   153  							Port:     80,
   154  						},
   155  					},
   156  				},
   157  			},
   158  		}
   159  		for _, ing := range ings {
   160  			_, err = kubeClient.ExtensionsV1beta1().Ingresses(ns).Create(ing)
   161  			require.NoError(t, err, "failed to create Ingress %s in namespace %s", ing.Name, ns)
   162  		}
   163  
   164  		for _, svc := range svcs {
   165  			_, err = kubeClient.CoreV1().Services(ns).Create(svc)
   166  			require.NoError(t, err, "failed to create Service %s in namespace %s", svc.Name, ns)
   167  		}
   168  
   169  		err = o.Run()
   170  		assert.NoError(t, err)
   171  
   172  		for _, svc := range svcs {
   173  			if expose.IsExposedService(svc) {
   174  				assertIngressForService(t, kubeClient, ns, svc, expectedRequirements)
   175  			} else {
   176  				assertNoIngressExistsForService(t, kubeClient, ns, svc)
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  func assertIngressForService(t *testing.T, kubeClient kubernetes.Interface, ns string, svc *corev1.Service, expectedRequirements *config.RequirementsConfig) {
   183  	svcName := svc.Name
   184  	ingress, err := kubeClient.ExtensionsV1beta1().Ingresses(ns).Get(svcName, metav1.GetOptions{})
   185  	if assert.NoError(t, err, "finding Ingress %s in namespace %s", svcName, ns) {
   186  		t.Logf("found the Ingress for service %s in namespace %s which should be exposed", svcName, ns)
   187  	}
   188  
   189  	rules := ingress.Spec.Rules
   190  	require.Equal(t, 1, len(rules), "Ingress rule count for Ingress %s", svcName)
   191  	rule := rules[0]
   192  	expectedHost := fmt.Sprintf("%s%s%s", svcName, expectedRequirements.Ingress.NamespaceSubDomain, expectedRequirements.Ingress.Domain)
   193  	if assert.Equal(t, expectedHost, rule.Host, "host for Ingress %s in namespace %s", svcName, ns) {
   194  		t.Logf("Ingress %s is at the correct host %s", svcName, expectedHost)
   195  	}
   196  	assert.True(t, rule.HTTP != nil && len(rule.HTTP.Paths) == 1, "has a HTTP path for Ingress %s in namespace %s", svcName, ns)
   197  
   198  	path := rule.HTTP.Paths[0]
   199  	if assert.Equal(t, svcName, path.Backend.ServiceName, "has a HTTP path backend service name for Ingress %s in namespace %s", svcName, ns) {
   200  		t.Logf("Ingress %s is using the correct backend service", svcName)
   201  	}
   202  
   203  	servicePort := svc.Spec.Ports[0].Port
   204  	if assert.Equal(t, servicePort, path.Backend.ServicePort.IntVal, "the exposed service port on the Ingress %s in namespace %s", svcName, ns) {
   205  		t.Logf("Ingress %s is at the correct service port %d", svcName, int(servicePort))
   206  	}
   207  
   208  	scheme := "http://"
   209  	if expectedRequirements.Ingress.TLS.Enabled {
   210  		scheme = "https://"
   211  	}
   212  	expectedURL := scheme + expectedHost
   213  	actualSvc, err := kubeClient.CoreV1().Services(ns).Get(svcName, metav1.GetOptions{})
   214  	require.NoError(t, err, "could not load Service %s", svcName)
   215  
   216  	actualURL := actualSvc.Annotations[expose.ExposeAnnotationKey]
   217  	if assert.Equal(t, expectedURL, actualURL, "the exposed service URL was not annotated correctly on the Service %s in namespace %s", svcName, ns) {
   218  		t.Logf("added the exposed URL %s to the Service %s using the annotation %s", actualURL, svcName, expose.ExposeAnnotationKey)
   219  	}
   220  
   221  	if expectedRequirements.Ingress.TLS.Enabled {
   222  		tlss := ingress.Spec.TLS
   223  		require.Equal(t, 1, len(tlss), "Ingress TLS count for Ingress %s", svcName)
   224  		tls := tlss[0]
   225  		t.Logf("got TLS secret name %s for Ingress %s", tls.SecretName, ingress.Name)
   226  
   227  		require.Equal(t, 1, len(tls.Hosts), "Ingress TLS host should be defined for Ingress %s", svcName)
   228  		host := tls.Hosts[0]
   229  		require.Equal(t, expectedHost, host, "Ingress TLS host for Ingress %s", svcName)
   230  	}
   231  }
   232  
   233  func assertNoIngressExistsForService(t *testing.T, kubeClient kubernetes.Interface, ns string, svc *corev1.Service) {
   234  	svcName := svc.Name
   235  	_, err := kubeClient.ExtensionsV1beta1().Ingresses(ns).Get(svcName, metav1.GetOptions{})
   236  	notExposed := err != nil && apierrors.IsNotFound(err)
   237  	if assert.True(t, notExposed, "we should not have around Ingress %s in namespace %s as the service is not meant to be exposed. error: %v", svcName, ns, err) {
   238  		t.Logf("did not create an Ingress for service %s in namespace %s which is not meant to be exposed", svcName, ns)
   239  	}
   240  }