github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/utils/util.go (about)

     1  package utils
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/tls"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"os/exec"
    16  	"strings"
    17  	"time"
    18  
    19  	corev1 "k8s.io/api/core/v1"
    20  	"k8s.io/apimachinery/pkg/runtime"
    21  	"k8s.io/apimachinery/pkg/types"
    22  	"k8s.io/apimachinery/pkg/util/wait"
    23  	"k8s.io/client-go/kubernetes"
    24  	"knative.dev/pkg/apis"
    25  
    26  	devfilePkg "github.com/devfile/library/pkg/devfile"
    27  	"github.com/devfile/library/pkg/devfile/parser"
    28  	"github.com/devfile/library/pkg/devfile/parser/data"
    29  	"github.com/devfile/library/pkg/util"
    30  	"github.com/google/go-containerregistry/pkg/authn"
    31  	"github.com/google/go-containerregistry/pkg/name"
    32  	remoteimg "github.com/google/go-containerregistry/pkg/v1/remote"
    33  	"github.com/mitchellh/go-homedir"
    34  	buildservice "github.com/redhat-appstudio/build-service/api/v1alpha1"
    35  	"github.com/redhat-appstudio/e2e-tests/pkg/constants"
    36  	"github.com/tektoncd/cli/pkg/bundle"
    37  	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
    38  	"github.com/tektoncd/pipeline/pkg/remote/oci"
    39  	"k8s.io/klog/v2"
    40  
    41  	"sigs.k8s.io/yaml"
    42  
    43  	crclient "sigs.k8s.io/controller-runtime/pkg/client"
    44  )
    45  
    46  type FailedPipelineRunDetails struct {
    47  	FailedTaskRunName   string
    48  	PodName             string
    49  	FailedContainerName string
    50  }
    51  
    52  // CheckIfEnvironmentExists return true/false if the environment variable exists
    53  func CheckIfEnvironmentExists(env string) bool {
    54  	_, exist := os.LookupEnv(env)
    55  	return exist
    56  }
    57  
    58  // Retrieve an environment variable. If will not exists will be used a default value
    59  func GetEnv(key, defaultVal string) string {
    60  	if val := os.Getenv(key); val != "" {
    61  		return val
    62  	}
    63  	return defaultVal
    64  }
    65  
    66  // Retrieve an environment variable. If it doesn't exist, returns result of a call to `defaultFunc`.
    67  func GetEnvOrFunc(key string, defaultFunc func() (string, error)) (string, error) {
    68  	if val := os.Getenv(key); val != "" {
    69  		return val, nil
    70  	}
    71  	return defaultFunc()
    72  }
    73  
    74  /*
    75  Right now DevFile status in HAS is a string:
    76  metadata:
    77  
    78  	attributes:
    79  		appModelRepository.url: https://github.com/redhat-appstudio-qe/pet-clinic-application-service-establish-danger
    80  		gitOpsRepository.url: https://github.com/redhat-appstudio-qe/pet-clinic-application-service-establish-danger
    81  	name: pet-clinic
    82  	schemaVersion: 2.1.0
    83  
    84  The ObtainGitUrlFromDevfile extract from the string the git url associated with a application
    85  */
    86  func ObtainGitOpsRepositoryName(devfileStatus string) string {
    87  	appDevfile, err := ParseDevfileModel(devfileStatus)
    88  	if err != nil {
    89  		err = fmt.Errorf("error parsing devfile: %v", err)
    90  	}
    91  	// Get the devfile attributes from the parsed object
    92  	devfileAttributes := appDevfile.GetMetadata().Attributes
    93  	gitOpsRepository := devfileAttributes.GetString("gitOpsRepository.url", &err)
    94  	parseUrl, err := url.Parse(gitOpsRepository)
    95  	if err != nil {
    96  		err = fmt.Errorf("fatal: %v", err)
    97  	}
    98  	repoParsed := strings.Split(parseUrl.Path, "/")
    99  
   100  	return repoParsed[len(repoParsed)-1]
   101  }
   102  
   103  func ObtainGitOpsRepositoryUrl(devfileStatus string) string {
   104  	appDevfile, err := ParseDevfileModel(devfileStatus)
   105  	if err != nil {
   106  		err = fmt.Errorf("error parsing devfile: %v", err)
   107  	}
   108  	// Get the devfile attributes from the parsed object
   109  	devfileAttributes := appDevfile.GetMetadata().Attributes
   110  	gitOpsRepository := devfileAttributes.GetString("gitOpsRepository.url", &err)
   111  
   112  	return gitOpsRepository
   113  }
   114  
   115  func GetQuayIOOrganization() string {
   116  	return GetEnv(constants.QUAY_E2E_ORGANIZATION_ENV, "redhat-appstudio-qe")
   117  }
   118  
   119  func IsPrivateHostname(url string) bool {
   120  	// https://www.ibm.com/docs/en/networkmanager/4.2.0?topic=translation-private-address-ranges
   121  	privateIPAddressPrefixes := []string{"10.", "172.1", "172.2", "172.3", "192.168"}
   122  	addr, err := net.LookupIP(url)
   123  	if err != nil {
   124  		klog.Infof("Unknown host: %v", err)
   125  		return true
   126  	}
   127  
   128  	ip := addr[0]
   129  	for _, ipPrefix := range privateIPAddressPrefixes {
   130  		if strings.HasPrefix(ip.String(), ipPrefix) {
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }
   136  
   137  func GetOpenshiftToken() (token string, err error) {
   138  	// Get the token for the current openshift user
   139  	tokenBytes, err := exec.Command("oc", "whoami", "--show-token").Output()
   140  	if err != nil {
   141  		return "", fmt.Errorf("error obtaining oc token %s", err.Error())
   142  	}
   143  	return strings.TrimSuffix(string(tokenBytes), "\n"), nil
   144  }
   145  
   146  func GetFailedPipelineRunDetails(c crclient.Client, pipelineRun *v1beta1.PipelineRun) (*FailedPipelineRunDetails, error) {
   147  	d := &FailedPipelineRunDetails{}
   148  	for _, chr := range pipelineRun.Status.PipelineRunStatusFields.ChildReferences {
   149  		taskRun := &v1beta1.TaskRun{}
   150  		taskRunKey := types.NamespacedName{Namespace: pipelineRun.Namespace, Name: chr.Name}
   151  		if err := c.Get(context.TODO(), taskRunKey, taskRun); err != nil {
   152  			return nil, fmt.Errorf("failed to get details for PR %s: %+v", pipelineRun.GetName(), err)
   153  		}
   154  		for _, c := range taskRun.Status.Conditions {
   155  			if c.Reason == "Failed" {
   156  				d.FailedTaskRunName = taskRun.Name
   157  				d.PodName = taskRun.Status.PodName
   158  				for _, s := range taskRun.Status.TaskRunStatusFields.Steps {
   159  					if s.Terminated.Reason == "Error" {
   160  						d.FailedContainerName = s.ContainerName
   161  						return d, nil
   162  					}
   163  				}
   164  			}
   165  		}
   166  	}
   167  	return d, nil
   168  }
   169  
   170  func GetGeneratedNamespace(name string) string {
   171  	return name + "-" + util.GenerateRandomString(4)
   172  }
   173  
   174  func WaitUntilWithInterval(cond wait.ConditionFunc, interval time.Duration, timeout time.Duration) error {
   175  	return wait.PollImmediate(interval, timeout, cond)
   176  }
   177  
   178  func WaitUntil(cond wait.ConditionFunc, timeout time.Duration) error {
   179  	return WaitUntilWithInterval(cond, time.Second, timeout)
   180  }
   181  
   182  func ExecuteCommandInASpecificDirectory(command string, args []string, directory string) error {
   183  	cmd := exec.Command(command, args...) // nolint:gosec
   184  	cmd.Dir = directory
   185  
   186  	stdin, err := cmd.StdinPipe()
   187  
   188  	if err != nil {
   189  		return err
   190  	}
   191  	defer stdin.Close() // the doc says subProcess.Wait will close it, but I'm not sure, so I kept this line
   192  
   193  	cmd.Stdout = os.Stdout
   194  	cmd.Stderr = os.Stderr
   195  
   196  	if err = cmd.Start(); err != nil {
   197  		klog.Errorf("an error occurred: %s", err)
   198  
   199  		return err
   200  	}
   201  
   202  	_, _ = io.WriteString(stdin, "4\n")
   203  
   204  	if err := cmd.Wait(); err != nil {
   205  		return err
   206  	}
   207  
   208  	return err
   209  }
   210  
   211  func ToPrettyJSONString(v interface{}) string {
   212  	s, _ := json.MarshalIndent(v, "", "  ")
   213  	return string(s)
   214  }
   215  
   216  // GetAdditionalInfo adds information regarding the application name and namespace of the test
   217  func GetAdditionalInfo(applicationName, namespace string) string {
   218  	return fmt.Sprintf("(application: %s, namespace: %s)", applicationName, namespace)
   219  }
   220  
   221  // contains checks if a string is present in a slice
   222  func Contains(s []string, str string) bool {
   223  	for _, v := range s {
   224  		if v == str {
   225  			return true
   226  		}
   227  	}
   228  
   229  	return false
   230  }
   231  
   232  func MergeMaps(m1, m2 map[string]string) map[string]string {
   233  	resultMap := make(map[string]string)
   234  	for k, v := range m1 {
   235  		resultMap[k] = v
   236  	}
   237  	for k, v := range m2 {
   238  		resultMap[k] = v
   239  	}
   240  	return resultMap
   241  }
   242  
   243  // CreateDockerConfigFile takes base64 encoded dockerconfig.json and saves it locally (/<home-directory/.docker/config.json)
   244  func CreateDockerConfigFile(base64EncodedString string) error {
   245  	var rawRegistryCreds []byte
   246  	var homeDir string
   247  	var err error
   248  
   249  	if rawRegistryCreds, err = base64.StdEncoding.DecodeString(base64EncodedString); err != nil {
   250  		return fmt.Errorf("unable to decode container registry credentials: %v", err)
   251  	}
   252  	if homeDir, err = homedir.Dir(); err != nil {
   253  		return fmt.Errorf("unable to locate home directory: %v", err)
   254  	}
   255  	if err = os.MkdirAll(homeDir+"/.docker", 0775); err != nil {
   256  		return fmt.Errorf("failed to create '.docker' config directory: %v", err)
   257  	}
   258  	if err = os.WriteFile(homeDir+"/.docker/config.json", rawRegistryCreds, 0644); err != nil {
   259  		return fmt.Errorf("failed to create a docker config file: %v", err)
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  // ExtractTektonObjectFromBundle extracts specified Tekton object from specified bundle reference
   266  func ExtractTektonObjectFromBundle(bundleRef, kind, name string) (runtime.Object, error) {
   267  	var obj runtime.Object
   268  	var err error
   269  
   270  	resolver := oci.NewResolver(bundleRef, authn.DefaultKeychain)
   271  	if obj, _, err = resolver.Get(context.TODO(), kind, name); err != nil {
   272  		return nil, fmt.Errorf("failed to fetch the tekton object %s with name %s: %v", kind, name, err)
   273  	}
   274  	return obj, nil
   275  }
   276  
   277  // BuildAndPushTektonBundle builds a Tekton bundle from YAML and pushes to remote container registry
   278  func BuildAndPushTektonBundle(YamlContent []byte, ref name.Reference, remoteOption remoteimg.Option) error {
   279  	img, err := bundle.BuildTektonBundle([]string{string(YamlContent)}, os.Stdout)
   280  	if err != nil {
   281  		return fmt.Errorf("error when building a bundle %s: %v", ref.String(), err)
   282  	}
   283  
   284  	outDigest, err := bundle.Write(img, ref, remoteOption)
   285  	if err != nil {
   286  		return fmt.Errorf("error when pushing a bundle %s to a container image registry repo: %v", ref.String(), err)
   287  	}
   288  	klog.Infof("image digest for a new tekton bundle %s: %+v", ref.String(), outDigest)
   289  
   290  	return nil
   291  }
   292  
   293  // GetDefaultPipelineBundleRef gets the specific Tekton pipeline bundle reference from a Build pipeline selector
   294  // (in a YAML format) from a URL specified in the parameter
   295  func GetDefaultPipelineBundleRef(buildPipelineSelectorYamlURL, selectorName string) (string, error) {
   296  	res, err := http.Get(buildPipelineSelectorYamlURL)
   297  	if err != nil {
   298  		return "", fmt.Errorf("failed to get a build pipeline selector from url %s: %v", buildPipelineSelectorYamlURL, err)
   299  	}
   300  	defer res.Body.Close()
   301  	body, err := io.ReadAll(res.Body)
   302  	if err != nil {
   303  		return "", fmt.Errorf("failed to read the body response of a build pipeline selector: %v", err)
   304  	}
   305  	ps := &buildservice.BuildPipelineSelector{}
   306  	if err = yaml.Unmarshal(body, ps); err != nil {
   307  		return "", fmt.Errorf("failed to unmarshal build pipeline selector: %v", err)
   308  	}
   309  	for _, s := range ps.Spec.Selectors {
   310  		if s.Name == selectorName {
   311  			return s.PipelineRef.Bundle, nil
   312  		}
   313  	}
   314  
   315  	return "", fmt.Errorf("could not find %s pipeline bundle in build pipeline selector fetched from %s", selectorName, buildPipelineSelectorYamlURL)
   316  }
   317  
   318  // ParseDevfileModel calls the devfile library's parse and returns the devfile data
   319  func ParseDevfileModel(devfileModel string) (data.DevfileData, error) {
   320  	// Retrieve the devfile from the body of the resource
   321  	devfileBytes := []byte(devfileModel)
   322  	parserArgs := parser.ParserArgs{
   323  		Data: devfileBytes,
   324  	}
   325  	devfileObj, _, err := devfilePkg.ParseDevfileAndValidate(parserArgs)
   326  	return devfileObj.Data, err
   327  }
   328  
   329  func HostIsAccessible(host string) bool {
   330  	tc := &http.Transport{
   331  		TLSClientConfig: &tls.Config{
   332  			InsecureSkipVerify: true,
   333  		},
   334  	}
   335  	client := http.Client{Transport: tc}
   336  	res, err := client.Get(host)
   337  	if err != nil || res.StatusCode > 499 {
   338  		return false
   339  	}
   340  	return true
   341  }
   342  
   343  func PipelineRunFailed(pr *v1beta1.PipelineRun) bool {
   344  	return pr.IsDone() && pr.GetStatusCondition().GetCondition(apis.ConditionSucceeded).IsFalse()
   345  }
   346  
   347  // Return a container logs from a given pod and namespace
   348  func GetContainerLogs(ki kubernetes.Interface, podName, containerName, namespace string) (string, error) {
   349  	podLogOpts := corev1.PodLogOptions{
   350  		Container: containerName,
   351  	}
   352  
   353  	req := ki.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
   354  	podLogs, err := req.Stream(context.TODO())
   355  	if err != nil {
   356  		return "", fmt.Errorf("error in opening the stream: %v", err)
   357  	}
   358  	defer podLogs.Close()
   359  
   360  	buf := new(bytes.Buffer)
   361  	_, err = io.Copy(buf, podLogs)
   362  	if err != nil {
   363  		return "", fmt.Errorf("error in copying logs to buf, %v", err)
   364  	}
   365  	return buf.String(), nil
   366  }