github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/argocd.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package pkg
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/go-retryablehttp"
    13  	"github.com/onsi/gomega"
    14  	"github.com/verrazzano/verrazzano/pkg/constants"
    15  	"github.com/verrazzano/verrazzano/pkg/httputil"
    16  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    17  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    18  	"go.uber.org/zap"
    19  	corev1 "k8s.io/api/core/v1"
    20  )
    21  
    22  const (
    23  	argoCdHelidonApplicationFile = "tests/e2e/config/scripts/hello-helidon-argocd-application.yaml"
    24  )
    25  
    26  // VerifyArgoCDAccess verifies that Argocd is accessible.
    27  func VerifyArgoCDAccess(log *zap.SugaredLogger) error {
    28  	var err error
    29  
    30  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    31  	api := EventuallyGetAPIEndpoint(kubeconfigPath)
    32  	argocdURL := EventuallyGetURLForIngress(log, api, constants.ArgoCDNamespace, "argocd-server", "https")
    33  	httpClient := EventuallyVerrazzanoRetryableHTTPClient()
    34  	var httpResponse *HTTPResponse
    35  
    36  	gomega.Eventually(func() (*HTTPResponse, error) {
    37  		httpResponse, err = GetWebPageWithClient(httpClient, argocdURL, "")
    38  		return httpResponse, err
    39  	}, waitTimeout, pollingInterval).Should(HasStatus(http.StatusOK))
    40  
    41  	gomega.Expect(CheckNoServerHeader(httpResponse)).To(gomega.BeTrue(), "Found unexpected server header in response")
    42  	return nil
    43  }
    44  
    45  func VerifyArgoCDApplicationAccess(log *zap.SugaredLogger) error {
    46  	var err error
    47  
    48  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
    49  	if err != nil {
    50  		return err
    51  	}
    52  	argocdAdminPassword, err := eventuallyGetArgocdAdminPassword(log)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	httpClient, err := GetVerrazzanoHTTPClient(kubeConfigPath)
    57  	if err != nil {
    58  		log.Error(fmt.Sprintf("Error getting argocd admin password: %v", err))
    59  		return err
    60  	}
    61  
    62  	api := EventuallyGetAPIEndpoint(kubeConfigPath)
    63  	argocdURL := EventuallyGetURLForIngress(log, api, constants.ArgoCDNamespace, "argocd-server", "https")
    64  
    65  	token, err := getArgoCDUserToken(log, argocdURL, "admin", string(argocdAdminPassword), httpClient)
    66  	if err != nil {
    67  		log.Error(fmt.Sprintf("Error getting user token from Argocd: %v", err))
    68  		return err
    69  	}
    70  	var emptyList bool
    71  	gomega.Eventually(func() (bool, error) {
    72  		contains, err := GetApplicationsWithClient(log, argocdURL, token)
    73  		emptyList = contains
    74  		return emptyList, err
    75  	}, waitTimeout, pollingInterval).Should(gomega.BeTrue())
    76  
    77  	gomega.Expect(emptyList).To(gomega.BeTrue(), "Argocd UI is accessible and no applications are deployed")
    78  	return nil
    79  }
    80  
    81  func eventuallyGetArgocdAdminPassword(log *zap.SugaredLogger) (string, error) {
    82  	var err error
    83  	var secret *corev1.Secret
    84  	gomega.Eventually(func() error {
    85  		secret, err = GetSecret(constants.ArgoCDNamespace, "argocd-initial-admin-secret")
    86  		if err != nil {
    87  			log.Error(fmt.Sprintf("Error getting argocd-initial-admin-secret, retrying: %v", err))
    88  		}
    89  		return err
    90  	}, waitTimeout, pollingInterval).Should(gomega.BeNil())
    91  
    92  	if secret == nil {
    93  		return "", fmt.Errorf("Unable to get argocd admin secret")
    94  	}
    95  
    96  	var argocdAdminPassword []byte
    97  	var ok bool
    98  	if argocdAdminPassword, ok = secret.Data["password"]; !ok {
    99  		return "", fmt.Errorf("Error getting argocd admin credentials")
   100  	}
   101  
   102  	return string(argocdAdminPassword), nil
   103  }
   104  
   105  func getArgoCDUserToken(log *zap.SugaredLogger, argoCDURL string, username string, password string, httpClient *retryablehttp.Client) (string, error) {
   106  	argoCDLoginURL := fmt.Sprintf("%s/%s", argoCDURL, "api/v1/session")
   107  	payload := `{"Username": "` + username + `", "Password": "` + password + `"}`
   108  	response, err := httpClient.Post(argoCDLoginURL, "application/json", strings.NewReader(payload))
   109  	if err != nil {
   110  		log.Error(fmt.Sprintf("Error getting argocd admin token: %v", err))
   111  		return "", err
   112  	}
   113  
   114  	err = httputil.ValidateResponseCode(response, http.StatusOK)
   115  	if err != nil {
   116  		log.Errorf("Invalid response code when fetching argocd token: %v", err)
   117  		return "", err
   118  	}
   119  
   120  	defer response.Body.Close()
   121  
   122  	// extract the response body
   123  	body, err := io.ReadAll(response.Body)
   124  	if err != nil {
   125  		log.Errorf("Failed to read argocd token response: %v", err)
   126  		return "", err
   127  	}
   128  
   129  	token, err := httputil.ExtractFieldFromResponseBodyOrReturnError(string(body), "token", "unable to find token in Argocd response")
   130  	if err != nil {
   131  		log.Errorf("Failed to extra token from argocd response: %v", err)
   132  		return "", err
   133  	}
   134  
   135  	return token, nil
   136  }
   137  
   138  // GetApplicationsWithClient returns true if the user is able to access the applications page post Argo CD install
   139  func GetApplicationsWithClient(log *zap.SugaredLogger, argoCDURL string, token string) (bool, error) {
   140  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
   141  	if err != nil {
   142  		return false, err
   143  	}
   144  
   145  	httpClient, err := GetVerrazzanoHTTPClient(kubeConfigPath)
   146  	if err != nil {
   147  		log.Error(fmt.Sprintf("Error getting argocd admin password: %v", err))
   148  		return false, err
   149  	}
   150  
   151  	argoCDLoginURL := fmt.Sprintf("%s/%s", argoCDURL, "api/v1/applications")
   152  	req, err := retryablehttp.NewRequest("GET", argoCDLoginURL, nil)
   153  	if err != nil {
   154  		log.Error("Unexpected error while creating new request=%v", err)
   155  		return false, err
   156  	}
   157  	var bearer = "Bearer " + token
   158  
   159  	req.Header.Add("Authorization", bearer)
   160  	response, err := httpClient.Do(req)
   161  	if err != nil {
   162  		return false, err
   163  	}
   164  
   165  	err = httputil.ValidateResponseCode(response, http.StatusOK)
   166  	if err != nil {
   167  		log.Errorf("Invalid response code when fetching argocd token: %v", err)
   168  		return false, err
   169  	}
   170  
   171  	defer response.Body.Close()
   172  
   173  	// extract the response body
   174  	body, err := io.ReadAll(response.Body)
   175  	if err != nil {
   176  		log.Errorf("Failed to read argocd  response: %v", err)
   177  		return false, err
   178  	}
   179  
   180  	token, err = httputil.ExtractFieldFromResponseBodyOrReturnError(string(body), "metadata", "unable to find metadata in Argocd response")
   181  	if err != nil {
   182  		log.Errorf("Failed to extract token from argocd response: %v", err)
   183  		return false, err
   184  	}
   185  
   186  	exists := strings.Contains(token, "resourceVersion")
   187  	return exists, nil
   188  
   189  }
   190  
   191  // CreateArgoCDGitApplication creates an application in Argo CD by connecting to the Git repo
   192  // Applies the Argo CD Application to the kubernetes cluster
   193  func CreateArgoCDGitApplication() error {
   194  	Log(Info, "Create Argo CD Application Project")
   195  	gomega.Eventually(func() error {
   196  		file, err := FindTestDataFile(argoCdHelidonApplicationFile)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, "argocd")
   201  	}, helidonWaitTimeout, helidonPollingInterval).ShouldNot(gomega.HaveOccurred(), "Failed to create Argo CD Application Project file")
   202  
   203  	return nil
   204  }
   205  
   206  // This function retrieves the ArgoCD password to log into rancher, based on the provided name and namespace of a secret that holds this information
   207  func RetrieveArgoCDPassword(namespace, name string) (string, error) {
   208  	s, err := GetSecret(namespace, name)
   209  	if err != nil {
   210  		Log(Error, fmt.Sprintf("Failed to get secret %s in namespace %s with error: %v", name, namespace, err))
   211  		return "", err
   212  	}
   213  	argoCDPasswordForSecret, ok := s.Data["password"]
   214  	if !ok {
   215  		Log(Error, fmt.Sprintf("Failed to find password value in ArgoCD secret %s in namespace %s", name, namespace))
   216  		return "", fmt.Errorf("Failed to find password value in ArgoCD secret %s in namespace %s", name, namespace)
   217  	}
   218  	return string(argoCDPasswordForSecret), nil
   219  }