github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/utils/spi/controller.go (about) 1 package spi 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/tls" 7 "fmt" 8 "net/http" 9 "time" 10 11 . "github.com/onsi/gomega" 12 kubeCl "github.com/redhat-appstudio/e2e-tests/pkg/apis/kubernetes" 13 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 14 spi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" 15 v1 "k8s.io/api/core/v1" 16 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/types" 19 "k8s.io/klog" 20 "sigs.k8s.io/controller-runtime/pkg/client" 21 ) 22 23 const ( 24 SPIAccessTokenBindingPrefixName = "e2e-access-token-binding" 25 ) 26 27 type SuiteController struct { 28 *kubeCl.CustomClient 29 } 30 31 func NewSuiteController(kube *kubeCl.CustomClient) (*SuiteController, error) { 32 // Initialize a new SPI controller with just the kube client 33 return &SuiteController{ 34 kube, 35 }, nil 36 } 37 38 // GetSPIAccessTokenBinding returns the requested SPIAccessTokenBinding object 39 func (s *SuiteController) GetSPIAccessTokenBinding(name, namespace string) (*spi.SPIAccessTokenBinding, error) { 40 namespacedName := types.NamespacedName{ 41 Name: name, 42 Namespace: namespace, 43 } 44 45 spiAccessTokenBinding := spi.SPIAccessTokenBinding{ 46 Spec: spi.SPIAccessTokenBindingSpec{}, 47 } 48 err := s.KubeRest().Get(context.TODO(), namespacedName, &spiAccessTokenBinding) 49 if err != nil { 50 return nil, err 51 } 52 return &spiAccessTokenBinding, nil 53 } 54 55 // CreateSPIAccessTokenBinding creates an SPIAccessTokenBinding object 56 func (s *SuiteController) CreateSPIAccessTokenBinding(name, namespace, repoURL, secretName string, secretType v1.SecretType) (*spi.SPIAccessTokenBinding, error) { 57 spiAccessTokenBinding := spi.SPIAccessTokenBinding{ 58 ObjectMeta: metav1.ObjectMeta{ 59 GenerateName: name, 60 Namespace: namespace, 61 }, 62 Spec: spi.SPIAccessTokenBindingSpec{ 63 Permissions: spi.Permissions{ 64 Required: []spi.Permission{ 65 { 66 Type: spi.PermissionTypeReadWrite, 67 Area: spi.PermissionAreaRepository, 68 }, 69 }, 70 }, 71 RepoUrl: repoURL, 72 Secret: spi.SecretSpec{ 73 Name: secretName, 74 Type: secretType, 75 }, 76 }, 77 } 78 err := s.KubeRest().Create(context.TODO(), &spiAccessTokenBinding) 79 if err != nil { 80 return nil, err 81 } 82 return &spiAccessTokenBinding, nil 83 } 84 85 // DeleteSPIAccessTokenBinding deletes an SPIAccessTokenBinding from a given name and namespace 86 func (h *SuiteController) DeleteSPIAccessTokenBinding(name, namespace string) error { 87 application := spi.SPIAccessTokenBinding{ 88 ObjectMeta: metav1.ObjectMeta{ 89 Name: name, 90 Namespace: namespace, 91 }, 92 } 93 return h.KubeRest().Delete(context.TODO(), &application) 94 } 95 96 // GetSPIAccessTokenBinding returns the requested SPIAccessTokenBinding object 97 func (s *SuiteController) GetSPIAccessToken(name, namespace string) (*spi.SPIAccessToken, error) { 98 namespacedName := types.NamespacedName{ 99 Name: name, 100 Namespace: namespace, 101 } 102 103 spiAccessToken := spi.SPIAccessToken{ 104 Spec: spi.SPIAccessTokenSpec{}, 105 } 106 err := s.KubeRest().Get(context.TODO(), namespacedName, &spiAccessToken) 107 if err != nil { 108 return nil, err 109 } 110 return &spiAccessToken, nil 111 } 112 113 // Inject manually access tokens using spi API 114 func (s *SuiteController) InjectManualSPIToken(namespace string, repoUrl string, oauthCredentials string, secretType v1.SecretType, secretName string) string { 115 var spiAccessTokenBinding *spi.SPIAccessTokenBinding 116 117 // Get the token for the current openshift user 118 bearerToken, err := utils.GetOpenshiftToken() 119 Expect(err).NotTo(HaveOccurred()) 120 121 // https://issues.redhat.com/browse/STONE-444. Is not possible to create more than 1 secret per user namespace 122 secret, err := s.KubeInterface().CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) 123 if k8sErrors.IsAlreadyExists(err) { 124 klog.Infof("secret %s already exists", secret.Name) 125 126 return secret.Name 127 } 128 129 spiAccessTokenBinding, err = s.CreateSPIAccessTokenBinding(SPIAccessTokenBindingPrefixName, namespace, repoUrl, secretName, secretType) 130 Expect(err).NotTo(HaveOccurred()) 131 132 Eventually(func() bool { 133 // application info should be stored even after deleting the application in application variable 134 spiAccessTokenBinding, err = s.GetSPIAccessTokenBinding(spiAccessTokenBinding.Name, namespace) 135 136 if err != nil { 137 return false 138 } 139 140 return (spiAccessTokenBinding.Status.Phase == spi.SPIAccessTokenBindingPhaseInjected || spiAccessTokenBinding.Status.Phase == spi.SPIAccessTokenBindingPhaseAwaitingTokenData) 141 }, 2*time.Minute, 100*time.Millisecond).Should(BeTrue(), "SPI controller didn't set SPIAccessTokenBinding to AwaitingTokenData/Injected") 142 143 Eventually(func() bool { 144 // application info should be stored even after deleting the application in application variable 145 spiAccessTokenBinding, err = s.GetSPIAccessTokenBinding(spiAccessTokenBinding.Name, namespace) 146 147 if err != nil { 148 return false 149 } 150 151 return spiAccessTokenBinding.Status.UploadUrl != "" 152 }, 5*time.Minute, 100*time.Millisecond).Should(BeTrue(), "SPI oauth url not set. Please check if spi oauth-config configmap contain all necessary providers for tests.") 153 154 if spiAccessTokenBinding.Status.Phase == spi.SPIAccessTokenBindingPhaseAwaitingTokenData { 155 // If the phase is AwaitingTokenData then manually inject the git token 156 // Get the oauth url and linkedAccessTokenName from the spiaccesstokenbinding resource 157 Expect(err).NotTo(HaveOccurred()) 158 linkedAccessTokenName := spiAccessTokenBinding.Status.LinkedAccessTokenName 159 160 // Before injecting the token, validate that the linkedaccesstoken resource exists, otherwise injecting will return a 404 error code 161 Eventually(func() bool { 162 // application info should be stored even after deleting the application in application variable 163 _, err := s.GetSPIAccessToken(linkedAccessTokenName, namespace) 164 return err == nil 165 }, 1*time.Minute, 100*time.Millisecond).Should(BeTrue(), "SPI controller didn't create the SPIAccessToken") 166 167 // Format for quay.io token injection: `{"access_token":"tokenToInject","username":"redhat-appstudio-qe+redhat_appstudio_qe_bot"}` 168 // Now that the spiaccesstokenbinding is in the AwaitingTokenData phase, inject the GitHub token 169 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 170 req, err := http.NewRequest("POST", spiAccessTokenBinding.Status.UploadUrl, bytes.NewBuffer([]byte(oauthCredentials))) 171 Expect(err).NotTo(HaveOccurred()) 172 req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", string(bearerToken))) 173 req.Header.Set("Content-Type", "application/json") 174 175 client := &http.Client{} 176 resp, err := client.Do(req) 177 Expect(err).NotTo(HaveOccurred()) 178 Expect(resp.StatusCode).Should(Equal(204)) 179 defer resp.Body.Close() 180 181 // Check to see if the token was successfully injected 182 Eventually(func() bool { 183 // application info should be stored even after deleting the application in application variable 184 spiAccessTokenBinding, err = s.GetSPIAccessTokenBinding(spiAccessTokenBinding.Name, namespace) 185 return err == nil && spiAccessTokenBinding.Status.Phase == spi.SPIAccessTokenBindingPhaseInjected 186 }, 1*time.Minute, 100*time.Millisecond).Should(BeTrue(), "SPI controller didn't set SPIAccessTokenBinding to Injected") 187 } 188 return secretName 189 } 190 191 // Remove all tokens from a given repository. Useful when creating a lot of resources and wanting to remove all of them 192 func (h *SuiteController) DeleteAllBindingTokensInASpecificNamespace(namespace string) error { 193 return h.KubeRest().DeleteAllOf(context.TODO(), &spi.SPIAccessTokenBinding{}, client.InNamespace(namespace)) 194 } 195 196 // Remove all tokens from a given repository. Useful when creating a lot of resources and wanting to remove all of them 197 func (h *SuiteController) DeleteAllAccessTokenDataInASpecificNamespace(namespace string) error { 198 return h.KubeRest().DeleteAllOf(context.TODO(), &spi.SPIAccessTokenDataUpdate{}, client.InNamespace(namespace)) 199 } 200 201 // Remove all tokens from a given repository. Useful when creating a lot of resources and wanting to remove all of them 202 func (h *SuiteController) DeleteAllAccessTokensInASpecificNamespace(namespace string) error { 203 return h.KubeRest().DeleteAllOf(context.TODO(), &spi.SPIAccessToken{}, client.InNamespace(namespace)) 204 } 205 206 // Perform http POST call to upload a token at the given upload URL 207 func (h *SuiteController) UploadWithRestEndpoint(uploadURL string, oauthCredentials string, bearerToken string) (int, error) { 208 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 209 req, err := http.NewRequest("POST", uploadURL, bytes.NewBuffer([]byte(oauthCredentials))) 210 if err != nil { 211 return 0, err 212 } 213 req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", string(bearerToken))) 214 req.Header.Set("Content-Type", "application/json") 215 216 client := &http.Client{} 217 resp, err := client.Do(req) 218 if err != nil { 219 return resp.StatusCode, err 220 } 221 defer resp.Body.Close() 222 return resp.StatusCode, nil 223 } 224 225 // UploadWithK8sSecret returns the requested Secret object 226 func (s *SuiteController) UploadWithK8sSecret(secretName, namespace, spiTokenName, providerURL, username, tokenData string) (*v1.Secret, error) { 227 k8sSecret := &v1.Secret{ 228 ObjectMeta: metav1.ObjectMeta{ 229 Namespace: namespace, 230 Name: secretName, 231 Labels: map[string]string{ 232 "spi.appstudio.redhat.com/upload-secret": "token", 233 }, 234 }, 235 Type: "Opaque", 236 StringData: map[string]string{ 237 "tokenData": tokenData, 238 }, 239 } 240 if spiTokenName != "" { 241 k8sSecret.StringData["spiTokenName"] = spiTokenName 242 } 243 if providerURL != "" { 244 k8sSecret.StringData["providerUrl"] = providerURL 245 } 246 if username != "" { 247 k8sSecret.StringData["userName"] = username 248 } 249 250 err := s.KubeRest().Create(context.TODO(), k8sSecret) 251 if err != nil { 252 return nil, err 253 } 254 return k8sSecret, nil 255 } 256 257 // CreateSPIAccessCheck creates a SPIAccessCheck object 258 func (s *SuiteController) CreateSPIAccessCheck(name, namespace, repoURL string) (*spi.SPIAccessCheck, error) { 259 spiAccessCheck := spi.SPIAccessCheck{ 260 ObjectMeta: metav1.ObjectMeta{ 261 GenerateName: name, 262 Namespace: namespace, 263 }, 264 Spec: spi.SPIAccessCheckSpec{RepoUrl: repoURL}, 265 } 266 err := s.KubeRest().Create(context.TODO(), &spiAccessCheck) 267 if err != nil { 268 return nil, err 269 } 270 return &spiAccessCheck, nil 271 } 272 273 // GetSPIAccessCheck returns the requested SPIAccessCheck object 274 func (s *SuiteController) GetSPIAccessCheck(name, namespace string) (*spi.SPIAccessCheck, error) { 275 namespacedName := types.NamespacedName{ 276 Name: name, 277 Namespace: namespace, 278 } 279 280 spiAccessCheck := spi.SPIAccessCheck{ 281 Spec: spi.SPIAccessCheckSpec{}, 282 } 283 err := s.KubeRest().Get(context.TODO(), namespacedName, &spiAccessCheck) 284 if err != nil { 285 return nil, err 286 } 287 return &spiAccessCheck, nil 288 } 289 290 // CreateSPIAccessTokenBindingWithSA creates SPIAccessTokenBinding with secret linked to a service account 291 // There are three ways of linking a secret to a service account: 292 // - Linking a secret to an existing service account 293 // - Linking a secret to an existing service account as image pull secret 294 // - Using a managed service account 295 func (s *SuiteController) CreateSPIAccessTokenBindingWithSA(name, namespace, serviceAccountName, repoURL, secretName string, isImagePullSecret, isManagedServiceAccount bool) (*spi.SPIAccessTokenBinding, error) { 296 spiAccessTokenBinding := spi.SPIAccessTokenBinding{ 297 ObjectMeta: metav1.ObjectMeta{ 298 GenerateName: name, 299 Namespace: namespace, 300 }, 301 Spec: spi.SPIAccessTokenBindingSpec{ 302 Permissions: spi.Permissions{ 303 Required: []spi.Permission{ 304 { 305 Type: spi.PermissionTypeReadWrite, 306 Area: spi.PermissionAreaRepository, 307 }, 308 }, 309 }, 310 RepoUrl: repoURL, 311 Secret: spi.SecretSpec{ 312 Name: secretName, 313 Type: "kubernetes.io/dockerconfigjson", 314 LinkedTo: []spi.SecretLink{ 315 { 316 ServiceAccount: spi.ServiceAccountLink{ 317 Reference: v1.LocalObjectReference{ 318 Name: serviceAccountName, 319 }, 320 }, 321 }, 322 }, 323 }, 324 }, 325 } 326 327 if isImagePullSecret { 328 spiAccessTokenBinding.Spec.Secret.LinkedTo[0].ServiceAccount.As = spi.ServiceAccountLinkTypeImagePullSecret 329 } 330 331 if isManagedServiceAccount { 332 spiAccessTokenBinding.Spec.Secret.Type = "kubernetes.io/basic-auth" 333 spiAccessTokenBinding.Spec.Secret.LinkedTo = []spi.SecretLink{ 334 { 335 ServiceAccount: spi.ServiceAccountLink{ 336 Managed: spi.ManagedServiceAccountSpec{ 337 GenerateName: serviceAccountName, 338 }, 339 }, 340 }, 341 } 342 } 343 344 err := s.KubeRest().Create(context.TODO(), &spiAccessTokenBinding) 345 if err != nil { 346 return nil, err 347 } 348 return &spiAccessTokenBinding, nil 349 } 350 351 // DeleteAllSPIAccessChecksInASpecificNamespace deletes all SPIAccessCheck from a given namespace 352 func (h *SuiteController) DeleteAllAccessChecksInASpecificNamespace(namespace string) error { 353 return h.KubeRest().DeleteAllOf(context.TODO(), &spi.SPIAccessCheck{}, client.InNamespace(namespace)) 354 } 355 356 func (s *SuiteController) CreateSPIFileContentRequest(name, namespace, repoURL, filePath string) (*spi.SPIFileContentRequest, error) { 357 spiFcr := spi.SPIFileContentRequest{ 358 ObjectMeta: metav1.ObjectMeta{ 359 GenerateName: name, 360 Namespace: namespace, 361 }, 362 Spec: spi.SPIFileContentRequestSpec{RepoUrl: repoURL, FilePath: filePath}, 363 } 364 err := s.KubeRest().Create(context.TODO(), &spiFcr) 365 if err != nil { 366 return nil, err 367 } 368 return &spiFcr, nil 369 } 370 371 // GetSPIAccessCheck returns the requested SPIAccessCheck object 372 func (s *SuiteController) GetSPIFileContentRequest(name, namespace string) (*spi.SPIFileContentRequest, error) { 373 namespacedName := types.NamespacedName{ 374 Name: name, 375 Namespace: namespace, 376 } 377 378 spiFcr := spi.SPIFileContentRequest{ 379 Spec: spi.SPIFileContentRequestSpec{}, 380 } 381 err := s.KubeRest().Get(context.TODO(), namespacedName, &spiFcr) 382 if err != nil { 383 return nil, err 384 } 385 return &spiFcr, nil 386 }