github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/azure/api.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  //go:generate mockgen -destination mock_azure/api_mock.go github.com/web-platform-tests/wpt.fyi/api/azure API
     6  
     7  package azure
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  
    16  	"github.com/web-platform-tests/wpt.fyi/shared"
    17  )
    18  
    19  // PipelinesAppID is the ID of the Azure Pipelines GitHub app.
    20  const PipelinesAppID = int64(9426)
    21  
    22  // https://docs.microsoft.com/en-us/rest/api/azure/devops/build/artifacts/get?view=azure-devops-rest-4.1
    23  
    24  // BuildArtifacts is a wrapper for multiple BuildArtifact results.
    25  type BuildArtifacts struct {
    26  	Count int64           `json:"count"`
    27  	Value []BuildArtifact `json:"value"`
    28  }
    29  
    30  // BuildArtifact is an artifact published by a build.
    31  type BuildArtifact struct {
    32  	ID       int64            `json:"id"`
    33  	Name     string           `json:"name"`
    34  	Resource ArtifactResource `json:"resource"`
    35  }
    36  
    37  // ArtifactResource is a resource for an artifact.
    38  type ArtifactResource struct {
    39  	Data        string `json:"data"`
    40  	DownloadURL string `json:"downloadUrl"`
    41  	Type        string `json:"type"`
    42  	URL         string `json:"url"`
    43  }
    44  
    45  // Build is an Azure Pipelines build object.
    46  type Build struct {
    47  	SourceBranch string           `json:"sourceBranch"`
    48  	TriggerInfo  BuildTriggerInfo `json:"triggerInfo"`
    49  }
    50  
    51  // BuildTriggerInfo is information about what triggered the build.
    52  type BuildTriggerInfo struct {
    53  	SourceBranch string `json:"pr.sourceBranch"`
    54  	SourceSHA    string `json:"pr.sourceSha"`
    55  }
    56  
    57  // IsMasterBranch returns whether the source branch for the build is the master branch.
    58  func (a *Build) IsMasterBranch() bool {
    59  	return a != nil && a.SourceBranch == "refs/heads/master"
    60  }
    61  
    62  // API is for Azure Pipelines related requests.
    63  type API interface {
    64  	GetBuildURL(owner, repo string, buildID int64) string
    65  	GetAzureArtifactsURL(owner, repo string, buildID int64) string
    66  	GetBuild(owner, repo string, buildID int64) (*Build, error)
    67  }
    68  
    69  type apiImpl struct {
    70  	ctx context.Context // nolint:containedctx // TODO: Fix containedctx lint error
    71  }
    72  
    73  // NewAPI returns an implementation of azure API.
    74  // nolint:ireturn // TODO: Fix ireturn lint error
    75  func NewAPI(ctx context.Context) API {
    76  	return apiImpl{
    77  		ctx: ctx,
    78  	}
    79  }
    80  
    81  func (a apiImpl) GetBuildURL(owner, repo string, buildID int64) string {
    82  	// https://docs.microsoft.com/en-us/rest/api/azure/devops/build/builds/get?view=azure-devops-rest-4.1#build
    83  	return fmt.Sprintf(
    84  		"https://dev.azure.com/%s/%s/_apis/build/builds/%v", owner, repo, buildID)
    85  }
    86  
    87  func (a apiImpl) GetAzureArtifactsURL(owner, repo string, buildID int64) string {
    88  	return fmt.Sprintf(
    89  		"https://dev.azure.com/%s/%s/_apis/build/builds/%v/artifacts",
    90  		owner,
    91  		repo,
    92  		buildID)
    93  }
    94  
    95  func (a apiImpl) GetBuild(owner, repo string, buildID int64) (*Build, error) {
    96  	buildURL := a.GetBuildURL(owner, repo, buildID)
    97  	client := shared.NewAppEngineAPI(a.ctx).GetHTTPClient()
    98  	req, err := http.NewRequestWithContext(a.ctx, http.MethodGet, buildURL, nil)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("failed to create GET request: %w", err)
   101  	}
   102  	resp, err := client.Do(req)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("failed to handle fetch build: %w", err)
   105  	}
   106  	defer resp.Body.Close()
   107  	data, err := io.ReadAll(resp.Body)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("failed to read request response: %w", err)
   110  	}
   111  	var build Build
   112  	if err := json.Unmarshal(data, &build); err != nil {
   113  		return nil, fmt.Errorf("failed to unmarshal request response: %w", err)
   114  	}
   115  	log := shared.GetLogger(a.ctx)
   116  	log.Debugf("Source branch: %s", build.SourceBranch)
   117  	log.Debugf("Trigger PR branch: %s", build.TriggerInfo.SourceBranch)
   118  
   119  	return &build, nil
   120  }