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  }