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  }