github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/integration_test/itest/namespace.go (about)

     1  package itest
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/datawire/dlib/dlog"
    17  	"github.com/telepresenceio/telepresence/v2/pkg/dos"
    18  )
    19  
    20  type NamespacePair interface {
    21  	Harness
    22  	ApplyApp(ctx context.Context, name, workload string)
    23  	ApplyEchoService(ctx context.Context, name string, port int)
    24  	ApplyTemplate(ctx context.Context, path string, values any)
    25  	DeleteTemplate(ctx context.Context, path string, values any)
    26  	AppNamespace() string
    27  	TelepresenceConnect(ctx context.Context, args ...string) string
    28  	DeleteSvcAndWorkload(ctx context.Context, workload, name string)
    29  	Kubectl(ctx context.Context, args ...string) error
    30  	KubectlOk(ctx context.Context, args ...string) string
    31  	KubectlOut(ctx context.Context, args ...string) (string, error)
    32  	ManagerNamespace() string
    33  	RollbackTM(ctx context.Context)
    34  	RolloutStatusWait(ctx context.Context, workload string) error
    35  }
    36  
    37  type Namespaces struct {
    38  	Namespace         string   `json:"namespace,omitempty"`
    39  	ManagedNamespaces []string `json:"managedNamespaces,omitempty"`
    40  }
    41  
    42  func (n *Namespaces) HelmString() string {
    43  	var sb strings.Builder
    44  	sb.WriteByte('{')
    45  	sb.WriteString(n.Namespace)
    46  	for _, m := range n.ManagedNamespaces {
    47  		if m != n.Namespace {
    48  			sb.WriteByte(',')
    49  			sb.WriteString(m)
    50  		}
    51  	}
    52  	sb.WriteByte('}')
    53  	return sb.String()
    54  }
    55  
    56  func (n *Namespaces) UniqueList() []string {
    57  	ul := make([]string, 0, len(n.ManagedNamespaces)+1)
    58  	ul = append(ul, n.Namespace)
    59  	for _, m := range n.ManagedNamespaces {
    60  		if m != n.Namespace {
    61  			ul = append(ul, m)
    62  		}
    63  	}
    64  	return ul
    65  }
    66  
    67  type namespacesContextKey struct{}
    68  
    69  func WithNamespaces(ctx context.Context, namespaces *Namespaces) context.Context {
    70  	return context.WithValue(ctx, namespacesContextKey{}, namespaces)
    71  }
    72  
    73  func GetNamespaces(ctx context.Context) *Namespaces {
    74  	if namespaces, ok := ctx.Value(namespacesContextKey{}).(*Namespaces); ok {
    75  		return namespaces
    76  	}
    77  	return nil
    78  }
    79  
    80  // The namespaceSuite has no tests. It's sole purpose is to create and destroy the namespaces and
    81  // any non-namespaced resources that we, ourselves, make nsPair specific, such as the
    82  // mutating webhook configuration for the traffic-agent injection.
    83  type nsPair struct {
    84  	Harness
    85  	Namespaces
    86  }
    87  
    88  // TelepresenceConnect connects using the AppNamespace and ManagerNamespace.
    89  func (s *nsPair) TelepresenceConnect(ctx context.Context, args ...string) string {
    90  	return TelepresenceOk(ctx,
    91  		append(
    92  			[]string{"connect", "--namespace", s.AppNamespace(), "--manager-namespace", s.ManagerNamespace()},
    93  			args...)...)
    94  }
    95  
    96  func WithNamespacePair(ctx context.Context, suffix string, f func(NamespacePair)) {
    97  	s := &nsPair{}
    98  	var namespace string
    99  	namespace, s.Namespace = AppAndMgrNSName(suffix)
   100  	s.ManagedNamespaces = []string{namespace}
   101  	getT(ctx).Run(fmt.Sprintf("Test_Namespaces_%s", suffix), func(t *testing.T) {
   102  		ctx = WithT(ctx, t)
   103  		ctx = WithUser(ctx, s.Namespace+":"+TestUser)
   104  		ctx = WithNamespaces(ctx, &s.Namespaces)
   105  		s.Harness = NewContextHarness(ctx)
   106  		s.PushHarness(ctx, s.setup, s.tearDown)
   107  		defer s.PopHarness()
   108  		f(s)
   109  	})
   110  }
   111  
   112  const purposeLabel = "tp-cli-testing"
   113  
   114  func (s *nsPair) setup(ctx context.Context) bool {
   115  	CreateNamespaces(ctx, s.AppNamespace(), s.Namespace)
   116  	t := getT(ctx)
   117  	if t.Failed() {
   118  		return false
   119  	}
   120  	err := Kubectl(ctx, s.Namespace, "apply", "-f", filepath.Join(GetOSSRoot(ctx), "testdata", "k8s", "client_sa.yaml"))
   121  	assert.NoError(t, err, "failed to create connect ServiceAccount")
   122  	return !t.Failed()
   123  }
   124  
   125  func AppAndMgrNSName(suffix string) (appNS, mgrNS string) {
   126  	mgrNS = fmt.Sprintf("ambassador-%s", suffix)
   127  	appNS = fmt.Sprintf("telepresence-%s", suffix)
   128  	return
   129  }
   130  
   131  func (s *nsPair) tearDown(ctx context.Context) {
   132  	wg := sync.WaitGroup{}
   133  	wg.Add(1)
   134  	go func() {
   135  		defer wg.Done()
   136  		DeleteNamespaces(ctx, s.AppNamespace(), s.Namespace)
   137  	}()
   138  	wg.Add(1)
   139  	go func() {
   140  		defer wg.Done()
   141  		_ = Kubectl(ctx, "", "delete", "--wait=false", "mutatingwebhookconfiguration", "agent-injector-webhook-"+s.Namespace)
   142  	}()
   143  	wg.Wait()
   144  }
   145  
   146  func (s *nsPair) RollbackTM(ctx context.Context) {
   147  	ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
   148  	defer cancel()
   149  	err := Command(ctx, "helm", "rollback", "--no-hooks", "--wait", "--namespace", s.ManagerNamespace(), "traffic-manager").Run()
   150  	t := getT(ctx)
   151  	require.NoError(t, err)
   152  	require.NoError(t, RolloutStatusWait(ctx, s.Namespace, "deploy/traffic-manager"))
   153  	s.CapturePodLogs(ctx, "traffic-manager", "", s.Namespace)
   154  }
   155  
   156  func (s *nsPair) AppNamespace() string {
   157  	return s.ManagedNamespaces[0]
   158  }
   159  
   160  func (s *nsPair) ManagerNamespace() string {
   161  	return s.Namespace
   162  }
   163  
   164  func (s *nsPair) ApplyEchoService(ctx context.Context, name string, port int) {
   165  	getT(ctx).Helper()
   166  	ApplyEchoService(ctx, name, s.AppNamespace(), port)
   167  }
   168  
   169  // ApplyApp calls kubectl apply -n <namespace> -f on the given app + .yaml found in testdata/k8s relative
   170  // to the directory returned by GetCurrentDirectory.
   171  func (s *nsPair) ApplyApp(ctx context.Context, name, workload string) {
   172  	getT(ctx).Helper()
   173  	ApplyApp(ctx, name, s.AppNamespace(), workload)
   174  }
   175  
   176  func (s *nsPair) RolloutStatusWait(ctx context.Context, workload string) error {
   177  	return RolloutStatusWait(ctx, s.AppNamespace(), workload)
   178  }
   179  
   180  func (s *nsPair) DeleteSvcAndWorkload(ctx context.Context, workload, name string) {
   181  	getT(ctx).Helper()
   182  	DeleteSvcAndWorkload(ctx, workload, name, s.AppNamespace())
   183  }
   184  
   185  func (s *nsPair) ApplyTemplate(ctx context.Context, path string, values any) {
   186  	s.doWithTemplate(ctx, "apply", path, values)
   187  }
   188  
   189  func (s *nsPair) DeleteTemplate(ctx context.Context, path string, values any) {
   190  	yml, err := ReadTemplate(ctx, path, values)
   191  	require.NoError(getT(ctx), err)
   192  	if err = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(yml)), "apply", "-f", "-"); err != nil {
   193  		dlog.Errorf(ctx, "unable to apply %q", string(yml))
   194  		getT(ctx).Fatal(err)
   195  	}
   196  }
   197  
   198  func (s *nsPair) doWithTemplate(ctx context.Context, action, path string, values any) {
   199  	yml, err := ReadTemplate(ctx, path, values)
   200  	require.NoError(getT(ctx), err)
   201  	if err = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(yml)), action, "-f", "-"); err != nil {
   202  		dlog.Errorf(ctx, "unable to %s %q", action, string(yml))
   203  		getT(ctx).Fatal(err)
   204  	}
   205  }
   206  
   207  // Kubectl runs kubectl with the default context and the application namespace.
   208  func (s *nsPair) Kubectl(ctx context.Context, args ...string) error {
   209  	getT(ctx).Helper()
   210  	return Kubectl(ctx, s.AppNamespace(), args...)
   211  }
   212  
   213  // KubectlOk runs kubectl with the default context and the application namespace and returns its combined output
   214  // and fails if an error occurred.
   215  func (s *nsPair) KubectlOk(ctx context.Context, args ...string) string {
   216  	out, err := KubectlOut(ctx, s.AppNamespace(), args...)
   217  	require.NoError(getT(ctx), err)
   218  	return out
   219  }
   220  
   221  // KubectlOut runs kubectl with the default context and the application namespace and returns its combined output.
   222  func (s *nsPair) KubectlOut(ctx context.Context, args ...string) (string, error) {
   223  	getT(ctx).Helper()
   224  	return KubectlOut(ctx, s.AppNamespace(), args...)
   225  }