golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/gomote/gomote_test.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build linux || darwin
     6  
     7  package gomote
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"net/http/httptest"
    16  	"testing"
    17  	"time"
    18  
    19  	"cloud.google.com/go/storage"
    20  	"github.com/google/go-cmp/cmp"
    21  	"golang.org/x/build/internal/access"
    22  	"golang.org/x/build/internal/coordinator/remote"
    23  	"golang.org/x/build/internal/coordinator/schedule"
    24  	"golang.org/x/build/internal/gomote/protos"
    25  	"golang.org/x/crypto/ssh"
    26  	"golang.org/x/net/nettest"
    27  	"google.golang.org/grpc"
    28  	"google.golang.org/grpc/codes"
    29  	"google.golang.org/grpc/status"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  )
    32  
    33  const testBucketName = "unit-testing-bucket"
    34  
    35  func fakeGomoteServer(t *testing.T, ctx context.Context) protos.GomoteServiceServer {
    36  	signer, err := ssh.ParsePrivateKey([]byte(devCertCAPrivate))
    37  	if err != nil {
    38  		t.Fatalf("unable to parse raw certificate authority private key into signer=%s", err)
    39  	}
    40  	return &Server{
    41  		bucket:                  &fakeBucketHandler{bucketName: testBucketName},
    42  		buildlets:               remote.NewSessionPool(ctx),
    43  		gceBucketName:           testBucketName,
    44  		scheduler:               schedule.NewFake(),
    45  		sshCertificateAuthority: signer,
    46  	}
    47  }
    48  
    49  func setupGomoteTest(t *testing.T, ctx context.Context) protos.GomoteServiceClient {
    50  	lis, err := nettest.NewLocalListener("tcp")
    51  	if err != nil {
    52  		t.Fatalf("unable to create net listener: %s", err)
    53  	}
    54  	sopts := access.FakeIAPAuthInterceptorOptions()
    55  	s := grpc.NewServer(sopts...)
    56  	protos.RegisterGomoteServiceServer(s, fakeGomoteServer(t, ctx))
    57  	go s.Serve(lis)
    58  
    59  	// create GRPC client
    60  	copts := []grpc.DialOption{
    61  		grpc.WithInsecure(),
    62  		grpc.WithBlock(),
    63  		grpc.WithTimeout(5 * time.Second),
    64  	}
    65  	conn, err := grpc.Dial(lis.Addr().String(), copts...)
    66  	if err != nil {
    67  		lis.Close()
    68  		t.Fatalf("unable to create GRPC client: %s", err)
    69  	}
    70  	gc := protos.NewGomoteServiceClient(conn)
    71  	t.Cleanup(func() {
    72  		conn.Close()
    73  		s.Stop()
    74  		lis.Close()
    75  	})
    76  	return gc
    77  }
    78  
    79  func TestAuthenticate(t *testing.T) {
    80  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
    81  	client := setupGomoteTest(t, context.Background())
    82  	got, err := client.Authenticate(ctx, &protos.AuthenticateRequest{})
    83  	if err != nil {
    84  		t.Fatalf("client.Authenticate(ctx, request) = %v,  %s; want no error", got, err)
    85  	}
    86  }
    87  
    88  func TestAuthenticateError(t *testing.T) {
    89  	wantCode := codes.Unauthenticated
    90  	client := setupGomoteTest(t, context.Background())
    91  	_, err := client.Authenticate(context.Background(), &protos.AuthenticateRequest{})
    92  	if status.Code(err) != wantCode {
    93  		t.Fatalf("client.Authenticate(ctx, request) = _, %s; want %s", status.Code(err), wantCode)
    94  	}
    95  }
    96  
    97  func TestAddBootstrap(t *testing.T) {
    98  	client := setupGomoteTest(t, context.Background())
    99  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   100  	req := &protos.AddBootstrapRequest{
   101  		GomoteId: gomoteID,
   102  	}
   103  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   104  	got, err := client.AddBootstrap(ctx, req)
   105  	if err != nil {
   106  		t.Fatalf("client.AddBootstrap(ctx, %v) = %v, %s; want no error", req, got, err)
   107  	}
   108  }
   109  
   110  func TestAddBootstrapError(t *testing.T) {
   111  	// This test will create a gomote instance and attempt to call AddBootstrap.
   112  	// If overrideID is set to true, the test will use a different gomoteID than
   113  	// the one created for the test.
   114  	testCases := []struct {
   115  		desc       string
   116  		ctx        context.Context
   117  		overrideID bool
   118  		gomoteID   string // Used iff overrideID is true.
   119  		wantCode   codes.Code
   120  	}{
   121  		{
   122  			desc:     "unauthenticated request",
   123  			ctx:      context.Background(),
   124  			wantCode: codes.Unauthenticated,
   125  		},
   126  		{
   127  			desc:       "missing gomote id",
   128  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   129  			overrideID: true,
   130  			wantCode:   codes.NotFound,
   131  		},
   132  		{
   133  			desc:       "gomote does not exist",
   134  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   135  			overrideID: true,
   136  			gomoteID:   "xyz",
   137  			wantCode:   codes.NotFound,
   138  		},
   139  		{
   140  			desc:     "gomote is not owned by caller",
   141  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("user-x", "email-y")),
   142  			wantCode: codes.PermissionDenied,
   143  		},
   144  	}
   145  	for _, tc := range testCases {
   146  		t.Run(tc.desc, func(t *testing.T) {
   147  			client := setupGomoteTest(t, context.Background())
   148  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   149  			if tc.overrideID {
   150  				gomoteID = tc.gomoteID
   151  			}
   152  			req := &protos.AddBootstrapRequest{
   153  				GomoteId: gomoteID,
   154  			}
   155  			got, err := client.AddBootstrap(tc.ctx, req)
   156  			if err != nil && status.Code(err) != tc.wantCode {
   157  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   158  			}
   159  			if err == nil {
   160  				t.Fatalf("client.AddBootstrap(ctx, %v) = %v, nil; want error", req, got)
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  func TestCreateInstance(t *testing.T) {
   167  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   168  	req := &protos.CreateInstanceRequest{BuilderType: "linux-amd64"}
   169  	client := setupGomoteTest(t, context.Background())
   170  	stream, err := client.CreateInstance(ctx, req)
   171  	if err != nil {
   172  		t.Fatalf("client.CreateInstance(ctx, %v) = %v,  %s; want no error", req, stream, err)
   173  	}
   174  	var updateComplete bool
   175  	for {
   176  		update, err := stream.Recv()
   177  		if err == io.EOF && !updateComplete {
   178  			t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
   179  		}
   180  		if err == io.EOF {
   181  			break
   182  		}
   183  		if err != nil {
   184  			t.Fatalf("stream.Recv() = nil, %s; want no error", err)
   185  		}
   186  		if update.GetStatus() == protos.CreateInstanceResponse_COMPLETE {
   187  			updateComplete = true
   188  		}
   189  	}
   190  }
   191  
   192  func TestCreateInstanceError(t *testing.T) {
   193  	testCases := []struct {
   194  		desc     string
   195  		ctx      context.Context
   196  		request  *protos.CreateInstanceRequest
   197  		wantCode codes.Code
   198  	}{
   199  		{
   200  			desc:     "unauthenticated request",
   201  			ctx:      context.Background(),
   202  			request:  &protos.CreateInstanceRequest{},
   203  			wantCode: codes.Unauthenticated,
   204  		},
   205  		{
   206  			desc:     "missing builder type",
   207  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   208  			request:  &protos.CreateInstanceRequest{},
   209  			wantCode: codes.InvalidArgument,
   210  		},
   211  		{
   212  			desc: "invalid builder type",
   213  			ctx:  access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   214  			request: &protos.CreateInstanceRequest{
   215  				BuilderType: "funky-time-builder",
   216  			},
   217  			wantCode: codes.InvalidArgument,
   218  		},
   219  	}
   220  	for _, tc := range testCases {
   221  		t.Run(tc.desc, func(t *testing.T) {
   222  			client := setupGomoteTest(t, context.Background())
   223  
   224  			stream, err := client.CreateInstance(tc.ctx, tc.request)
   225  			if err != nil {
   226  				t.Fatalf("client.CreateInstance(ctx, %v) = %v,  %s; want no error", tc.request, stream, err)
   227  			}
   228  			for {
   229  				_, got := stream.Recv()
   230  				if got == io.EOF {
   231  					t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
   232  				}
   233  				if got != nil && status.Code(got) != tc.wantCode {
   234  					t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   235  				}
   236  				return
   237  			}
   238  		})
   239  	}
   240  }
   241  
   242  func TestInstanceAlive(t *testing.T) {
   243  	client := setupGomoteTest(t, context.Background())
   244  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   245  	req := &protos.InstanceAliveRequest{
   246  		GomoteId: gomoteID,
   247  	}
   248  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   249  	got, err := client.InstanceAlive(ctx, req)
   250  	if err != nil {
   251  		t.Fatalf("client.InstanceAlive(ctx, %v) = %v, %s; want no error", req, got, err)
   252  	}
   253  }
   254  
   255  func TestInstanceAliveError(t *testing.T) {
   256  	// This test will create a gomote instance and attempt to call InstanceAlive.
   257  	// If overrideID is set to true, the test will use a different gomoteID than
   258  	// the one created for the test.
   259  	testCases := []struct {
   260  		desc       string
   261  		ctx        context.Context
   262  		overrideID bool
   263  		gomoteID   string // Used iff overrideID is true.
   264  		wantCode   codes.Code
   265  	}{
   266  		{
   267  			desc:     "unauthenticated request",
   268  			ctx:      context.Background(),
   269  			wantCode: codes.Unauthenticated,
   270  		},
   271  		{
   272  			desc:       "missing gomote id",
   273  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   274  			overrideID: true,
   275  			wantCode:   codes.InvalidArgument,
   276  		},
   277  		{
   278  			desc:       "gomote does not exist",
   279  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   280  			overrideID: true,
   281  			gomoteID:   "xyz",
   282  			wantCode:   codes.NotFound,
   283  		},
   284  		{
   285  			desc:     "gomote is not owned by caller",
   286  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("user-x", "email-y")),
   287  			wantCode: codes.PermissionDenied,
   288  		},
   289  	}
   290  	for _, tc := range testCases {
   291  		t.Run(tc.desc, func(t *testing.T) {
   292  			client := setupGomoteTest(t, context.Background())
   293  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   294  			if tc.overrideID {
   295  				gomoteID = tc.gomoteID
   296  			}
   297  			req := &protos.InstanceAliveRequest{
   298  				GomoteId: gomoteID,
   299  			}
   300  			got, err := client.InstanceAlive(tc.ctx, req)
   301  			if err != nil && status.Code(err) != tc.wantCode {
   302  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   303  			}
   304  			if err == nil {
   305  				t.Fatalf("client.InstanceAlive(ctx, %v) = %v, nil; want error", req, got)
   306  			}
   307  		})
   308  	}
   309  }
   310  
   311  func TestListDirectory(t *testing.T) {
   312  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   313  	client := setupGomoteTest(t, context.Background())
   314  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   315  	if _, err := client.ListDirectory(ctx, &protos.ListDirectoryRequest{
   316  		GomoteId:  gomoteID,
   317  		Directory: "/foo",
   318  	}); err != nil {
   319  		t.Fatalf("client.RemoveFiles(ctx, req) = response, %s; want no error", err)
   320  	}
   321  }
   322  
   323  func TestListDirectoryError(t *testing.T) {
   324  	// This test will create a gomote instance and attempt to call ListDirectory.
   325  	// If overrideID is set to true, the test will use a different gomoteID than
   326  	// the one created for the test.
   327  	testCases := []struct {
   328  		desc       string
   329  		ctx        context.Context
   330  		overrideID bool
   331  		gomoteID   string // Used iff overrideID is true.
   332  		directory  string
   333  		recursive  bool
   334  		skipFiles  []string
   335  		digest     bool
   336  		wantCode   codes.Code
   337  	}{
   338  		{
   339  			desc:     "unauthenticated request",
   340  			ctx:      context.Background(),
   341  			wantCode: codes.Unauthenticated,
   342  		},
   343  		{
   344  			desc:       "missing gomote id",
   345  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   346  			overrideID: true,
   347  			gomoteID:   "",
   348  			wantCode:   codes.InvalidArgument,
   349  		},
   350  		{
   351  			desc:     "missing directory",
   352  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   353  			wantCode: codes.InvalidArgument,
   354  		},
   355  		{
   356  			desc:       "gomote does not exist",
   357  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   358  			overrideID: true,
   359  			gomoteID:   "chucky",
   360  			directory:  "/foo",
   361  			wantCode:   codes.NotFound,
   362  		},
   363  		{
   364  			desc:       "wrong gomote id",
   365  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   366  			overrideID: false,
   367  			directory:  "/foo",
   368  			wantCode:   codes.PermissionDenied,
   369  		},
   370  	}
   371  	for _, tc := range testCases {
   372  		t.Run(tc.desc, func(t *testing.T) {
   373  			client := setupGomoteTest(t, context.Background())
   374  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   375  			if tc.overrideID {
   376  				gomoteID = tc.gomoteID
   377  			}
   378  			req := &protos.ListDirectoryRequest{
   379  				GomoteId:  gomoteID,
   380  				Directory: tc.directory,
   381  				Recursive: false,
   382  				SkipFiles: []string{},
   383  				Digest:    false,
   384  			}
   385  			got, err := client.ListDirectory(tc.ctx, req)
   386  			if err != nil && status.Code(err) != tc.wantCode {
   387  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   388  			}
   389  			if err == nil {
   390  				t.Fatalf("client.RemoveFiles(ctx, %v) = %v, nil; want error", req, got)
   391  			}
   392  		})
   393  	}
   394  }
   395  
   396  func TestListInstance(t *testing.T) {
   397  	client := setupGomoteTest(t, context.Background())
   398  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   399  	var want []*protos.Instance
   400  	for i := 0; i < 3; i++ {
   401  		want = append(want, &protos.Instance{
   402  			GomoteId:    mustCreateInstance(t, client, fakeIAP()),
   403  			BuilderType: "linux-amd64",
   404  		})
   405  	}
   406  	mustCreateInstance(t, client, fakeIAPWithUser("user-x", "uuid-user-x"))
   407  	response, err := client.ListInstances(ctx, &protos.ListInstancesRequest{})
   408  	if err != nil {
   409  		t.Fatalf("client.ListInstances = nil, %s; want no error", err)
   410  	}
   411  	got := response.GetInstances()
   412  	if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields(&protos.Instance{}, "expires", "host_type")); diff != "" {
   413  		t.Errorf("ListInstances() mismatch (-want, +got):\n%s", diff)
   414  	}
   415  }
   416  
   417  func TestDestroyInstance(t *testing.T) {
   418  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   419  	client := setupGomoteTest(t, context.Background())
   420  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   421  	if _, err := client.DestroyInstance(ctx, &protos.DestroyInstanceRequest{
   422  		GomoteId: gomoteID,
   423  	}); err != nil {
   424  		t.Fatalf("client.DestroyInstance(ctx, req) = response, %s; want no error", err)
   425  	}
   426  }
   427  
   428  func TestDestroyInstanceError(t *testing.T) {
   429  	// This test will create a gomote instance and attempt to call DestroyInstance.
   430  	// If overrideID is set to true, the test will use a different gomoteID than
   431  	// the one created for the test.
   432  	testCases := []struct {
   433  		desc       string
   434  		ctx        context.Context
   435  		overrideID bool
   436  		gomoteID   string // Used iff overrideID is true.
   437  		wantCode   codes.Code
   438  	}{
   439  		{
   440  			desc:     "unauthenticated request",
   441  			ctx:      context.Background(),
   442  			wantCode: codes.Unauthenticated,
   443  		},
   444  		{
   445  			desc:       "missing gomote id",
   446  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   447  			overrideID: true,
   448  			gomoteID:   "",
   449  			wantCode:   codes.InvalidArgument,
   450  		},
   451  		{
   452  			desc:       "gomote does not exist",
   453  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   454  			overrideID: true,
   455  			gomoteID:   "chucky",
   456  			wantCode:   codes.NotFound,
   457  		},
   458  		{
   459  			desc:       "wrong gomote id",
   460  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   461  			overrideID: false,
   462  			wantCode:   codes.PermissionDenied,
   463  		},
   464  	}
   465  	for _, tc := range testCases {
   466  		t.Run(tc.desc, func(t *testing.T) {
   467  			client := setupGomoteTest(t, context.Background())
   468  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   469  			if tc.overrideID {
   470  				gomoteID = tc.gomoteID
   471  			}
   472  			req := &protos.DestroyInstanceRequest{
   473  				GomoteId: gomoteID,
   474  			}
   475  			got, err := client.DestroyInstance(tc.ctx, req)
   476  			if err != nil && status.Code(err) != tc.wantCode {
   477  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   478  			}
   479  			if err == nil {
   480  				t.Fatalf("client.DestroyInstance(ctx, %v) = %v, nil; want error", req, got)
   481  			}
   482  		})
   483  	}
   484  }
   485  
   486  func TestExecuteCommand(t *testing.T) {
   487  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   488  	client := setupGomoteTest(t, context.Background())
   489  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   490  	stream, err := client.ExecuteCommand(ctx, &protos.ExecuteCommandRequest{
   491  		GomoteId:          gomoteID,
   492  		Command:           "ls",
   493  		SystemLevel:       false,
   494  		Debug:             false,
   495  		AppendEnvironment: nil,
   496  		Path:              nil,
   497  		Directory:         "/workdir",
   498  		Args:              []string{"-alh"},
   499  	})
   500  	if err != nil {
   501  		t.Fatalf("client.ExecuteCommand(ctx, req) = response, %s; want no error", err)
   502  	}
   503  	var out []byte
   504  	for {
   505  		res, err := stream.Recv()
   506  		if err != nil && err == io.EOF {
   507  			break
   508  		}
   509  		if err != nil {
   510  			t.Fatalf("stream.Recv() = _, %s; want no error", err)
   511  		}
   512  		out = append(out, res.GetOutput()...)
   513  	}
   514  	if len(out) == 0 {
   515  		t.Fatalf("output: %q, expected non-empty", out)
   516  	}
   517  }
   518  
   519  func TestExecuteCommandError(t *testing.T) {
   520  	// This test will create a gomote instance and attempt to call TestExecuteCommand.
   521  	// If overrideID is set to true, the test will use a different gomoteID than
   522  	// the one created for the test.
   523  	testCases := []struct {
   524  		desc       string
   525  		ctx        context.Context
   526  		overrideID bool
   527  		gomoteID   string // Used iff overrideID is true.
   528  		cmd        string
   529  		wantCode   codes.Code
   530  	}{
   531  		{
   532  			desc:     "unauthenticated request",
   533  			ctx:      context.Background(),
   534  			wantCode: codes.Unauthenticated,
   535  		},
   536  		{
   537  			desc:       "missing gomote id",
   538  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   539  			overrideID: true,
   540  			gomoteID:   "",
   541  			wantCode:   codes.NotFound,
   542  		},
   543  		{
   544  			desc:     "missing command",
   545  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   546  			wantCode: codes.Aborted,
   547  		},
   548  		{
   549  			desc:       "gomote does not exist",
   550  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   551  			overrideID: true,
   552  			gomoteID:   "chucky",
   553  			cmd:        "ls",
   554  			wantCode:   codes.NotFound,
   555  		},
   556  		{
   557  			desc:       "wrong gomote id",
   558  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   559  			overrideID: false,
   560  			cmd:        "ls",
   561  			wantCode:   codes.PermissionDenied,
   562  		},
   563  	}
   564  	for _, tc := range testCases {
   565  		t.Run(tc.desc, func(t *testing.T) {
   566  			client := setupGomoteTest(t, context.Background())
   567  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   568  			if tc.overrideID {
   569  				gomoteID = tc.gomoteID
   570  			}
   571  			stream, err := client.ExecuteCommand(tc.ctx, &protos.ExecuteCommandRequest{
   572  				GomoteId:          gomoteID,
   573  				Command:           tc.cmd,
   574  				SystemLevel:       false,
   575  				Debug:             false,
   576  				AppendEnvironment: nil,
   577  				Path:              nil,
   578  				Directory:         "/workdir",
   579  				Args:              []string{"-alh"},
   580  			})
   581  			if err != nil {
   582  				t.Fatalf("unexpected error: %s", err)
   583  			}
   584  			res, err := stream.Recv()
   585  			if err != nil && status.Code(err) != tc.wantCode {
   586  				t.Fatalf("unexpected error: %s", err)
   587  			}
   588  			if err == nil {
   589  				t.Fatalf("client.ExecuteCommand(ctx, req) = %v, nil; want error", res)
   590  			}
   591  		})
   592  	}
   593  }
   594  
   595  func TestReadTGZToURLError(t *testing.T) {
   596  	// This test will create a gomote instance and attempt to call ReadTGZToURL.
   597  	// If overrideID is set to true, the test will use a different gomoteID than
   598  	// the one created for the test.
   599  	testCases := []struct {
   600  		desc       string
   601  		ctx        context.Context
   602  		overrideID bool
   603  		gomoteID   string // Used iff overrideID is true.
   604  		directory  string
   605  		wantCode   codes.Code
   606  	}{
   607  		{
   608  			desc:     "unauthenticated request",
   609  			ctx:      context.Background(),
   610  			wantCode: codes.Unauthenticated,
   611  		},
   612  		{
   613  			desc:       "missing gomote id",
   614  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   615  			overrideID: true,
   616  			gomoteID:   "",
   617  			wantCode:   codes.NotFound,
   618  		},
   619  		{
   620  			desc:       "gomote does not exist",
   621  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   622  			overrideID: true,
   623  			gomoteID:   "chucky",
   624  			wantCode:   codes.NotFound,
   625  		},
   626  		{
   627  			desc:       "wrong gomote id",
   628  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   629  			overrideID: false,
   630  			wantCode:   codes.PermissionDenied,
   631  		},
   632  	}
   633  	for _, tc := range testCases {
   634  		t.Run(tc.desc, func(t *testing.T) {
   635  			client := setupGomoteTest(t, context.Background())
   636  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   637  			if tc.overrideID {
   638  				gomoteID = tc.gomoteID
   639  			}
   640  			req := &protos.ReadTGZToURLRequest{
   641  				GomoteId:  gomoteID,
   642  				Directory: tc.directory,
   643  			}
   644  			got, err := client.ReadTGZToURL(tc.ctx, req)
   645  			if err != nil && status.Code(err) != tc.wantCode {
   646  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   647  			}
   648  			if err == nil {
   649  				t.Fatalf("client.ReadTGZToURL(ctx, %v) = %v, nil; want error", req, got)
   650  			}
   651  		})
   652  	}
   653  }
   654  
   655  func TestRemoveFiles(t *testing.T) {
   656  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   657  	client := setupGomoteTest(t, context.Background())
   658  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   659  	if _, err := client.RemoveFiles(ctx, &protos.RemoveFilesRequest{
   660  		GomoteId: gomoteID,
   661  		Paths:    []string{"temp_file.log"},
   662  	}); err != nil {
   663  		t.Fatalf("client.RemoveFiles(ctx, req) = response, %s; want no error", err)
   664  	}
   665  }
   666  
   667  func TestRemoveFilesError(t *testing.T) {
   668  	// This test will create a gomote instance and attempt to call RemoveFiles.
   669  	// If overrideID is set to true, the test will use a different gomoteID than
   670  	// the one created for the test.
   671  	testCases := []struct {
   672  		desc       string
   673  		ctx        context.Context
   674  		overrideID bool
   675  		gomoteID   string // Used iff overrideID is true.
   676  		paths      []string
   677  		wantCode   codes.Code
   678  	}{
   679  		{
   680  			desc:     "unauthenticated request",
   681  			ctx:      context.Background(),
   682  			wantCode: codes.Unauthenticated,
   683  		},
   684  		{
   685  			desc:       "missing gomote id",
   686  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   687  			overrideID: true,
   688  			gomoteID:   "",
   689  			wantCode:   codes.InvalidArgument,
   690  		},
   691  		{
   692  			desc:     "missing paths",
   693  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   694  			paths:    []string{},
   695  			wantCode: codes.InvalidArgument,
   696  		},
   697  		{
   698  			desc:       "gomote does not exist",
   699  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   700  			overrideID: true,
   701  			gomoteID:   "chucky",
   702  			paths:      []string{"file.a"},
   703  			wantCode:   codes.NotFound,
   704  		},
   705  		{
   706  			desc:       "wrong gomote id",
   707  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   708  			overrideID: false,
   709  			paths:      []string{"file.a"},
   710  			wantCode:   codes.PermissionDenied,
   711  		},
   712  	}
   713  	for _, tc := range testCases {
   714  		t.Run(tc.desc, func(t *testing.T) {
   715  			client := setupGomoteTest(t, context.Background())
   716  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   717  			if tc.overrideID {
   718  				gomoteID = tc.gomoteID
   719  			}
   720  			req := &protos.RemoveFilesRequest{
   721  				GomoteId: gomoteID,
   722  				Paths:    tc.paths,
   723  			}
   724  			got, err := client.RemoveFiles(tc.ctx, req)
   725  			if err != nil && status.Code(err) != tc.wantCode {
   726  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   727  			}
   728  			if err == nil {
   729  				t.Fatalf("client.RemoveFiles(ctx, %v) = %v, nil; want error", req, got)
   730  			}
   731  		})
   732  	}
   733  }
   734  
   735  func TestSignSSHKey(t *testing.T) {
   736  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   737  	client := setupGomoteTest(t, context.Background())
   738  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   739  	if _, err := client.SignSSHKey(ctx, &protos.SignSSHKeyRequest{
   740  		GomoteId:     gomoteID,
   741  		PublicSshKey: []byte(devCertCAPublic),
   742  	}); err != nil {
   743  		t.Fatalf("client.SignSSHKey(ctx, req) = response, %s; want no error", err)
   744  	}
   745  }
   746  
   747  func TestSignSSHKeyError(t *testing.T) {
   748  	// This test will create a gomote instance and attempt to call SignSSHKey.
   749  	// If overrideID is set to true, the test will use a different gomoteID than
   750  	// the one created for the test.
   751  	testCases := []struct {
   752  		desc          string
   753  		ctx           context.Context
   754  		overrideID    bool
   755  		gomoteID      string // Used iff overrideID is true.
   756  		publickSSHKey []byte
   757  		wantCode      codes.Code
   758  	}{
   759  		{
   760  			desc:     "unauthenticated request",
   761  			ctx:      context.Background(),
   762  			wantCode: codes.Unauthenticated,
   763  		},
   764  		{
   765  			desc:       "missing gomote id",
   766  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   767  			overrideID: true,
   768  			gomoteID:   "",
   769  			wantCode:   codes.NotFound,
   770  		},
   771  		{
   772  			desc:     "missing public key",
   773  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   774  			wantCode: codes.InvalidArgument,
   775  		},
   776  		{
   777  			desc:          "gomote does not exist",
   778  			ctx:           access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   779  			overrideID:    true,
   780  			gomoteID:      "chucky",
   781  			publickSSHKey: []byte(devCertCAPublic),
   782  			wantCode:      codes.NotFound,
   783  		},
   784  		{
   785  			desc:          "wrong gomote id",
   786  			ctx:           access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   787  			overrideID:    false,
   788  			publickSSHKey: []byte(devCertCAPublic),
   789  			wantCode:      codes.PermissionDenied,
   790  		},
   791  	}
   792  	for _, tc := range testCases {
   793  		t.Run(tc.desc, func(t *testing.T) {
   794  			client := setupGomoteTest(t, context.Background())
   795  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   796  			if tc.overrideID {
   797  				gomoteID = tc.gomoteID
   798  			}
   799  			req := &protos.SignSSHKeyRequest{
   800  				GomoteId:     gomoteID,
   801  				PublicSshKey: tc.publickSSHKey,
   802  			}
   803  			got, err := client.SignSSHKey(tc.ctx, req)
   804  			if err != nil && status.Code(err) != tc.wantCode {
   805  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   806  			}
   807  			if err == nil {
   808  				t.Fatalf("client.SignSSHKey(ctx, %v) = %v, nil; want error", req, got)
   809  			}
   810  		})
   811  	}
   812  }
   813  
   814  func TestUploadFile(t *testing.T) {
   815  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   816  	client := setupGomoteTest(t, context.Background())
   817  	_ = mustCreateInstance(t, client, fakeIAP())
   818  	if _, err := client.UploadFile(ctx, &protos.UploadFileRequest{}); err != nil {
   819  		t.Fatalf("client.UploadFile(ctx, req) = response, %s; want no error", err)
   820  	}
   821  }
   822  
   823  func TestUploadFileError(t *testing.T) {
   824  	// This test will create a gomote instance and attempt to call UploadFile.
   825  	// If overrideID is set to true, the test will use a different gomoteID than
   826  	// the one created for the test.
   827  	testCases := []struct {
   828  		desc       string
   829  		ctx        context.Context
   830  		overrideID bool
   831  		filename   string
   832  		wantCode   codes.Code
   833  	}{
   834  		{
   835  			desc:     "unauthenticated request",
   836  			ctx:      context.Background(),
   837  			wantCode: codes.Unauthenticated,
   838  		},
   839  	}
   840  	for _, tc := range testCases {
   841  		t.Run(tc.desc, func(t *testing.T) {
   842  			client := setupGomoteTest(t, context.Background())
   843  			_ = mustCreateInstance(t, client, fakeIAP())
   844  			req := &protos.UploadFileRequest{}
   845  			got, err := client.UploadFile(tc.ctx, req)
   846  			if err != nil && status.Code(err) != tc.wantCode {
   847  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   848  			}
   849  			if err == nil {
   850  				t.Fatalf("client.UploadFile(ctx, %v) = %v, nil; want error", req, got)
   851  			}
   852  		})
   853  	}
   854  }
   855  
   856  // TODO(go.dev/issue/48737) add test for files on GCS
   857  func TestWriteFileFromURL(t *testing.T) {
   858  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   859  	client := setupGomoteTest(t, context.Background())
   860  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   861  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   862  		fmt.Fprintln(w, "Go is an open source programming language")
   863  	}))
   864  	defer ts.Close()
   865  	if _, err := client.WriteFileFromURL(ctx, &protos.WriteFileFromURLRequest{
   866  		GomoteId: gomoteID,
   867  		Url:      ts.URL,
   868  		Filename: "foo",
   869  		Mode:     0777,
   870  	}); err != nil {
   871  		t.Fatalf("client.WriteFileFromURL(ctx, req) = response, %s; want no error", err)
   872  	}
   873  }
   874  
   875  func TestWriteFileFromURLError(t *testing.T) {
   876  	// This test will create a gomote instance and attempt to call TestWriteFileFromURL.
   877  	// If overrideID is set to true, the test will use a different gomoteID than
   878  	// the one created for the test.
   879  	testCases := []struct {
   880  		desc       string
   881  		ctx        context.Context
   882  		overrideID bool
   883  		gomoteID   string // Used iff overrideID is true.
   884  		url        string
   885  		filename   string
   886  		mode       uint32
   887  		wantCode   codes.Code
   888  	}{
   889  		{
   890  			desc:     "unauthenticated request",
   891  			ctx:      context.Background(),
   892  			wantCode: codes.Unauthenticated,
   893  		},
   894  		{
   895  			desc:       "missing gomote id",
   896  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   897  			overrideID: true,
   898  			gomoteID:   "",
   899  			wantCode:   codes.NotFound,
   900  		},
   901  		{
   902  			desc:       "gomote does not exist",
   903  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   904  			overrideID: true,
   905  			gomoteID:   "chucky",
   906  			url:        "go.dev/dl/1_14.tar.gz",
   907  			wantCode:   codes.NotFound,
   908  		},
   909  		{
   910  			desc:       "wrong gomote id",
   911  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
   912  			overrideID: false,
   913  			url:        "go.dev/dl/1_14.tar.gz",
   914  			wantCode:   codes.PermissionDenied,
   915  		},
   916  	}
   917  	for _, tc := range testCases {
   918  		t.Run(tc.desc, func(t *testing.T) {
   919  			client := setupGomoteTest(t, context.Background())
   920  			gomoteID := mustCreateInstance(t, client, fakeIAP())
   921  			if tc.overrideID {
   922  				gomoteID = tc.gomoteID
   923  			}
   924  			req := &protos.WriteFileFromURLRequest{
   925  				GomoteId: gomoteID,
   926  				Url:      tc.url,
   927  				Filename: tc.filename,
   928  				Mode:     0,
   929  			}
   930  			got, err := client.WriteFileFromURL(tc.ctx, req)
   931  			if err != nil && status.Code(err) != tc.wantCode {
   932  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
   933  			}
   934  			if err == nil {
   935  				t.Fatalf("client.WriteFileFromURL(ctx, %v) = %v, nil; want error", req, got)
   936  			}
   937  		})
   938  	}
   939  }
   940  
   941  func TestWriteTGZFromURL(t *testing.T) {
   942  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   943  	client := setupGomoteTest(t, context.Background())
   944  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   945  	if _, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
   946  		GomoteId:  gomoteID,
   947  		Directory: "foo",
   948  		Url:       `https://go.dev/dl/go1.17.6.linux-amd64.tar.gz`,
   949  	}); err != nil {
   950  		t.Fatalf("client.WriteTGZFromURL(ctx, req) = response, %s; want no error", err)
   951  	}
   952  }
   953  
   954  func TestWriteTGZFromURLGomoteStaging(t *testing.T) {
   955  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
   956  	client := setupGomoteTest(t, context.Background())
   957  	gomoteID := mustCreateInstance(t, client, fakeIAP())
   958  	if _, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
   959  		GomoteId:  gomoteID,
   960  		Directory: "foo",
   961  		Url:       fmt.Sprintf("https://storage.googleapis.com/%s/go1.17.6.linux-amd64.tar.gz?field=x", testBucketName),
   962  	}); err != nil {
   963  		t.Fatalf("client.WriteTGZFromURL(ctx, req) = response, %s; want no error", err)
   964  	}
   965  }
   966  
   967  func TestWriteTGZFromURLError(t *testing.T) {
   968  	// This test will create a gomote instance and attempt to call TestWriteTGZFromURL.
   969  	// If overrideID is set to true, the test will use a different gomoteID than
   970  	// the one created for the test.
   971  	testCases := []struct {
   972  		desc       string
   973  		ctx        context.Context
   974  		overrideID bool
   975  		gomoteID   string // Used iff overrideID is true.
   976  		url        string
   977  		directory  string
   978  		wantCode   codes.Code
   979  	}{
   980  		{
   981  			desc:     "unauthenticated request",
   982  			ctx:      context.Background(),
   983  			wantCode: codes.Unauthenticated,
   984  		},
   985  		{
   986  			desc:       "missing gomote id",
   987  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   988  			overrideID: true,
   989  			gomoteID:   "",
   990  			wantCode:   codes.InvalidArgument,
   991  		},
   992  		{
   993  			desc:     "missing URL",
   994  			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
   995  			wantCode: codes.InvalidArgument,
   996  		},
   997  		{
   998  			desc:       "gomote does not exist",
   999  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
  1000  			overrideID: true,
  1001  			gomoteID:   "chucky",
  1002  			url:        "go.dev/dl/1_14.tar.gz",
  1003  			wantCode:   codes.NotFound,
  1004  		},
  1005  		{
  1006  			desc:       "wrong gomote id",
  1007  			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
  1008  			overrideID: false,
  1009  			url:        "go.dev/dl/1_14.tar.gz",
  1010  			wantCode:   codes.PermissionDenied,
  1011  		},
  1012  	}
  1013  	for _, tc := range testCases {
  1014  		t.Run(tc.desc, func(t *testing.T) {
  1015  			client := setupGomoteTest(t, context.Background())
  1016  			gomoteID := mustCreateInstance(t, client, fakeIAP())
  1017  			if tc.overrideID {
  1018  				gomoteID = tc.gomoteID
  1019  			}
  1020  			req := &protos.WriteTGZFromURLRequest{
  1021  				GomoteId:  gomoteID,
  1022  				Url:       tc.url,
  1023  				Directory: tc.directory,
  1024  			}
  1025  			got, err := client.WriteTGZFromURL(tc.ctx, req)
  1026  			if err != nil && status.Code(err) != tc.wantCode {
  1027  				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
  1028  			}
  1029  			if err == nil {
  1030  				t.Fatalf("client.WriteTGZFromURL(ctx, %v) = %v, nil; want error", req, got)
  1031  			}
  1032  		})
  1033  	}
  1034  }
  1035  
  1036  func TestIsPrivilegedUser(t *testing.T) {
  1037  	in := "accounts.google.com:example@google.com"
  1038  	if !isPrivilegedUser(in) {
  1039  		t.Errorf("isPrivilagedUser(%q) = false; want true", in)
  1040  	}
  1041  }
  1042  
  1043  func TestObjectFromURL(t *testing.T) {
  1044  	url := `https://storage.googleapis.com/example-bucket/cat.jpeg`
  1045  	bucket := "example-bucket"
  1046  	wantObject := "cat.jpeg"
  1047  	object, err := objectFromURL(bucket, url)
  1048  	if err != nil {
  1049  		t.Fatalf("urlToBucketObject(%q) = %q, %s; want %q, no error", url, object, err, wantObject)
  1050  	}
  1051  	if object != wantObject {
  1052  		t.Fatalf("urlToBucketObject(%q) = %q; want %q", url, object, wantObject)
  1053  	}
  1054  }
  1055  
  1056  func TestObjectFromURLError(t *testing.T) {
  1057  	bucket := "example-bucket"
  1058  	object := "cat.jpeg"
  1059  	url := fmt.Sprintf("https://bunker.googleapis.com/%s/%s", bucket, object)
  1060  	got, err := objectFromURL(bucket, url)
  1061  	if err == nil {
  1062  		t.Fatalf("urlToBucketObject(url) = %q, nil; want \"\", error", got)
  1063  	}
  1064  }
  1065  
  1066  func TestEmailToUser(t *testing.T) {
  1067  	testCases := []struct {
  1068  		desc  string
  1069  		email string
  1070  		want  string
  1071  	}{
  1072  		{"valid email", "accounts.google.com:example@gmail.com", "example"},
  1073  		{"valid email", "accounts.google.com:mary@google.com", "mary"},
  1074  		{"valid email", "accounts.google.com:george@funky.com", "george"},
  1075  		{"single digit local", "accounts.google.com:g@funky.com", "g"},
  1076  		{"single digit domain", "accounts.google.com:g@funky.com", "g"},
  1077  		{"multiple colon", "accounts.google.com:example@gmail.com:more-info", "example"},                        // while not desired, wont lead to a panic
  1078  		{"multiple at", "accounts.google.com:example@gmail.com:example@gmail.com", "example@gmail.com:example"}, // while not desired, wont lead to a panic
  1079  	}
  1080  	for _, tc := range testCases {
  1081  		t.Run(tc.desc, func(t *testing.T) {
  1082  			if got, err := emailToUser(tc.email); got != tc.want || err != nil {
  1083  				t.Errorf("emailToUser(%q) = %q, %s; want %q, no error", tc.email, got, err, tc.want)
  1084  			}
  1085  		})
  1086  	}
  1087  }
  1088  
  1089  func TestEmailToUserError(t *testing.T) {
  1090  	testCases := []struct {
  1091  		desc  string
  1092  		email string
  1093  	}{
  1094  		{"no local", "accounts.google.com:@funky.com"},
  1095  		{"incorrect authority", "accountsxgoogleycom:george@funky.com"},
  1096  		{"hyphens authority", "accounts-google-com:george@funky.com"},
  1097  		{"no domain", "accounts.google.com:george@"},
  1098  		{"missing colon", "accounts.google.comxgeorge@a.b"},
  1099  	}
  1100  	for _, tc := range testCases {
  1101  		t.Run(tc.desc, func(t *testing.T) {
  1102  			if got, err := emailToUser(tc.email); err == nil {
  1103  				t.Errorf("emailToUser(%q) = %q, no error; want error", tc.email, got)
  1104  			}
  1105  		})
  1106  	}
  1107  }
  1108  
  1109  func fakeAuthContext(ctx context.Context, privileged bool) context.Context {
  1110  	iap := access.IAPFields{
  1111  		Email: "accounts.google.com:example@gmail.com",
  1112  		ID:    "accounts.google.com:randomuuidstuff",
  1113  	}
  1114  	if privileged {
  1115  		iap.Email = "accounts.google.com:test@google.com"
  1116  	}
  1117  	return access.ContextWithIAP(ctx, iap)
  1118  }
  1119  
  1120  func fakeIAP() access.IAPFields {
  1121  	return fakeIAPWithUser("example", "randomuuidstuff")
  1122  }
  1123  
  1124  func fakeIAPWithUser(user string, id string) access.IAPFields {
  1125  	return access.IAPFields{
  1126  		Email: fmt.Sprintf("accounts.google.com:%s@gmail.com", user),
  1127  		ID:    fmt.Sprintf("accounts.google.com:%s", id),
  1128  	}
  1129  }
  1130  
  1131  func mustCreateInstance(t *testing.T, client protos.GomoteServiceClient, iap access.IAPFields) string {
  1132  	req := &protos.CreateInstanceRequest{
  1133  		BuilderType: "linux-amd64",
  1134  	}
  1135  	stream, err := client.CreateInstance(access.FakeContextWithOutgoingIAPAuth(context.Background(), iap), req)
  1136  	if err != nil {
  1137  		t.Fatalf("client.CreateInstance(ctx, %v) = %v,  %s; want no error", req, stream, err)
  1138  	}
  1139  	var updateComplete bool
  1140  	var gomoteID string
  1141  	for {
  1142  		update, err := stream.Recv()
  1143  		if err == io.EOF && !updateComplete {
  1144  			t.Fatal("stream.Recv = stream, io.EOF; want no EOF")
  1145  		}
  1146  		if err == io.EOF {
  1147  			break
  1148  		}
  1149  		if err != nil {
  1150  			t.Fatalf("stream.Recv() = nil, %s; want no error", err)
  1151  		}
  1152  		if update.GetStatus() == protos.CreateInstanceResponse_COMPLETE {
  1153  			gomoteID = update.Instance.GetGomoteId()
  1154  			updateComplete = true
  1155  		}
  1156  	}
  1157  	return gomoteID
  1158  }
  1159  
  1160  const (
  1161  	// devCertCAPrivate is a private SSH CA certificate to be used for development.
  1162  	devCertCAPrivate = `-----BEGIN OPENSSH PRIVATE KEY-----
  1163  b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
  1164  QyNTUxOQAAACCVd2FJ3Db/oV53iRDt1RLscTn41hYXbunuCWIlXze2WAAAAJhjy3ePY8t3
  1165  jwAAAAtzc2gtZWQyNTUxOQAAACCVd2FJ3Db/oV53iRDt1RLscTn41hYXbunuCWIlXze2WA
  1166  AAAEALuUJMb/rEaFNa+vn5RejeoBiiViyda7djgEvMnQ8fRJV3YUncNv+hXneJEO3VEuxx
  1167  OfjWFhdu6e4JYiVfN7ZYAAAAE3Rlc3R1c2VyQGdvbGFuZy5vcmcBAg==
  1168  -----END OPENSSH PRIVATE KEY-----`
  1169  
  1170  	// devCertCAPublic is a public SSH CA certificate to be used for development.
  1171  	devCertCAPublic = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJV3YUncNv+hXneJEO3VEuxxOfjWFhdu6e4JYiVfN7ZY testuser@golang.org`
  1172  )
  1173  
  1174  type fakeBucketHandler struct{ bucketName string }
  1175  
  1176  func (fbc *fakeBucketHandler) GenerateSignedPostPolicyV4(object string, opts *storage.PostPolicyV4Options) (*storage.PostPolicyV4, error) {
  1177  	if object == "" || opts == nil {
  1178  		return nil, errors.New("invalid arguments")
  1179  	}
  1180  	return &storage.PostPolicyV4{
  1181  		URL: fmt.Sprintf("https://localhost/%s/%s", fbc.bucketName, object),
  1182  		Fields: map[string]string{
  1183  			"x-permission-to-post": "granted",
  1184  		},
  1185  	}, nil
  1186  }
  1187  
  1188  func (fbc *fakeBucketHandler) SignedURL(object string, opts *storage.SignedURLOptions) (string, error) {
  1189  	if object == "" || opts == nil {
  1190  		return "", errors.New("invalid arguments")
  1191  	}
  1192  	return fmt.Sprintf("https://localhost/%s?X-Goog-Algorithm=GOOG4-yyy", object), nil
  1193  }
  1194  
  1195  func (fbc *fakeBucketHandler) Object(name string) *storage.ObjectHandle {
  1196  	return &storage.ObjectHandle{}
  1197  }