go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/internal/rotationproxy/rotationproxy.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package rotationproxy contains the logic to query for the on-call arborists
    16  // from the Chrome Ops Rotation Proxy
    17  package rotationproxy
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  	"time"
    25  
    26  	"go.chromium.org/luci/bisection/util"
    27  
    28  	"go.chromium.org/luci/common/errors"
    29  )
    30  
    31  var MockedRotationProxyClientKey = "mocked chrome-ops-rotation-proxy client"
    32  
    33  // GetOnCallEmails returns the emails of the on-call arborists for the given
    34  // project.
    35  func GetOnCallEmails(ctx context.Context, project string) ([]string, error) {
    36  	switch project {
    37  	case "chromium/src":
    38  		return getOnCallEmails(ctx, "oncallator:chrome-build-sheriff")
    39  	default:
    40  		// getting on-call rotation not supported
    41  	}
    42  
    43  	return nil, fmt.Errorf("could not get on-call rotation for project %s", project)
    44  }
    45  
    46  type rotationResponse struct {
    47  	Emails           []string `json:"emails"`
    48  	UpdatedTimestamp int64    `json:"updated_unix_timestamp"`
    49  }
    50  
    51  // getOnCallEmails is a helper function to get the emails of the on-call
    52  // arborists using the given rotation proxy name.
    53  func getOnCallEmails(ctx context.Context, rotationProxyName string) ([]string, error) {
    54  	client := GetClient(ctx)
    55  	data, err := client.sendRequest(ctx, rotationProxyName)
    56  	if err != nil {
    57  		return nil, errors.Annotate(err,
    58  			"error when querying for on-call rotation").Err()
    59  	}
    60  
    61  	res := &rotationResponse{}
    62  	if err = json.Unmarshal([]byte(data), res); err != nil {
    63  		return nil, errors.Annotate(err,
    64  			"failed to unmarshal rotation response (data = %s)", data).Err()
    65  	}
    66  
    67  	return res.Emails, nil
    68  }
    69  
    70  func GetClient(c context.Context) Client {
    71  	if mockClient, ok := c.Value(MockedRotationProxyClientKey).(*MockedRotationProxyClient); ok {
    72  		return mockClient
    73  	}
    74  
    75  	return &RotationProxyClient{}
    76  }
    77  
    78  // Client interface is needed for testing purposes
    79  type Client interface {
    80  	sendRequest(ctx context.Context, rotationProxyName string) (string, error)
    81  }
    82  
    83  type RotationProxyClient struct{}
    84  
    85  func (client *RotationProxyClient) sendRequest(ctx context.Context, rotationProxyName string) (string, error) {
    86  	url := fmt.Sprintf("https://chrome-ops-rotation-proxy.appspot.com/current/%s", rotationProxyName)
    87  	req, err := http.NewRequest("GET", url, nil)
    88  	if err != nil {
    89  		return "", errors.Annotate(err,
    90  			"failed to construct request when getting on-call rotation with name '%s'", rotationProxyName).Err()
    91  	}
    92  
    93  	// Get the on-call rotation (timeout of 30s)
    94  	return util.SendHTTPRequest(ctx, req, 30*time.Second)
    95  }