go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/admin/admin.go (about) 1 // Copyright 2018 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 admin 16 17 import ( 18 "context" 19 20 "google.golang.org/grpc/codes" 21 "google.golang.org/grpc/status" 22 "google.golang.org/protobuf/proto" 23 "google.golang.org/protobuf/types/known/emptypb" 24 25 "go.chromium.org/luci/common/errors" 26 "go.chromium.org/luci/common/retry/transient" 27 "go.chromium.org/luci/server/dsmapper" 28 29 api "go.chromium.org/luci/cipd/api/admin/v1" 30 "go.chromium.org/luci/cipd/appengine/impl/rpcacl" 31 ) 32 33 // AdminAPI returns an ACL-protected implementation of cipd.AdminServer that can 34 // be exposed as a public API (i.e. admins can use it via external RPCs). 35 func AdminAPI(ctl *dsmapper.Controller) api.AdminServer { 36 impl := &adminImpl{ 37 acl: rpcacl.CheckAdmin, 38 ctl: ctl, 39 } 40 impl.init() 41 return impl 42 } 43 44 // adminImpl implements cipd.AdminServer. 45 type adminImpl struct { 46 api.UnimplementedAdminServer 47 48 acl func(context.Context) error 49 ctl *dsmapper.Controller 50 } 51 52 // init initializes mapper controller and registers mapping tasks. 53 func (impl *adminImpl) init() { 54 for _, m := range mappers { // see mappers.go 55 impl.ctl.RegisterFactory(m.mapperID(), m.newMapper) 56 } 57 } 58 59 // toStatus converts an error from dsmapper.Controller to an grpc status. 60 // 61 // Passes nil as is. 62 func toStatus(err error) error { 63 switch { 64 case err == dsmapper.ErrNoSuchJob: 65 return status.Errorf(codes.NotFound, "no such mapping job") 66 case transient.Tag.In(err): 67 return status.Errorf(codes.Internal, "%s", err.Error()) 68 case err != nil: 69 return status.Errorf(codes.InvalidArgument, "%s", err.Error()) 70 default: 71 return nil 72 } 73 } 74 75 // LaunchJob implements the corresponding RPC method, see the proto doc. 76 func (impl *adminImpl) LaunchJob(ctx context.Context, cfg *api.JobConfig) (*api.JobID, error) { 77 if err := impl.acl(ctx); err != nil { 78 return nil, err 79 } 80 81 def, ok := mappers[cfg.Kind] // see mappers.go 82 if !ok { 83 return nil, status.Errorf(codes.InvalidArgument, "unknown mapper kind") 84 } 85 86 cfgBlob, err := proto.Marshal(cfg) 87 if err != nil { 88 return nil, status.Errorf(codes.Internal, "failed to marshal JobConfig - %s", err) 89 } 90 91 launchCfg := def.Config 92 launchCfg.Mapper = def.mapperID() 93 launchCfg.Params = cfgBlob 94 95 jid, err := impl.ctl.LaunchJob(ctx, &launchCfg) 96 if err != nil { 97 return nil, toStatus(err) 98 } 99 100 return &api.JobID{JobId: int64(jid)}, nil 101 } 102 103 // AbortJob implements the corresponding RPC method, see the proto doc. 104 func (impl *adminImpl) AbortJob(ctx context.Context, id *api.JobID) (*emptypb.Empty, error) { 105 if err := impl.acl(ctx); err != nil { 106 return nil, err 107 } 108 _, err := impl.ctl.AbortJob(ctx, dsmapper.JobID(id.JobId)) 109 if err != nil { 110 return nil, toStatus(err) 111 } 112 return &emptypb.Empty{}, nil 113 } 114 115 // GetJobState implements the corresponding RPC method, see the proto doc. 116 func (impl *adminImpl) GetJobState(ctx context.Context, id *api.JobID) (*api.JobState, error) { 117 if err := impl.acl(ctx); err != nil { 118 return nil, err 119 } 120 job, err := impl.ctl.GetJob(ctx, dsmapper.JobID(id.JobId)) 121 if err != nil { 122 return nil, toStatus(err) 123 } 124 cfg := &api.JobConfig{} 125 if err := proto.Unmarshal(job.Config.Params, cfg); err != nil { 126 return nil, toStatus(errors.Annotate(err, "failed to unmarshal JobConfig").Err()) 127 } 128 info, err := job.FetchInfo(ctx) 129 if err != nil { 130 return nil, toStatus(err) 131 } 132 return &api.JobState{Config: cfg, Info: info}, nil 133 } 134 135 // GetJobState implements the corresponding RPC method, see the proto doc. 136 func (impl *adminImpl) FixMarkedTags(ctx context.Context, id *api.JobID) (*api.TagFixReport, error) { 137 if err := impl.acl(ctx); err != nil { 138 return nil, err 139 } 140 tags, err := fixMarkedTags(ctx, dsmapper.JobID(id.JobId)) 141 if err != nil { 142 return nil, toStatus(err) 143 } 144 return &api.TagFixReport{Fixed: tags}, nil 145 }