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 }