Skip to content

Commit 7ca839f

Browse files
committed
remote asset: reject unsupported Qualifiers
The specification requires that any `Fetch` requests containing unsupported qualifiers are rejected with an `INVALID_ARGUMENT` rpc error: - https://github.com/bazelbuild/remote-apis/blob/7f922028fcfac63bdd8431e68de152d9e7a9e2a0/build/bazel/remote/asset/v1/remote_asset.proto#L109-L112 - https://github.com/bazelbuild/remote-apis/blob/7f922028fcfac63bdd8431e68de152d9e7a9e2a0/build/bazel/remote/asset/v1/remote_asset.proto#L127-L128 Additionally, the status returned should include a `BadRequest` error detail with `FieldViolation`s indicating the names of unsupported qualifiers: - https://github.com/bazelbuild/remote-apis/blob/7f922028fcfac63bdd8431e68de152d9e7a9e2a0/build/bazel/remote/asset/v1/remote_asset.proto#L139-L143
1 parent 5cd6081 commit 7ca839f

3 files changed

Lines changed: 88 additions & 6 deletions

File tree

server/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ go_library(
3131
"@com_github_mostynb_zstdpool_syncpool//:go_default_library",
3232
"@org_golang_google_genproto_googleapis_bytestream//:go_default_library",
3333
"@org_golang_google_genproto_googleapis_rpc//code:go_default_library",
34+
"@org_golang_google_genproto_googleapis_rpc//errdetails:go_default_library",
3435
"@org_golang_google_genproto_googleapis_rpc//status:go_default_library",
3536
"@org_golang_google_grpc//:go_default_library",
3637
"@org_golang_google_grpc//codes:go_default_library",
@@ -64,6 +65,7 @@ go_test(
6465
"@com_github_google_uuid//:go_default_library",
6566
"@com_github_klauspost_compress//zstd:go_default_library",
6667
"@org_golang_google_genproto_googleapis_bytestream//:go_default_library",
68+
"@org_golang_google_genproto_googleapis_rpc//errdetails:go_default_library",
6769
"@org_golang_google_grpc//:go_default_library",
6870
"@org_golang_google_grpc//codes:go_default_library",
6971
"@org_golang_google_grpc//credentials/insecure:go_default_library",

server/grpc_asset.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/url"
1313
"strings"
1414

15+
"google.golang.org/genproto/googleapis/rpc/errdetails"
1516
"google.golang.org/genproto/googleapis/rpc/status"
1617
"google.golang.org/grpc/codes"
1718
grpc_status "google.golang.org/grpc/status"
@@ -64,6 +65,7 @@ func (s *grpcServer) FetchBlob(ctx context.Context, req *asset.FetchBlobRequest)
6465

6566
headers := http.Header{}
6667

68+
var unsupportedQualifierNames []string
6769
for _, q := range req.GetQualifiers() {
6870
if q == nil {
6971
return nil, errNilQualifier
@@ -93,12 +95,17 @@ func (s *grpcServer) FetchBlob(ctx context.Context, req *asset.FetchBlobRequest)
9395
}
9496

9597
sha256Str = hex.EncodeToString(decoded)
98+
continue
99+
}
96100

97-
found, size := s.cache.Contains(ctx, cache.CAS, sha256Str, -1)
98-
if !found {
99-
continue
100-
}
101+
unsupportedQualifierNames = append(unsupportedQualifierNames, q.Name)
102+
}
103+
if len(unsupportedQualifierNames) > 0 {
104+
return nil, s.unsupportedQualifiersErrStatus(unsupportedQualifierNames)
105+
}
101106

107+
if len(sha256Str) != 0 {
108+
if found, size := s.cache.Contains(ctx, cache.CAS, sha256Str, -1); found {
102109
if size < 0 {
103110
// We don't know the size yet (bad http backend?).
104111
r, actualSize, err := s.cache.Get(ctx, cache.CAS, sha256Str, -1, 0)
@@ -108,7 +115,8 @@ func (s *grpcServer) FetchBlob(ctx context.Context, req *asset.FetchBlobRequest)
108115
if err != nil || actualSize < 0 {
109116
s.errorLogger.Printf("failed to get CAS %s from proxy backend size: %d err: %v",
110117
sha256Str, actualSize, err)
111-
continue
118+
return nil, grpc_status.Error(codes.Internal, fmt.Sprintf("failed to get CAS %s from proxy backend size: %d err: %v",
119+
sha256Str, actualSize, err))
112120
}
113121
size = actualSize
114122
}
@@ -214,6 +222,25 @@ func (s *grpcServer) fetchItem(ctx context.Context, uri string, headers http.Hea
214222
return true, expectedHash, expectedSize
215223
}
216224

225+
// unsupportedQualifiersErrStatus creates a gRPC status error that includes a list of unsupported qualifiers.
226+
func (s *grpcServer) unsupportedQualifiersErrStatus(qualifierNames []string) error {
227+
fieldViolations := make([]*errdetails.BadRequest_FieldViolation, 0, len(qualifierNames))
228+
for _, name := range qualifierNames {
229+
fieldViolations = append(fieldViolations, &errdetails.BadRequest_FieldViolation{
230+
Field: "qualifiers.name",
231+
Description: fmt.Sprintf("%q not supported", name),
232+
})
233+
}
234+
statusWithoutDetails := grpc_status.New(codes.InvalidArgument, fmt.Sprintf("Unsupported qualifiers: %s", strings.Join(qualifierNames, ", ")))
235+
statusWithDetails, err := statusWithoutDetails.WithDetails(&errdetails.BadRequest{FieldViolations: fieldViolations})
236+
// should never happen
237+
if err != nil {
238+
s.errorLogger.Printf("failed to add details to status: %v", err)
239+
return statusWithoutDetails.Err()
240+
}
241+
return statusWithDetails.Err()
242+
}
243+
217244
func (s *grpcServer) FetchDirectory(context.Context, *asset.FetchDirectoryRequest) (*asset.FetchDirectoryResponse, error) {
218245
return nil, nil
219246
}

server/grpc_asset_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import (
1111
"testing"
1212

1313
asset "github.com/buchgr/bazel-remote/v2/genproto/build/bazel/remote/asset/v1"
14-
//pb "github.com/buchgr/bazel-remote/v2/genproto/build/bazel/remote/execution/v2"
1514

15+
"google.golang.org/genproto/googleapis/rpc/errdetails"
1616
"google.golang.org/grpc/codes"
17+
"google.golang.org/grpc/status"
18+
"google.golang.org/protobuf/proto"
1719

1820
testutils "github.com/buchgr/bazel-remote/v2/utils"
1921
)
@@ -63,6 +65,57 @@ func TestAssetFetchBlob(t *testing.T) {
6365
}
6466
}
6567

68+
func TestAssetUnsupportedQualifier(t *testing.T) {
69+
t.Parallel()
70+
71+
fixture := grpcTestSetup(t)
72+
defer os.Remove(fixture.tempdir)
73+
74+
ts := newTestGetServer()
75+
76+
req := asset.FetchBlobRequest{
77+
Uris: []string{
78+
ts.srv.URL + "/" + ts.path, // This URL should work.
79+
},
80+
Qualifiers: []*asset.Qualifier{
81+
{
82+
Name: "unknown-qualifier",
83+
Value: "some-value",
84+
},
85+
},
86+
}
87+
88+
_, err := fixture.assetClient.FetchBlob(ctx, &req)
89+
if err == nil {
90+
t.Fatal(err, "expected rpc error from fetch")
91+
}
92+
gstatus, ok := status.FromError(err)
93+
if !ok {
94+
t.Fatal("expected a status in rpc error")
95+
}
96+
if gstatus.Code() != codes.InvalidArgument {
97+
t.Fatalf("expected %v status code, got %v", codes.InvalidArgument, gstatus.Code())
98+
}
99+
if len(gstatus.Details()) != 1 {
100+
t.Fatal("expected one detail in rpc status")
101+
}
102+
expectedDetail := &errdetails.BadRequest{
103+
FieldViolations: []*errdetails.BadRequest_FieldViolation{
104+
{
105+
Field: "qualifiers.name",
106+
Description: `"unknown-qualifier" not supported`,
107+
},
108+
},
109+
}
110+
protoDetail, ok := gstatus.Details()[0].(*errdetails.BadRequest)
111+
if !ok {
112+
t.Fatal("expected BadRequest detail in rpc status")
113+
}
114+
if !proto.Equal(protoDetail, expectedDetail) {
115+
t.Fatalf("expected %v BadRequest, got %v", expectedDetail, protoDetail)
116+
}
117+
}
118+
66119
type testGetServer struct {
67120
srv *httptest.Server
68121

0 commit comments

Comments
 (0)