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  }