diff --git a/components/image-builder-api/generate.sh b/components/image-builder-api/generate.sh index f5238bd41a7477..14195f975ed0ca 100755 --- a/components/image-builder-api/generate.sh +++ b/components/image-builder-api/generate.sh @@ -19,8 +19,6 @@ install_dependencies go_protoc "$COMPONENTS_DIR" typescript_protoc "$COMPONENTS_DIR" -go generate typescript/util/generate-ws-ready.go - # cd go pushd go diff --git a/components/image-builder-api/go/imgbuilder.pb.go b/components/image-builder-api/go/imgbuilder.pb.go index f7355f4ec96a62..cdd1bd46745cef 100644 --- a/components/image-builder-api/go/imgbuilder.pb.go +++ b/components/image-builder-api/go/imgbuilder.pb.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Copyright (c) 2025 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. @@ -281,8 +281,9 @@ type ResolveBaseImageRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` - Auth *BuildRegistryAuth `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` + Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Auth *BuildRegistryAuth `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` + UseRetryClient bool `protobuf:"varint,3,opt,name=use_retry_client,json=useRetryClient,proto3" json:"use_retry_client,omitempty"` } func (x *ResolveBaseImageRequest) Reset() { @@ -331,6 +332,13 @@ func (x *ResolveBaseImageRequest) GetAuth() *BuildRegistryAuth { return nil } +func (x *ResolveBaseImageRequest) GetUseRetryClient() bool { + if x != nil { + return x.UseRetryClient + } + return false +} + type ResolveBaseImageResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -383,8 +391,9 @@ type ResolveWorkspaceImageRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Source *BuildSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` - Auth *BuildRegistryAuth `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` + Source *BuildSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + Auth *BuildRegistryAuth `protobuf:"bytes,2,opt,name=auth,proto3" json:"auth,omitempty"` + UseRetryClient bool `protobuf:"varint,3,opt,name=use_retry_client,json=useRetryClient,proto3" json:"use_retry_client,omitempty"` } func (x *ResolveWorkspaceImageRequest) Reset() { @@ -433,6 +442,13 @@ func (x *ResolveWorkspaceImageRequest) GetAuth() *BuildRegistryAuth { return nil } +func (x *ResolveWorkspaceImageRequest) GetUseRetryClient() bool { + if x != nil { + return x.UseRetryClient + } + return false +} + type ResolveWorkspaceImageResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -507,6 +523,7 @@ type BuildRequest struct { TriggeredBy string `protobuf:"bytes,4,opt,name=triggered_by,json=triggeredBy,proto3" json:"triggered_by,omitempty"` SupervisorRef string `protobuf:"bytes,5,opt,name=supervisor_ref,json=supervisorRef,proto3" json:"supervisor_ref,omitempty"` BaseImageNameResolved string `protobuf:"bytes,6,opt,name=base_image_name_resolved,json=baseImageNameResolved,proto3" json:"base_image_name_resolved,omitempty"` + UseRetryClient bool `protobuf:"varint,7,opt,name=use_retry_client,json=useRetryClient,proto3" json:"use_retry_client,omitempty"` } func (x *BuildRequest) Reset() { @@ -583,6 +600,13 @@ func (x *BuildRequest) GetBaseImageNameResolved() string { return "" } +func (x *BuildRequest) GetUseRetryClient() bool { + if x != nil { + return x.UseRetryClient + } + return false +} + type BuildRegistryAuth struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1235,48 +1259,56 @@ var file_imgbuilder_proto_rawDesc = []byte{ 0x63, 0x6b, 0x65, 0x72, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x50, 0x61, 0x74, 0x68, 0x22, - 0x5b, 0x0a, 0x17, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x42, 0x61, 0x73, 0x65, 0x49, 0x6d, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, - 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x2e, 0x0a, 0x04, - 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x79, 0x41, 0x75, 0x74, 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x2c, 0x0a, 0x18, - 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x42, 0x61, 0x73, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x22, 0x7c, 0x0a, 0x1c, 0x52, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6d, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, - 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x41, 0x75, - 0x74, 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x7a, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6d, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x19, 0x0a, 0x08, 0x62, - 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, - 0x61, 0x73, 0x65, 0x52, 0x65, 0x66, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, - 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x94, 0x02, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x41, 0x75, 0x74, 0x68, 0x52, 0x04, 0x61, - 0x75, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x69, 0x67, - 0x67, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x65, 0x64, 0x42, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x73, - 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x52, - 0x65, 0x66, 0x12, 0x37, 0x0a, 0x18, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x62, 0x61, 0x73, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x22, 0xa4, 0x02, 0x0a, 0x11, + 0x85, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x42, 0x61, 0x73, 0x65, 0x49, + 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, + 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x2e, 0x0a, + 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x41, 0x75, 0x74, 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x12, 0x28, 0x0a, + 0x10, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x52, 0x65, 0x74, 0x72, + 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x42, 0x61, 0x73, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x72, 0x65, 0x66, 0x22, 0xa6, 0x01, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, + 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x41, 0x75, 0x74, 0x68, 0x52, 0x04, + 0x61, 0x75, 0x74, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x74, 0x72, + 0x79, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x75, 0x73, 0x65, 0x52, 0x65, 0x74, 0x72, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x7a, + 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, + 0x66, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x66, 0x12, 0x2c, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xbe, 0x02, 0x0a, 0x0c, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x61, 0x75, 0x74, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, + 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x41, + 0x75, 0x74, 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x5f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x65, 0x64, 0x42, + 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x5f, + 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x65, 0x72, + 0x76, 0x69, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x18, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, + 0x6c, 0x76, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x62, 0x61, 0x73, 0x65, + 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x75, 0x73, 0x65, + 0x52, 0x65, 0x74, 0x72, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xa4, 0x02, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, diff --git a/components/image-builder-api/go/imgbuilder_grpc.pb.go b/components/image-builder-api/go/imgbuilder_grpc.pb.go index 04d6b649da4fe3..0e9a2e0891dc60 100644 --- a/components/image-builder-api/go/imgbuilder_grpc.pb.go +++ b/components/image-builder-api/go/imgbuilder_grpc.pb.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Copyright (c) 2025 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. diff --git a/components/image-builder-api/go/mock/mock.go b/components/image-builder-api/go/mock/mock.go index 1718a005501a84..ee4d78922b3422 100644 --- a/components/image-builder-api/go/mock/mock.go +++ b/components/image-builder-api/go/mock/mock.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Copyright (c) 2025 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. diff --git a/components/image-builder-api/go/subassembly.pb.go b/components/image-builder-api/go/subassembly.pb.go index a4fee72ae393d3..646fd12a9ee8a5 100644 --- a/components/image-builder-api/go/subassembly.pb.go +++ b/components/image-builder-api/go/subassembly.pb.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Copyright (c) 2025 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. diff --git a/components/image-builder-api/go/subassembly_grpc.pb.go b/components/image-builder-api/go/subassembly_grpc.pb.go index 3d48eca93fd0d3..a8ec6eedc61630 100644 --- a/components/image-builder-api/go/subassembly_grpc.pb.go +++ b/components/image-builder-api/go/subassembly_grpc.pb.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Gitpod GmbH. All rights reserved. +// Copyright (c) 2025 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License.AGPL.txt in the project root for license information. diff --git a/components/image-builder-api/imgbuilder.proto b/components/image-builder-api/imgbuilder.proto index ab07a797143d6e..5becaa0c5446cd 100644 --- a/components/image-builder-api/imgbuilder.proto +++ b/components/image-builder-api/imgbuilder.proto @@ -44,6 +44,7 @@ message BuildSourceDockerfile { message ResolveBaseImageRequest { string ref = 1; BuildRegistryAuth auth = 2; + bool use_retry_client = 3; } message ResolveBaseImageResponse { @@ -53,6 +54,7 @@ message ResolveBaseImageResponse { message ResolveWorkspaceImageRequest { BuildSource source = 1; BuildRegistryAuth auth = 2; + bool use_retry_client = 3; } message ResolveWorkspaceImageResponse { @@ -68,6 +70,7 @@ message BuildRequest { string triggered_by = 4; string supervisor_ref = 5; string base_image_name_resolved = 6; + bool use_retry_client = 7; } message BuildRegistryAuth { diff --git a/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.d.ts b/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.d.ts index 159b0356672b86..c4321054705fca 100644 --- a/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.d.ts +++ b/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.js b/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.js index 7f29063ca7c7be..61acca32e54f4f 100644 --- a/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.js +++ b/components/image-builder-api/typescript/src/imgbuilder_grpc_pb.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-api/typescript/src/imgbuilder_pb.d.ts b/components/image-builder-api/typescript/src/imgbuilder_pb.d.ts index 84a73c6c5dbacd..84d8d71d34d132 100644 --- a/components/image-builder-api/typescript/src/imgbuilder_pb.d.ts +++ b/components/image-builder-api/typescript/src/imgbuilder_pb.d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ @@ -111,6 +111,8 @@ export class ResolveBaseImageRequest extends jspb.Message { clearAuth(): void; getAuth(): BuildRegistryAuth | undefined; setAuth(value?: BuildRegistryAuth): ResolveBaseImageRequest; + getUseRetryClient(): boolean; + setUseRetryClient(value: boolean): ResolveBaseImageRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ResolveBaseImageRequest.AsObject; @@ -126,6 +128,7 @@ export namespace ResolveBaseImageRequest { export type AsObject = { ref: string, auth?: BuildRegistryAuth.AsObject, + useRetryClient: boolean, } } @@ -160,6 +163,8 @@ export class ResolveWorkspaceImageRequest extends jspb.Message { clearAuth(): void; getAuth(): BuildRegistryAuth | undefined; setAuth(value?: BuildRegistryAuth): ResolveWorkspaceImageRequest; + getUseRetryClient(): boolean; + setUseRetryClient(value: boolean): ResolveWorkspaceImageRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ResolveWorkspaceImageRequest.AsObject; @@ -175,6 +180,7 @@ export namespace ResolveWorkspaceImageRequest { export type AsObject = { source?: BuildSource.AsObject, auth?: BuildRegistryAuth.AsObject, + useRetryClient: boolean, } } @@ -223,6 +229,8 @@ export class BuildRequest extends jspb.Message { setSupervisorRef(value: string): BuildRequest; getBaseImageNameResolved(): string; setBaseImageNameResolved(value: string): BuildRequest; + getUseRetryClient(): boolean; + setUseRetryClient(value: boolean): BuildRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BuildRequest.AsObject; @@ -242,6 +250,7 @@ export namespace BuildRequest { triggeredBy: string, supervisorRef: string, baseImageNameResolved: string, + useRetryClient: boolean, } } diff --git a/components/image-builder-api/typescript/src/imgbuilder_pb.js b/components/image-builder-api/typescript/src/imgbuilder_pb.js index c8c87e21acad54..077d64cf95c0da 100644 --- a/components/image-builder-api/typescript/src/imgbuilder_pb.js +++ b/components/image-builder-api/typescript/src/imgbuilder_pb.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ @@ -1054,7 +1054,8 @@ proto.builder.ResolveBaseImageRequest.prototype.toObject = function(opt_includeI proto.builder.ResolveBaseImageRequest.toObject = function(includeInstance, msg) { var f, obj = { ref: jspb.Message.getFieldWithDefault(msg, 1, ""), - auth: (f = msg.getAuth()) && proto.builder.BuildRegistryAuth.toObject(includeInstance, f) + auth: (f = msg.getAuth()) && proto.builder.BuildRegistryAuth.toObject(includeInstance, f), + useRetryClient: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) }; if (includeInstance) { @@ -1100,6 +1101,10 @@ proto.builder.ResolveBaseImageRequest.deserializeBinaryFromReader = function(msg reader.readMessage(value,proto.builder.BuildRegistryAuth.deserializeBinaryFromReader); msg.setAuth(value); break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setUseRetryClient(value); + break; default: reader.skipField(); break; @@ -1144,6 +1149,13 @@ proto.builder.ResolveBaseImageRequest.serializeBinaryToWriter = function(message proto.builder.BuildRegistryAuth.serializeBinaryToWriter ); } + f = message.getUseRetryClient(); + if (f) { + writer.writeBool( + 3, + f + ); + } }; @@ -1202,6 +1214,24 @@ proto.builder.ResolveBaseImageRequest.prototype.hasAuth = function() { }; +/** + * optional bool use_retry_client = 3; + * @return {boolean} + */ +proto.builder.ResolveBaseImageRequest.prototype.getUseRetryClient = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.builder.ResolveBaseImageRequest} returns this + */ +proto.builder.ResolveBaseImageRequest.prototype.setUseRetryClient = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + @@ -1365,7 +1395,8 @@ proto.builder.ResolveWorkspaceImageRequest.prototype.toObject = function(opt_inc proto.builder.ResolveWorkspaceImageRequest.toObject = function(includeInstance, msg) { var f, obj = { source: (f = msg.getSource()) && proto.builder.BuildSource.toObject(includeInstance, f), - auth: (f = msg.getAuth()) && proto.builder.BuildRegistryAuth.toObject(includeInstance, f) + auth: (f = msg.getAuth()) && proto.builder.BuildRegistryAuth.toObject(includeInstance, f), + useRetryClient: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) }; if (includeInstance) { @@ -1412,6 +1443,10 @@ proto.builder.ResolveWorkspaceImageRequest.deserializeBinaryFromReader = functio reader.readMessage(value,proto.builder.BuildRegistryAuth.deserializeBinaryFromReader); msg.setAuth(value); break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setUseRetryClient(value); + break; default: reader.skipField(); break; @@ -1457,6 +1492,13 @@ proto.builder.ResolveWorkspaceImageRequest.serializeBinaryToWriter = function(me proto.builder.BuildRegistryAuth.serializeBinaryToWriter ); } + f = message.getUseRetryClient(); + if (f) { + writer.writeBool( + 3, + f + ); + } }; @@ -1534,6 +1576,24 @@ proto.builder.ResolveWorkspaceImageRequest.prototype.hasAuth = function() { }; +/** + * optional bool use_retry_client = 3; + * @return {boolean} + */ +proto.builder.ResolveWorkspaceImageRequest.prototype.getUseRetryClient = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.builder.ResolveWorkspaceImageRequest} returns this + */ +proto.builder.ResolveWorkspaceImageRequest.prototype.setUseRetryClient = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + @@ -1761,7 +1821,8 @@ proto.builder.BuildRequest.toObject = function(includeInstance, msg) { forceRebuild: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), triggeredBy: jspb.Message.getFieldWithDefault(msg, 4, ""), supervisorRef: jspb.Message.getFieldWithDefault(msg, 5, ""), - baseImageNameResolved: jspb.Message.getFieldWithDefault(msg, 6, "") + baseImageNameResolved: jspb.Message.getFieldWithDefault(msg, 6, ""), + useRetryClient: jspb.Message.getBooleanFieldWithDefault(msg, 7, false) }; if (includeInstance) { @@ -1824,6 +1885,10 @@ proto.builder.BuildRequest.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setBaseImageNameResolved(value); break; + case 7: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setUseRetryClient(value); + break; default: reader.skipField(); break; @@ -1897,6 +1962,13 @@ proto.builder.BuildRequest.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getUseRetryClient(); + if (f) { + writer.writeBool( + 7, + f + ); + } }; @@ -2046,6 +2118,24 @@ proto.builder.BuildRequest.prototype.setBaseImageNameResolved = function(value) }; +/** + * optional bool use_retry_client = 7; + * @return {boolean} + */ +proto.builder.BuildRequest.prototype.getUseRetryClient = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 7, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.builder.BuildRequest} returns this + */ +proto.builder.BuildRequest.prototype.setUseRetryClient = function(value) { + return jspb.Message.setProto3BooleanField(this, 7, value); +}; + + /** * Oneof group definitions for this message. Each group defines the field diff --git a/components/image-builder-api/typescript/src/subassembly_grpc_pb.d.ts b/components/image-builder-api/typescript/src/subassembly_grpc_pb.d.ts index efb3e4f7262f2c..6be8495c0fe7e3 100644 --- a/components/image-builder-api/typescript/src/subassembly_grpc_pb.d.ts +++ b/components/image-builder-api/typescript/src/subassembly_grpc_pb.d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-api/typescript/src/subassembly_grpc_pb.js b/components/image-builder-api/typescript/src/subassembly_grpc_pb.js index 98860ae51925c5..fbe1bff067e096 100644 --- a/components/image-builder-api/typescript/src/subassembly_grpc_pb.js +++ b/components/image-builder-api/typescript/src/subassembly_grpc_pb.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-api/typescript/src/subassembly_pb.d.ts b/components/image-builder-api/typescript/src/subassembly_pb.d.ts index 7dec74a64ee0b8..10966e653e17ac 100644 --- a/components/image-builder-api/typescript/src/subassembly_pb.d.ts +++ b/components/image-builder-api/typescript/src/subassembly_pb.d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-api/typescript/src/subassembly_pb.js b/components/image-builder-api/typescript/src/subassembly_pb.js index e48d86656e56ef..4945ebc3061e81 100644 --- a/components/image-builder-api/typescript/src/subassembly_pb.js +++ b/components/image-builder-api/typescript/src/subassembly_pb.js @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024 Gitpod GmbH. All rights reserved. + * Copyright (c) 2025 Gitpod GmbH. All rights reserved. * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ diff --git a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go index 19cdba9edf41b2..d5e0581d991a28 100644 --- a/components/image-builder-mk3/pkg/orchestrator/orchestrator.go +++ b/components/image-builder-mk3/pkg/orchestrator/orchestrator.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "path/filepath" "sort" @@ -21,6 +22,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" "github.com/distribution/reference" "github.com/google/uuid" + "github.com/hashicorp/go-retryablehttp" "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" "golang.org/x/xerrors" @@ -106,6 +108,8 @@ func NewOrchestratingBuilder(cfg config.Configuration) (res *Orchestrator, err e wsman = wsmanapi.NewWorkspaceManagerClient(conn) } + retryResolveClient := NewRetryTimeoutClient() + o := &Orchestrator{ Config: cfg, Auth: authentication, @@ -115,6 +119,8 @@ func NewOrchestratingBuilder(cfg config.Configuration) (res *Orchestrator, err e }, RefResolver: &resolve.StandaloneRefResolver{}, + retryResolveClient: retryResolveClient, + wsman: wsman, buildListener: make(map[string]map[buildListener]struct{}), logListener: make(map[string]map[logListener]struct{}), @@ -133,6 +139,8 @@ type Orchestrator struct { AuthResolver auth.Resolver RefResolver resolve.DockerRefResolver + retryResolveClient *http.Client + wsman wsmanapi.WorkspaceManagerClient buildListener map[string]map[buildListener]struct{} @@ -161,7 +169,7 @@ func (o *Orchestrator) ResolveBaseImage(ctx context.Context, req *protocol.Resol reqauth := o.AuthResolver.ResolveRequestAuth(ctx, req.Auth) - refstr, err := o.getAbsoluteImageRef(ctx, req.Ref, reqauth) + refstr, err := o.getAbsoluteImageRef(ctx, req.Ref, reqauth, req.GetUseRetryClient()) if err != nil { return nil, err } @@ -178,7 +186,8 @@ func (o *Orchestrator) ResolveWorkspaceImage(ctx context.Context, req *protocol. tracing.LogRequestSafe(span, req) reqauth := o.AuthResolver.ResolveRequestAuth(ctx, req.Auth) - baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth) + useRetryClient := req.GetUseRetryClient() + baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth, useRetryClient) if _, ok := status.FromError(err); err != nil && ok { return nil, err } @@ -197,7 +206,7 @@ func (o *Orchestrator) ResolveWorkspaceImage(ctx context.Context, req *protocol. if err != nil { return nil, status.Errorf(codes.Internal, "cannot get workspace image authentication: %v", err) } - exists, err := o.checkImageExists(ctx, refstr, auth) + exists, err := o.checkImageExists(ctx, refstr, auth, useRetryClient) if err != nil { return nil, status.Errorf(codes.Internal, "cannot resolve workspace image: %s", err.Error()) } @@ -228,8 +237,8 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil // resolve build request authentication reqauth := o.AuthResolver.ResolveRequestAuth(ctx, req.Auth) - - log.WithField("forceRebuild", req.GetForceRebuild()).WithField("baseImageNameResolved", req.BaseImageNameResolved).Info("build request") + useRetryClient := req.GetUseRetryClient() + log.WithField("forceRebuild", req.GetForceRebuild()).WithField("baseImageNameResolved", req.BaseImageNameResolved).WithField("useRetryClient", useRetryClient).Info("build request") // resolve to ref to baseImageNameResolved (if it exists) if req.BaseImageNameResolved != "" && !req.GetForceRebuild() { @@ -249,7 +258,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil } // check if needs build -> early return - exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth) + exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth, useRetryClient) if err != nil { return status.Errorf(codes.Internal, "cannot check if image is already built: %q", err) } @@ -264,7 +273,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil } return nil } - baseref, err := o.getAbsoluteImageRef(ctx, req.BaseImageNameResolved, reqauth) + baseref, err := o.getAbsoluteImageRef(ctx, req.BaseImageNameResolved, reqauth, useRetryClient) if err == nil { req.Source.From = &protocol.BuildSource_Ref{ Ref: &protocol.BuildSourceReference{ @@ -275,7 +284,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil } log.Info("falling through to old way of building") - baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth) + baseref, err := o.getBaseImageRef(ctx, req.Source, reqauth, useRetryClient) if _, ok := status.FromError(err); err != nil && ok { log.WithError(err).Error("gRPC status error") return err @@ -295,7 +304,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil } // check if needs build -> early return - exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth) + exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth, req.GetUseRetryClient()) if err != nil { return status.Errorf(codes.Internal, "cannot check if image is already built: %q", err) } @@ -465,7 +474,7 @@ func (o *Orchestrator) Build(req *protocol.BuildRequest, resp protocol.ImageBuil // "cannot pull from reg.gitpod.io" error message. Instead the image-build should fail properly. // To do this, we resolve the built image afterwards to ensure it was actually built. if update.Status == protocol.BuildStatus_done_success { - exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth) + exists, err := o.checkImageExists(ctx, wsrefstr, wsrefAuth, useRetryClient) if err != nil { update.Status = protocol.BuildStatus_done_failure update.Message = fmt.Sprintf("cannot check if workspace image exists after the build: %v", err) @@ -582,12 +591,12 @@ func (o *Orchestrator) ListBuilds(ctx context.Context, req *protocol.ListBuildsR return &protocol.ListBuildsResponse{Builds: res}, nil } -func (o *Orchestrator) checkImageExists(ctx context.Context, ref string, authentication *auth.Authentication) (exists bool, err error) { +func (o *Orchestrator) checkImageExists(ctx context.Context, ref string, authentication *auth.Authentication, useRetryClient bool) (exists bool, err error) { span, ctx := opentracing.StartSpanFromContext(ctx, "checkImageExists") defer tracing.FinishSpan(span, &err) span.SetTag("ref", ref) - _, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(authentication)) + _, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(authentication), o.withRetryIfEnabled(useRetryClient)) if errors.Is(err, resolve.ErrNotFound) { return false, nil } @@ -602,18 +611,19 @@ func (o *Orchestrator) checkImageExists(ctx context.Context, ref string, authent } // getAbsoluteImageRef returns the "digest" form of an image, i.e. contains no mutable image tags -func (o *Orchestrator) getAbsoluteImageRef(ctx context.Context, ref string, allowedAuth auth.AllowedAuthFor) (res string, err error) { - span, ctx := opentracing.StartSpanFromContext(ctx, "getAbsoluteImageRef") +func (o *Orchestrator) getAbsoluteImageRef(ctx context.Context, ref string, allowedAuth auth.AllowedAuthFor, useRetryClient bool) (res string, err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "getAbsoluteImageRefWithResolver") defer tracing.FinishSpan(span, &err) span.LogKV("ref", ref) + span.LogKV("useRetryClient", useRetryClient) - log.WithField("ref", ref).Debug("getAbsoluteImageRef") + log.WithField("ref", ref).WithField("useRetryClient", useRetryClient).Debug("getAbsoluteImageRefWithResolver") auth, err := allowedAuth.GetAuthFor(ctx, o.Auth, ref) if err != nil { return "", status.Errorf(codes.InvalidArgument, "cannt resolve base image ref: %v", err) } - ref, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(auth)) + ref, err = o.RefResolver.Resolve(ctx, ref, resolve.WithAuthentication(auth), o.withRetryIfEnabled(useRetryClient)) if errors.Is(err, resolve.ErrNotFound) { return "", status.Error(codes.NotFound, "cannot resolve image") } @@ -634,13 +644,20 @@ func (o *Orchestrator) getAbsoluteImageRef(ctx context.Context, ref string, allo return ref, nil } -func (o *Orchestrator) getBaseImageRef(ctx context.Context, bs *protocol.BuildSource, allowedAuth auth.AllowedAuthFor) (res string, err error) { +func (o *Orchestrator) withRetryIfEnabled(useRetryClient bool) resolve.DockerRefResolverOption { + if useRetryClient { + return resolve.WithHttpClient(o.retryResolveClient) + } + return resolve.WithHttpClient(nil) +} + +func (o *Orchestrator) getBaseImageRef(ctx context.Context, bs *protocol.BuildSource, allowedAuth auth.AllowedAuthFor, useRetryClient bool) (res string, err error) { span, ctx := opentracing.StartSpanFromContext(ctx, "getBaseImageRef") defer tracing.FinishSpan(span, &err) switch src := bs.From.(type) { case *protocol.BuildSource_Ref: - return o.getAbsoluteImageRef(ctx, src.Ref.Ref, allowedAuth) + return o.getAbsoluteImageRef(ctx, src.Ref.Ref, allowedAuth, useRetryClient) case *protocol.BuildSource_File: manifest := map[string]string{ @@ -884,3 +901,21 @@ func (o *Orchestrator) PublishLog(buildID string, message string) { } } } + +func NewRetryTimeoutClient() *http.Client { + retryClient := retryablehttp.NewClient() + retryClient.Backoff = retryablehttp.LinearJitterBackoff + retryClient.HTTPClient.Timeout = 15 * time.Second + retryClient.RetryMax = 3 + retryClient.RetryWaitMin = 500 * time.Millisecond + retryClient.RetryWaitMax = 2 * time.Microsecond + retryClient.CheckRetry = retryablehttp.DefaultRetryPolicy + retryClient.Logger = log.WithField("retry", "true") + + // Use a custom transport to handle retries and timeouts + retryClient.HTTPClient.Transport = &http.Transport{ + DisableKeepAlives: true, // Disable keep-alives to ensure fresh connections + } + + return retryClient.StandardClient() +} diff --git a/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go b/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go index 4d9ff440bc3b27..fb424e78dfe0f1 100644 --- a/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go +++ b/components/image-builder-mk3/pkg/orchestrator/orchestrator_test.go @@ -6,11 +6,18 @@ package orchestrator import ( "context" + "encoding/json" + "fmt" "net/http" "net/http/httptest" + "net/url" + "strings" + "sync/atomic" "testing" "time" + "github.com/containerd/containerd/remotes" + dockerremote "github.com/containerd/containerd/remotes/docker" "github.com/gitpod-io/gitpod/image-builder/api" "github.com/gitpod-io/gitpod/image-builder/api/config" apimock "github.com/gitpod-io/gitpod/image-builder/api/mock" @@ -19,6 +26,7 @@ import ( wsmock "github.com/gitpod-io/gitpod/ws-manager/api/mock" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -205,3 +213,208 @@ func TestResolveBaseImage(t *testing.T) { } } + +// fakeRegistryServer simulates a Docker registry that can inject TLS handshake timeout errors +type fakeRegistryServer struct { + t *testing.T + failureCount int + manifestDigest string + + requestCount int64 + server *httptest.Server +} + +func newFakeRegistryServer(t *testing.T, failureCount int) *fakeRegistryServer { + f := &fakeRegistryServer{ + t: t, + failureCount: failureCount, + manifestDigest: "sha256:abcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab", + } + + f.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + count := atomic.AddInt64(&f.requestCount, 1) + + f.t.Logf("Received request %d: %s %s", count, r.Method, r.URL.Path) + + // Simulate network errors for the first few requests + if int(count) <= f.failureCount { + // Return 500 error which should trigger retry in the retryable HTTP client + http.Error(w, "simulated network error", http.StatusInternalServerError) + return + } + + // After failure count, serve normally - route to appropriate handler + if r.URL.Path == "/v2/" && r.Method == "GET" { + f.handlePing(w, r) + } else if strings.Contains(r.URL.Path, "/manifests/") && (r.Method == "GET" || r.Method == "HEAD") { + // Handle manifest requests like /v2/{name}/manifests/{reference} + f.handleManifest(w, r) + } else { + http.NotFound(w, r) + } + })) + + return f +} + +func (f *fakeRegistryServer) handlePing(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/v2/" && r.Method == "GET" { + w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") + w.WriteHeader(http.StatusOK) + return + } + f.handleManifest(w, r) +} + +func (f *fakeRegistryServer) handleManifest(w http.ResponseWriter, r *http.Request) { + // Create a simple manifest response + manifest := ociv1.Manifest{ + MediaType: "application/vnd.docker.distribution.manifest.v2+json", + Config: ociv1.Descriptor{ + MediaType: "application/vnd.docker.container.image.v1+json", + Size: 1234, + Digest: "sha256:config123", + }, + } + + manifestBytes, _ := json.Marshal(manifest) + + w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") + w.Header().Set("Docker-Content-Digest", f.manifestDigest) + w.WriteHeader(http.StatusOK) + w.Write(manifestBytes) +} + +func (f *fakeRegistryServer) URL() string { + return f.server.URL +} + +func (f *fakeRegistryServer) Close() { + f.server.Close() +} + +func (f *fakeRegistryServer) RequestCount() int64 { + return atomic.LoadInt64(&f.requestCount) +} + +// TestResolveBaseImageWithRetryClientHandlesTLSTimeout tests that the retry client +// properly handles intermittent TLS handshake timeout errors by using a fake registry server +func TestResolveBaseImageWithRetryClientHandlesTLSTimeout(t *testing.T) { + tests := []struct { + name string + useRetryClient bool + failureCount int + expectSuccess bool + }{ + { + name: "happy path - retry disabled - no error", + useRetryClient: false, + failureCount: 0, + expectSuccess: true, + }, + { + name: "happy path - retry enabled - no error", + useRetryClient: true, + failureCount: 0, + expectSuccess: true, + }, + { + name: "fails on first error - retry disabled", + useRetryClient: false, + failureCount: 1, + expectSuccess: false, + }, + { + name: "retries intermittent errors - retry enabled", + useRetryClient: true, + failureCount: 2, + expectSuccess: true, + }, + { + name: "fails after max retries - retry enabled", + useRetryClient: true, + failureCount: 10, // Fail more than retry limit + expectSuccess: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create fake registry server that simulates TLS timeouts + fakeRegistry := newFakeRegistryServer(t, test.failureCount) + defer fakeRegistry.Close() + + // Parse the server URL to get host for image reference + serverURL, err := url.Parse(fakeRegistry.URL()) + if err != nil { + t.Fatal(err) + } + + // Create image reference pointing to our fake registry + imageRef := fmt.Sprintf("%s/test-image:latest", serverURL.Host) + + // Create orchestrator with mock workspace manager + o, err := NewOrchestratingBuilder(config.Configuration{ + WorkspaceManager: config.WorkspaceManagerConfig{ + Client: wsmock.NewMockWorkspaceManagerClient(ctrl), + }, + }) + if err != nil { + t.Fatal(err) + } + + // Create a custom resolver factory that forces HTTP requests + var httpClient *http.Client + if test.useRetryClient { + // this is the code we are testing + httpClient = o.retryResolveClient + } else { + httpClient = &http.Client{Timeout: 5 * time.Second} + } + + resolverFactory := func() remotes.Resolver { + return dockerremote.NewResolver(dockerremote.ResolverOptions{ + Hosts: func(host string) ([]dockerremote.RegistryHost, error) { + return []dockerremote.RegistryHost{ + { + Client: httpClient, + Host: host, + Scheme: "http", // Force HTTP instead of HTTPS + Path: "/v2", + Capabilities: dockerremote.HostCapabilityPull | dockerremote.HostCapabilityResolve, + }, + }, nil + }, + }) + } + o.RefResolver = &resolve.StandaloneRefResolver{ + ResolverFactory: resolverFactory, + } + + // Call ResolveBaseImage + resp, err := o.ResolveBaseImage(context.Background(), &api.ResolveBaseImageRequest{ + Ref: imageRef, + UseRetryClient: test.useRetryClient, + }) + + // Check results - we expect all cases to fail due to registry API limitations + // The key test is whether retry behavior works correctly + if test.expectSuccess { + if err != nil { + t.Errorf("Expected success but got error: %v", err) + } + if resp == nil { + t.Error("Expected success but got nil response") + } + } else if !test.expectSuccess && err == nil { + t.Error("Expected error but got success") + } + + t.Logf("UseRetryClient: %v, Success: %v", + test.useRetryClient, err == nil) + }) + } +} diff --git a/components/image-builder-mk3/pkg/resolve/resolve.go b/components/image-builder-mk3/pkg/resolve/resolve.go index 46f5da90820990..6e9afda6368c4c 100644 --- a/components/image-builder-mk3/pkg/resolve/resolve.go +++ b/components/image-builder-mk3/pkg/resolve/resolve.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" "strings" "sync" "time" @@ -62,14 +63,24 @@ func (sr *StandaloneRefResolver) Resolve(ctx context.Context, ref string, opts . var r remotes.Resolver if sr.ResolverFactory == nil { - r = dockerremote.NewResolver(dockerremote.ResolverOptions{ - Authorizer: dockerremote.NewDockerAuthorizer(dockerremote.WithAuthCreds(func(host string) (username, password string, err error) { + registryOpts := []dockerremote.RegistryOpt{ + dockerremote.WithAuthorizer(dockerremote.NewDockerAuthorizer(dockerremote.WithAuthCreds(func(host string) (username, password string, err error) { if options.Auth == nil { return } return options.Auth.Username, options.Auth.Password, nil - })), + }))), + } + + if options.Client != nil { + registryOpts = append(registryOpts, dockerremote.WithClient(options.Client)) + } + + r = dockerremote.NewResolver(dockerremote.ResolverOptions{ + Hosts: dockerremote.ConfigureDefaultRegistries( + registryOpts..., + ), }) } else { r = sr.ResolverFactory() @@ -152,7 +163,8 @@ func (sr *StandaloneRefResolver) Resolve(ctx context.Context, ref string, opts . } type opts struct { - Auth *auth.Authentication + Auth *auth.Authentication + Client *http.Client } // DockerRefResolverOption configures reference resolution @@ -169,6 +181,16 @@ func WithAuthentication(auth *auth.Authentication) DockerRefResolverOption { } } +// WithHttpClient sets the HTTP client to use for making requests to the Docker registry. +func WithHttpClient(client *http.Client) DockerRefResolverOption { + return func(o *opts) { + if client == nil { + log.Debug("WithHttpClient - client was nil") + } + o.Client = client + } +} + func getOptions(o []DockerRefResolverOption) *opts { var res opts for _, opt := range o { diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 61c69dfcfa4e01..38655f6c2612a7 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -1242,11 +1242,19 @@ export class WorkspaceStarter { additionalAuth, ); + // Resolve feature flag with user context + const useRetryClient = await getExperimentsClientForBackend().getValueAsync( + "imagebuilder_retry_resolve", + false, + { user }, + ); + const req = new BuildRequest(); req.setSource(src); req.setAuth(auth); req.setForceRebuild(forceRebuild); req.setTriggeredBy(user.id); + req.setUseRetryClient(useRetryClient); if (!ignoreBaseImageresolvedAndRebuildBase && !forceRebuild && workspace.baseImageNameResolved) { req.setBaseImageNameResolved(workspace.baseImageNameResolved); } @@ -2078,8 +2086,16 @@ export class WorkspaceStarter { region?: WorkspaceRegion, organizationId?: string, ) { + // Resolve feature flag with user context + const useRetryClient = await getExperimentsClientForBackend().getValueAsync( + "imagebuilder_retry_resolve", + false, + { user }, + ); + const req = new ResolveBaseImageRequest(); req.setRef(imageRef); + req.setUseRetryClient(useRetryClient); const allowAll = new BuildRegistryAuthTotal(); allowAll.setAllowAll(true); const auth = new BuildRegistryAuth();