github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/azure/webhook.go (about) 1 // Copyright 2018 The WPT Dashboard Project. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package azure 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "regexp" 13 14 mapset "github.com/deckarep/golang-set" 15 16 uc "github.com/web-platform-tests/wpt.fyi/api/receiver/client" 17 "github.com/web-platform-tests/wpt.fyi/shared" 18 ) 19 20 const uploaderName = "azure" 21 22 // Labels for runs from Azure Pipelines are determined from the artifact names. 23 // For master runs, artifact name may be either just "results" or something 24 // like "safari-results". 25 var ( 26 masterRegex = regexp.MustCompile(`\bresults$`) 27 prHeadRegex = regexp.MustCompile(`\baffected-tests$`) 28 prBaseRegex = regexp.MustCompile(`\baffected-tests-without-changes$`) 29 epochBranchesRegex = regexp.MustCompile("^refs/heads/epochs/.*") 30 ) 31 32 func processBuild( 33 aeAPI shared.AppEngineAPI, 34 azureAPI API, 35 owner, 36 repo, 37 sender, 38 artifactName string, 39 buildID int64, 40 ) (bool, error) { 41 build, err := azureAPI.GetBuild(owner, repo, buildID) 42 if err != nil { 43 return false, err 44 } 45 if build == nil { 46 return false, fmt.Errorf("cannot get build %s/%s/%d", owner, repo, buildID) 47 } 48 sha := build.TriggerInfo.SourceSHA 49 50 // https://docs.microsoft.com/en-us/rest/api/azure/devops/build/artifacts/get?view=azure-devops-rest-4.1 51 artifactsURL := azureAPI.GetAzureArtifactsURL(owner, repo, buildID) 52 53 log := shared.GetLogger(aeAPI.Context()) 54 log.Infof("Fetching %s", artifactsURL) 55 56 client := aeAPI.GetHTTPClient() 57 req, err := http.NewRequestWithContext(aeAPI.Context(), http.MethodGet, artifactsURL, nil) 58 if err != nil { 59 return false, fmt.Errorf("failed to create get for %s/%s/%d: %w", owner, repo, buildID, err) 60 } 61 resp, err := client.Do(req) 62 if err != nil { 63 return false, fmt.Errorf("failed to fetch artifacts for %s/%s/%d: %w", owner, repo, buildID, err) 64 } 65 66 var artifacts BuildArtifacts 67 if body, err := io.ReadAll(resp.Body); err != nil { 68 return false, fmt.Errorf("failed to read response body: %w", err) 69 } else if err = json.Unmarshal(body, &artifacts); err != nil { 70 return false, fmt.Errorf("failed to unmarshal JSON: %w", err) 71 } 72 73 uploadedAny := false 74 errors := make(chan (error), artifacts.Count) 75 for _, artifact := range artifacts.Value { 76 if artifactName != "" && artifactName != artifact.Name { 77 log.Infof("Skipping artifact %s (looking for %s)", artifact.Name, artifactName) 78 79 continue 80 } 81 log.Infof("Uploading %s for %s/%s build %v...", artifact.Name, owner, repo, buildID) 82 83 labels := mapset.NewSet() 84 if sender != "" { 85 labels.Add(shared.GetUserLabel(sender)) 86 } 87 88 if masterRegex.MatchString(artifact.Name) { 89 if build.IsMasterBranch() || epochBranchesRegex.MatchString(build.SourceBranch) { 90 labels.Add(shared.MasterLabel) 91 } 92 } else if prHeadRegex.MatchString(artifact.Name) { 93 labels.Add(shared.PRHeadLabel) 94 } else if prBaseRegex.MatchString(artifact.Name) { 95 labels.Add(shared.PRBaseLabel) 96 } 97 98 uploader, err := aeAPI.GetUploader(uploaderName) 99 if err != nil { 100 return false, fmt.Errorf("failed to get uploader creds from Datastore: %w", err) 101 } 102 103 uploadClient := uc.NewClient(aeAPI) 104 err = uploadClient.CreateRun( 105 sha, 106 uploader.Username, 107 uploader.Password, 108 // Azure has a single zip artifact, special-cased by the receiver. 109 []string{artifact.Resource.DownloadURL}, 110 nil, 111 shared.ToStringSlice(labels)) 112 if err != nil { 113 errors <- fmt.Errorf("failed to create run: %w", err) 114 } else { 115 uploadedAny = true 116 } 117 } 118 close(errors) 119 for err := range errors { 120 return uploadedAny, err 121 } 122 123 return uploadedAny, nil 124 }