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 }