go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/rpc/projects.go (about)

     1  // Copyright 2019 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  
    20  	"github.com/golang/protobuf/proto"
    21  
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  	"google.golang.org/protobuf/types/known/emptypb"
    25  
    26  	"go.chromium.org/luci/common/errors"
    27  	"go.chromium.org/luci/common/logging"
    28  	"go.chromium.org/luci/common/proto/paged"
    29  	"go.chromium.org/luci/gae/service/datastore"
    30  	"go.chromium.org/luci/server/auth"
    31  
    32  	"go.chromium.org/luci/gce/api/projects/v1"
    33  	"go.chromium.org/luci/gce/appengine/model"
    34  )
    35  
    36  // Projects implements projects.ProjectsServer.
    37  type Projects struct {
    38  }
    39  
    40  // Ensure Projects implements projects.ProjectsServer.
    41  var _ projects.ProjectsServer = &Projects{}
    42  
    43  // Delete handles a request to delete a project.
    44  func (*Projects) Delete(c context.Context, req *projects.DeleteRequest) (*emptypb.Empty, error) {
    45  	if req.GetId() == "" {
    46  		return nil, status.Errorf(codes.InvalidArgument, "ID is required")
    47  	}
    48  	if err := datastore.Delete(c, &model.Project{ID: req.Id}); err != nil {
    49  		return nil, errors.Annotate(err, "failed to delete project").Err()
    50  	}
    51  	return &emptypb.Empty{}, nil
    52  }
    53  
    54  // Ensure handles a request to create or update a project.
    55  func (*Projects) Ensure(c context.Context, req *projects.EnsureRequest) (*projects.Config, error) {
    56  	if req.GetId() == "" {
    57  		return nil, status.Errorf(codes.InvalidArgument, "ID is required")
    58  	}
    59  	p := &model.Project{
    60  		ID:     req.Id,
    61  		Config: req.Project,
    62  	}
    63  	if err := datastore.Put(c, p); err != nil {
    64  		return nil, errors.Annotate(err, "failed to store project").Err()
    65  	}
    66  	return p.Config, nil
    67  }
    68  
    69  // Get handles a request to get a project.
    70  func (*Projects) Get(c context.Context, req *projects.GetRequest) (*projects.Config, error) {
    71  	if req.GetId() == "" {
    72  		return nil, status.Errorf(codes.InvalidArgument, "ID is required")
    73  	}
    74  	p := &model.Project{
    75  		ID: req.Id,
    76  	}
    77  	switch err := datastore.Get(c, p); err {
    78  	case nil:
    79  		return p.Config, nil
    80  	case datastore.ErrNoSuchEntity:
    81  		return nil, status.Errorf(codes.NotFound, "no project found with ID %q", req.Id)
    82  	default:
    83  		return nil, errors.Annotate(err, "failed to fetch project").Err()
    84  	}
    85  }
    86  
    87  // List handles a request to list all projects.
    88  func (*Projects) List(c context.Context, req *projects.ListRequest) (*projects.ListResponse, error) {
    89  	rsp := &projects.ListResponse{}
    90  	if err := paged.Query(c, req.GetPageSize(), req.GetPageToken(), rsp, datastore.NewQuery(model.ProjectKind), func(p *model.Project) error {
    91  		rsp.Projects = append(rsp.Projects, p.Config)
    92  		return nil
    93  	}); err != nil {
    94  		return nil, err
    95  	}
    96  	return rsp, nil
    97  }
    98  
    99  // projectsPrelude ensures the user is authorized to use the read-only projects
   100  // API. Always returns permission denied for write methods.
   101  func projectsPrelude(c context.Context, methodName string, req proto.Message) (context.Context, error) {
   102  	if !isReadOnly(methodName) {
   103  		return c, status.Errorf(codes.PermissionDenied, "unauthorized user")
   104  	}
   105  	switch is, err := auth.IsMember(c, admins, writers, readers); {
   106  	case err != nil:
   107  		return c, err
   108  	case is:
   109  		logging.Debugf(c, "%s called %q:\n%s", auth.CurrentIdentity(c), methodName, req)
   110  		return c, nil
   111  	}
   112  	return c, status.Errorf(codes.PermissionDenied, "unauthorized user")
   113  }
   114  
   115  // NewProjectsServer returns a new projects server.
   116  func NewProjectsServer() projects.ProjectsServer {
   117  	return &projects.DecoratedProjects{
   118  		Prelude:  projectsPrelude,
   119  		Service:  &Projects{},
   120  		Postlude: gRPCifyAndLogErr,
   121  	}
   122  }