go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/rpc/projects.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 rpc
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  
    23  	"go.chromium.org/luci/common/errors"
    24  	"go.chromium.org/luci/server/auth"
    25  	"go.chromium.org/luci/server/auth/realms"
    26  
    27  	"go.chromium.org/luci/analysis/internal/perms"
    28  	configpb "go.chromium.org/luci/analysis/proto/config"
    29  	pb "go.chromium.org/luci/analysis/proto/v1"
    30  )
    31  
    32  type projectServer struct{}
    33  
    34  func NewProjectsServer() *pb.DecoratedProjects {
    35  	return &pb.DecoratedProjects{
    36  		Prelude:  checkAllowedPrelude,
    37  		Service:  &projectServer{},
    38  		Postlude: gRPCifyAndLogPostlude,
    39  	}
    40  }
    41  
    42  func (*projectServer) GetConfig(ctx context.Context, req *pb.GetProjectConfigRequest) (*pb.ProjectConfig, error) {
    43  	project, err := parseProjectConfigName(req.Name)
    44  	if err != nil {
    45  		return nil, invalidArgumentError(errors.Annotate(err, "name").Err())
    46  	}
    47  
    48  	if err := perms.VerifyProjectPermissions(ctx, project, perms.PermGetConfig); err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	// Fetch a recent project configuration.
    53  	// (May be a recent value that was cached.)
    54  	cfg, err := readProjectConfig(ctx, project)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	response := createProjectConfigPB(project, cfg.Config)
    60  	return response, nil
    61  }
    62  
    63  func createProjectConfigPB(project string, cfg *configpb.ProjectConfig) *pb.ProjectConfig {
    64  	result := &pb.ProjectConfig{
    65  		Name:          fmt.Sprintf("projects/%s/config", project),
    66  		BugManagement: toBugManagementPB(cfg.BugManagement),
    67  	}
    68  	return result
    69  }
    70  
    71  func toBugManagementPB(bm *configpb.BugManagement) *pb.BugManagement {
    72  	// Always set BugManagement struct to avoid clients needing to
    73  	// do pointless nullness checks. Having an empty BugManagement struct
    74  	// is semantically identical to having an unset BugManagement struct.
    75  	result := &pb.BugManagement{}
    76  
    77  	if bm != nil {
    78  		policies := make([]*pb.BugManagementPolicy, 0, len(bm.Policies))
    79  		for _, policy := range bm.Policies {
    80  			policies = append(policies, toBugManagementPolicyPB(policy))
    81  		}
    82  		result.Policies = policies
    83  		result.Monorail = toMonorailProjectPB(bm.Monorail)
    84  	}
    85  	return result
    86  }
    87  
    88  func toBugManagementPolicyPB(policy *configpb.BugManagementPolicy) *pb.BugManagementPolicy {
    89  	metrics := make([]*pb.BugManagementPolicy_Metric, 0, len(policy.Metrics))
    90  	for _, m := range policy.Metrics {
    91  		metrics = append(metrics, &pb.BugManagementPolicy_Metric{
    92  			MetricId:              m.MetricId,
    93  			ActivationThreshold:   toMetricThresholdPB(m.ActivationThreshold),
    94  			DeactivationThreshold: toMetricThresholdPB(m.DeactivationThreshold),
    95  		})
    96  	}
    97  
    98  	return &pb.BugManagementPolicy{
    99  		Id:                policy.Id,
   100  		Owners:            policy.Owners,
   101  		HumanReadableName: policy.HumanReadableName,
   102  		Priority:          pb.BuganizerPriority(policy.Priority),
   103  		Metrics:           metrics,
   104  		Explanation: &pb.BugManagementPolicy_Explanation{
   105  			// Explanation should never be nil. Config validation enforces this.
   106  			ProblemHtml: policy.Explanation.ProblemHtml,
   107  			ActionHtml:  policy.Explanation.ActionHtml,
   108  		},
   109  	}
   110  }
   111  
   112  func toMonorailProjectPB(cfg *configpb.MonorailProject) *pb.MonorailProject {
   113  	if cfg == nil {
   114  		return nil
   115  	}
   116  	return &pb.MonorailProject{
   117  		Project:       cfg.Project,
   118  		DisplayPrefix: cfg.DisplayPrefix,
   119  	}
   120  }
   121  
   122  func toMetricThresholdPB(threshold *configpb.MetricThreshold) *pb.MetricThreshold {
   123  	if threshold == nil {
   124  		return nil
   125  	}
   126  	return &pb.MetricThreshold{
   127  		OneDay:   threshold.OneDay,
   128  		ThreeDay: threshold.ThreeDay,
   129  		SevenDay: threshold.SevenDay,
   130  	}
   131  }
   132  
   133  func (*projectServer) List(ctx context.Context, request *pb.ListProjectsRequest) (*pb.ListProjectsResponse, error) {
   134  	readableRealms, err := auth.QueryRealms(ctx, perms.PermGetConfig, "", nil)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	readableProjects := make([]string, 0)
   139  	for _, r := range readableRealms {
   140  		project, realm := realms.Split(r)
   141  		if realm == realms.RootRealm || realm == realms.ProjectRealm {
   142  			readableProjects = append(readableProjects, project)
   143  		}
   144  	}
   145  	// Return projects in a stable order.
   146  	sort.Strings(readableProjects)
   147  
   148  	return &pb.ListProjectsResponse{
   149  		Projects: createProjectPBs(readableProjects),
   150  	}, nil
   151  }
   152  
   153  func createProjectPBs(projects []string) []*pb.Project {
   154  	projectsPbs := make([]*pb.Project, 0, len(projects))
   155  	for _, project := range projects {
   156  		projectsPbs = append(projectsPbs, &pb.Project{
   157  			Name:        fmt.Sprintf("projects/%s", project),
   158  			DisplayName: strings.Title(project),
   159  			Project:     project,
   160  		})
   161  	}
   162  	return projectsPbs
   163  }