diff --git a/Makefile b/Makefile index f40320156..418f46ff8 100644 --- a/Makefile +++ b/Makefile @@ -613,6 +613,7 @@ GO_GENERATE_PACKAGES := \ ./platform/extension/hook/... \ ./platform/extension/messagequeue/... \ ./runway/extension/merger/... \ + ./stovepipe/controller/... \ ./stovepipe/core/requestlog/... \ ./stovepipe/extension/buildrunner/... \ ./stovepipe/extension/projectstatus/... \ diff --git a/api/stovepipe/proto/stovepipe.proto b/api/stovepipe/proto/stovepipe.proto index 24b657d79..c0050c51c 100644 --- a/api/stovepipe/proto/stovepipe.proto +++ b/api/stovepipe/proto/stovepipe.proto @@ -106,6 +106,20 @@ message GetRequestHistoryByURIResponse { repeated RequestHistory histories = 1; } +// GetProjectStatusByIDRequest selects the validation status for the ID returned from Ingest. +message GetProjectStatusByIDRequest { + // Logical queue containing the validation request. + string queue = 1; + // Canonical decimal request identifier returned from Ingest. + string request_id = 2; + // Required consumer-defined project IDs whose recorded results are requested. + repeated string projects = 3; + // Maximum requested projects to return in one page. Zero selects the server default. + int32 page_size = 4; + // Opaque continuation token for requested-project result pagination. + string page_token = 5; +} + // GetProjectStatusByURIRequest selects the validation status for an exact commit URI. message GetProjectStatusByURIRequest { // Logical queue containing the validation request. @@ -130,6 +144,32 @@ message ProjectValidation { } } +// GetProjectStatusByIDResponse contains the selected validation's current projection. +message GetProjectStatusByIDResponse { + // Queue-scoped decimal identifier of the validation request. + string request_id = 1; + // Logical queue containing the validation request. + string queue = 2; + // VCS-agnostic URI of the commit under validation. + string change_uri = 3; + // Baseline URI for incremental validation. Empty for a full build. + string base_uri = 4; + // Stable public lifecycle state of the validation request. + string request_state = 5; + // Unix millisecond timestamp of the newest durable lifecycle or result record. + int64 updated_at_ms = 10; + // Whole-repository breakage degree. Absent until the result is durable. + oneof repository_result { + double repository_breakage_degree = 6; + } + // True after the implementation has durably finished producing relevant project results. + bool project_results_complete = 7; + // Recorded results for requested projects. + repeated ProjectValidation projects = 8; + // Opaque continuation token for another requested-project result page. Empty on the final page. + string next_page_token = 9; +} + // GetProjectStatusByURIResponse contains the selected validation's current projection. message GetProjectStatusByURIResponse { // Queue-scoped decimal identifier of the validation request. @@ -214,6 +254,8 @@ service Stovepipe { rpc GetRequestHistoryByID(GetRequestHistoryByIDRequest) returns (GetRequestHistoryByIDResponse) {} // GetRequestHistoryByURI returns retained histories for an exact commit URI. rpc GetRequestHistoryByURI(GetRequestHistoryByURIRequest) returns (GetRequestHistoryByURIResponse) {} + // GetProjectStatusByID returns the current validation status for one request ID. + rpc GetProjectStatusByID(GetProjectStatusByIDRequest) returns (GetProjectStatusByIDResponse) {} // GetProjectStatusByURI returns the current validation status for an exact commit URI. rpc GetProjectStatusByURI(GetProjectStatusByURIRequest) returns (GetProjectStatusByURIResponse) {} // List returns a queue-scoped page of current request summaries. diff --git a/api/stovepipe/protopb/stovepipe.pb.go b/api/stovepipe/protopb/stovepipe.pb.go index 48dc97754..2af9042d8 100644 --- a/api/stovepipe/protopb/stovepipe.pb.go +++ b/api/stovepipe/protopb/stovepipe.pb.go @@ -620,6 +620,88 @@ func (x *GetRequestHistoryByURIResponse) GetHistories() []*RequestHistory { return nil } +// GetProjectStatusByIDRequest selects the validation status for the ID returned from Ingest. +type GetProjectStatusByIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Logical queue containing the validation request. + Queue string `protobuf:"bytes,1,opt,name=queue,proto3" json:"queue,omitempty"` + // Canonical decimal request identifier returned from Ingest. + RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Required consumer-defined project IDs whose recorded results are requested. + Projects []string `protobuf:"bytes,3,rep,name=projects,proto3" json:"projects,omitempty"` + // Maximum requested projects to return in one page. Zero selects the server default. + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Opaque continuation token for requested-project result pagination. + PageToken string `protobuf:"bytes,5,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProjectStatusByIDRequest) Reset() { + *x = GetProjectStatusByIDRequest{} + mi := &file_stovepipe_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProjectStatusByIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProjectStatusByIDRequest) ProtoMessage() {} + +func (x *GetProjectStatusByIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_stovepipe_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProjectStatusByIDRequest.ProtoReflect.Descriptor instead. +func (*GetProjectStatusByIDRequest) Descriptor() ([]byte, []int) { + return file_stovepipe_proto_rawDescGZIP(), []int{10} +} + +func (x *GetProjectStatusByIDRequest) GetQueue() string { + if x != nil { + return x.Queue + } + return "" +} + +func (x *GetProjectStatusByIDRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetProjectStatusByIDRequest) GetProjects() []string { + if x != nil { + return x.Projects + } + return nil +} + +func (x *GetProjectStatusByIDRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *GetProjectStatusByIDRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + // GetProjectStatusByURIRequest selects the validation status for an exact commit URI. type GetProjectStatusByURIRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -639,7 +721,7 @@ type GetProjectStatusByURIRequest struct { func (x *GetProjectStatusByURIRequest) Reset() { *x = GetProjectStatusByURIRequest{} - mi := &file_stovepipe_proto_msgTypes[10] + mi := &file_stovepipe_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -651,7 +733,7 @@ func (x *GetProjectStatusByURIRequest) String() string { func (*GetProjectStatusByURIRequest) ProtoMessage() {} func (x *GetProjectStatusByURIRequest) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[10] + mi := &file_stovepipe_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -664,7 +746,7 @@ func (x *GetProjectStatusByURIRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProjectStatusByURIRequest.ProtoReflect.Descriptor instead. func (*GetProjectStatusByURIRequest) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{10} + return file_stovepipe_proto_rawDescGZIP(), []int{11} } func (x *GetProjectStatusByURIRequest) GetQueue() string { @@ -719,7 +801,7 @@ type ProjectValidation struct { func (x *ProjectValidation) Reset() { *x = ProjectValidation{} - mi := &file_stovepipe_proto_msgTypes[11] + mi := &file_stovepipe_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -731,7 +813,7 @@ func (x *ProjectValidation) String() string { func (*ProjectValidation) ProtoMessage() {} func (x *ProjectValidation) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[11] + mi := &file_stovepipe_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -744,7 +826,7 @@ func (x *ProjectValidation) ProtoReflect() protoreflect.Message { // Deprecated: Use ProjectValidation.ProtoReflect.Descriptor instead. func (*ProjectValidation) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{11} + return file_stovepipe_proto_rawDescGZIP(), []int{12} } func (x *ProjectValidation) GetProject() string { @@ -780,6 +862,157 @@ type ProjectValidation_BreakageDegree struct { func (*ProjectValidation_BreakageDegree) isProjectValidation_Result() {} +// GetProjectStatusByIDResponse contains the selected validation's current projection. +type GetProjectStatusByIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Queue-scoped decimal identifier of the validation request. + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Logical queue containing the validation request. + Queue string `protobuf:"bytes,2,opt,name=queue,proto3" json:"queue,omitempty"` + // VCS-agnostic URI of the commit under validation. + ChangeUri string `protobuf:"bytes,3,opt,name=change_uri,json=changeUri,proto3" json:"change_uri,omitempty"` + // Baseline URI for incremental validation. Empty for a full build. + BaseUri string `protobuf:"bytes,4,opt,name=base_uri,json=baseUri,proto3" json:"base_uri,omitempty"` + // Stable public lifecycle state of the validation request. + RequestState string `protobuf:"bytes,5,opt,name=request_state,json=requestState,proto3" json:"request_state,omitempty"` + // Unix millisecond timestamp of the newest durable lifecycle or result record. + UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + // Whole-repository breakage degree. Absent until the result is durable. + // + // Types that are valid to be assigned to RepositoryResult: + // + // *GetProjectStatusByIDResponse_RepositoryBreakageDegree + RepositoryResult isGetProjectStatusByIDResponse_RepositoryResult `protobuf_oneof:"repository_result"` + // True after the implementation has durably finished producing relevant project results. + ProjectResultsComplete bool `protobuf:"varint,7,opt,name=project_results_complete,json=projectResultsComplete,proto3" json:"project_results_complete,omitempty"` + // Recorded results for requested projects. + Projects []*ProjectValidation `protobuf:"bytes,8,rep,name=projects,proto3" json:"projects,omitempty"` + // Opaque continuation token for another requested-project result page. Empty on the final page. + NextPageToken string `protobuf:"bytes,9,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetProjectStatusByIDResponse) Reset() { + *x = GetProjectStatusByIDResponse{} + mi := &file_stovepipe_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetProjectStatusByIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProjectStatusByIDResponse) ProtoMessage() {} + +func (x *GetProjectStatusByIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_stovepipe_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProjectStatusByIDResponse.ProtoReflect.Descriptor instead. +func (*GetProjectStatusByIDResponse) Descriptor() ([]byte, []int) { + return file_stovepipe_proto_rawDescGZIP(), []int{13} +} + +func (x *GetProjectStatusByIDResponse) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetProjectStatusByIDResponse) GetQueue() string { + if x != nil { + return x.Queue + } + return "" +} + +func (x *GetProjectStatusByIDResponse) GetChangeUri() string { + if x != nil { + return x.ChangeUri + } + return "" +} + +func (x *GetProjectStatusByIDResponse) GetBaseUri() string { + if x != nil { + return x.BaseUri + } + return "" +} + +func (x *GetProjectStatusByIDResponse) GetRequestState() string { + if x != nil { + return x.RequestState + } + return "" +} + +func (x *GetProjectStatusByIDResponse) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *GetProjectStatusByIDResponse) GetRepositoryResult() isGetProjectStatusByIDResponse_RepositoryResult { + if x != nil { + return x.RepositoryResult + } + return nil +} + +func (x *GetProjectStatusByIDResponse) GetRepositoryBreakageDegree() float64 { + if x != nil { + if x, ok := x.RepositoryResult.(*GetProjectStatusByIDResponse_RepositoryBreakageDegree); ok { + return x.RepositoryBreakageDegree + } + } + return 0 +} + +func (x *GetProjectStatusByIDResponse) GetProjectResultsComplete() bool { + if x != nil { + return x.ProjectResultsComplete + } + return false +} + +func (x *GetProjectStatusByIDResponse) GetProjects() []*ProjectValidation { + if x != nil { + return x.Projects + } + return nil +} + +func (x *GetProjectStatusByIDResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type isGetProjectStatusByIDResponse_RepositoryResult interface { + isGetProjectStatusByIDResponse_RepositoryResult() +} + +type GetProjectStatusByIDResponse_RepositoryBreakageDegree struct { + RepositoryBreakageDegree float64 `protobuf:"fixed64,6,opt,name=repository_breakage_degree,json=repositoryBreakageDegree,proto3,oneof"` +} + +func (*GetProjectStatusByIDResponse_RepositoryBreakageDegree) isGetProjectStatusByIDResponse_RepositoryResult() { +} + // GetProjectStatusByURIResponse contains the selected validation's current projection. type GetProjectStatusByURIResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -813,7 +1046,7 @@ type GetProjectStatusByURIResponse struct { func (x *GetProjectStatusByURIResponse) Reset() { *x = GetProjectStatusByURIResponse{} - mi := &file_stovepipe_proto_msgTypes[12] + mi := &file_stovepipe_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -825,7 +1058,7 @@ func (x *GetProjectStatusByURIResponse) String() string { func (*GetProjectStatusByURIResponse) ProtoMessage() {} func (x *GetProjectStatusByURIResponse) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[12] + mi := &file_stovepipe_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -838,7 +1071,7 @@ func (x *GetProjectStatusByURIResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProjectStatusByURIResponse.ProtoReflect.Descriptor instead. func (*GetProjectStatusByURIResponse) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{12} + return file_stovepipe_proto_rawDescGZIP(), []int{14} } func (x *GetProjectStatusByURIResponse) GetRequestId() string { @@ -956,7 +1189,7 @@ type RequestSummary struct { func (x *RequestSummary) Reset() { *x = RequestSummary{} - mi := &file_stovepipe_proto_msgTypes[13] + mi := &file_stovepipe_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -968,7 +1201,7 @@ func (x *RequestSummary) String() string { func (*RequestSummary) ProtoMessage() {} func (x *RequestSummary) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[13] + mi := &file_stovepipe_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -981,7 +1214,7 @@ func (x *RequestSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestSummary.ProtoReflect.Descriptor instead. func (*RequestSummary) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{13} + return file_stovepipe_proto_rawDescGZIP(), []int{15} } func (x *RequestSummary) GetRequestId() string { @@ -1063,7 +1296,7 @@ type ListRequest struct { func (x *ListRequest) Reset() { *x = ListRequest{} - mi := &file_stovepipe_proto_msgTypes[14] + mi := &file_stovepipe_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1075,7 +1308,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[14] + mi := &file_stovepipe_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1088,7 +1321,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{14} + return file_stovepipe_proto_rawDescGZIP(), []int{16} } func (x *ListRequest) GetQueue() string { @@ -1180,7 +1413,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_stovepipe_proto_msgTypes[15] + mi := &file_stovepipe_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1192,7 +1425,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_stovepipe_proto_msgTypes[15] + mi := &file_stovepipe_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1205,7 +1438,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_stovepipe_proto_rawDescGZIP(), []int{15} + return file_stovepipe_proto_rawDescGZIP(), []int{17} } func (x *ListResponse) GetRequests() []*RequestSummary { @@ -1260,7 +1493,15 @@ const file_stovepipe_proto_rawDesc = "" + "\x1dGetRequestHistoryByIDResponse\x12@\n" + "\x06events\x18\x01 \x03(\v2(.uber.submitqueue.stovepipe.HistoryEventR\x06events\"j\n" + "\x1eGetRequestHistoryByURIResponse\x12H\n" + - "\thistories\x18\x01 \x03(\v2*.uber.submitqueue.stovepipe.RequestHistoryR\thistories\"\xab\x01\n" + + "\thistories\x18\x01 \x03(\v2*.uber.submitqueue.stovepipe.RequestHistoryR\thistories\"\xaa\x01\n" + + "\x1bGetProjectStatusByIDRequest\x12\x14\n" + + "\x05queue\x18\x01 \x01(\tR\x05queue\x12\x1d\n" + + "\n" + + "request_id\x18\x02 \x01(\tR\trequestId\x12\x1a\n" + + "\bprojects\x18\x03 \x03(\tR\bprojects\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x05 \x01(\tR\tpageToken\"\xab\x01\n" + "\x1cGetProjectStatusByURIRequest\x12\x14\n" + "\x05queue\x18\x01 \x01(\tR\x05queue\x12\x1d\n" + "\n" + @@ -1272,7 +1513,22 @@ const file_stovepipe_proto_rawDesc = "" + "\x11ProjectValidation\x12\x18\n" + "\aproject\x18\x01 \x01(\tR\aproject\x12)\n" + "\x0fbreakage_degree\x18\x02 \x01(\x01H\x00R\x0ebreakageDegreeB\b\n" + - "\x06result\"\xd9\x03\n" + + "\x06result\"\xd8\x03\n" + + "\x1cGetProjectStatusByIDResponse\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x14\n" + + "\x05queue\x18\x02 \x01(\tR\x05queue\x12\x1d\n" + + "\n" + + "change_uri\x18\x03 \x01(\tR\tchangeUri\x12\x19\n" + + "\bbase_uri\x18\x04 \x01(\tR\abaseUri\x12#\n" + + "\rrequest_state\x18\x05 \x01(\tR\frequestState\x12\"\n" + + "\rupdated_at_ms\x18\n" + + " \x01(\x03R\vupdatedAtMs\x12>\n" + + "\x1arepository_breakage_degree\x18\x06 \x01(\x01H\x00R\x18repositoryBreakageDegree\x128\n" + + "\x18project_results_complete\x18\a \x01(\bR\x16projectResultsComplete\x12I\n" + + "\bprojects\x18\b \x03(\v2-.uber.submitqueue.stovepipe.ProjectValidationR\bprojects\x12&\n" + + "\x0fnext_page_token\x18\t \x01(\tR\rnextPageTokenB\x13\n" + + "\x11repository_result\"\xd9\x03\n" + "\x1dGetProjectStatusByURIResponse\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x14\n" + @@ -1310,12 +1566,13 @@ const file_stovepipe_proto_rawDesc = "" + "\x14accepted_upper_bound\"~\n" + "\fListResponse\x12F\n" + "\brequests\x18\x01 \x03(\v2*.uber.submitqueue.stovepipe.RequestSummaryR\brequests\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken2\xde\x05\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken2\xec\x06\n" + "\tStovepipe\x12[\n" + "\x04Ping\x12'.uber.submitqueue.stovepipe.PingRequest\x1a(.uber.submitqueue.stovepipe.PingResponse\"\x00\x12a\n" + "\x06Ingest\x12).uber.submitqueue.stovepipe.IngestRequest\x1a*.uber.submitqueue.stovepipe.IngestResponse\"\x00\x12\x8e\x01\n" + "\x15GetRequestHistoryByID\x128.uber.submitqueue.stovepipe.GetRequestHistoryByIDRequest\x1a9.uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse\"\x00\x12\x91\x01\n" + - "\x16GetRequestHistoryByURI\x129.uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest\x1a:.uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse\"\x00\x12\x8e\x01\n" + + "\x16GetRequestHistoryByURI\x129.uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest\x1a:.uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse\"\x00\x12\x8b\x01\n" + + "\x14GetProjectStatusByID\x127.uber.submitqueue.stovepipe.GetProjectStatusByIDRequest\x1a8.uber.submitqueue.stovepipe.GetProjectStatusByIDResponse\"\x00\x12\x8e\x01\n" + "\x15GetProjectStatusByURI\x128.uber.submitqueue.stovepipe.GetProjectStatusByURIRequest\x1a9.uber.submitqueue.stovepipe.GetProjectStatusByURIResponse\"\x00\x12[\n" + "\x04List\x12'.uber.submitqueue.stovepipe.ListRequest\x1a(.uber.submitqueue.stovepipe.ListResponse\"\x00Be\n" + "\x1ecom.uber.submitqueue.stovepipeB\x0eStovepipeProtoP\x01Z1github.com/uber/submitqueue/api/stovepipe/protopbb\x06proto3" @@ -1332,7 +1589,7 @@ func file_stovepipe_proto_rawDescGZIP() []byte { return file_stovepipe_proto_rawDescData } -var file_stovepipe_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_stovepipe_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_stovepipe_proto_goTypes = []any{ (*PingRequest)(nil), // 0: uber.submitqueue.stovepipe.PingRequest (*PingResponse)(nil), // 1: uber.submitqueue.stovepipe.PingResponse @@ -1344,36 +1601,41 @@ var file_stovepipe_proto_goTypes = []any{ (*RequestHistory)(nil), // 7: uber.submitqueue.stovepipe.RequestHistory (*GetRequestHistoryByIDResponse)(nil), // 8: uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse (*GetRequestHistoryByURIResponse)(nil), // 9: uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse - (*GetProjectStatusByURIRequest)(nil), // 10: uber.submitqueue.stovepipe.GetProjectStatusByURIRequest - (*ProjectValidation)(nil), // 11: uber.submitqueue.stovepipe.ProjectValidation - (*GetProjectStatusByURIResponse)(nil), // 12: uber.submitqueue.stovepipe.GetProjectStatusByURIResponse - (*RequestSummary)(nil), // 13: uber.submitqueue.stovepipe.RequestSummary - (*ListRequest)(nil), // 14: uber.submitqueue.stovepipe.ListRequest - (*ListResponse)(nil), // 15: uber.submitqueue.stovepipe.ListResponse + (*GetProjectStatusByIDRequest)(nil), // 10: uber.submitqueue.stovepipe.GetProjectStatusByIDRequest + (*GetProjectStatusByURIRequest)(nil), // 11: uber.submitqueue.stovepipe.GetProjectStatusByURIRequest + (*ProjectValidation)(nil), // 12: uber.submitqueue.stovepipe.ProjectValidation + (*GetProjectStatusByIDResponse)(nil), // 13: uber.submitqueue.stovepipe.GetProjectStatusByIDResponse + (*GetProjectStatusByURIResponse)(nil), // 14: uber.submitqueue.stovepipe.GetProjectStatusByURIResponse + (*RequestSummary)(nil), // 15: uber.submitqueue.stovepipe.RequestSummary + (*ListRequest)(nil), // 16: uber.submitqueue.stovepipe.ListRequest + (*ListResponse)(nil), // 17: uber.submitqueue.stovepipe.ListResponse } var file_stovepipe_proto_depIdxs = []int32{ 6, // 0: uber.submitqueue.stovepipe.RequestHistory.events:type_name -> uber.submitqueue.stovepipe.HistoryEvent 6, // 1: uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse.events:type_name -> uber.submitqueue.stovepipe.HistoryEvent 7, // 2: uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse.histories:type_name -> uber.submitqueue.stovepipe.RequestHistory - 11, // 3: uber.submitqueue.stovepipe.GetProjectStatusByURIResponse.projects:type_name -> uber.submitqueue.stovepipe.ProjectValidation - 13, // 4: uber.submitqueue.stovepipe.ListResponse.requests:type_name -> uber.submitqueue.stovepipe.RequestSummary - 0, // 5: uber.submitqueue.stovepipe.Stovepipe.Ping:input_type -> uber.submitqueue.stovepipe.PingRequest - 2, // 6: uber.submitqueue.stovepipe.Stovepipe.Ingest:input_type -> uber.submitqueue.stovepipe.IngestRequest - 4, // 7: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDRequest - 5, // 8: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest - 10, // 9: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIRequest - 14, // 10: uber.submitqueue.stovepipe.Stovepipe.List:input_type -> uber.submitqueue.stovepipe.ListRequest - 1, // 11: uber.submitqueue.stovepipe.Stovepipe.Ping:output_type -> uber.submitqueue.stovepipe.PingResponse - 3, // 12: uber.submitqueue.stovepipe.Stovepipe.Ingest:output_type -> uber.submitqueue.stovepipe.IngestResponse - 8, // 13: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse - 9, // 14: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse - 12, // 15: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIResponse - 15, // 16: uber.submitqueue.stovepipe.Stovepipe.List:output_type -> uber.submitqueue.stovepipe.ListResponse - 11, // [11:17] is the sub-list for method output_type - 5, // [5:11] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 12, // 3: uber.submitqueue.stovepipe.GetProjectStatusByIDResponse.projects:type_name -> uber.submitqueue.stovepipe.ProjectValidation + 12, // 4: uber.submitqueue.stovepipe.GetProjectStatusByURIResponse.projects:type_name -> uber.submitqueue.stovepipe.ProjectValidation + 15, // 5: uber.submitqueue.stovepipe.ListResponse.requests:type_name -> uber.submitqueue.stovepipe.RequestSummary + 0, // 6: uber.submitqueue.stovepipe.Stovepipe.Ping:input_type -> uber.submitqueue.stovepipe.PingRequest + 2, // 7: uber.submitqueue.stovepipe.Stovepipe.Ingest:input_type -> uber.submitqueue.stovepipe.IngestRequest + 4, // 8: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDRequest + 5, // 9: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest + 10, // 10: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDRequest + 11, // 11: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIRequest + 16, // 12: uber.submitqueue.stovepipe.Stovepipe.List:input_type -> uber.submitqueue.stovepipe.ListRequest + 1, // 13: uber.submitqueue.stovepipe.Stovepipe.Ping:output_type -> uber.submitqueue.stovepipe.PingResponse + 3, // 14: uber.submitqueue.stovepipe.Stovepipe.Ingest:output_type -> uber.submitqueue.stovepipe.IngestResponse + 8, // 15: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse + 9, // 16: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse + 13, // 17: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDResponse + 14, // 18: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIResponse + 17, // 19: uber.submitqueue.stovepipe.Stovepipe.List:output_type -> uber.submitqueue.stovepipe.ListResponse + 13, // [13:20] is the sub-list for method output_type + 6, // [6:13] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_stovepipe_proto_init() } @@ -1385,13 +1647,16 @@ func file_stovepipe_proto_init() { (*HistoryEvent_RequestState)(nil), (*HistoryEvent_Event)(nil), } - file_stovepipe_proto_msgTypes[11].OneofWrappers = []any{ + file_stovepipe_proto_msgTypes[12].OneofWrappers = []any{ (*ProjectValidation_BreakageDegree)(nil), } - file_stovepipe_proto_msgTypes[12].OneofWrappers = []any{ - (*GetProjectStatusByURIResponse_RepositoryBreakageDegree)(nil), + file_stovepipe_proto_msgTypes[13].OneofWrappers = []any{ + (*GetProjectStatusByIDResponse_RepositoryBreakageDegree)(nil), } file_stovepipe_proto_msgTypes[14].OneofWrappers = []any{ + (*GetProjectStatusByURIResponse_RepositoryBreakageDegree)(nil), + } + file_stovepipe_proto_msgTypes[16].OneofWrappers = []any{ (*ListRequest_AcceptedAtOrAfterMs)(nil), (*ListRequest_AcceptedBeforeMs)(nil), } @@ -1401,7 +1666,7 @@ func file_stovepipe_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_stovepipe_proto_rawDesc), len(file_stovepipe_proto_rawDesc)), NumEnums: 0, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/api/stovepipe/protopb/stovepipe.pb.yarpc.go b/api/stovepipe/protopb/stovepipe.pb.yarpc.go index b55bcac99..681438a1f 100644 --- a/api/stovepipe/protopb/stovepipe.pb.yarpc.go +++ b/api/stovepipe/protopb/stovepipe.pb.yarpc.go @@ -25,6 +25,7 @@ type StovepipeYARPCClient interface { Ingest(context.Context, *IngestRequest, ...yarpc.CallOption) (*IngestResponse, error) GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest, ...yarpc.CallOption) (*GetRequestHistoryByIDResponse, error) GetRequestHistoryByURI(context.Context, *GetRequestHistoryByURIRequest, ...yarpc.CallOption) (*GetRequestHistoryByURIResponse, error) + GetProjectStatusByID(context.Context, *GetProjectStatusByIDRequest, ...yarpc.CallOption) (*GetProjectStatusByIDResponse, error) GetProjectStatusByURI(context.Context, *GetProjectStatusByURIRequest, ...yarpc.CallOption) (*GetProjectStatusByURIResponse, error) List(context.Context, *ListRequest, ...yarpc.CallOption) (*ListResponse, error) } @@ -51,6 +52,7 @@ type StovepipeYARPCServer interface { Ingest(context.Context, *IngestRequest) (*IngestResponse, error) GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest) (*GetRequestHistoryByIDResponse, error) GetRequestHistoryByURI(context.Context, *GetRequestHistoryByURIRequest) (*GetRequestHistoryByURIResponse, error) + GetProjectStatusByID(context.Context, *GetProjectStatusByIDRequest) (*GetProjectStatusByIDResponse, error) GetProjectStatusByURI(context.Context, *GetProjectStatusByURIRequest) (*GetProjectStatusByURIResponse, error) List(context.Context, *ListRequest) (*ListResponse, error) } @@ -106,6 +108,16 @@ func buildStovepipeYARPCProcedures(params buildStovepipeYARPCProceduresParams) [ }, ), }, + { + MethodName: "GetProjectStatusByID", + Handler: v2.NewUnaryHandler( + v2.UnaryHandlerParams{ + Handle: handler.GetProjectStatusByID, + NewRequest: newStovepipeServiceGetProjectStatusByIDYARPCRequest, + AnyResolver: params.AnyResolver, + }, + ), + }, { MethodName: "GetProjectStatusByURI", Handler: v2.NewUnaryHandler( @@ -286,6 +298,18 @@ func (c *_StovepipeYARPCCaller) GetRequestHistoryByURI(ctx context.Context, requ return response, err } +func (c *_StovepipeYARPCCaller) GetProjectStatusByID(ctx context.Context, request *GetProjectStatusByIDRequest, options ...yarpc.CallOption) (*GetProjectStatusByIDResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "GetProjectStatusByID", request, newStovepipeServiceGetProjectStatusByIDYARPCResponse, options...) + if responseMessage == nil { + return nil, err + } + response, ok := responseMessage.(*GetProjectStatusByIDResponse) + if !ok { + return nil, v2.CastError(emptyStovepipeServiceGetProjectStatusByIDYARPCResponse, responseMessage) + } + return response, err +} + func (c *_StovepipeYARPCCaller) GetProjectStatusByURI(ctx context.Context, request *GetProjectStatusByURIRequest, options ...yarpc.CallOption) (*GetProjectStatusByURIResponse, error) { responseMessage, err := c.streamClient.Call(ctx, "GetProjectStatusByURI", request, newStovepipeServiceGetProjectStatusByURIYARPCResponse, options...) if responseMessage == nil { @@ -378,6 +402,22 @@ func (h *_StovepipeYARPCHandler) GetRequestHistoryByURI(ctx context.Context, req return response, err } +func (h *_StovepipeYARPCHandler) GetProjectStatusByID(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *GetProjectStatusByIDRequest + var ok bool + if requestMessage != nil { + request, ok = requestMessage.(*GetProjectStatusByIDRequest) + if !ok { + return nil, v2.CastError(emptyStovepipeServiceGetProjectStatusByIDYARPCRequest, requestMessage) + } + } + response, err := h.server.GetProjectStatusByID(ctx, request) + if response == nil { + return nil, err + } + return response, err +} + func (h *_StovepipeYARPCHandler) GetProjectStatusByURI(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { var request *GetProjectStatusByURIRequest var ok bool @@ -442,6 +482,14 @@ func newStovepipeServiceGetRequestHistoryByURIYARPCResponse() proto.Message { return &GetRequestHistoryByURIResponse{} } +func newStovepipeServiceGetProjectStatusByIDYARPCRequest() proto.Message { + return &GetProjectStatusByIDRequest{} +} + +func newStovepipeServiceGetProjectStatusByIDYARPCResponse() proto.Message { + return &GetProjectStatusByIDResponse{} +} + func newStovepipeServiceGetProjectStatusByURIYARPCRequest() proto.Message { return &GetProjectStatusByURIRequest{} } @@ -467,6 +515,8 @@ var ( emptyStovepipeServiceGetRequestHistoryByIDYARPCResponse = &GetRequestHistoryByIDResponse{} emptyStovepipeServiceGetRequestHistoryByURIYARPCRequest = &GetRequestHistoryByURIRequest{} emptyStovepipeServiceGetRequestHistoryByURIYARPCResponse = &GetRequestHistoryByURIResponse{} + emptyStovepipeServiceGetProjectStatusByIDYARPCRequest = &GetProjectStatusByIDRequest{} + emptyStovepipeServiceGetProjectStatusByIDYARPCResponse = &GetProjectStatusByIDResponse{} emptyStovepipeServiceGetProjectStatusByURIYARPCRequest = &GetProjectStatusByURIRequest{} emptyStovepipeServiceGetProjectStatusByURIYARPCResponse = &GetProjectStatusByURIResponse{} emptyStovepipeServiceListYARPCRequest = &ListRequest{} @@ -476,73 +526,76 @@ var ( var yarpcFileDescriptorClosurefabdb6b3c0b09022 = [][]byte{ // stovepipe.proto []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0x4f, 0x73, 0xdb, 0x44, - 0x14, 0xb7, 0xec, 0xc6, 0xb1, 0x5f, 0x1c, 0x27, 0xdd, 0x94, 0xe0, 0x9a, 0xa6, 0x13, 0x04, 0xa1, - 0x6e, 0x67, 0xea, 0x0c, 0x65, 0x86, 0x29, 0x1c, 0x18, 0x6a, 0x0a, 0xb5, 0x67, 0x08, 0x64, 0x64, - 0xc2, 0x01, 0x0e, 0x1a, 0x59, 0x7e, 0x75, 0xb6, 0x8d, 0xb4, 0xca, 0xee, 0x2a, 0xd0, 0x1e, 0xb8, - 0x71, 0x65, 0x86, 0x6f, 0xc0, 0x9d, 0x2f, 0xc1, 0xd7, 0xe0, 0xc2, 0xd7, 0xe0, 0xca, 0xec, 0x6a, - 0x25, 0xcb, 0x8e, 0x2d, 0xb7, 0xe1, 0xd0, 0x9b, 0xf6, 0xfd, 0xdd, 0xfd, 0xbd, 0xdf, 0x7b, 0xbb, - 0x82, 0x2d, 0x21, 0xd9, 0x05, 0x46, 0x34, 0xc2, 0x6e, 0xc4, 0x99, 0x64, 0xa4, 0x1d, 0x8f, 0x90, - 0x77, 0x45, 0x3c, 0x0a, 0xa8, 0x3c, 0x8f, 0x31, 0xc6, 0x6e, 0x66, 0x61, 0xdf, 0x81, 0x8d, 0x63, - 0x1a, 0x4e, 0x1c, 0x3c, 0x8f, 0x51, 0x48, 0xd2, 0x82, 0xf5, 0x00, 0x85, 0xf0, 0x26, 0xd8, 0xb2, - 0xf6, 0xad, 0x4e, 0xdd, 0x49, 0x97, 0xf6, 0xaf, 0x16, 0x34, 0x12, 0x4b, 0x11, 0xb1, 0x50, 0xe0, - 0x72, 0x53, 0xf2, 0x2e, 0x34, 0x04, 0xf2, 0x0b, 0xea, 0xa3, 0x1b, 0x7a, 0x01, 0xb6, 0xca, 0x5a, - 0xbd, 0x61, 0x64, 0xdf, 0x78, 0x01, 0x92, 0x5b, 0x50, 0x97, 0x34, 0x40, 0x21, 0xbd, 0x20, 0x6a, - 0x55, 0xf6, 0xad, 0x4e, 0xc5, 0x99, 0x0a, 0x48, 0x1b, 0x6a, 0xa7, 0x4c, 0x48, 0xed, 0x7c, 0x4d, - 0x3b, 0x67, 0x6b, 0xfb, 0x00, 0x36, 0x07, 0xe1, 0x04, 0x85, 0x4c, 0xb7, 0x7c, 0x03, 0xd6, 0xf4, - 0xa1, 0xcc, 0x2e, 0x92, 0x85, 0xbd, 0x0f, 0xcd, 0xd4, 0xcc, 0xec, 0xb7, 0x09, 0x65, 0x3a, 0x36, - 0x46, 0x65, 0x3a, 0xb6, 0x87, 0x70, 0xeb, 0x09, 0xa6, 0x51, 0xfa, 0x54, 0x48, 0xc6, 0x5f, 0xf4, - 0x5e, 0x0c, 0x1e, 0x17, 0xc6, 0x25, 0x7b, 0x00, 0x3c, 0x31, 0x70, 0xe9, 0xd8, 0x9c, 0xac, 0x6e, - 0x24, 0x83, 0xb1, 0xfd, 0x04, 0xf6, 0x16, 0x04, 0x3d, 0x71, 0x06, 0xc5, 0x51, 0xb7, 0xa1, 0x12, - 0x73, 0x6a, 0xc2, 0xa9, 0x4f, 0xfb, 0x2f, 0x0b, 0x1a, 0xc6, 0xff, 0xcb, 0x0b, 0x0c, 0x25, 0xb9, - 0x09, 0x35, 0x54, 0x1f, 0x6e, 0x76, 0x88, 0x75, 0xbd, 0x1e, 0x8c, 0x15, 0xde, 0x19, 0x76, 0x6e, - 0x20, 0x74, 0x98, 0x8a, 0xb3, 0x91, 0xc9, 0x8e, 0x04, 0x39, 0x80, 0xcd, 0x74, 0xdb, 0x42, 0x7a, - 0x12, 0x35, 0xe6, 0xf5, 0x7e, 0xc9, 0x69, 0x18, 0xf1, 0x50, 0x49, 0xc9, 0x2e, 0xac, 0xe9, 0xa0, - 0x09, 0xea, 0xfd, 0x92, 0x93, 0x2c, 0xc9, 0x01, 0x34, 0x59, 0x2c, 0x7d, 0x16, 0xa0, 0xcb, 0xd1, - 0x13, 0x2c, 0x6c, 0xad, 0xe9, 0x2d, 0x6c, 0x1a, 0xa9, 0xa3, 0x85, 0xbd, 0x06, 0x00, 0xf3, 0xfd, - 0x98, 0x73, 0x0c, 0x7d, 0xb4, 0xcf, 0xa1, 0x39, 0x0b, 0xc4, 0x1c, 0x78, 0xd6, 0x1c, 0x78, 0xe4, - 0x73, 0xa8, 0xea, 0x74, 0xea, 0x04, 0x95, 0xce, 0xc6, 0x83, 0x4e, 0x77, 0x39, 0x71, 0xbb, 0x79, - 0x70, 0x1c, 0xe3, 0x67, 0x7b, 0x0b, 0xe1, 0x57, 0x35, 0x35, 0x24, 0x98, 0xa6, 0xb0, 0xae, 0x98, - 0xe2, 0x19, 0xdc, 0x5e, 0x56, 0x61, 0x93, 0xa3, 0x0f, 0xf5, 0x53, 0x2d, 0xa7, 0x98, 0xa6, 0xb9, - 0x57, 0x94, 0x66, 0x36, 0x96, 0x33, 0x75, 0xb6, 0xff, 0xb4, 0x34, 0x47, 0x8f, 0x39, 0x7b, 0x86, - 0xbe, 0x2e, 0x51, 0x2c, 0x5e, 0x81, 0x4d, 0x7b, 0x00, 0xfe, 0xa9, 0x17, 0x4e, 0xd0, 0x9d, 0x92, - 0xaa, 0x9e, 0x48, 0x4e, 0x38, 0x55, 0xdd, 0x15, 0x25, 0x11, 0x45, 0xab, 0xb2, 0x5f, 0x51, 0xdd, - 0x95, 0xae, 0xc9, 0x3b, 0x50, 0x8f, 0xbc, 0x09, 0xba, 0x82, 0xbe, 0x4c, 0x5a, 0x6f, 0xcd, 0xa9, - 0x29, 0xc1, 0x90, 0xbe, 0xd4, 0x71, 0xb5, 0x52, 0xb2, 0xe7, 0x98, 0x32, 0x40, 0x9b, 0x7f, 0xa7, - 0x04, 0xf6, 0x08, 0xae, 0x9b, 0x9d, 0x7e, 0xef, 0x9d, 0xd1, 0xb1, 0x27, 0x29, 0x0b, 0xd5, 0x94, - 0x30, 0xc1, 0x53, 0xd6, 0x9a, 0x25, 0xb9, 0x0b, 0x5b, 0x23, 0x8e, 0xde, 0x73, 0x15, 0x71, 0x8c, - 0x13, 0x8e, 0xc9, 0xa0, 0xb0, 0xfa, 0x25, 0xa7, 0x99, 0x2a, 0x1e, 0x6b, 0x79, 0xaf, 0x06, 0x55, - 0x8e, 0x22, 0x3e, 0x93, 0xf6, 0xdf, 0x15, 0x5d, 0xe1, 0x45, 0x88, 0x18, 0xf4, 0x57, 0x70, 0x2c, - 0x43, 0xac, 0xbc, 0x1c, 0xb1, 0xca, 0x3c, 0x62, 0x37, 0xa1, 0x36, 0xf2, 0x44, 0xa2, 0x4c, 0xe6, - 0xd1, 0xba, 0x5a, 0x2b, 0xd5, 0x7b, 0xf3, 0x8d, 0x95, 0xc0, 0x32, 0xdb, 0x56, 0x36, 0x6c, 0xc6, - 0xd1, 0xd8, 0x93, 0x38, 0x76, 0x3d, 0xa9, 0x3a, 0x14, 0x92, 0x0e, 0x35, 0xc2, 0x47, 0xf2, 0x48, - 0x90, 0xcf, 0xa0, 0xcd, 0x31, 0x62, 0x82, 0x2a, 0x12, 0xb8, 0xf3, 0xc8, 0x54, 0x0d, 0x32, 0xad, - 0xa9, 0x4d, 0x6f, 0x06, 0x23, 0xf2, 0x10, 0x5a, 0x06, 0x59, 0x37, 0xc1, 0x4a, 0xb8, 0x3e, 0x0b, - 0xa2, 0x33, 0x94, 0xd8, 0x5a, 0xdf, 0xb7, 0x3a, 0x35, 0x67, 0xd7, 0xe8, 0x9d, 0x44, 0xfd, 0x85, - 0xd1, 0x92, 0x41, 0x8e, 0x0f, 0x35, 0x4d, 0xd7, 0xfb, 0x45, 0x74, 0xbd, 0x54, 0xe3, 0x1c, 0x7d, - 0x3e, 0x80, 0xad, 0x10, 0x7f, 0x96, 0x6e, 0x8e, 0x26, 0xf5, 0x64, 0x50, 0x28, 0xf1, 0x71, 0x4a, - 0x95, 0xde, 0x0e, 0x5c, 0xcf, 0x1d, 0xd6, 0xd4, 0xf6, 0x8f, 0x72, 0x36, 0x30, 0x86, 0x71, 0x10, - 0x78, 0xab, 0x07, 0xc6, 0x1b, 0x29, 0xe6, 0x7d, 0xd8, 0xd1, 0x4a, 0x77, 0xb6, 0xa4, 0x55, 0x5d, - 0xd2, 0x6d, 0xad, 0x3a, 0xc9, 0xd5, 0xf5, 0x7d, 0x68, 0x7a, 0xbe, 0x8f, 0xd1, 0xd4, 0x72, 0x5d, - 0x5b, 0x36, 0x52, 0xa9, 0xb6, 0xba, 0x3c, 0x60, 0x6b, 0x0b, 0x06, 0xac, 0xfd, 0xaf, 0x05, 0x1b, - 0x5f, 0xd3, 0x15, 0x77, 0x1f, 0xf9, 0x18, 0xde, 0xce, 0xa7, 0x64, 0xdc, 0xf5, 0x9e, 0x4a, 0xe4, - 0xd9, 0xd5, 0xd0, 0x2f, 0x39, 0x3b, 0xd3, 0xec, 0xdf, 0xf2, 0x47, 0x4a, 0x7b, 0x24, 0x48, 0x17, - 0x48, 0xe6, 0x37, 0xc2, 0xa7, 0x8c, 0xa3, 0x72, 0xd1, 0xb7, 0x73, 0xdf, 0x72, 0xb6, 0x53, 0x5d, - 0x4f, 0xab, 0x8e, 0xfe, 0xd7, 0xb0, 0xe8, 0xed, 0xc2, 0x8d, 0x2c, 0xd7, 0x19, 0xfb, 0x09, 0xb9, - 0x3b, 0x62, 0x71, 0x38, 0x9e, 0x91, 0xc7, 0x51, 0x94, 0xca, 0xed, 0x5f, 0xa0, 0x91, 0x1c, 0xdc, - 0xb4, 0xf9, 0x57, 0x50, 0x33, 0x55, 0x79, 0x9d, 0x19, 0x6b, 0x78, 0xe5, 0x64, 0xbe, 0x8b, 0x18, - 0x5b, 0x5e, 0xc0, 0xd8, 0x07, 0xff, 0xac, 0x41, 0x7d, 0x98, 0x86, 0x23, 0x3f, 0xc2, 0x35, 0xf5, - 0x16, 0x22, 0x77, 0x0a, 0x1b, 0x65, 0xfa, 0xae, 0x6a, 0x77, 0x56, 0x1b, 0x26, 0x07, 0xb3, 0x4b, - 0xc4, 0x83, 0x6a, 0xf2, 0x74, 0x21, 0x77, 0x8b, 0xbc, 0x66, 0x5e, 0x41, 0xed, 0x7b, 0xaf, 0x62, - 0x9a, 0xa5, 0xf8, 0xcd, 0x82, 0xb7, 0x16, 0x5e, 0x94, 0xe4, 0x61, 0x51, 0x9c, 0xa2, 0xf7, 0x52, - 0xfb, 0x93, 0x2b, 0x78, 0x66, 0x1b, 0xfa, 0xdd, 0x82, 0xdd, 0xc5, 0xd7, 0x2a, 0x79, 0xdd, 0xb8, - 0xd3, 0xeb, 0xb1, 0xfd, 0xe9, 0x55, 0x5c, 0xe7, 0x41, 0xba, 0x7c, 0xd7, 0xac, 0x04, 0x69, 0xe9, - 0x85, 0xbd, 0x12, 0xa4, 0xe5, 0x17, 0x9b, 0x5d, 0x52, 0xac, 0x53, 0x3d, 0x50, 0xcc, 0xba, 0xdc, - 0x78, 0x28, 0x66, 0x5d, 0xbe, 0x9d, 0xec, 0x52, 0x0f, 0xe1, 0xb6, 0xcf, 0x82, 0x02, 0x87, 0x5e, - 0x33, 0xe3, 0xff, 0xb1, 0xfa, 0xad, 0x38, 0xb6, 0x7e, 0xf8, 0x70, 0x42, 0xe5, 0x69, 0x3c, 0xea, - 0xfa, 0x2c, 0x38, 0x54, 0x8e, 0x87, 0x39, 0xc7, 0x43, 0x2f, 0xa2, 0x87, 0x99, 0xf3, 0xa1, 0xfe, - 0x13, 0x89, 0x46, 0xa3, 0xaa, 0xfe, 0xf8, 0xe8, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa6, 0xd3, - 0x4f, 0x39, 0xa5, 0x0c, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xf7, 0xda, 0x89, 0x63, 0xbf, 0x38, 0x4e, 0x3a, 0x09, 0xc1, 0x75, 0x9b, 0x2a, 0x2c, 0x84, + 0xba, 0x95, 0xea, 0x88, 0x22, 0x41, 0xca, 0x01, 0x51, 0x53, 0x88, 0x2d, 0x11, 0x88, 0x36, 0x84, + 0x03, 0x3d, 0xac, 0xd6, 0xeb, 0x57, 0x67, 0xdb, 0xec, 0xce, 0x66, 0x66, 0x36, 0xd0, 0x1e, 0xb8, + 0x71, 0x42, 0x42, 0xe2, 0x1b, 0x70, 0x86, 0x2f, 0xc1, 0xc7, 0x80, 0xef, 0xc0, 0x9d, 0x2b, 0x9a, + 0xd9, 0xd9, 0xf5, 0xda, 0xb1, 0xd7, 0x24, 0x15, 0x42, 0x48, 0xbd, 0xed, 0xbc, 0xff, 0xf3, 0x7b, + 0x7f, 0xe6, 0x2d, 0xac, 0x72, 0x41, 0xcf, 0x31, 0xf4, 0x42, 0x6c, 0x87, 0x8c, 0x0a, 0x4a, 0x9a, + 0x51, 0x1f, 0x59, 0x9b, 0x47, 0x7d, 0xdf, 0x13, 0x67, 0x11, 0x46, 0xd8, 0x4e, 0x25, 0xcc, 0xdb, + 0xb0, 0x7c, 0xe8, 0x05, 0x43, 0x0b, 0xcf, 0x22, 0xe4, 0x82, 0x34, 0x60, 0xc9, 0x47, 0xce, 0x9d, + 0x21, 0x36, 0x8c, 0x6d, 0xa3, 0x55, 0xb5, 0x92, 0xa3, 0xf9, 0xbd, 0x01, 0xb5, 0x58, 0x92, 0x87, + 0x34, 0xe0, 0x38, 0x5b, 0x94, 0xbc, 0x01, 0x35, 0x8e, 0xec, 0xdc, 0x73, 0xd1, 0x0e, 0x1c, 0x1f, + 0x1b, 0x45, 0xc5, 0x5e, 0xd6, 0xb4, 0xcf, 0x1d, 0x1f, 0xc9, 0x4d, 0xa8, 0x0a, 0xcf, 0x47, 0x2e, + 0x1c, 0x3f, 0x6c, 0x94, 0xb6, 0x8d, 0x56, 0xc9, 0x1a, 0x11, 0x48, 0x13, 0x2a, 0x27, 0x94, 0x0b, + 0xa5, 0xbc, 0xa0, 0x94, 0xd3, 0xb3, 0xb9, 0x03, 0x2b, 0xbd, 0x60, 0x88, 0x5c, 0x24, 0x21, 0x6f, + 0xc0, 0xa2, 0xba, 0x94, 0x8e, 0x22, 0x3e, 0x98, 0xdb, 0x50, 0x4f, 0xc4, 0x74, 0xbc, 0x75, 0x28, + 0x7a, 0x03, 0x2d, 0x54, 0xf4, 0x06, 0xe6, 0x11, 0xdc, 0xdc, 0xc7, 0xc4, 0x4a, 0xd7, 0xe3, 0x82, + 0xb2, 0xe7, 0x9d, 0xe7, 0xbd, 0x47, 0xb9, 0x76, 0xc9, 0x16, 0x00, 0x8b, 0x05, 0x6c, 0x6f, 0xa0, + 0x6f, 0x56, 0xd5, 0x94, 0xde, 0xc0, 0xdc, 0x87, 0xad, 0x29, 0x46, 0x8f, 0xad, 0x5e, 0xbe, 0xd5, + 0x35, 0x28, 0x45, 0xcc, 0xd3, 0xe6, 0xe4, 0xa7, 0xf9, 0x9b, 0x01, 0x35, 0xad, 0xff, 0xc9, 0x39, + 0x06, 0x82, 0x5c, 0x87, 0x0a, 0xca, 0x0f, 0x3b, 0xbd, 0xc4, 0x92, 0x3a, 0xf7, 0x06, 0x12, 0xef, + 0x14, 0x3b, 0xdb, 0xe7, 0xca, 0x4c, 0xc9, 0x5a, 0x4e, 0x69, 0x07, 0x9c, 0xec, 0xc0, 0x4a, 0x12, + 0x36, 0x17, 0x8e, 0x40, 0x85, 0x79, 0xb5, 0x5b, 0xb0, 0x6a, 0x9a, 0x7c, 0x24, 0xa9, 0x64, 0x13, + 0x16, 0x95, 0xd1, 0x18, 0xf5, 0x6e, 0xc1, 0x8a, 0x8f, 0x64, 0x07, 0xea, 0x34, 0x12, 0x2e, 0xf5, + 0xd1, 0x66, 0xe8, 0x70, 0x1a, 0x34, 0x16, 0x55, 0x08, 0x2b, 0x9a, 0x6a, 0x29, 0x62, 0xa7, 0x06, + 0x40, 0x5d, 0x37, 0x62, 0x0c, 0x03, 0x17, 0xcd, 0x33, 0xa8, 0x8f, 0x03, 0x31, 0x01, 0x9e, 0x31, + 0x01, 0x1e, 0xf9, 0x08, 0xca, 0xca, 0x9d, 0xbc, 0x41, 0xa9, 0xb5, 0x7c, 0xbf, 0xd5, 0x9e, 0x5d, + 0xb8, 0xed, 0x2c, 0x38, 0x96, 0xd6, 0x33, 0x9d, 0xa9, 0xf0, 0xcb, 0x9c, 0xea, 0x22, 0x18, 0xb9, + 0x30, 0xae, 0xe8, 0xe2, 0x29, 0xdc, 0x9a, 0x95, 0x61, 0xed, 0xa3, 0x0b, 0xd5, 0x13, 0x45, 0xf7, + 0x30, 0x71, 0x73, 0x37, 0xcf, 0xcd, 0xb8, 0x2d, 0x6b, 0xa4, 0x6c, 0xfe, 0x62, 0xc0, 0x8d, 0x7d, + 0x14, 0x87, 0x8c, 0x3e, 0x45, 0x57, 0xa5, 0x28, 0xe2, 0x2f, 0x5b, 0xa2, 0xb2, 0xb9, 0xc2, 0xd8, + 0x20, 0x6f, 0x94, 0xb6, 0x4b, 0xb2, 0xb9, 0x92, 0x33, 0xb9, 0x01, 0xd5, 0xd0, 0x19, 0xa2, 0xcd, + 0xbd, 0x17, 0x71, 0xe7, 0x2d, 0x5a, 0x15, 0x49, 0x38, 0xf2, 0x5e, 0x28, 0xbb, 0x8a, 0x29, 0xe8, + 0x33, 0x4c, 0x0a, 0x40, 0x89, 0x7f, 0x29, 0x09, 0xe6, 0xaf, 0x86, 0x6a, 0xa8, 0x89, 0x60, 0xe7, + 0x96, 0xfe, 0x16, 0x80, 0x7b, 0xe2, 0x04, 0x43, 0xb4, 0x47, 0x1d, 0x50, 0x8d, 0x29, 0xc7, 0xcc, + 0xfb, 0xd7, 0xa2, 0xed, 0xc3, 0x35, 0x1d, 0xe9, 0x57, 0xce, 0xa9, 0x37, 0x70, 0x84, 0x47, 0x03, + 0x39, 0xd2, 0xb4, 0xf1, 0xa4, 0xc5, 0xf4, 0x91, 0xdc, 0x81, 0xd5, 0x3e, 0x43, 0xe7, 0x99, 0xb4, + 0x38, 0xc0, 0x21, 0xc3, 0x78, 0xaa, 0x19, 0xdd, 0x82, 0x55, 0x4f, 0x18, 0x8f, 0x14, 0xbd, 0x53, + 0x81, 0x32, 0x43, 0x1e, 0x9d, 0x0a, 0xf3, 0xf7, 0xd2, 0x34, 0x44, 0x32, 0xd5, 0x38, 0xa7, 0x1f, + 0x52, 0xc0, 0x8a, 0xb3, 0x01, 0x2b, 0x4d, 0x02, 0x76, 0x1d, 0x2a, 0x7d, 0x87, 0xc7, 0xcc, 0x78, + 0x76, 0x2e, 0xc9, 0xb3, 0x64, 0xbd, 0x39, 0x39, 0x04, 0x62, 0x54, 0xc6, 0x47, 0x80, 0x09, 0x2b, + 0x51, 0x38, 0x70, 0x04, 0x0e, 0x6c, 0x47, 0xc8, 0x69, 0x02, 0xf1, 0x34, 0xd1, 0xc4, 0x87, 0xe2, + 0x80, 0x93, 0x0f, 0xa1, 0xc9, 0x30, 0xa4, 0xdc, 0x93, 0x05, 0x6b, 0x4f, 0x02, 0x53, 0xd6, 0xc0, + 0x34, 0x46, 0x32, 0x9d, 0x31, 0x88, 0xc8, 0x1e, 0x34, 0x34, 0xb0, 0x76, 0x0c, 0x15, 0xb7, 0x5d, + 0xea, 0x87, 0xa7, 0x28, 0xb0, 0xb1, 0xb4, 0x6d, 0xb4, 0x2a, 0xd6, 0xa6, 0xe6, 0x5b, 0x31, 0xfb, + 0x63, 0xcd, 0x25, 0xbd, 0x4c, 0x39, 0x54, 0x54, 0x6b, 0xdd, 0xcb, 0x6b, 0xad, 0x0b, 0x29, 0xce, + 0x54, 0xcf, 0xdb, 0xb0, 0x1a, 0xe0, 0xb7, 0xc2, 0xce, 0x54, 0x49, 0x35, 0x1e, 0x6a, 0x92, 0x7c, + 0x98, 0x54, 0x4a, 0x67, 0x1d, 0xae, 0x65, 0x2e, 0xab, 0x53, 0xfb, 0x47, 0x49, 0x4d, 0x9a, 0x69, + 0xc5, 0xfe, 0x2a, 0xb7, 0xff, 0xf3, 0xdc, 0xfe, 0x5c, 0x4c, 0x1f, 0xae, 0xa3, 0xc8, 0xf7, 0x9d, + 0xf9, 0x0f, 0xd7, 0x7f, 0x92, 0xcc, 0x7b, 0xb0, 0xae, 0x98, 0xf6, 0x78, 0x4a, 0xcb, 0x2a, 0xa5, + 0x6b, 0x8a, 0x75, 0x9c, 0xc9, 0xeb, 0x5b, 0x50, 0x77, 0x5c, 0x17, 0xc3, 0x91, 0xe4, 0x92, 0x92, + 0xac, 0x25, 0x54, 0x25, 0x75, 0xf1, 0xa1, 0xaf, 0x4c, 0x79, 0xe8, 0xcd, 0xbf, 0x0c, 0x58, 0xfe, + 0xcc, 0x9b, 0xb3, 0x83, 0x91, 0xf7, 0xe0, 0xf5, 0xac, 0x4b, 0xca, 0x6c, 0xe7, 0x89, 0x40, 0x96, + 0xae, 0x28, 0xdd, 0x82, 0xb5, 0x3e, 0xf2, 0xfe, 0x05, 0x7b, 0x28, 0xb9, 0x07, 0x9c, 0xb4, 0x81, + 0xa4, 0x7a, 0x7d, 0x7c, 0x42, 0x19, 0x4a, 0x15, 0xb5, 0x25, 0x76, 0x0d, 0x6b, 0x2d, 0xe1, 0x75, + 0x14, 0xeb, 0xe0, 0xa5, 0xde, 0x81, 0xce, 0x26, 0x6c, 0xa4, 0xbe, 0x4e, 0xe9, 0x37, 0xc8, 0xec, + 0x3e, 0x8d, 0x82, 0xc1, 0x18, 0x3d, 0x0a, 0xc3, 0x84, 0x6e, 0x7e, 0x07, 0xb5, 0xf8, 0xe2, 0xba, + 0xcd, 0x3f, 0x85, 0x8a, 0xce, 0xca, 0x65, 0xde, 0x7a, 0x5d, 0x57, 0x56, 0xaa, 0x3b, 0xad, 0x62, + 0x8b, 0x53, 0x2a, 0xf6, 0xfe, 0x9f, 0x65, 0xa8, 0x1e, 0x25, 0xe6, 0xc8, 0x63, 0x58, 0x90, 0x3b, + 0x39, 0xb9, 0x9d, 0xdb, 0x28, 0xa3, 0xfd, 0xbe, 0xd9, 0x9a, 0x2f, 0x18, 0x5f, 0xcc, 0x2c, 0x10, + 0x07, 0xca, 0xf1, 0x0a, 0x4d, 0xee, 0xe4, 0x69, 0x8d, 0x6d, 0xe3, 0xcd, 0xbb, 0xff, 0x44, 0x34, + 0x75, 0xf1, 0xa3, 0x01, 0xaf, 0x4d, 0x5d, 0xd8, 0xc8, 0x5e, 0x9e, 0x9d, 0xbc, 0xbd, 0xbd, 0xf9, + 0xe0, 0x0a, 0x9a, 0x69, 0x40, 0x3f, 0x19, 0xb0, 0x39, 0x7d, 0xbd, 0x23, 0x97, 0xb5, 0x3b, 0xda, + 0x7c, 0x9a, 0x1f, 0x5c, 0x45, 0x35, 0x8d, 0xe9, 0x07, 0x03, 0x36, 0xa6, 0xad, 0x11, 0xe4, 0xfd, + 0x39, 0x66, 0x67, 0xed, 0x8d, 0xcd, 0xbd, 0xcb, 0x2b, 0x4e, 0xa6, 0xec, 0xe2, 0xcb, 0x47, 0x2e, + 0x69, 0x35, 0x83, 0xcf, 0x83, 0x2b, 0x68, 0xa6, 0x01, 0x3d, 0x86, 0x05, 0xd9, 0x91, 0xf9, 0x3d, + 0x90, 0x19, 0x56, 0xf9, 0x3d, 0x90, 0x6d, 0x6e, 0xb3, 0xd0, 0x41, 0xb8, 0xe5, 0x52, 0x3f, 0x47, + 0xa1, 0x53, 0x4f, 0xbb, 0xf1, 0x50, 0xfe, 0x6c, 0x1f, 0x1a, 0x5f, 0xbf, 0x33, 0xf4, 0xc4, 0x49, + 0xd4, 0x6f, 0xbb, 0xd4, 0xdf, 0x95, 0x8a, 0xbb, 0x19, 0xc5, 0x5d, 0x27, 0xf4, 0x76, 0x53, 0xe5, + 0x5d, 0xf5, 0x7f, 0x1e, 0xf6, 0xfb, 0x65, 0xf5, 0xf1, 0xee, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x16, 0xde, 0xcb, 0xc9, 0xbb, 0x0f, 0x00, 0x00, }, } diff --git a/api/stovepipe/protopb/stovepipe_grpc.pb.go b/api/stovepipe/protopb/stovepipe_grpc.pb.go index b766f9290..4bd5c6601 100644 --- a/api/stovepipe/protopb/stovepipe_grpc.pb.go +++ b/api/stovepipe/protopb/stovepipe_grpc.pb.go @@ -38,6 +38,7 @@ const ( Stovepipe_Ingest_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/Ingest" Stovepipe_GetRequestHistoryByID_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetRequestHistoryByID" Stovepipe_GetRequestHistoryByURI_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetRequestHistoryByURI" + Stovepipe_GetProjectStatusByID_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetProjectStatusByID" Stovepipe_GetProjectStatusByURI_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetProjectStatusByURI" Stovepipe_List_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/List" ) @@ -57,6 +58,8 @@ type StovepipeClient interface { GetRequestHistoryByID(ctx context.Context, in *GetRequestHistoryByIDRequest, opts ...grpc.CallOption) (*GetRequestHistoryByIDResponse, error) // GetRequestHistoryByURI returns retained histories for an exact commit URI. GetRequestHistoryByURI(ctx context.Context, in *GetRequestHistoryByURIRequest, opts ...grpc.CallOption) (*GetRequestHistoryByURIResponse, error) + // GetProjectStatusByID returns the current validation status for one request ID. + GetProjectStatusByID(ctx context.Context, in *GetProjectStatusByIDRequest, opts ...grpc.CallOption) (*GetProjectStatusByIDResponse, error) // GetProjectStatusByURI returns the current validation status for an exact commit URI. GetProjectStatusByURI(ctx context.Context, in *GetProjectStatusByURIRequest, opts ...grpc.CallOption) (*GetProjectStatusByURIResponse, error) // List returns a queue-scoped page of current request summaries. @@ -111,6 +114,16 @@ func (c *stovepipeClient) GetRequestHistoryByURI(ctx context.Context, in *GetReq return out, nil } +func (c *stovepipeClient) GetProjectStatusByID(ctx context.Context, in *GetProjectStatusByIDRequest, opts ...grpc.CallOption) (*GetProjectStatusByIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetProjectStatusByIDResponse) + err := c.cc.Invoke(ctx, Stovepipe_GetProjectStatusByID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *stovepipeClient) GetProjectStatusByURI(ctx context.Context, in *GetProjectStatusByURIRequest, opts ...grpc.CallOption) (*GetProjectStatusByURIResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetProjectStatusByURIResponse) @@ -146,6 +159,8 @@ type StovepipeServer interface { GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest) (*GetRequestHistoryByIDResponse, error) // GetRequestHistoryByURI returns retained histories for an exact commit URI. GetRequestHistoryByURI(context.Context, *GetRequestHistoryByURIRequest) (*GetRequestHistoryByURIResponse, error) + // GetProjectStatusByID returns the current validation status for one request ID. + GetProjectStatusByID(context.Context, *GetProjectStatusByIDRequest) (*GetProjectStatusByIDResponse, error) // GetProjectStatusByURI returns the current validation status for an exact commit URI. GetProjectStatusByURI(context.Context, *GetProjectStatusByURIRequest) (*GetProjectStatusByURIResponse, error) // List returns a queue-scoped page of current request summaries. @@ -172,6 +187,9 @@ func (UnimplementedStovepipeServer) GetRequestHistoryByID(context.Context, *GetR func (UnimplementedStovepipeServer) GetRequestHistoryByURI(context.Context, *GetRequestHistoryByURIRequest) (*GetRequestHistoryByURIResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRequestHistoryByURI not implemented") } +func (UnimplementedStovepipeServer) GetProjectStatusByID(context.Context, *GetProjectStatusByIDRequest) (*GetProjectStatusByIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProjectStatusByID not implemented") +} func (UnimplementedStovepipeServer) GetProjectStatusByURI(context.Context, *GetProjectStatusByURIRequest) (*GetProjectStatusByURIResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProjectStatusByURI not implemented") } @@ -271,6 +289,24 @@ func _Stovepipe_GetRequestHistoryByURI_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _Stovepipe_GetProjectStatusByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProjectStatusByIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StovepipeServer).GetProjectStatusByID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Stovepipe_GetProjectStatusByID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StovepipeServer).GetProjectStatusByID(ctx, req.(*GetProjectStatusByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Stovepipe_GetProjectStatusByURI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetProjectStatusByURIRequest) if err := dec(in); err != nil { @@ -330,6 +366,10 @@ var Stovepipe_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetRequestHistoryByURI", Handler: _Stovepipe_GetRequestHistoryByURI_Handler, }, + { + MethodName: "GetProjectStatusByID", + Handler: _Stovepipe_GetProjectStatusByID_Handler, + }, { MethodName: "GetProjectStatusByURI", Handler: _Stovepipe_GetProjectStatusByURI_Handler, diff --git a/doc/rfc/index.md b/doc/rfc/index.md index 02f012002..b0d15c89f 100644 --- a/doc/rfc/index.md +++ b/doc/rfc/index.md @@ -36,7 +36,7 @@ Design documents and technical proposals, grouped by scope. Shared/cross-cutting - [Request Log](stovepipe/request-log.md) - Append-only request lifecycle log, durable source context, idempotent storage, and reliable write and repair paths - [Request History API](stovepipe/request-history-api.md) - Queue-scoped request-ID and URI lookup, public projection, materialization decision, ordering, and retention - [List API](stovepipe/list-api.md) - Queue-scoped request listing by acceptance time, immutable acceptance time in summaries, cursor pagination, and a portable time-to-request mapping -- [GetProjectStatusByURI API](stovepipe/get-project-status-by-uri-api.md) - Queue-scoped current validation lookup for a commit, with repository and future project-level results +- [Project Status APIs](stovepipe/project-status-api.md) - Queue-scoped current validation lookup by request ID or commit URI, with repository and future project-level results ## Runway diff --git a/doc/rfc/stovepipe/get-project-status-by-uri-api.md b/doc/rfc/stovepipe/project-status-api.md similarity index 63% rename from doc/rfc/stovepipe/get-project-status-by-uri-api.md rename to doc/rfc/stovepipe/project-status-api.md index 5901cfc90..514a97866 100644 --- a/doc/rfc/stovepipe/get-project-status-by-uri-api.md +++ b/doc/rfc/stovepipe/project-status-api.md @@ -1,12 +1,12 @@ -# Stovepipe GetProjectStatusByURI API +# Stovepipe Project Status APIs ## Summary -`GetProjectStatusByURI` exposes the current validation status for one exact commit URI in a queue. The response identifies the validation request, its baseline and lifecycle state, its whole-repository result when one has been recorded, and any project results the implementation exposes. +`GetProjectStatusByID` and `GetProjectStatusByURI` expose the current validation status for one request ID or exact commit URI in a queue. The response identifies the validation request, its baseline and lifecycle state, its whole-repository result when one has been recorded, and any project results the implementation exposes. A project is a consumer-defined deployable or consumable unit in the repository. The API does not define how projects are discovered, how validation work is selected, or how a project result is derived. Those are integration responsibilities outside this contract. -The API is the durable source of truth for validation status. Lifecycle notifications are advisory: consumers can reconcile missed or duplicate notifications by querying this endpoint. +The API is the durable source of truth for validation status. Lifecycle notifications are advisory: consumers can reconcile missed or duplicate notifications by querying either endpoint. ## Contract @@ -15,7 +15,8 @@ The request and response fields have the following API-level meaning. The publis | Request field | Required | Meaning | | --- | --- | --- | | `queue` | Yes | Identifies the queue in which to find the validation request. | -| `change_uri` | Yes | Identifies the exact commit URI under validation. | +| `request_id` | ID lookup | Canonical positive decimal request ID returned by `Ingest`. | +| `change_uri` | URI lookup | Identifies the exact commit URI under validation. | | `projects` | Yes | Consumer-defined project IDs whose results are requested. At least one ID is required. | | `page_size` | No | Limits one page of results for the requested projects. | | `page_token` | No | Continues a previous page of results for the requested projects. | @@ -37,24 +38,25 @@ Repository and project breakage degrees are independent projections. A degree is | Response data | Durable source | | --- | --- | -| Request identity, baseline, and lifecycle | Validation request | +| Request identity, baseline, and lifecycle | Durable request summary | | Repository breakage degree | Repository validation fact | | Project results | Implementation-defined project-result records | | Project completion and pagination | Project-result completion record and cursor | ## Request Selection -The lookup is queue-scoped. Stovepipe resolves `change_uri` through its request-URI mapping, loads the resulting request, and verifies that its queue and URI match the selector before reading validation facts. +Both lookups are queue-scoped and use the same durable request summary and validation facts. `GetProjectStatusByID` loads the summary directly by `request_id`. `GetProjectStatusByURI` resolves `change_uri` through its request-URI mapping, then loads the summary. The selected summary must match the queue and request ID, and for URI lookups its URI must match the selector, before validation facts are read. | Observed state | Result | | --- | --- | -| No request-URI mapping | Not found | -| Mapping exists but the Request is not visible | Unavailable and retryable | -| Mapping and Request disagree | Internal consistency error | +| ID lookup has no request summary | Not found | +| URI lookup has no request-URI mapping | Not found | +| URI mapping exists but the request summary is not visible | Unavailable and retryable | +| Selector and request summary disagree | Internal consistency error | -The initial request-URI mapping admits one request per `(queue, change_uri)`, so the endpoint returns one authoritative request. Revalidation support must introduce an explicit authoritative-request rule; it must not silently change this lookup's meaning. +The request-URI mapping admits one request per `(queue, change_uri)`, so the endpoint returns one authoritative request. Revalidation support must introduce an explicit authoritative-request rule; it must not silently change this lookup's meaning. -Queue, URI, request ID, and project ID comparisons are byte-exact and are limited to 255 bytes. An empty queue, URI, project list, or project ID is invalid. A request cannot contain duplicate project IDs. `page_size=0` selects the default of 50; the maximum is 200. Page tokens are opaque and bound to the selected request and requested project list. +Queue, URI, request ID, and project ID comparisons are byte-exact and are limited to 255 bytes. An empty queue, lookup selector, project list, or project ID is invalid. A request cannot contain duplicate project IDs. `page_size=0` selects the default of 50; the maximum is 200. Request IDs use the same canonical positive decimal format as `Ingest` and request-history lookups. Page tokens are opaque and bound to the selected request and requested project list; either endpoint can continue a page for the same queue, request, and projects. ## Project Results and Pagination @@ -64,20 +66,18 @@ The response includes only results for the supplied project IDs, in the supplied Each project result is an immutable validation fact keyed by queue, commit URI, and project ID. A result must identify the selected request. `project_results_complete` is false until the implementation has durably finished producing results for the supplied project IDs; a completed empty set is valid. -Callers use `project_results_complete`, rather than the presence or absence of an individual project result, to determine whether the implementation has finished producing results for the requested projects. `next_page_token` is empty only on the final page. A terminal lifecycle notification is emitted only after the repository result and any applicable project completion record are durable. If notification delivery fails, consumers can recover the same result through this endpoint. +Callers use `project_results_complete`, rather than the presence or absence of an individual project result, to determine whether the implementation has finished producing results for the requested projects. `next_page_token` is empty only on the final page. A terminal lifecycle notification is emitted only after the repository result and any applicable project completion record are durable. If notification delivery fails, consumers can recover the same result through either endpoint. -## Rollout +## Implementation -The initial implementation returns request lifecycle and whole-repository result only. It returns an empty `projects` list and `project_results_complete=false` because it does not yet produce project results. Its `updated_at_ms` value is returned only after a durable lifecycle record reflects the request state in the response. - -A later implementation can add durable project results, completion recording, and pagination using its documented result-set semantics. The public response shape remains unchanged. +Both endpoints return durable lifecycle summaries, the whole-repository fact when present, and any recorded facts for requested projects in paginated request order. `project_results_complete` remains false until a durable completion projection is available. Missing facts are omitted; an explicitly recorded zero degree remains present. `updated_at_ms` is the maximum timestamp of the summary and the facts represented on the page. ## Errors and Authorization - Invalid selectors, an empty project list, an invalid project ID, duplicate project IDs, or malformed page tokens are user errors. -- An unknown queue-scoped commit URI is not found. -- A request-URI mapping whose Request is not visible is retryable. -- A mapping and Request that disagree, a result belonging to another request, or a completion record with a missing project result is an internal consistency error. +- An unknown queue-scoped request ID or commit URI is not found. +- A request-URI mapping whose summary is not visible is retryable. +- A selector and summary that disagree, a result belonging to another request, or a completion record with a missing project result is an internal consistency error. - Authorization follows the queue policy applied to other Stovepipe reads. A commit URI or request ID does not bypass queue access control. ## Testing diff --git a/service/stovepipe/server/handler/BUILD.bazel b/service/stovepipe/server/handler/BUILD.bazel index ce8429cc7..d25dbfbdd 100644 --- a/service/stovepipe/server/handler/BUILD.bazel +++ b/service/stovepipe/server/handler/BUILD.bazel @@ -22,9 +22,11 @@ go_test( deps = [ "//api/stovepipe/protopb:go_default_library", "//stovepipe/controller:go_default_library", + "//stovepipe/controller/mock:go_default_library", "//stovepipe/entity:go_default_library", "//stovepipe/extension/storage:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", ], ) diff --git a/service/stovepipe/server/handler/server.go b/service/stovepipe/server/handler/server.go index 37f31608c..028e76a45 100644 --- a/service/stovepipe/server/handler/server.go +++ b/service/stovepipe/server/handler/server.go @@ -29,7 +29,7 @@ type StovepipeServer struct { pingController *controller.PingController ingestController *controller.IngestController requestHistoryController controller.RequestHistoryController - projectStatusController *controller.GetProjectStatusByURIController + projectStatusController controller.ProjectStatusController listController controller.ListController } @@ -38,7 +38,7 @@ func NewStovepipeServer( pingController *controller.PingController, ingestController *controller.IngestController, requestHistoryController controller.RequestHistoryController, - projectStatusController *controller.GetProjectStatusByURIController, + projectStatusController controller.ProjectStatusController, listController controller.ListController, ) *StovepipeServer { return &StovepipeServer{ @@ -83,9 +83,18 @@ func (s *StovepipeServer) GetRequestHistoryByURI(ctx context.Context, req *pb.Ge return &pb.GetRequestHistoryByURIResponse{Histories: mapper.RequestHistoriesToProto(histories)}, nil } -// GetProjectStatusByURI returns the current repository validation status for a commit. +// GetProjectStatusByID returns the current validation status for a request ID. +func (s *StovepipeServer) GetProjectStatusByID(ctx context.Context, req *pb.GetProjectStatusByIDRequest) (*pb.GetProjectStatusByIDResponse, error) { + result, err := s.projectStatusController.GetProjectStatus(ctx, mapper.ProtoToGetProjectStatusByIDRequest(req)) + if err != nil { + return nil, err + } + return mapper.GetProjectStatusByIDResultToProto(result), nil +} + +// GetProjectStatusByURI returns the current validation status for a commit. func (s *StovepipeServer) GetProjectStatusByURI(ctx context.Context, req *pb.GetProjectStatusByURIRequest) (*pb.GetProjectStatusByURIResponse, error) { - result, err := s.projectStatusController.GetProjectStatusByURI(ctx, mapper.ProtoToGetProjectStatusByURIRequest(req)) + result, err := s.projectStatusController.GetProjectStatus(ctx, mapper.ProtoToGetProjectStatusByURIRequest(req)) if err != nil { return nil, err } diff --git a/service/stovepipe/server/handler/server_test.go b/service/stovepipe/server/handler/server_test.go index 42c035e95..533d450c2 100644 --- a/service/stovepipe/server/handler/server_test.go +++ b/service/stovepipe/server/handler/server_test.go @@ -23,7 +23,9 @@ import ( "github.com/stretchr/testify/require" pb "github.com/uber/submitqueue/api/stovepipe/protopb" "github.com/uber/submitqueue/stovepipe/controller" + controllermock "github.com/uber/submitqueue/stovepipe/controller/mock" "github.com/uber/submitqueue/stovepipe/entity" + "go.uber.org/mock/gomock" ) type fakeRequestHistoryController struct { @@ -137,3 +139,53 @@ func TestGetRequestHistoryByURI(t *testing.T) { }) } } + +func TestGetProjectStatus(t *testing.T) { + controllerErr := errors.New("controller failed") + for _, selector := range []string{"ID", "URI"} { + for _, callErr := range []error{nil, controllerErr} { + name := selector + "/success" + if callErr != nil { + name = selector + "/controller error" + } + t.Run(name, func(t *testing.T) { + wantReq := entity.GetProjectStatusRequest{Queue: "queue", Projects: []string{"project-a"}, PageSize: 25, PageToken: "token"} + if selector == "ID" { + wantReq.RequestID = "7" + } else { + wantReq.ChangeURI = "uri" + } + projectStatusController := controllermock.NewMockProjectStatusController(gomock.NewController(t)) + projectStatusController.EXPECT().GetProjectStatus(gomock.Any(), wantReq).Return(entity.GetProjectStatusResult{ + RequestSummary: entity.RequestSummary{RequestID: "7", Queue: "queue", URI: "uri", State: entity.RequestStateSucceeded}, + }, callErr) + srv := &StovepipeServer{projectStatusController: projectStatusController} + if selector == "ID" { + response, err := srv.GetProjectStatusByID(context.Background(), &pb.GetProjectStatusByIDRequest{ + Queue: "queue", RequestId: "7", Projects: []string{"project-a"}, PageSize: 25, PageToken: "token", + }) + if callErr != nil { + require.ErrorIs(t, err, callErr) + assert.Nil(t, response) + return + } + require.NoError(t, err) + assert.Equal(t, "7", response.GetRequestId()) + assert.Equal(t, "succeeded", response.GetRequestState()) + } else { + response, err := srv.GetProjectStatusByURI(context.Background(), &pb.GetProjectStatusByURIRequest{ + Queue: "queue", ChangeUri: "uri", Projects: []string{"project-a"}, PageSize: 25, PageToken: "token", + }) + if callErr != nil { + require.ErrorIs(t, err, callErr) + assert.Nil(t, response) + return + } + require.NoError(t, err) + assert.Equal(t, "7", response.GetRequestId()) + assert.Equal(t, "succeeded", response.GetRequestState()) + } + }) + } + } +} diff --git a/service/stovepipe/server/main.go b/service/stovepipe/server/main.go index c7055dd96..34589d671 100644 --- a/service/stovepipe/server/main.go +++ b/service/stovepipe/server/main.go @@ -285,7 +285,7 @@ func run() error { tenants, ) requestHistoryController := controller.NewRequestHistoryController(logger.Sugar(), scope, storageFty) - projectStatusController := controller.NewGetProjectStatusByURIController(logger.Sugar(), scope, storageFty) + projectStatusController := controller.NewProjectStatusController(logger.Sugar(), scope, storageFty) listController := controller.NewListController(logger.Sugar(), scope, storageFty, tenants) srv := handler.NewStovepipeServer(pingController, ingestController, requestHistoryController, projectStatusController, listController) pb.RegisterStovepipeServer(grpcServer, srv) diff --git a/service/stovepipe/server/mapper/project_status.go b/service/stovepipe/server/mapper/project_status.go index 705841dc5..dbdbbc556 100644 --- a/service/stovepipe/server/mapper/project_status.go +++ b/service/stovepipe/server/mapper/project_status.go @@ -19,9 +19,21 @@ import ( "github.com/uber/submitqueue/stovepipe/entity" ) +// ProtoToGetProjectStatusByIDRequest maps a wire selector to its domain form. +func ProtoToGetProjectStatusByIDRequest(req *pb.GetProjectStatusByIDRequest) entity.GetProjectStatusRequest { + result := entity.GetProjectStatusRequest{ + Queue: req.GetQueue(), + RequestID: req.GetRequestId(), + PageSize: req.GetPageSize(), + PageToken: req.GetPageToken(), + } + result.Projects = req.GetProjects() + return result +} + // ProtoToGetProjectStatusByURIRequest maps a wire selector to its domain form. -func ProtoToGetProjectStatusByURIRequest(req *pb.GetProjectStatusByURIRequest) entity.GetProjectStatusByURIRequest { - result := entity.GetProjectStatusByURIRequest{ +func ProtoToGetProjectStatusByURIRequest(req *pb.GetProjectStatusByURIRequest) entity.GetProjectStatusRequest { + result := entity.GetProjectStatusRequest{ Queue: req.GetQueue(), ChangeURI: req.GetChangeUri(), PageSize: req.GetPageSize(), @@ -31,8 +43,29 @@ func ProtoToGetProjectStatusByURIRequest(req *pb.GetProjectStatusByURIRequest) e return result } +// GetProjectStatusByIDResultToProto maps a domain status projection to its wire response. +func GetProjectStatusByIDResultToProto(result entity.GetProjectStatusResult) *pb.GetProjectStatusByIDResponse { + response := &pb.GetProjectStatusByIDResponse{ + RequestId: result.RequestSummary.RequestID, + Queue: result.RequestSummary.Queue, + ChangeUri: result.RequestSummary.URI, + BaseUri: result.RequestSummary.BaseURI, + RequestState: string(result.RequestSummary.State), + UpdatedAtMs: result.UpdatedAtMs, + ProjectResultsComplete: result.ProjectResultsComplete, + NextPageToken: result.NextPageToken, + } + if result.HasRepositoryValidationFact { + response.RepositoryResult = &pb.GetProjectStatusByIDResponse_RepositoryBreakageDegree{ + RepositoryBreakageDegree: result.RepositoryValidationFact.Degree, + } + } + response.Projects = validationFactsToProto(result.ProjectValidationFacts) + return response +} + // GetProjectStatusByURIResultToProto maps a domain status projection to its wire response. -func GetProjectStatusByURIResultToProto(result entity.GetProjectStatusByURIResult) *pb.GetProjectStatusByURIResponse { +func GetProjectStatusByURIResultToProto(result entity.GetProjectStatusResult) *pb.GetProjectStatusByURIResponse { response := &pb.GetProjectStatusByURIResponse{ RequestId: result.RequestSummary.RequestID, Queue: result.RequestSummary.Queue, @@ -48,15 +81,20 @@ func GetProjectStatusByURIResultToProto(result entity.GetProjectStatusByURIResul RepositoryBreakageDegree: result.RepositoryValidationFact.Degree, } } - response.Projects = make([]*pb.ProjectValidation, 0, len(result.ProjectValidationFacts)) - for _, fact := range result.ProjectValidationFacts { + response.Projects = validationFactsToProto(result.ProjectValidationFacts) + return response +} + +func validationFactsToProto(facts []entity.ValidationFact) []*pb.ProjectValidation { + projects := make([]*pb.ProjectValidation, 0, len(facts)) + for _, fact := range facts { degree := fact.Degree - response.Projects = append(response.Projects, &pb.ProjectValidation{ + projects = append(projects, &pb.ProjectValidation{ Project: fact.Project, Result: &pb.ProjectValidation_BreakageDegree{ BreakageDegree: degree, }, }) } - return response + return projects } diff --git a/service/stovepipe/server/mapper/project_status_test.go b/service/stovepipe/server/mapper/project_status_test.go index fed19c252..701cf815e 100644 --- a/service/stovepipe/server/mapper/project_status_test.go +++ b/service/stovepipe/server/mapper/project_status_test.go @@ -26,12 +26,12 @@ func TestProjectStatusRequestAndResponseMapping(t *testing.T) { request := &pb.GetProjectStatusByURIRequest{ Queue: "queue", ChangeUri: "uri", Projects: []string{"project-a", "project-b"}, PageSize: 25, PageToken: "token", } - assert.Equal(t, entity.GetProjectStatusByURIRequest{ + assert.Equal(t, entity.GetProjectStatusRequest{ Queue: "queue", ChangeURI: "uri", Projects: []string{"project-a", "project-b"}, PageSize: 25, PageToken: "token", }, ProtoToGetProjectStatusByURIRequest(request)) degree := entity.DegreeGreen - response := GetProjectStatusByURIResultToProto(entity.GetProjectStatusByURIResult{ + response := GetProjectStatusByURIResultToProto(entity.GetProjectStatusResult{ RequestSummary: entity.RequestSummary{RequestID: "1", Queue: "queue", URI: "uri", BaseURI: "base", State: entity.RequestStateSucceeded}, RepositoryValidationFact: entity.ValidationFact{Degree: degree}, HasRepositoryValidationFact: true, @@ -53,7 +53,7 @@ func TestProjectStatusRequestAndResponseMapping(t *testing.T) { } func TestProjectStatusMappingPreservesExplicitGreenResults(t *testing.T) { - response := GetProjectStatusByURIResultToProto(entity.GetProjectStatusByURIResult{ + response := GetProjectStatusByURIResultToProto(entity.GetProjectStatusResult{ HasRepositoryValidationFact: true, RepositoryValidationFact: entity.ValidationFact{ Degree: entity.DegreeGreen, @@ -69,3 +69,46 @@ func TestProjectStatusMappingPreservesExplicitGreenResults(t *testing.T) { assert.IsType(t, &pb.ProjectValidation_BreakageDegree{}, response.GetProjects()[0].GetResult()) } } + +func TestProjectStatusByIDRequestAndResponseMapping(t *testing.T) { + assert.Equal(t, entity.GetProjectStatusRequest{ + Queue: "queue", RequestID: "1", Projects: []string{"project-a", "project-b"}, PageSize: 25, PageToken: "token", + }, ProtoToGetProjectStatusByIDRequest(&pb.GetProjectStatusByIDRequest{ + Queue: "queue", RequestId: "1", Projects: []string{"project-a", "project-b"}, PageSize: 25, PageToken: "token", + })) + for _, hasFact := range []bool{false, true} { + t.Run(map[bool]string{false: "absent repository result", true: "explicit green repository result"}[hasFact], func(t *testing.T) { + result := entity.GetProjectStatusResult{ + RequestSummary: entity.RequestSummary{RequestID: "1", Queue: "queue", URI: "uri", BaseURI: "base", State: entity.RequestStateSucceeded}, + HasRepositoryValidationFact: hasFact, + RepositoryValidationFact: entity.ValidationFact{Degree: entity.DegreeGreen}, + ProjectResultsComplete: true, + ProjectValidationFacts: []entity.ValidationFact{{Project: "project-a", Degree: entity.DegreeGreen}, {Project: "project-b", Degree: entity.DegreeBroken}}, + NextPageToken: "next-token", + UpdatedAtMs: 123, + } + response := GetProjectStatusByIDResultToProto(result) + assert.Equal(t, "1", response.GetRequestId()) + assert.Equal(t, "queue", response.GetQueue()) + assert.Equal(t, "uri", response.GetChangeUri()) + assert.Equal(t, "base", response.GetBaseUri()) + assert.Equal(t, "succeeded", response.GetRequestState()) + assert.Equal(t, int64(123), response.GetUpdatedAtMs()) + assert.True(t, response.GetProjectResultsComplete()) + assert.Equal(t, "next-token", response.GetNextPageToken()) + if hasFact { + assert.IsType(t, &pb.GetProjectStatusByIDResponse_RepositoryBreakageDegree{}, response.GetRepositoryResult()) + assert.Equal(t, entity.DegreeGreen, response.GetRepositoryBreakageDegree()) + } else { + assert.Nil(t, response.GetRepositoryResult()) + } + if assert.Len(t, response.Projects, 2) { + assert.Equal(t, "project-a", response.Projects[0].GetProject()) + assert.IsType(t, &pb.ProjectValidation_BreakageDegree{}, response.Projects[0].GetResult()) + assert.Equal(t, entity.DegreeGreen, response.Projects[0].GetBreakageDegree()) + assert.Equal(t, "project-b", response.Projects[1].GetProject()) + assert.Equal(t, entity.DegreeBroken, response.Projects[1].GetBreakageDegree()) + } + }) + } +} diff --git a/stovepipe/controller/BUILD.bazel b/stovepipe/controller/BUILD.bazel index 6f25456aa..7988c25cb 100644 --- a/stovepipe/controller/BUILD.bazel +++ b/stovepipe/controller/BUILD.bazel @@ -3,11 +3,11 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "get_project_status_by_uri.go", "ingest.go", "list.go", "list_pagination.go", "ping.go", + "project_status.go", "read_errors.go", "request_history.go", ], @@ -34,12 +34,12 @@ go_library( go_test( name = "go_default_test", srcs = [ - "get_project_status_by_uri_test.go", "ingest_test.go", "list_consistency_test.go", "list_pagination_test.go", "list_test.go", "ping_test.go", + "project_status_test.go", "request_history_test.go", ], embed = [":go_default_library"], diff --git a/stovepipe/controller/mock/BUILD.bazel b/stovepipe/controller/mock/BUILD.bazel new file mode 100644 index 000000000..5eddd07c1 --- /dev/null +++ b/stovepipe/controller/mock/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["project_status_mock.go"], + importpath = "github.com/uber/submitqueue/stovepipe/controller/mock", + visibility = ["//visibility:public"], + deps = [ + "//stovepipe/entity:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", + ], +) diff --git a/stovepipe/controller/mock/project_status_mock.go b/stovepipe/controller/mock/project_status_mock.go new file mode 100644 index 000000000..a644ad025 --- /dev/null +++ b/stovepipe/controller/mock/project_status_mock.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: project_status.go +// +// Generated by this command: +// +// mockgen -source=project_status.go -destination=mock/project_status_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/stovepipe/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockProjectStatusController is a mock of ProjectStatusController interface. +type MockProjectStatusController struct { + ctrl *gomock.Controller + recorder *MockProjectStatusControllerMockRecorder + isgomock struct{} +} + +// MockProjectStatusControllerMockRecorder is the mock recorder for MockProjectStatusController. +type MockProjectStatusControllerMockRecorder struct { + mock *MockProjectStatusController +} + +// NewMockProjectStatusController creates a new mock instance. +func NewMockProjectStatusController(ctrl *gomock.Controller) *MockProjectStatusController { + mock := &MockProjectStatusController{ctrl: ctrl} + mock.recorder = &MockProjectStatusControllerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProjectStatusController) EXPECT() *MockProjectStatusControllerMockRecorder { + return m.recorder +} + +// GetProjectStatus mocks base method. +func (m *MockProjectStatusController) GetProjectStatus(arg0 context.Context, arg1 entity.GetProjectStatusRequest) (entity.GetProjectStatusResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProjectStatus", arg0, arg1) + ret0, _ := ret[0].(entity.GetProjectStatusResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProjectStatus indicates an expected call of GetProjectStatus. +func (mr *MockProjectStatusControllerMockRecorder) GetProjectStatus(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectStatus", reflect.TypeOf((*MockProjectStatusController)(nil).GetProjectStatus), arg0, arg1) +} diff --git a/stovepipe/controller/get_project_status_by_uri.go b/stovepipe/controller/project_status.go similarity index 59% rename from stovepipe/controller/get_project_status_by_uri.go rename to stovepipe/controller/project_status.go index b7cd09720..4eb15937a 100644 --- a/stovepipe/controller/get_project_status_by_uri.go +++ b/stovepipe/controller/project_status.go @@ -14,6 +14,8 @@ package controller +//go:generate mockgen -source=project_status.go -destination=mock/project_status_mock.go -package=mock + import ( "context" "encoding/base64" @@ -46,11 +48,16 @@ type projectStatusPageToken struct { type ProjectStatusNotFoundError struct { // Queue is the queue in the selector. Queue string + // RequestID is the request identifier in an ID selector, empty for a URI selector. + RequestID string // ChangeURI is the commit URI in the selector. ChangeURI string } func (e *ProjectStatusNotFoundError) Error() string { + if e.RequestID != "" { + return fmt.Sprintf("project status not found for queue %q and request ID %q", e.Queue, e.RequestID) + } return fmt.Sprintf("project status not found for queue %q and change URI %q", e.Queue, e.ChangeURI) } @@ -73,53 +80,65 @@ func IsProjectStatusConsistency(err error) bool { return errors.As(err, &target) } -// GetProjectStatusByURIController reads the durable validation projections. -type GetProjectStatusByURIController struct { +// ProjectStatusController exposes durable validation projections selected by ID or URI. +type ProjectStatusController interface { + GetProjectStatus(context.Context, entity.GetProjectStatusRequest) (entity.GetProjectStatusResult, error) +} + +var _ ProjectStatusController = (*projectStatusController)(nil) + +type projectStatusController struct { logger *zap.SugaredLogger metricsScope tally.Scope stores storage.Factory } -// NewGetProjectStatusByURIController creates a controller for validation-status lookups. -func NewGetProjectStatusByURIController(logger *zap.SugaredLogger, scope tally.Scope, stores storage.Factory) *GetProjectStatusByURIController { - return &GetProjectStatusByURIController{ +// NewProjectStatusController creates a controller for validation-status lookups. +func NewProjectStatusController(logger *zap.SugaredLogger, scope tally.Scope, stores storage.Factory) ProjectStatusController { + return &projectStatusController{ logger: logger, - metricsScope: scope.SubScope("get_project_status_by_uri_controller"), + metricsScope: scope.SubScope("project_status_controller"), stores: stores, } } -// GetProjectStatusByURI returns the selected request summary and its repository validation fact, if recorded. -func (c *GetProjectStatusByURIController) GetProjectStatusByURI(ctx context.Context, req entity.GetProjectStatusByURIRequest) (result entity.GetProjectStatusByURIResult, retErr error) { - op := metrics.Begin(c.metricsScope, "get_project_status_by_uri", metrics.StorageLatencyBuckets, metrics.TagsFromContext(ctx)...) +// GetProjectStatus returns the selected request summary and its repository validation fact, if recorded. +func (c *projectStatusController) GetProjectStatus(ctx context.Context, req entity.GetProjectStatusRequest) (result entity.GetProjectStatusResult, retErr error) { + op := metrics.Begin(c.metricsScope, "get_project_status", metrics.StorageLatencyBuckets, metrics.TagsFromContext(ctx)...) defer func() { op.Complete(retErr) }() if err := validateProjectStatusRequest(req); err != nil { - return entity.GetProjectStatusByURIResult{}, err + return entity.GetProjectStatusResult{}, err } store, err := c.stores.For(storage.Config{QueueName: req.Queue}) if err != nil { - return entity.GetProjectStatusByURIResult{}, fmt.Errorf("GetProjectStatusByURI failed to resolve storage for queue %q: %w", req.Queue, err) + return entity.GetProjectStatusResult{}, fmt.Errorf("GetProjectStatus failed to resolve storage for queue %q: %w", req.Queue, err) } - requestID, err := store.GetRequestURIStore().GetIDByURI(ctx, req.ChangeURI) - if err != nil { - if storage.IsNotFound(err) { - return entity.GetProjectStatusByURIResult{}, errs.NewUserError(&ProjectStatusNotFoundError{Queue: req.Queue, ChangeURI: req.ChangeURI}) + requestID := req.RequestID + if req.ChangeURI != "" { + requestID, err = store.GetRequestURIStore().GetIDByURI(ctx, req.ChangeURI) + if err != nil { + if storage.IsNotFound(err) { + return entity.GetProjectStatusResult{}, errs.NewUserError(&ProjectStatusNotFoundError{Queue: req.Queue, ChangeURI: req.ChangeURI}) + } + return entity.GetProjectStatusResult{}, fmt.Errorf("GetProjectStatus failed to resolve request for URI %q: %w", req.ChangeURI, err) } - return entity.GetProjectStatusByURIResult{}, fmt.Errorf("GetProjectStatusByURI failed to resolve request for URI %q: %w", req.ChangeURI, err) } requestSummary, err := store.GetRequestSummaryStore().Get(ctx, requestID) if err != nil { if storage.IsNotFound(err) { + if req.RequestID != "" { + return entity.GetProjectStatusResult{}, errs.NewUserError(&ProjectStatusNotFoundError{Queue: req.Queue, RequestID: req.RequestID}) + } // The URI mapping is created before the summary, so this gap is retryable. - return entity.GetProjectStatusByURIResult{}, errs.NewRetryableError(fmt.Errorf("GetProjectStatusByURI request summary %q is not visible yet", requestID)) + return entity.GetProjectStatusResult{}, errs.NewRetryableError(fmt.Errorf("GetProjectStatus request summary %q is not visible yet", requestID)) } - return entity.GetProjectStatusByURIResult{}, fmt.Errorf("GetProjectStatusByURI failed to load request summary %q: %w", requestID, err) + return entity.GetProjectStatusResult{}, fmt.Errorf("GetProjectStatus failed to load request summary %q: %w", requestID, err) } - if requestSummary.RequestID != requestID || requestSummary.Queue != req.Queue || requestSummary.URI != req.ChangeURI { - return entity.GetProjectStatusByURIResult{}, &ProjectStatusConsistencyError{message: "request URI mapping disagrees with stored request summary"} + if requestSummary.RequestID != requestID || requestSummary.Queue != req.Queue || requestSummary.URI == "" || (req.ChangeURI != "" && requestSummary.URI != req.ChangeURI) { + return entity.GetProjectStatusResult{}, &ProjectStatusConsistencyError{message: "project status selector disagrees with stored request summary"} } result.RequestSummary = requestSummary result.UpdatedAtMs = requestSummary.StateTimestampMs @@ -127,11 +146,11 @@ func (c *GetProjectStatusByURIController) GetProjectStatusByURI(ctx context.Cont fact, err := factStore.Get(ctx, requestSummary.URI, "") if err != nil { if !storage.IsNotFound(err) { - return entity.GetProjectStatusByURIResult{}, fmt.Errorf("GetProjectStatusByURI failed to load repository fact for request %q: %w", requestSummary.RequestID, err) + return entity.GetProjectStatusResult{}, fmt.Errorf("GetProjectStatus failed to load repository fact for request %q: %w", requestSummary.RequestID, err) } } else { if err := validateRepositoryFact(fact, requestSummary); err != nil { - return entity.GetProjectStatusByURIResult{}, err + return entity.GetProjectStatusResult{}, err } result.RepositoryValidationFact = fact result.HasRepositoryValidationFact = true @@ -141,7 +160,7 @@ func (c *GetProjectStatusByURIController) GetProjectStatusByURI(ctx context.Cont } page, err := selectProjectStatusPage(req, requestSummary.RequestID) if err != nil { - return entity.GetProjectStatusByURIResult{}, err + return entity.GetProjectStatusResult{}, err } for _, project := range page.projects { projectFact, err := factStore.Get(ctx, requestSummary.URI, project) @@ -149,10 +168,10 @@ func (c *GetProjectStatusByURIController) GetProjectStatusByURI(ctx context.Cont if storage.IsNotFound(err) { continue } - return entity.GetProjectStatusByURIResult{}, fmt.Errorf("GetProjectStatusByURI failed to load project fact for request %q and project %q: %w", requestSummary.RequestID, project, err) + return entity.GetProjectStatusResult{}, fmt.Errorf("GetProjectStatus failed to load project fact for request %q and project %q: %w", requestSummary.RequestID, project, err) } if err := validateProjectFact(projectFact, requestSummary, project); err != nil { - return entity.GetProjectStatusByURIResult{}, err + return entity.GetProjectStatusResult{}, err } result.ProjectValidationFacts = append(result.ProjectValidationFacts, projectFact) if projectFact.CreatedAt > result.UpdatedAtMs { @@ -165,28 +184,37 @@ func (c *GetProjectStatusByURIController) GetProjectStatusByURI(ctx context.Cont return result, nil } -func validateProjectStatusRequest(req entity.GetProjectStatusByURIRequest) error { +func validateProjectStatusRequest(req entity.GetProjectStatusRequest) error { if err := validateHistoryIdentifier("queue", req.Queue); err != nil { - return fmt.Errorf("GetProjectStatusByURI invalid queue=%q: %w", req.Queue, err) + return fmt.Errorf("GetProjectStatus invalid queue=%q: %w", req.Queue, err) + } + if (req.RequestID == "") == (req.ChangeURI == "") { + return fmt.Errorf("GetProjectStatus requires exactly one of request_id or change_uri: %w", ErrInvalidRequest) } - if err := validateHistoryIdentifier("change URI", req.ChangeURI); err != nil { - return fmt.Errorf("GetProjectStatusByURI invalid change_uri=%q: %w", req.ChangeURI, err) + if req.RequestID != "" { + if err := validateRequestID(req.RequestID); err != nil { + return fmt.Errorf("GetProjectStatus invalid request_id=%q: %w", req.RequestID, err) + } + } else { + if err := validateHistoryIdentifier("change URI", req.ChangeURI); err != nil { + return fmt.Errorf("GetProjectStatus invalid change_uri=%q: %w", req.ChangeURI, err) + } } if len(req.Projects) == 0 { - return fmt.Errorf("GetProjectStatusByURI projects must be non-empty: %w", ErrInvalidRequest) + return fmt.Errorf("GetProjectStatus projects must be non-empty: %w", ErrInvalidRequest) } seen := make(map[string]struct{}, len(req.Projects)) for _, project := range req.Projects { if err := validateHistoryIdentifier("project", project); err != nil { - return fmt.Errorf("GetProjectStatusByURI invalid project=%q: %w", project, err) + return fmt.Errorf("GetProjectStatus invalid project=%q: %w", project, err) } if _, ok := seen[project]; ok { - return fmt.Errorf("GetProjectStatusByURI project %q is duplicated: %w", project, ErrInvalidRequest) + return fmt.Errorf("GetProjectStatus project %q is duplicated: %w", project, ErrInvalidRequest) } seen[project] = struct{}{} } if req.PageSize < 0 || req.PageSize > maxProjectStatusPageSize { - return fmt.Errorf("GetProjectStatusByURI page_size must be between 0 and %d: %w", maxProjectStatusPageSize, ErrInvalidRequest) + return fmt.Errorf("GetProjectStatus page_size must be between 0 and %d: %w", maxProjectStatusPageSize, ErrInvalidRequest) } return nil } @@ -196,7 +224,7 @@ type projectStatusPage struct { nextToken string } -func selectProjectStatusPage(req entity.GetProjectStatusByURIRequest, requestID string) (projectStatusPage, error) { +func selectProjectStatusPage(req entity.GetProjectStatusRequest, requestID string) (projectStatusPage, error) { start := 0 if req.PageToken != "" { token, err := decodeProjectStatusPageToken(req.PageToken) @@ -204,7 +232,7 @@ func selectProjectStatusPage(req entity.GetProjectStatusByURIRequest, requestID return projectStatusPage{}, err } if token.RequestID != requestID || !reflect.DeepEqual(token.Projects, req.Projects) || token.NextIndex <= 0 || token.NextIndex >= len(req.Projects) { - return projectStatusPage{}, fmt.Errorf("GetProjectStatusByURI page_token does not match the selected request and projects: %w", ErrInvalidRequest) + return projectStatusPage{}, fmt.Errorf("GetProjectStatus page_token does not match the selected request and projects: %w", ErrInvalidRequest) } start = token.NextIndex } @@ -217,7 +245,7 @@ func selectProjectStatusPage(req entity.GetProjectStatusByURIRequest, requestID if end < len(req.Projects) { token, err := encodeProjectStatusPageToken(projectStatusPageToken{RequestID: requestID, Projects: req.Projects, NextIndex: end}) if err != nil { - return projectStatusPage{}, fmt.Errorf("GetProjectStatusByURI failed to create page token: %w", err) + return projectStatusPage{}, fmt.Errorf("GetProjectStatus failed to create page token: %w", err) } page.nextToken = token } @@ -235,11 +263,11 @@ func encodeProjectStatusPageToken(token projectStatusPageToken) (string, error) func decodeProjectStatusPageToken(encoded string) (projectStatusPageToken, error) { contents, err := base64.RawURLEncoding.DecodeString(encoded) if err != nil { - return projectStatusPageToken{}, fmt.Errorf("GetProjectStatusByURI invalid page_token: %w", ErrInvalidRequest) + return projectStatusPageToken{}, fmt.Errorf("GetProjectStatus invalid page_token: %w", ErrInvalidRequest) } var token projectStatusPageToken if err := json.Unmarshal(contents, &token); err != nil { - return projectStatusPageToken{}, fmt.Errorf("GetProjectStatusByURI invalid page_token: %w", ErrInvalidRequest) + return projectStatusPageToken{}, fmt.Errorf("GetProjectStatus invalid page_token: %w", ErrInvalidRequest) } return token, nil } diff --git a/stovepipe/controller/get_project_status_by_uri_test.go b/stovepipe/controller/project_status_test.go similarity index 54% rename from stovepipe/controller/get_project_status_by_uri_test.go rename to stovepipe/controller/project_status_test.go index dbe04e662..06f644fe0 100644 --- a/stovepipe/controller/get_project_status_by_uri_test.go +++ b/stovepipe/controller/project_status_test.go @@ -18,6 +18,7 @@ import ( "context" "errors" "math" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -37,11 +38,11 @@ const ( projectStatusID = "7" ) -func TestGetProjectStatusByURI(t *testing.T) { +func TestGetProjectStatus(t *testing.T) { requestSummary := entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: projectStatusURI, State: entity.RequestStateProcessing, RequestVersion: 1, StateTimestampMs: 1} tests := []struct { name string - request entity.GetProjectStatusByURIRequest + request entity.GetProjectStatusRequest uriErr error summaryErr error fact entity.ValidationFact @@ -53,17 +54,17 @@ func TestGetProjectStatusByURI(t *testing.T) { noProjectLookup bool wantConsistency bool }{ - {name: "in progress without fact", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, factErr: storage.ErrNotFound}, - {name: "recorded green fact", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Degree: entity.DegreeGreen}, wantFact: true}, - {name: "missing uri", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, uriErr: storage.ErrNotFound, wantNotFound: true}, - {name: "request summary visibility gap", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, summaryErr: storage.ErrNotFound, wantRetryable: true}, - {name: "empty queue", request: entity.GetProjectStatusByURIRequest{ChangeURI: projectStatusURI}, wantInvalid: true}, - {name: "empty projects", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI}, wantInvalid: true}, - {name: "empty project", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{""}}, wantInvalid: true}, - {name: "duplicate projects", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a", "project-a"}}, wantInvalid: true}, - {name: "invalid page size", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, PageSize: maxProjectStatusPageSize + 1}, wantInvalid: true}, - {name: "malformed page token", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}, PageToken: "token"}, factErr: storage.ErrNotFound, wantInvalid: true, noProjectLookup: true}, - {name: "invalid fact degree", request: entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Degree: math.NaN()}, wantConsistency: true}, + {name: "in progress without fact", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, factErr: storage.ErrNotFound}, + {name: "recorded green fact", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Degree: entity.DegreeGreen}, wantFact: true}, + {name: "missing uri", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, uriErr: storage.ErrNotFound, wantNotFound: true}, + {name: "request summary visibility gap", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, summaryErr: storage.ErrNotFound, wantRetryable: true}, + {name: "empty queue", request: entity.GetProjectStatusRequest{ChangeURI: projectStatusURI}, wantInvalid: true}, + {name: "empty projects", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI}, wantInvalid: true}, + {name: "empty project", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{""}}, wantInvalid: true}, + {name: "duplicate projects", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a", "project-a"}}, wantInvalid: true}, + {name: "invalid page size", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, PageSize: maxProjectStatusPageSize + 1}, wantInvalid: true}, + {name: "malformed page token", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}, PageToken: "token"}, factErr: storage.ErrNotFound, wantInvalid: true, noProjectLookup: true}, + {name: "invalid fact degree", request: entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Degree: math.NaN()}, wantConsistency: true}, } for _, tt := range tests { @@ -93,8 +94,8 @@ func TestGetProjectStatusByURI(t *testing.T) { } } - controller := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory) - got, err := controller.GetProjectStatusByURI(context.Background(), tt.request) + controller := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory) + got, err := controller.GetProjectStatus(context.Background(), tt.request) if tt.wantInvalid { assert.True(t, IsInvalidRequest(err)) @@ -117,7 +118,107 @@ func TestGetProjectStatusByURI(t *testing.T) { } } -func TestGetProjectStatusByURIUsesRequestSummaryTimestamp(t *testing.T) { +func TestGetProjectStatusByID(t *testing.T) { + readErr := errors.New("storage read failed") + summary := entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: projectStatusURI, BaseURI: "git://monorepo/main/base", State: entity.RequestStateSucceeded, StateTimestampMs: 10} + tests := []struct { + name string + summary entity.RequestSummary + summaryErr error + fact entity.ValidationFact + factErr error + wantNotFound bool + wantConsistency bool + wantErr error + }{ + {name: "in progress without facts", summary: summary, factErr: storage.ErrNotFound}, + {name: "recorded green fact", summary: summary, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Degree: entity.DegreeGreen, CreatedAt: 20}}, + {name: "unknown ID", summaryErr: storage.ErrNotFound, wantNotFound: true}, + {name: "summary read failure", summaryErr: readErr, wantErr: readErr}, + {name: "wrong queue", summary: entity.RequestSummary{RequestID: projectStatusID, Queue: "other/main", URI: projectStatusURI}, wantConsistency: true}, + {name: "wrong ID", summary: entity.RequestSummary{RequestID: "8", Queue: projectStatusQueue, URI: projectStatusURI}, wantConsistency: true}, + {name: "missing URI", summary: entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue}, wantConsistency: true}, + {name: "fact belongs to another request", summary: summary, fact: entity.ValidationFact{URI: projectStatusURI, RequestID: "8"}, wantConsistency: true}, + {name: "fact read failure", summary: summary, factErr: readErr, wantErr: readErr}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + factory := storagemock.NewMockFactory(mockCtrl) + store := storagemock.NewMockStorage(mockCtrl) + summaries := storagemock.NewMockRequestSummaryStore(mockCtrl) + facts := storagemock.NewMockValidationFactStore(mockCtrl) + factory.EXPECT().For(storage.Config{QueueName: projectStatusQueue}).Return(store, nil) + store.EXPECT().GetRequestSummaryStore().Return(summaries) + summaries.EXPECT().Get(gomock.Any(), projectStatusID).Return(tt.summary, tt.summaryErr) + if tt.summaryErr == nil && tt.summary == summary { + store.EXPECT().GetValidationFactStore().Return(facts) + facts.EXPECT().Get(gomock.Any(), projectStatusURI, "").Return(tt.fact, tt.factErr) + if !tt.wantConsistency && tt.wantErr == nil { + facts.EXPECT().Get(gomock.Any(), projectStatusURI, "project-a").Return(entity.ValidationFact{}, storage.ErrNotFound) + } + } + got, err := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{ + Queue: projectStatusQueue, RequestID: projectStatusID, Projects: []string{"project-a"}, + }) + if tt.wantNotFound || tt.wantConsistency || tt.wantErr != nil { + require.Error(t, err) + assert.Equal(t, tt.wantNotFound, IsProjectStatusNotFound(err)) + assert.Equal(t, tt.wantConsistency, IsProjectStatusConsistency(err)) + assert.False(t, errs.IsRetryable(err)) + if tt.wantNotFound { + assert.True(t, errs.IsUserError(err)) + var notFound *ProjectStatusNotFoundError + require.ErrorAs(t, err, ¬Found) + assert.Equal(t, projectStatusID, notFound.RequestID) + assert.Equal(t, projectStatusQueue, notFound.Queue) + } + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + } + return + } + require.NoError(t, err) + assert.Equal(t, summary, got.RequestSummary) + assert.Equal(t, tt.factErr == nil, got.HasRepositoryValidationFact) + assert.Equal(t, max(summary.StateTimestampMs, tt.fact.CreatedAt), got.UpdatedAtMs) + }) + } +} + +func TestGetProjectStatusRejectsInvalidIDSelectors(t *testing.T) { + tests := []struct { + name string + modify func(*entity.GetProjectStatusRequest) + }{ + {name: "missing selector", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "" }}, + {name: "both selectors", modify: func(req *entity.GetProjectStatusRequest) { req.ChangeURI = projectStatusURI }}, + {name: "empty queue", modify: func(req *entity.GetProjectStatusRequest) { req.Queue = "" }}, + {name: "long queue", modify: func(req *entity.GetProjectStatusRequest) { req.Queue = strings.Repeat("q", 256) }}, + {name: "noncanonical ID", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "07" }}, + {name: "nonnumeric ID", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "request/7" }}, + {name: "zero ID", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "0" }}, + {name: "negative ID", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "-7" }}, + {name: "overflow ID", modify: func(req *entity.GetProjectStatusRequest) { req.RequestID = "9223372036854775808" }}, + {name: "empty projects", modify: func(req *entity.GetProjectStatusRequest) { req.Projects = nil }}, + {name: "empty project", modify: func(req *entity.GetProjectStatusRequest) { req.Projects = []string{""} }}, + {name: "duplicate project", modify: func(req *entity.GetProjectStatusRequest) { req.Projects = []string{"p", "p"} }}, + {name: "negative page size", modify: func(req *entity.GetProjectStatusRequest) { req.PageSize = -1 }}, + {name: "large page size", modify: func(req *entity.GetProjectStatusRequest) { req.PageSize = maxProjectStatusPageSize + 1 }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := entity.GetProjectStatusRequest{Queue: projectStatusQueue, RequestID: projectStatusID, Projects: []string{"project-a"}} + tt.modify(&req) + factory := storagemock.NewMockFactory(gomock.NewController(t)) + _, err := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatus(context.Background(), req) + require.Error(t, err) + assert.True(t, IsInvalidRequest(err)) + }) + } +} + +func TestGetProjectStatusUsesRequestSummaryTimestamp(t *testing.T) { requestSummary := entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: projectStatusURI, State: entity.RequestStateProcessing, RequestVersion: 2, StateTimestampMs: 10} mockCtrl := gomock.NewController(t) factory := storagemock.NewMockFactory(mockCtrl) @@ -134,13 +235,13 @@ func TestGetProjectStatusByURIUsesRequestSummaryTimestamp(t *testing.T) { factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "").Return(entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, CreatedAt: 20}, nil) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "project-a").Return(entity.ValidationFact{}, storage.ErrNotFound) - controller := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory) - got, err := controller.GetProjectStatusByURI(context.Background(), entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}) + controller := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory) + got, err := controller.GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}) require.NoError(t, err) assert.Equal(t, int64(20), got.UpdatedAtMs) } -func TestGetProjectStatusByURIReturnsProjectFactsInRequestedOrder(t *testing.T) { +func TestGetProjectStatusReturnsProjectFactsInRequestedOrder(t *testing.T) { requestSummary := entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: projectStatusURI, StateTimestampMs: 10} mockCtrl := gomock.NewController(t) factory := storagemock.NewMockFactory(mockCtrl) @@ -159,8 +260,8 @@ func TestGetProjectStatusByURIReturnsProjectFactsInRequestedOrder(t *testing.T) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "missing").Return(entity.ValidationFact{}, storage.ErrNotFound) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "project-a").Return(entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Project: "project-a", Degree: entity.DegreeGreen, CreatedAt: 30}, nil) - controller := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory) - got, err := controller.GetProjectStatusByURI(context.Background(), entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-b", "missing", "project-a"}}) + controller := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory) + got, err := controller.GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-b", "missing", "project-a"}}) require.NoError(t, err) require.Len(t, got.ProjectValidationFacts, 2) assert.Equal(t, []string{"project-b", "project-a"}, []string{got.ProjectValidationFacts[0].Project, got.ProjectValidationFacts[1].Project}) @@ -169,7 +270,7 @@ func TestGetProjectStatusByURIReturnsProjectFactsInRequestedOrder(t *testing.T) assert.False(t, got.ProjectResultsComplete) } -func TestGetProjectStatusByURIPaginatesProjectFacts(t *testing.T) { +func TestGetProjectStatusPaginatesProjectFacts(t *testing.T) { requestSummary := entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: projectStatusURI, StateTimestampMs: 10} projects := []string{"project-a", "project-b"} @@ -189,7 +290,7 @@ func TestGetProjectStatusByURIPaginatesProjectFacts(t *testing.T) { factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "").Return(entity.ValidationFact{}, storage.ErrNotFound) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "project-a").Return(entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Project: "project-a", Degree: entity.DegreeGreen}, nil) - got, err := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatusByURI(context.Background(), entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: projects, PageSize: 1}) + got, err := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: projects, PageSize: 1}) require.NoError(t, err) require.Len(t, got.ProjectValidationFacts, 1) assert.Equal(t, "project-a", got.ProjectValidationFacts[0].Project) @@ -200,18 +301,15 @@ func TestGetProjectStatusByURIPaginatesProjectFacts(t *testing.T) { mockCtrl := gomock.NewController(t) factory := storagemock.NewMockFactory(mockCtrl) store := storagemock.NewMockStorage(mockCtrl) - uriStore := storagemock.NewMockRequestURIStore(mockCtrl) summaryStore := storagemock.NewMockRequestSummaryStore(mockCtrl) factStore := storagemock.NewMockValidationFactStore(mockCtrl) factory.EXPECT().For(storage.Config{QueueName: projectStatusQueue}).Return(store, nil) - store.EXPECT().GetRequestURIStore().Return(uriStore) - uriStore.EXPECT().GetIDByURI(gomock.Any(), projectStatusURI).Return(projectStatusID, nil) store.EXPECT().GetRequestSummaryStore().Return(summaryStore) summaryStore.EXPECT().Get(gomock.Any(), projectStatusID).Return(requestSummary, nil) store.EXPECT().GetValidationFactStore().Return(factStore) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "").Return(entity.ValidationFact{}, storage.ErrNotFound) factStore.EXPECT().Get(gomock.Any(), projectStatusURI, "project-b").Return(entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Project: "project-b", Degree: entity.DegreeBroken}, nil) - got, err := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatusByURI(context.Background(), entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: projects, PageSize: 1, PageToken: firstPage}) + got, err := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory).GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{Queue: projectStatusQueue, RequestID: projectStatusID, Projects: projects, PageSize: 1, PageToken: firstPage}) require.NoError(t, err) require.Len(t, got.ProjectValidationFacts, 1) assert.Equal(t, "project-b", got.ProjectValidationFacts[0].Project) @@ -221,7 +319,7 @@ func TestGetProjectStatusByURIPaginatesProjectFacts(t *testing.T) { func TestSelectProjectStatusPageRejectsMismatchedToken(t *testing.T) { token, err := encodeProjectStatusPageToken(projectStatusPageToken{RequestID: projectStatusID, Projects: []string{"project-a", "project-b"}, NextIndex: 1}) require.NoError(t, err) - _, err = selectProjectStatusPage(entity.GetProjectStatusByURIRequest{Projects: []string{"project-b", "project-a"}, PageToken: token}, projectStatusID) + _, err = selectProjectStatusPage(entity.GetProjectStatusRequest{Projects: []string{"project-b", "project-a"}, PageToken: token}, projectStatusID) require.Error(t, err) assert.True(t, IsInvalidRequest(err)) } @@ -233,7 +331,7 @@ func TestValidateProjectFact(t *testing.T) { assert.True(t, IsProjectStatusConsistency(validateProjectFact(entity.ValidationFact{URI: projectStatusURI, RequestID: projectStatusID, Project: "project-a", Degree: math.NaN()}, requestSummary, "project-a"))) } -func TestGetProjectStatusByURIRejectsInconsistentRequest(t *testing.T) { +func TestGetProjectStatusRejectsInconsistentRequest(t *testing.T) { mockCtrl := gomock.NewController(t) factory := storagemock.NewMockFactory(mockCtrl) store := storagemock.NewMockStorage(mockCtrl) @@ -245,8 +343,8 @@ func TestGetProjectStatusByURIRejectsInconsistentRequest(t *testing.T) { store.EXPECT().GetRequestSummaryStore().Return(summaryStore) summaryStore.EXPECT().Get(gomock.Any(), projectStatusID).Return(entity.RequestSummary{RequestID: projectStatusID, Queue: projectStatusQueue, URI: "other"}, nil) - controller := NewGetProjectStatusByURIController(zap.NewNop().Sugar(), tally.NoopScope, factory) - _, err := controller.GetProjectStatusByURI(context.Background(), entity.GetProjectStatusByURIRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}) + controller := NewProjectStatusController(zap.NewNop().Sugar(), tally.NoopScope, factory) + _, err := controller.GetProjectStatus(context.Background(), entity.GetProjectStatusRequest{Queue: projectStatusQueue, ChangeURI: projectStatusURI, Projects: []string{"project-a"}}) require.Error(t, err) assert.True(t, IsProjectStatusConsistency(err)) assert.False(t, errors.Is(err, storage.ErrNotFound)) diff --git a/stovepipe/entity/project_status.go b/stovepipe/entity/project_status.go index 312dbcf63..5d1c7f261 100644 --- a/stovepipe/entity/project_status.go +++ b/stovepipe/entity/project_status.go @@ -14,11 +14,13 @@ package entity -// GetProjectStatusByURIRequest selects a validation request by queue and commit URI. -type GetProjectStatusByURIRequest struct { +// GetProjectStatusRequest selects a validation request by queue and exactly one of request ID or commit URI. +type GetProjectStatusRequest struct { // Queue identifies the queue containing the validation request. Queue string - // ChangeURI identifies the exact commit under validation. + // RequestID is the queue-scoped decimal identifier, empty when selecting by URI. + RequestID string + // ChangeURI identifies the exact commit under validation, empty when selecting by request ID. ChangeURI string // Projects limits results to the supplied consumer-defined project IDs. Projects []string @@ -28,8 +30,8 @@ type GetProjectStatusByURIRequest struct { PageToken string } -// GetProjectStatusByURIResult is the current validation projection for one request. -type GetProjectStatusByURIResult struct { +// GetProjectStatusResult is the current validation projection for one request. +type GetProjectStatusResult struct { // RequestSummary is the authoritative validation projection selected by the lookup. RequestSummary RequestSummary // RepositoryValidationFact is the repository result when HasRepositoryValidationFact is true. diff --git a/test/integration/stovepipe/suite_test.go b/test/integration/stovepipe/suite_test.go index e9402df70..c7febc1aa 100644 --- a/test/integration/stovepipe/suite_test.go +++ b/test/integration/stovepipe/suite_test.go @@ -270,6 +270,60 @@ func (s *StovepipeIntegrationSuite) TestRequestHistoryAbsence() { } } +func (s *StovepipeIntegrationSuite) TestProjectStatusAPI() { + t := s.T() + const ( + queue = "project-status-api/main" + id = "7" + uri = "git://project-status-api/main/commit" + ) + _, err := s.db.Exec(`INSERT INTO request_summary + (queue, request_id, uri, base_uri, state, request_version, state_timestamp_ms, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, queue, id, uri, "git://project-status-api/main/base", "succeeded", 3, 1000, 1) + require.NoError(t, err) + _, err = s.db.Exec("INSERT INTO request_uri (queue, uri, request_id, version) VALUES (?, ?, ?, ?)", queue, uri, id, 1) + require.NoError(t, err) + _, err = s.db.Exec(`INSERT INTO validation_fact (queue, uri, project, degree, request_id, created_at) + VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)`, + queue, uri, "", 0.0, id, 2000, queue, uri, "project-a", 0.5, id, 3000) + require.NoError(t, err) + + byURI, err := s.client.GetProjectStatusByURI(s.ctx, &pb.GetProjectStatusByURIRequest{ + Queue: queue, ChangeUri: uri, Projects: []string{"project-a", "missing"}, PageSize: 1, + }) + require.NoError(t, err) + byID, err := s.client.GetProjectStatusByID(s.ctx, &pb.GetProjectStatusByIDRequest{ + Queue: queue, RequestId: id, Projects: []string{"project-a", "missing"}, PageSize: 1, + }) + require.NoError(t, err) + assert.Equal(t, byURI.GetRequestId(), byID.GetRequestId()) + assert.Equal(t, queue, byID.GetQueue()) + assert.Equal(t, uri, byID.GetChangeUri()) + assert.Equal(t, byURI.GetBaseUri(), byID.GetBaseUri()) + assert.Equal(t, "succeeded", byID.GetRequestState()) + assert.Equal(t, int64(3000), byID.GetUpdatedAtMs()) + assert.IsType(t, &pb.GetProjectStatusByIDResponse_RepositoryBreakageDegree{}, byID.GetRepositoryResult()) + assert.Zero(t, byID.GetRepositoryBreakageDegree()) + require.Len(t, byID.Projects, 1) + assert.Equal(t, "project-a", byID.Projects[0].GetProject()) + assert.Equal(t, 0.5, byID.Projects[0].GetBreakageDegree()) + assert.Equal(t, byURI.GetNextPageToken(), byID.GetNextPageToken()) + require.NotEmpty(t, byID.GetNextPageToken()) + lastPage, err := s.client.GetProjectStatusByID(s.ctx, &pb.GetProjectStatusByIDRequest{ + Queue: queue, RequestId: id, Projects: []string{"project-a", "missing"}, PageSize: 1, PageToken: byURI.GetNextPageToken(), + }) + require.NoError(t, err) + assert.Empty(t, lastPage.Projects) + assert.Empty(t, lastPage.NextPageToken) + + for _, selector := range []struct{ queue, id string }{{queue, "999"}, {"project-status-api/other", id}} { + _, err := s.client.GetProjectStatusByID(s.ctx, &pb.GetProjectStatusByIDRequest{ + Queue: selector.queue, RequestId: selector.id, Projects: []string{"project-a"}, + }) + require.Error(t, err) + } +} + func assertHistoryEvents(t *testing.T, events []*pb.HistoryEvent) { t.Helper() require.Len(t, events, 3)