@@ -10,6 +10,7 @@ package gomote
10
10
import (
11
11
"context"
12
12
"errors"
13
+ "fmt"
13
14
"log"
14
15
"regexp"
15
16
"strings"
@@ -149,12 +150,10 @@ func (s *Server) InstanceAlive(ctx context.Context, req *protos.InstanceAliveReq
149
150
if req .GetGomoteId () == "" {
150
151
return nil , status .Errorf (codes .InvalidArgument , "invalid gomote ID" )
151
152
}
152
- session , err : = s .buildlets . Session (req .GetGomoteId ())
153
+ _ , err = s .session (req .GetGomoteId (), creds . ID )
153
154
if err != nil {
154
- return nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
155
- }
156
- if session .OwnerID != creds .ID {
157
- return nil , status .Errorf (codes .PermissionDenied , "not allowed to modify this gomote session" )
155
+ // the helper function returns meaningful GRPC error.
156
+ return nil , err
158
157
}
159
158
if err := s .buildlets .RenewTimeout (req .GetGomoteId ()); err != nil {
160
159
return nil , status .Errorf (codes .Internal , "unable to renew timeout" )
@@ -170,16 +169,10 @@ func (s *Server) ListDirectory(ctx context.Context, req *protos.ListDirectoryReq
170
169
if req .GetGomoteId () == "" || req .GetDirectory () == "" {
171
170
return nil , status .Errorf (codes .InvalidArgument , "invalid arguments" )
172
171
}
173
- session , err := s .buildlets . Session (req .GetGomoteId ())
172
+ _ , bc , err := s .sessionAndClient (req .GetGomoteId (), creds . ID )
174
173
if err != nil {
175
- return nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
176
- }
177
- if session .OwnerID != creds .ID {
178
- return nil , status .Errorf (codes .PermissionDenied , "not allowed to modify this gomote session" )
179
- }
180
- bc , err := s .buildlets .BuildletClient (req .GetGomoteId ())
181
- if err != nil {
182
- return nil , status .Errorf (codes .Internal , "unable to retrieve buildlet client" )
174
+ // the helper function returns meaningful GRPC error.
175
+ return nil , err
183
176
}
184
177
opt := buildlet.ListDirOpts {
185
178
Recursive : req .GetRecursive (),
@@ -230,12 +223,10 @@ func (s *Server) DestroyInstance(ctx context.Context, req *protos.DestroyInstanc
230
223
if req .GetGomoteId () == "" {
231
224
return nil , status .Errorf (codes .InvalidArgument , "invalid gomote ID" )
232
225
}
233
- session , err : = s .buildlets . Session (req .GetGomoteId ())
226
+ _ , err = s .session (req .GetGomoteId (), creds . ID )
234
227
if err != nil {
235
- return nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
236
- }
237
- if session .OwnerID != creds .ID {
238
- return nil , status .Errorf (codes .PermissionDenied , "not allowed to modify this gomote session" )
228
+ // the helper function returns meaningful GRPC error.
229
+ return nil , err
239
230
}
240
231
if err := s .buildlets .DestroySession (req .GetGomoteId ()); err != nil {
241
232
log .Printf ("DestroyInstance remote.DestroySession(%s) = %s" , req .GetGomoteId (), err )
@@ -244,6 +235,54 @@ func (s *Server) DestroyInstance(ctx context.Context, req *protos.DestroyInstanc
244
235
return & protos.DestroyInstanceResponse {}, nil
245
236
}
246
237
238
+ // ExecuteCommand will execute a command on a gomote instance. The output from the command will be streamed back to the caller if the output is set.
239
+ func (s * Server ) ExecuteCommand (req * protos.ExecuteCommandRequest , stream protos.GomoteService_ExecuteCommandServer ) error {
240
+ creds , err := access .IAPFromContext (stream .Context ())
241
+ if err != nil {
242
+ return status .Errorf (codes .Unauthenticated , "request does not contain the required authentication" )
243
+ }
244
+ _ , bc , err := s .sessionAndClient (req .GetGomoteId (), creds .ID )
245
+ if err != nil {
246
+ // the helper function returns meaningful GRPC error.
247
+ return err
248
+ }
249
+ remoteErr , execErr := bc .Exec (stream .Context (), req .GetCommand (), buildlet.ExecOpts {
250
+ Dir : req .GetCommand (),
251
+ SystemLevel : req .GetSystemLevel (),
252
+ Output : & execStreamWriter {stream : stream },
253
+ Args : req .GetArgs (),
254
+ ExtraEnv : req .GetAppendEnvironment (),
255
+ Debug : req .GetDebug (),
256
+ Path : req .GetPath (),
257
+ })
258
+ if execErr != nil {
259
+ // there were system errors preventing the command from being started or seen to completition.
260
+ return status .Errorf (codes .Aborted , "unable to execute command: %s" , execErr )
261
+ }
262
+ if remoteErr != nil {
263
+ // the command succeeded remotely
264
+ return status .Errorf (codes .Unknown , "command execution failed: %s" , remoteErr )
265
+ }
266
+ return nil
267
+ }
268
+
269
+ // execStreamWriter implements the io.Writer interface. Any data writen to it will be streamed
270
+ // as an execute command response.
271
+ type execStreamWriter struct {
272
+ stream protos.GomoteService_ExecuteCommandServer
273
+ }
274
+
275
+ // Write sends data writen to it as an execute command response.
276
+ func (sw * execStreamWriter ) Write (p []byte ) (int , error ) {
277
+ err := sw .stream .Send (& protos.ExecuteCommandResponse {
278
+ Output : string (p ),
279
+ })
280
+ if err != nil {
281
+ return 0 , fmt .Errorf ("unable to send data=%w" , err )
282
+ }
283
+ return len (p ), nil
284
+ }
285
+
247
286
// RemoveFiles removes files or directories from the gomote instance.
248
287
func (s * Server ) RemoveFiles (ctx context.Context , req * protos.RemoveFilesRequest ) (* protos.RemoveFilesResponse , error ) {
249
288
creds , err := access .IAPFromContext (ctx )
@@ -255,16 +294,10 @@ func (s *Server) RemoveFiles(ctx context.Context, req *protos.RemoveFilesRequest
255
294
if req .GetGomoteId () == "" || len (req .GetPaths ()) == 0 {
256
295
return nil , status .Errorf (codes .InvalidArgument , "invalid arguments" )
257
296
}
258
- session , err := s .buildlets . Session (req .GetGomoteId ())
297
+ _ , bc , err := s .sessionAndClient (req .GetGomoteId (), creds . ID )
259
298
if err != nil {
260
- return nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
261
- }
262
- if session .OwnerID != creds .ID {
263
- return nil , status .Errorf (codes .PermissionDenied , "not allowed to modify this gomote session" )
264
- }
265
- bc , err := s .buildlets .BuildletClient (req .GetGomoteId ())
266
- if err != nil {
267
- return nil , status .Errorf (codes .Internal , "unable to retrieve buildlet client" )
299
+ // the helper function returns meaningful GRPC error.
300
+ return nil , err
268
301
}
269
302
if err := bc .RemoveAll (ctx , req .GetPaths ()... ); err != nil {
270
303
log .Printf ("RemoveFiles buildletClient.RemoveAll(ctx, %q) = %s" , req .GetPaths (), err )
@@ -287,21 +320,41 @@ func (s *Server) WriteTGZFromURL(ctx context.Context, req *protos.WriteTGZFromUR
287
320
if req .GetUrl () == "" {
288
321
return nil , status .Errorf (codes .InvalidArgument , "missing URL" )
289
322
}
290
- session , err := s .buildlets .Session (req .GetGomoteId ())
323
+ _ , bc , err := s .sessionAndClient (req .GetGomoteId (), creds .ID )
324
+ if err != nil {
325
+ // the helper function returns meaningful GRPC error.
326
+ return nil , err
327
+ }
328
+ if err = bc .PutTarFromURL (ctx , req .GetUrl (), req .GetDirectory ()); err != nil {
329
+ return nil , status .Errorf (codes .FailedPrecondition , "unable to write tar.gz: %s" , err )
330
+ }
331
+ return & protos.WriteTGZFromURLResponse {}, nil
332
+ }
333
+
334
+ // session is a helper function that retreives a session associated with the gomoteID and ownerID.
335
+ func (s * Server ) session (gomoteID , ownerID string ) (* remote.Session , error ) {
336
+ session , err := s .buildlets .Session (gomoteID )
291
337
if err != nil {
292
338
return nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
293
339
}
294
- if session .OwnerID != creds . ID {
340
+ if session .OwnerID != ownerID {
295
341
return nil , status .Errorf (codes .PermissionDenied , "not allowed to modify this gomote session" )
296
342
}
297
- bc , err := s .buildlets .BuildletClient (req .GetGomoteId ())
343
+ return session , nil
344
+ }
345
+
346
+ // sessionAndClient is a helper function that retrieves a session and buildlet client for the
347
+ // associated gomoteID and ownerID.
348
+ func (s * Server ) sessionAndClient (gomoteID , ownerID string ) (* remote.Session , buildlet.Client , error ) {
349
+ session , err := s .session (gomoteID , ownerID )
298
350
if err != nil {
299
- return nil , status . Errorf ( codes . NotFound , "specified gomote instance does not exist" )
351
+ return nil , nil , err
300
352
}
301
- if err = bc .PutTarFromURL (ctx , req .GetUrl (), req .GetDirectory ()); err != nil {
302
- return nil , status .Errorf (codes .FailedPrecondition , "unable to write tar.gz: %s" , err )
353
+ bc , err := s .buildlets .BuildletClient (gomoteID )
354
+ if err != nil {
355
+ return nil , nil , status .Errorf (codes .NotFound , "specified gomote instance does not exist" )
303
356
}
304
- return & protos. WriteTGZFromURLResponse {} , nil
357
+ return session , bc , nil
305
358
}
306
359
307
360
// isPrivilagedUser returns true if the user is using a Google account.
0 commit comments