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 }