vendor: update buildkit to v0.32.0-rc1
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+292
-295
File diff suppressed because it is too large
Load Diff
+220
-221
@@ -1,330 +1,329 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package containerd.services.content.v1;
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/field_mask.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option go_package = "github.com/containerd/containerd/api/services/content/v1;content";
|
||||
|
||||
// Content provides access to a content addressable storage system.
|
||||
service Content {
|
||||
// Info returns information about a committed object.
|
||||
//
|
||||
// This call can be used for getting the size of content and checking for
|
||||
// existence.
|
||||
rpc Info(InfoRequest) returns (InfoResponse);
|
||||
// Info returns information about a committed object.
|
||||
//
|
||||
// This call can be used for getting the size of content and checking for
|
||||
// existence.
|
||||
rpc Info(InfoRequest) returns (InfoResponse);
|
||||
|
||||
// Update updates content metadata.
|
||||
//
|
||||
// This call can be used to manage the mutable content labels. The
|
||||
// immutable metadata such as digest, size, and committed at cannot
|
||||
// be updated.
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
// Update updates content metadata.
|
||||
//
|
||||
// This call can be used to manage the mutable content labels. The
|
||||
// immutable metadata such as digest, size, and committed at cannot
|
||||
// be updated.
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
|
||||
// List streams the entire set of content as Info objects and closes the
|
||||
// stream.
|
||||
//
|
||||
// Typically, this will yield a large response, chunked into messages.
|
||||
// Clients should make provisions to ensure they can handle the entire data
|
||||
// set.
|
||||
rpc List(ListContentRequest) returns (stream ListContentResponse);
|
||||
// List streams the entire set of content as Info objects and closes the
|
||||
// stream.
|
||||
//
|
||||
// Typically, this will yield a large response, chunked into messages.
|
||||
// Clients should make provisions to ensure they can handle the entire data
|
||||
// set.
|
||||
rpc List(ListContentRequest) returns (stream ListContentResponse);
|
||||
|
||||
// Delete will delete the referenced object.
|
||||
rpc Delete(DeleteContentRequest) returns (google.protobuf.Empty);
|
||||
// Delete will delete the referenced object.
|
||||
rpc Delete(DeleteContentRequest) returns (google.protobuf.Empty);
|
||||
|
||||
// Read allows one to read an object based on the offset into the content.
|
||||
//
|
||||
// The requested data may be returned in one or more messages.
|
||||
rpc Read(ReadContentRequest) returns (stream ReadContentResponse);
|
||||
// Read allows one to read an object based on the offset into the content.
|
||||
//
|
||||
// The requested data may be returned in one or more messages.
|
||||
rpc Read(ReadContentRequest) returns (stream ReadContentResponse);
|
||||
|
||||
// Status returns the status for a single reference.
|
||||
rpc Status(StatusRequest) returns (StatusResponse);
|
||||
// Status returns the status for a single reference.
|
||||
rpc Status(StatusRequest) returns (StatusResponse);
|
||||
|
||||
// ListStatuses returns the status of ongoing object ingestions, started via
|
||||
// Write.
|
||||
//
|
||||
// Only those matching the regular expression will be provided in the
|
||||
// response. If the provided regular expression is empty, all ingestions
|
||||
// will be provided.
|
||||
rpc ListStatuses(ListStatusesRequest) returns (ListStatusesResponse);
|
||||
// ListStatuses returns the status of ongoing object ingestions, started via
|
||||
// Write.
|
||||
//
|
||||
// Only those matching the regular expression will be provided in the
|
||||
// response. If the provided regular expression is empty, all ingestions
|
||||
// will be provided.
|
||||
rpc ListStatuses(ListStatusesRequest) returns (ListStatusesResponse);
|
||||
|
||||
// Write begins or resumes writes to a resource identified by a unique ref.
|
||||
// Only one active stream may exist at a time for each ref.
|
||||
//
|
||||
// Once a write stream has started, it may only write to a single ref, thus
|
||||
// once a stream is started, the ref may be omitted on subsequent writes.
|
||||
//
|
||||
// For any write transaction represented by a ref, only a single write may
|
||||
// be made to a given offset. If overlapping writes occur, it is an error.
|
||||
// Writes should be sequential and implementations may throw an error if
|
||||
// this is required.
|
||||
//
|
||||
// If expected_digest is set and already part of the content store, the
|
||||
// write will fail.
|
||||
//
|
||||
// When completed, the commit flag should be set to true. If expected size
|
||||
// or digest is set, the content will be validated against those values.
|
||||
rpc Write(stream WriteContentRequest) returns (stream WriteContentResponse);
|
||||
// Write begins or resumes writes to a resource identified by a unique ref.
|
||||
// Only one active stream may exist at a time for each ref.
|
||||
//
|
||||
// Once a write stream has started, it may only write to a single ref, thus
|
||||
// once a stream is started, the ref may be omitted on subsequent writes.
|
||||
//
|
||||
// For any write transaction represented by a ref, only a single write may
|
||||
// be made to a given offset. If overlapping writes occur, it is an error.
|
||||
// Writes should be sequential and implementations may throw an error if
|
||||
// this is required.
|
||||
//
|
||||
// If expected_digest is set and already part of the content store, the
|
||||
// write will fail.
|
||||
//
|
||||
// When completed, the commit flag should be set to true. If expected size
|
||||
// or digest is set, the content will be validated against those values.
|
||||
rpc Write(stream WriteContentRequest) returns (stream WriteContentResponse);
|
||||
|
||||
// Abort cancels the ongoing write named in the request. Any resources
|
||||
// associated with the write will be collected.
|
||||
rpc Abort(AbortRequest) returns (google.protobuf.Empty);
|
||||
// Abort cancels the ongoing write named in the request. Any resources
|
||||
// associated with the write will be collected.
|
||||
rpc Abort(AbortRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
||||
message Info {
|
||||
// Digest is the hash identity of the blob.
|
||||
string digest = 1;
|
||||
// Digest is the hash identity of the blob.
|
||||
string digest = 1;
|
||||
|
||||
// Size is the total number of bytes in the blob.
|
||||
int64 size = 2;
|
||||
// Size is the total number of bytes in the blob.
|
||||
int64 size = 2;
|
||||
|
||||
// CreatedAt provides the time at which the blob was committed.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
// CreatedAt provides the time at which the blob was committed.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
|
||||
// UpdatedAt provides the time the info was last updated.
|
||||
google.protobuf.Timestamp updated_at = 4;
|
||||
// UpdatedAt provides the time the info was last updated.
|
||||
google.protobuf.Timestamp updated_at = 4;
|
||||
|
||||
// Labels are arbitrary data on snapshots.
|
||||
//
|
||||
// The combined size of a key/value pair cannot exceed 4096 bytes.
|
||||
map<string, string> labels = 5;
|
||||
// Labels are arbitrary data on snapshots.
|
||||
//
|
||||
// The combined size of a key/value pair cannot exceed 4096 bytes.
|
||||
map<string, string> labels = 5;
|
||||
}
|
||||
|
||||
message InfoRequest {
|
||||
string digest = 1;
|
||||
string digest = 1;
|
||||
}
|
||||
|
||||
message InfoResponse {
|
||||
Info info = 1;
|
||||
Info info = 1;
|
||||
}
|
||||
|
||||
message UpdateRequest {
|
||||
Info info = 1;
|
||||
Info info = 1;
|
||||
|
||||
// UpdateMask specifies which fields to perform the update on. If empty,
|
||||
// the operation applies to all fields.
|
||||
//
|
||||
// In info, Digest, Size, and CreatedAt are immutable,
|
||||
// other field may be updated using this mask.
|
||||
// If no mask is provided, all mutable field are updated.
|
||||
google.protobuf.FieldMask update_mask = 2;
|
||||
// UpdateMask specifies which fields to perform the update on. If empty,
|
||||
// the operation applies to all fields.
|
||||
//
|
||||
// In info, Digest, Size, and CreatedAt are immutable,
|
||||
// other field may be updated using this mask.
|
||||
// If no mask is provided, all mutable field are updated.
|
||||
google.protobuf.FieldMask update_mask = 2;
|
||||
}
|
||||
|
||||
message UpdateResponse {
|
||||
Info info = 1;
|
||||
Info info = 1;
|
||||
}
|
||||
|
||||
message ListContentRequest {
|
||||
// Filters contains one or more filters using the syntax defined in the
|
||||
// containerd filter package.
|
||||
//
|
||||
// The returned result will be those that match any of the provided
|
||||
// filters. Expanded, containers that match the following will be
|
||||
// returned:
|
||||
//
|
||||
// filters[0] or filters[1] or ... or filters[n-1] or filters[n]
|
||||
//
|
||||
// If filters is zero-length or nil, all items will be returned.
|
||||
repeated string filters = 1;
|
||||
// Filters contains one or more filters using the syntax defined in the
|
||||
// containerd filter package.
|
||||
//
|
||||
// The returned result will be those that match any of the provided
|
||||
// filters. Expanded, containers that match the following will be
|
||||
// returned:
|
||||
//
|
||||
// filters[0] or filters[1] or ... or filters[n-1] or filters[n]
|
||||
//
|
||||
// If filters is zero-length or nil, all items will be returned.
|
||||
repeated string filters = 1;
|
||||
}
|
||||
|
||||
message ListContentResponse {
|
||||
repeated Info info = 1;
|
||||
repeated Info info = 1;
|
||||
}
|
||||
|
||||
message DeleteContentRequest {
|
||||
// Digest specifies which content to delete.
|
||||
string digest = 1;
|
||||
// Digest specifies which content to delete.
|
||||
string digest = 1;
|
||||
}
|
||||
|
||||
// ReadContentRequest defines the fields that make up a request to read a portion of
|
||||
// data from a stored object.
|
||||
message ReadContentRequest {
|
||||
// Digest is the hash identity to read.
|
||||
string digest = 1;
|
||||
// Digest is the hash identity to read.
|
||||
string digest = 1;
|
||||
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the read. If zero or less, the read will be from the start. This uses
|
||||
// standard zero-indexed semantics.
|
||||
int64 offset = 2;
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the read. If zero or less, the read will be from the start. This uses
|
||||
// standard zero-indexed semantics.
|
||||
int64 offset = 2;
|
||||
|
||||
// size is the total size of the read. If zero, the entire blob will be
|
||||
// returned by the service.
|
||||
int64 size = 3;
|
||||
// size is the total size of the read. If zero, the entire blob will be
|
||||
// returned by the service.
|
||||
int64 size = 3;
|
||||
}
|
||||
|
||||
// ReadContentResponse carries byte data for a read request.
|
||||
message ReadContentResponse {
|
||||
int64 offset = 1; // offset of the returned data
|
||||
bytes data = 2; // actual data
|
||||
int64 offset = 1; // offset of the returned data
|
||||
bytes data = 2; // actual data
|
||||
}
|
||||
|
||||
message Status {
|
||||
google.protobuf.Timestamp started_at = 1;
|
||||
google.protobuf.Timestamp updated_at = 2;
|
||||
string ref = 3;
|
||||
int64 offset = 4;
|
||||
int64 total = 5;
|
||||
string expected = 6;
|
||||
google.protobuf.Timestamp started_at = 1;
|
||||
google.protobuf.Timestamp updated_at = 2;
|
||||
string ref = 3;
|
||||
int64 offset = 4;
|
||||
int64 total = 5;
|
||||
string expected = 6;
|
||||
}
|
||||
|
||||
|
||||
message StatusRequest {
|
||||
string ref = 1;
|
||||
string ref = 1;
|
||||
}
|
||||
|
||||
message StatusResponse {
|
||||
Status status = 1;
|
||||
Status status = 1;
|
||||
}
|
||||
|
||||
message ListStatusesRequest {
|
||||
repeated string filters = 1;
|
||||
repeated string filters = 1;
|
||||
}
|
||||
|
||||
message ListStatusesResponse {
|
||||
repeated Status statuses = 1;
|
||||
repeated Status statuses = 1;
|
||||
}
|
||||
|
||||
// WriteAction defines the behavior of a WriteRequest.
|
||||
enum WriteAction {
|
||||
// WriteActionStat instructs the writer to return the current status while
|
||||
// holding the lock on the write.
|
||||
STAT = 0;
|
||||
// WriteActionStat instructs the writer to return the current status while
|
||||
// holding the lock on the write.
|
||||
STAT = 0;
|
||||
|
||||
// WriteActionWrite sets the action for the write request to write data.
|
||||
//
|
||||
// Any data included will be written at the provided offset. The
|
||||
// transaction will be left open for further writes.
|
||||
//
|
||||
// This is the default.
|
||||
WRITE = 1;
|
||||
// WriteActionWrite sets the action for the write request to write data.
|
||||
//
|
||||
// Any data included will be written at the provided offset. The
|
||||
// transaction will be left open for further writes.
|
||||
//
|
||||
// This is the default.
|
||||
WRITE = 1;
|
||||
|
||||
// WriteActionCommit will write any outstanding data in the message and
|
||||
// commit the write, storing it under the digest.
|
||||
//
|
||||
// This can be used in a single message to send the data, verify it and
|
||||
// commit it.
|
||||
//
|
||||
// This action will always terminate the write.
|
||||
COMMIT = 2;
|
||||
// WriteActionCommit will write any outstanding data in the message and
|
||||
// commit the write, storing it under the digest.
|
||||
//
|
||||
// This can be used in a single message to send the data, verify it and
|
||||
// commit it.
|
||||
//
|
||||
// This action will always terminate the write.
|
||||
COMMIT = 2;
|
||||
}
|
||||
|
||||
// WriteContentRequest writes data to the request ref at offset.
|
||||
message WriteContentRequest {
|
||||
// Action sets the behavior of the write.
|
||||
//
|
||||
// When this is a write and the ref is not yet allocated, the ref will be
|
||||
// allocated and the data will be written at offset.
|
||||
//
|
||||
// If the action is write and the ref is allocated, it will accept data to
|
||||
// an offset that has not yet been written.
|
||||
//
|
||||
// If the action is write and there is no data, the current write status
|
||||
// will be returned. This works differently from status because the stream
|
||||
// holds a lock.
|
||||
WriteAction action = 1;
|
||||
// Action sets the behavior of the write.
|
||||
//
|
||||
// When this is a write and the ref is not yet allocated, the ref will be
|
||||
// allocated and the data will be written at offset.
|
||||
//
|
||||
// If the action is write and the ref is allocated, it will accept data to
|
||||
// an offset that has not yet been written.
|
||||
//
|
||||
// If the action is write and there is no data, the current write status
|
||||
// will be returned. This works differently from status because the stream
|
||||
// holds a lock.
|
||||
WriteAction action = 1;
|
||||
|
||||
// Ref identifies the pre-commit object to write to.
|
||||
string ref = 2;
|
||||
// Ref identifies the pre-commit object to write to.
|
||||
string ref = 2;
|
||||
|
||||
// Total can be set to have the service validate the total size of the
|
||||
// committed content.
|
||||
//
|
||||
// The latest value before or with the commit action message will be use to
|
||||
// validate the content. If the offset overflows total, the service may
|
||||
// report an error. It is only required on one message for the write.
|
||||
//
|
||||
// If the value is zero or less, no validation of the final content will be
|
||||
// performed.
|
||||
int64 total = 3;
|
||||
// Total can be set to have the service validate the total size of the
|
||||
// committed content.
|
||||
//
|
||||
// The latest value before or with the commit action message will be use to
|
||||
// validate the content. If the offset overflows total, the service may
|
||||
// report an error. It is only required on one message for the write.
|
||||
//
|
||||
// If the value is zero or less, no validation of the final content will be
|
||||
// performed.
|
||||
int64 total = 3;
|
||||
|
||||
// Expected can be set to have the service validate the final content against
|
||||
// the provided digest.
|
||||
//
|
||||
// If the digest is already present in the object store, an AlreadyExists
|
||||
// error will be returned.
|
||||
//
|
||||
// Only the latest version will be used to check the content against the
|
||||
// digest. It is only required to include it on a single message, before or
|
||||
// with the commit action message.
|
||||
string expected = 4;
|
||||
// Expected can be set to have the service validate the final content against
|
||||
// the provided digest.
|
||||
//
|
||||
// If the digest is already present in the object store, an AlreadyExists
|
||||
// error will be returned.
|
||||
//
|
||||
// Only the latest version will be used to check the content against the
|
||||
// digest. It is only required to include it on a single message, before or
|
||||
// with the commit action message.
|
||||
string expected = 4;
|
||||
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the write. For most implementations, this means from the start of the
|
||||
// file. This uses standard, zero-indexed semantics.
|
||||
//
|
||||
// If the action is write, the remote may remove all previously written
|
||||
// data after the offset. Implementations may support arbitrary offsets but
|
||||
// MUST support reseting this value to zero with a write. If an
|
||||
// implementation does not support a write at a particular offset, an
|
||||
// OutOfRange error must be returned.
|
||||
int64 offset = 5;
|
||||
// Offset specifies the number of bytes from the start at which to begin
|
||||
// the write. For most implementations, this means from the start of the
|
||||
// file. This uses standard, zero-indexed semantics.
|
||||
//
|
||||
// If the action is write, the remote may remove all previously written
|
||||
// data after the offset. Implementations may support arbitrary offsets but
|
||||
// MUST support reseting this value to zero with a write. If an
|
||||
// implementation does not support a write at a particular offset, an
|
||||
// OutOfRange error must be returned.
|
||||
int64 offset = 5;
|
||||
|
||||
// Data is the actual bytes to be written.
|
||||
//
|
||||
// If this is empty and the message is not a commit, a response will be
|
||||
// returned with the current write state.
|
||||
bytes data = 6;
|
||||
// Data is the actual bytes to be written.
|
||||
//
|
||||
// If this is empty and the message is not a commit, a response will be
|
||||
// returned with the current write state.
|
||||
bytes data = 6;
|
||||
|
||||
// Labels are arbitrary data on snapshots.
|
||||
//
|
||||
// The combined size of a key/value pair cannot exceed 4096 bytes.
|
||||
map<string, string> labels = 7;
|
||||
// Labels are arbitrary data on snapshots.
|
||||
//
|
||||
// The combined size of a key/value pair cannot exceed 4096 bytes.
|
||||
map<string, string> labels = 7;
|
||||
}
|
||||
|
||||
// WriteContentResponse is returned on the culmination of a write call.
|
||||
message WriteContentResponse {
|
||||
// Action contains the action for the final message of the stream. A writer
|
||||
// should confirm that they match the intended result.
|
||||
WriteAction action = 1;
|
||||
// Action contains the action for the final message of the stream. A writer
|
||||
// should confirm that they match the intended result.
|
||||
WriteAction action = 1;
|
||||
|
||||
// StartedAt provides the time at which the write began.
|
||||
//
|
||||
// This must be set for stat and commit write actions. All other write
|
||||
// actions may omit this.
|
||||
google.protobuf.Timestamp started_at = 2;
|
||||
// StartedAt provides the time at which the write began.
|
||||
//
|
||||
// This must be set for stat and commit write actions. All other write
|
||||
// actions may omit this.
|
||||
google.protobuf.Timestamp started_at = 2;
|
||||
|
||||
// UpdatedAt provides the last time of a successful write.
|
||||
//
|
||||
// This must be set for stat and commit write actions. All other write
|
||||
// actions may omit this.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
// UpdatedAt provides the last time of a successful write.
|
||||
//
|
||||
// This must be set for stat and commit write actions. All other write
|
||||
// actions may omit this.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
|
||||
// Offset is the current committed size for the write.
|
||||
int64 offset = 4;
|
||||
// Offset is the current committed size for the write.
|
||||
int64 offset = 4;
|
||||
|
||||
// Total provides the current, expected total size of the write.
|
||||
//
|
||||
// We include this to provide consistency with the Status structure on the
|
||||
// client writer.
|
||||
//
|
||||
// This is only valid on the Stat and Commit response.
|
||||
int64 total = 5;
|
||||
// Total provides the current, expected total size of the write.
|
||||
//
|
||||
// We include this to provide consistency with the Status structure on the
|
||||
// client writer.
|
||||
//
|
||||
// This is only valid on the Stat and Commit response.
|
||||
int64 total = 5;
|
||||
|
||||
// Digest, if present, includes the digest up to the currently committed
|
||||
// bytes. If action is commit, this field will be set. It is implementation
|
||||
// defined if this is set for other actions.
|
||||
string digest = 6;
|
||||
// Digest, if present, includes the digest up to the currently committed
|
||||
// bytes. If action is commit, this field will be set. It is implementation
|
||||
// defined if this is set for other actions.
|
||||
string digest = 6;
|
||||
}
|
||||
|
||||
message AbortRequest {
|
||||
string ref = 1;
|
||||
string ref = 1;
|
||||
}
|
||||
|
||||
Generated
Vendored
+3
-3
@@ -3,8 +3,8 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.20.1
|
||||
// source: github.com/containerd/containerd/api/services/content/v1/content.proto
|
||||
// - protoc (unknown)
|
||||
// source: services/content/v1/content.proto
|
||||
|
||||
package content
|
||||
|
||||
@@ -567,5 +567,5 @@ var Content_ServiceDesc = grpc.ServiceDesc{
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "github.com/containerd/containerd/api/services/content/v1/content.proto",
|
||||
Metadata: "services/content/v1/content.proto",
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT.
|
||||
// source: github.com/containerd/containerd/api/services/content/v1/content.proto
|
||||
// source: services/content/v1/content.proto
|
||||
package content
|
||||
|
||||
import (
|
||||
|
||||
+21
-3
@@ -35,7 +35,7 @@ import (
|
||||
var ErrReset = errors.New("writer has been reset")
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buffer := make([]byte, 1<<20)
|
||||
return &buffer
|
||||
},
|
||||
@@ -65,9 +65,23 @@ type nopCloserSectionReader struct {
|
||||
|
||||
func (*nopCloserSectionReader) Close() error { return nil }
|
||||
|
||||
func useDescriptorData(desc ocispec.Descriptor) (bool, error) {
|
||||
if int64(len(desc.Data)) != desc.Size {
|
||||
return false, nil
|
||||
}
|
||||
if err := desc.Digest.Validate(); err != nil {
|
||||
return false, fmt.Errorf("invalid descriptor digest: %w", err)
|
||||
}
|
||||
return desc.Digest.Algorithm().FromBytes(desc.Data) == desc.Digest, nil
|
||||
}
|
||||
|
||||
// BlobReadSeeker returns a read seeker for the blob from the provider.
|
||||
func BlobReadSeeker(ctx context.Context, provider Provider, desc ocispec.Descriptor) (io.ReadSeekCloser, error) {
|
||||
if int64(len(desc.Data)) == desc.Size && digest.FromBytes(desc.Data) == desc.Digest {
|
||||
useData, err := useDescriptorData(desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if useData {
|
||||
return &nopCloserBytesReader{bytes.NewReader(desc.Data)}, nil
|
||||
}
|
||||
|
||||
@@ -82,7 +96,11 @@ func BlobReadSeeker(ctx context.Context, provider Provider, desc ocispec.Descrip
|
||||
//
|
||||
// Avoid using this for large blobs, such as layers.
|
||||
func ReadBlob(ctx context.Context, provider Provider, desc ocispec.Descriptor) ([]byte, error) {
|
||||
if int64(len(desc.Data)) == desc.Size && digest.FromBytes(desc.Data) == desc.Digest {
|
||||
useData, err := useDescriptorData(desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if useData {
|
||||
return desc.Data, nil
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -195,9 +196,7 @@ func WithSkipMissing(store content.InfoReaderProvider) ExportOpt {
|
||||
|
||||
func addNameAnnotation(name string, base map[string]string) map[string]string {
|
||||
annotations := map[string]string{}
|
||||
for k, v := range base {
|
||||
annotations[k] = v
|
||||
}
|
||||
maps.Copy(annotations, base)
|
||||
|
||||
annotations[images.AnnotationImageName] = name
|
||||
annotations[ocispec.AnnotationRefName] = ociReferenceName(name)
|
||||
|
||||
+2
-2
@@ -241,7 +241,7 @@ const (
|
||||
jsonLimit = 20 * mib
|
||||
)
|
||||
|
||||
func onUntarJSON(r io.Reader, j interface{}) error {
|
||||
func onUntarJSON(r io.Reader, j any) error {
|
||||
return json.NewDecoder(io.LimitReader(r, jsonLimit)).Decode(j)
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ func compressBlob(ctx context.Context, cs content.Store, r io.Reader, ref string
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
func writeManifest(ctx context.Context, cs content.Ingester, manifest interface{}, mediaType string) (ocispec.Descriptor, error) {
|
||||
func writeManifest(ctx context.Context, cs content.Ingester, manifest any, mediaType string) (ocispec.Descriptor, error) {
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, err
|
||||
|
||||
+5
-3
@@ -91,12 +91,14 @@ func familiarizeReference(ref string) (string, error) {
|
||||
}
|
||||
|
||||
func ociReferenceName(name string) string {
|
||||
// OCI defines the reference name as only a tag excluding the
|
||||
// OCI suggests the reference name as only a tag excluding the
|
||||
// repository. The containerd annotation contains the full image name
|
||||
// since the tag is insufficient for correctly naming and referring to an
|
||||
// image
|
||||
// image. In the case of a by-digest image referencing only a SHA hash,
|
||||
// the full image name with hash is preferred to match the required
|
||||
// grammar of the OCI spec.
|
||||
var ociRef string
|
||||
if spec, err := reference.Parse(name); err == nil {
|
||||
if spec, err := reference.Parse(name); err == nil && spec.Object != "" && spec.Object[0] != '@' {
|
||||
ociRef = spec.Object
|
||||
} else {
|
||||
ociRef = name
|
||||
|
||||
+7
-1
@@ -59,6 +59,9 @@ const (
|
||||
MediaTypeImageLayerEncrypted = ocispec.MediaTypeImageLayer + "+encrypted"
|
||||
MediaTypeImageLayerGzipEncrypted = ocispec.MediaTypeImageLayerGzip + "+encrypted"
|
||||
|
||||
// EROFS media type
|
||||
MediaTypeErofsLayer = "application/vnd.erofs.layer.v1"
|
||||
|
||||
// In-toto attestation
|
||||
MediaTypeInToto = "application/vnd.in-toto+json"
|
||||
)
|
||||
@@ -139,11 +142,14 @@ func IsLayerType(mt string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Parse Docker media types, strip off any + suffixes first
|
||||
switch base, _ := parseMediaTypes(mt); base {
|
||||
// Parse Docker media types, strip off any + suffixes first
|
||||
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
|
||||
MediaTypeDockerSchema2LayerForeign, MediaTypeDockerSchema2LayerForeignGzip, MediaTypeDockerSchema2LayerZstd:
|
||||
return true
|
||||
// Allow EROFS native layers for efficient container image distribution.
|
||||
case MediaTypeErofsLayer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+2
-3
@@ -18,6 +18,7 @@ package leases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -84,9 +85,7 @@ func WithLabels(labels map[string]string) Opt {
|
||||
if l.Labels == nil {
|
||||
l.Labels = map[string]string{}
|
||||
}
|
||||
for k, v := range labels {
|
||||
l.Labels[k] = v
|
||||
}
|
||||
maps.Copy(l.Labels, labels)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ func init() {
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
for c := range 256 {
|
||||
var t octetType
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
|
||||
+12
-11
@@ -100,7 +100,7 @@ func (ec ErrorCode) WithMessage(message string) Error {
|
||||
|
||||
// WithDetail creates a new Error struct based on the passed-in info and
|
||||
// set the Detail property appropriately
|
||||
func (ec ErrorCode) WithDetail(detail interface{}) Error {
|
||||
func (ec ErrorCode) WithDetail(detail any) Error {
|
||||
return Error{
|
||||
Code: ec,
|
||||
Message: ec.Message(),
|
||||
@@ -108,7 +108,7 @@ func (ec ErrorCode) WithDetail(detail interface{}) Error {
|
||||
}
|
||||
|
||||
// WithArgs creates a new Error struct and sets the Args slice
|
||||
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
|
||||
func (ec ErrorCode) WithArgs(args ...any) Error {
|
||||
return Error{
|
||||
Code: ec,
|
||||
Message: ec.Message(),
|
||||
@@ -117,9 +117,9 @@ func (ec ErrorCode) WithArgs(args ...interface{}) Error {
|
||||
|
||||
// Error provides a wrapper around ErrorCode with extra Details provided.
|
||||
type Error struct {
|
||||
Code ErrorCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Detail interface{} `json:"detail,omitempty"`
|
||||
Code ErrorCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Detail any `json:"detail,omitempty"`
|
||||
|
||||
// TODO(duglin): See if we need an "args" property so we can do the
|
||||
// variable substitution right before showing the message to the user
|
||||
@@ -139,7 +139,7 @@ func (e Error) Error() string {
|
||||
|
||||
// WithDetail will return a new Error, based on the current one, but with
|
||||
// some Detail info added
|
||||
func (e Error) WithDetail(detail interface{}) Error {
|
||||
func (e Error) WithDetail(detail any) Error {
|
||||
return Error{
|
||||
Code: e.Code,
|
||||
Message: e.Message,
|
||||
@@ -147,9 +147,9 @@ func (e Error) WithDetail(detail interface{}) Error {
|
||||
}
|
||||
}
|
||||
|
||||
// WithArgs uses the passed-in list of interface{} as the substitution
|
||||
// WithArgs uses the passed-in list of args as the substitution
|
||||
// variables in the Error's Message string, but returns a new Error
|
||||
func (e Error) WithArgs(args ...interface{}) Error {
|
||||
func (e Error) WithArgs(args ...any) Error {
|
||||
return Error{
|
||||
Code: e.Code,
|
||||
Message: fmt.Sprintf(e.Code.Message(), args...),
|
||||
@@ -204,11 +204,12 @@ func (errs Errors) Error() string {
|
||||
case 1:
|
||||
return errs[0].Error()
|
||||
default:
|
||||
msg := "errors:\n"
|
||||
var msg strings.Builder
|
||||
msg.WriteString("errors:\n")
|
||||
for _, err := range errs {
|
||||
msg += err.Error() + "\n"
|
||||
msg.WriteString(err.Error() + "\n")
|
||||
}
|
||||
return msg
|
||||
return msg.String()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-20
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/klauspost/compress/zstd"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/images"
|
||||
"github.com/containerd/containerd/v2/core/remotes"
|
||||
@@ -60,6 +61,7 @@ func (p *bufferPool) Get() *bytes.Buffer {
|
||||
}
|
||||
|
||||
func (p *bufferPool) Put(buffer *bytes.Buffer) {
|
||||
buffer.Reset()
|
||||
p.pool.Put(buffer)
|
||||
}
|
||||
|
||||
@@ -347,7 +349,7 @@ func (r dockerFetcher) createGetReq(ctx context.Context, host RegistryHost, last
|
||||
headResp.Body.Close()
|
||||
}
|
||||
if headResp.StatusCode > 299 {
|
||||
return nil, 0, fmt.Errorf("unexpected HEAD status code %v: %s", headReq.String(), headResp.Status)
|
||||
return nil, 0, fmt.Errorf("unexpected HEAD status code %v: %s", headReq.sanitizedURL(), headResp.Status)
|
||||
}
|
||||
|
||||
getReq := r.request(host, http.MethodGet, ps...)
|
||||
@@ -510,9 +512,11 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
|
||||
if numChunks < parallelism {
|
||||
parallelism = numChunks
|
||||
}
|
||||
|
||||
// Prepare channels, buffer pool, and readers/writers for parallel fetching.
|
||||
queue := make(chan int64, parallelism)
|
||||
ctx, cancelCtx := context.WithCancel(ctx)
|
||||
done := ctx.Done()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
readers, writers := make([]io.Reader, numChunks), make([]*pipeWriter, numChunks)
|
||||
bufPool := newbufferPool(chunkSize)
|
||||
for i := range numChunks {
|
||||
@@ -520,21 +524,23 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
|
||||
}
|
||||
// keep reference of the initial body value to ensure it is closed
|
||||
ibody := body
|
||||
go func() {
|
||||
eg.Go(func() error {
|
||||
defer close(queue)
|
||||
for i := range numChunks {
|
||||
select {
|
||||
case queue <- i:
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
if i == 0 {
|
||||
ibody.Close()
|
||||
}
|
||||
return // avoid leaking a goroutine if we exit early.
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
close(queue)
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
|
||||
for range parallelism {
|
||||
go func() {
|
||||
eg.Go(func() error {
|
||||
for i := range queue { // first in first out
|
||||
copy := func() error {
|
||||
var body io.ReadCloser
|
||||
@@ -542,6 +548,7 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
|
||||
body = ibody
|
||||
} else {
|
||||
if err := r.Acquire(ctx, 1); err != nil {
|
||||
_ = writers[i].CloseWithError(err)
|
||||
return err
|
||||
}
|
||||
defer r.Release(1)
|
||||
@@ -550,12 +557,6 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
|
||||
nresp, err := reqClone.doWithRetries(ctx, lastHost, withErrorCheck)
|
||||
if err != nil {
|
||||
_ = writers[i].CloseWithError(err)
|
||||
select {
|
||||
case <-done:
|
||||
return ctx.Err()
|
||||
default:
|
||||
cancelCtx()
|
||||
}
|
||||
return err
|
||||
}
|
||||
body = nresp.Body
|
||||
@@ -564,20 +565,23 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
|
||||
_ = body.Close()
|
||||
_ = writers[i].CloseWithError(err)
|
||||
if err != nil && err != io.EOF {
|
||||
cancelCtx()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if copy() != nil {
|
||||
return
|
||||
if err := copy(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
body = &fnOnClose{
|
||||
BeforeClose: func() {
|
||||
cancelCtx()
|
||||
cancel()
|
||||
if err := eg.Wait(); err != nil {
|
||||
log.G(ctx).WithError(err).Warn("parallel fetch failed")
|
||||
}
|
||||
},
|
||||
ReadCloser: io.NopCloser(io.MultiReader(readers...)),
|
||||
}
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ func selectRepositoryMountCandidate(refspec reference.Spec, sources map[string]s
|
||||
|
||||
n, match := 0, ""
|
||||
components := strings.Split(target, "/")
|
||||
for _, repo := range strings.Split(repoLabel, ",") {
|
||||
for repo := range strings.SplitSeq(repoLabel, ",") {
|
||||
// the target repo is not a candidate
|
||||
if repo == target {
|
||||
continue
|
||||
|
||||
+5
-5
@@ -105,7 +105,7 @@ func (hrs *httpReadSeeker) Close() error {
|
||||
|
||||
func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
if hrs.closed {
|
||||
return 0, fmt.Errorf("Fetcher.Seek: closed: %w", errdefs.ErrUnavailable)
|
||||
return 0, fmt.Errorf("httpReadSeeker.Seek: closed: %w", errdefs.ErrUnavailable)
|
||||
}
|
||||
|
||||
abs := hrs.offset
|
||||
@@ -116,21 +116,21 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
abs += offset
|
||||
case io.SeekEnd:
|
||||
if hrs.size == -1 {
|
||||
return 0, fmt.Errorf("Fetcher.Seek: unknown size, cannot seek from end: %w", errdefs.ErrUnavailable)
|
||||
return 0, fmt.Errorf("httpReadSeeker.Seek: unknown size, cannot seek from end: %w", errdefs.ErrUnavailable)
|
||||
}
|
||||
abs = hrs.size + offset
|
||||
default:
|
||||
return 0, fmt.Errorf("Fetcher.Seek: invalid whence: %w", errdefs.ErrInvalidArgument)
|
||||
return 0, fmt.Errorf("httpReadSeeker.Seek: invalid whence: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
|
||||
if abs < 0 {
|
||||
return 0, fmt.Errorf("Fetcher.Seek: negative offset: %w", errdefs.ErrInvalidArgument)
|
||||
return 0, fmt.Errorf("httpReadSeeker.Seek: negative offset: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
|
||||
if abs != hrs.offset {
|
||||
if hrs.rc != nil {
|
||||
if err := hrs.rc.Close(); err != nil {
|
||||
log.L.WithError(err).Error("Fetcher.Seek: failed to close ReadCloser")
|
||||
log.L.WithError(err).Error("httpReadSeeker.Seek: failed to close ReadCloser")
|
||||
}
|
||||
|
||||
hrs.rc = nil
|
||||
|
||||
+66
-1
@@ -60,6 +60,12 @@ func (p dockerPusher) Writer(ctx context.Context, opts ...content.WriterOpt) (co
|
||||
if wOpts.Ref == "" {
|
||||
return nil, fmt.Errorf("ref must not be empty: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
if wOpts.Desc.Digest == "" {
|
||||
return nil, fmt.Errorf("descriptor digest must not be empty: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
if wOpts.Desc.MediaType == "" {
|
||||
return nil, fmt.Errorf("descriptor media type must not be empty: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
return p.push(ctx, wOpts.Desc, wOpts.Ref, true)
|
||||
}
|
||||
|
||||
@@ -111,9 +117,12 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
|
||||
}
|
||||
|
||||
req := p.request(host, http.MethodHead, existCheck...)
|
||||
if err := req.addNamespace(p.refspec.Hostname()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.header.Set("Accept", strings.Join([]string{desc.MediaType, `*/*`}, ", "))
|
||||
|
||||
log.G(ctx).WithField("url", req.String()).Debugf("checking and pushing to")
|
||||
log.G(ctx).WithField("url", req.sanitizedURL()).Debugf("checking and pushing to")
|
||||
|
||||
resp, err := req.doWithRetries(ctx, true)
|
||||
if err != nil {
|
||||
@@ -149,6 +158,18 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
|
||||
}
|
||||
} else if resp.StatusCode != http.StatusNotFound {
|
||||
err := unexpectedResponseErr(resp)
|
||||
// A HEAD 403 carries no body, so issue a follow-up GET to the
|
||||
// same URL to surface the registry's error details for diagnostics.
|
||||
if resp.StatusCode == http.StatusForbidden && req.method == http.MethodHead {
|
||||
err = withGETErrorBody(ctx, err, resp, func() (*http.Response, error) {
|
||||
getReq := p.request(host, http.MethodGet, existCheck...)
|
||||
getReq.header.Set("Accept", strings.Join([]string{desc.MediaType, `*/*`}, ", "))
|
||||
if addErr := getReq.addNamespace(p.refspec.Hostname()); addErr != nil {
|
||||
return nil, addErr
|
||||
}
|
||||
return getReq.doWithRetries(ctx, false)
|
||||
})
|
||||
}
|
||||
log.G(ctx).WithError(err).Debug("unexpected response")
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
@@ -159,10 +180,16 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
|
||||
if isManifest {
|
||||
putPath := getManifestPath(p.object, desc.Digest)
|
||||
req = p.request(host, http.MethodPut, putPath...)
|
||||
if err := req.addNamespace(p.refspec.Hostname()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.header.Add("Content-Type", desc.MediaType)
|
||||
} else {
|
||||
// Start upload request
|
||||
req = p.request(host, http.MethodPost, "blobs", "uploads/")
|
||||
if err := req.addNamespace(p.refspec.Hostname()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mountedFrom := ""
|
||||
var resp *http.Response
|
||||
@@ -267,6 +294,9 @@ func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref str
|
||||
req = p.request(lhost, http.MethodPut)
|
||||
req.header.Set("Content-Type", "application/octet-stream")
|
||||
req.path = lurl.Path + "?" + q.Encode()
|
||||
if err := req.addNamespace(p.refspec.Hostname()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
p.tracker.SetStatus(ref, Status{
|
||||
Status: content.Status{
|
||||
@@ -556,6 +586,41 @@ func (pw *pushWriter) Truncate(size int64) error {
|
||||
return errors.New("cannot truncate remote upload")
|
||||
}
|
||||
|
||||
// withGETErrorBody enriches originalErr, produced from a bodyless HEAD
|
||||
// response, with the error body from a follow-up GET to the same URL. HEAD
|
||||
// responses carry no body, so a 403 only surfaces its status code; a GET
|
||||
// returns the registry's error details (e.g. "key vault access denied", IP
|
||||
// restrictions) that explain the failure.
|
||||
//
|
||||
// The GET body is only used when the GET also returns 403, so the enriched
|
||||
// error's status and body stay consistent. In that case the original HEAD
|
||||
// request's method and status are preserved and only the body is borrowed from
|
||||
// the GET; any other outcome (GET failed, or returned a different status)
|
||||
// leaves originalErr untouched.
|
||||
func withGETErrorBody(ctx context.Context, originalErr error, headResp *http.Response, doGET func() (*http.Response, error)) error {
|
||||
getResp, err := doGET()
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Debug("failed to retrieve error body with GET fallback")
|
||||
return originalErr
|
||||
}
|
||||
defer getResp.Body.Close()
|
||||
|
||||
if getResp.StatusCode != http.StatusForbidden {
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"head_status": headResp.Status,
|
||||
"get_status": getResp.Status,
|
||||
}).Debug("ignoring GET fallback response with different status")
|
||||
return originalErr
|
||||
}
|
||||
|
||||
// Preserve the original HEAD request's method and status and borrow only
|
||||
// the body from the GET, so the error still reflects the request the caller
|
||||
// actually made.
|
||||
enriched := *headResp
|
||||
enriched.Body = getResp.Body
|
||||
return unexpectedResponseErr(&enriched)
|
||||
}
|
||||
|
||||
func requestWithMountFrom(req *request, mount, from string) *request {
|
||||
creq := *req
|
||||
|
||||
|
||||
+1
@@ -264,5 +264,6 @@ func DefaultHTTPTransport(defaultTLSConfig *tls.Config) *http.Transport {
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: defaultTLSConfig,
|
||||
ExpectContinueTimeout: 5 * time.Second,
|
||||
ResponseHeaderTimeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
+135
-18
@@ -29,6 +29,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
@@ -292,9 +293,15 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||
}
|
||||
|
||||
for _, u := range paths {
|
||||
// falling back to /blobs endpoint should happen in extreme cases - those to
|
||||
// support legacy registries. we want to limit the fallback to when /manifests endpoint
|
||||
// returned 404. Falling back on transient errors could do more harm, like polluting
|
||||
// the local content store with incorrectly typed descriptors as /blobs endpoint tends
|
||||
// always return with application/octet-stream.
|
||||
if firstErrPriority > 2 {
|
||||
break
|
||||
}
|
||||
for i, host := range hosts {
|
||||
ctx := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host))
|
||||
|
||||
req := base.request(host, http.MethodHead, u...)
|
||||
if err := req.addNamespace(base.refspec.Hostname()); err != nil {
|
||||
return "", ocispec.Descriptor{}, err
|
||||
@@ -304,6 +311,11 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||
req.header[key] = append(req.header[key], value...)
|
||||
}
|
||||
|
||||
ctx := log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
||||
"host": req.host.Host,
|
||||
"method": req.method,
|
||||
"url": req.sanitizedURL(),
|
||||
}))
|
||||
log.G(ctx).Debug("resolving")
|
||||
resp, err := req.doWithRetries(ctx, i == len(hosts)-1)
|
||||
if err != nil {
|
||||
@@ -329,8 +341,25 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode > 399 {
|
||||
err := unexpectedResponseErr(resp)
|
||||
// A HEAD 403 carries no body, so issue a follow-up GET to
|
||||
// the same URL to surface the registry's error details
|
||||
// (e.g. "key vault access denied", IP restrictions) for
|
||||
// diagnostics.
|
||||
if resp.StatusCode == http.StatusForbidden && req.method == http.MethodHead {
|
||||
err = withGETErrorBody(ctx, err, resp, func() (*http.Response, error) {
|
||||
getReq := base.request(host, http.MethodGet, u...)
|
||||
if addErr := getReq.addNamespace(base.refspec.Hostname()); addErr != nil {
|
||||
return nil, addErr
|
||||
}
|
||||
for key, value := range r.resolveHeader {
|
||||
getReq.header[key] = append(getReq.header[key], value...)
|
||||
}
|
||||
return getReq.doWithRetries(ctx, false)
|
||||
})
|
||||
}
|
||||
if firstErrPriority < 3 {
|
||||
firstErr = unexpectedResponseErr(resp)
|
||||
firstErr = err
|
||||
firstErrPriority = 3
|
||||
}
|
||||
log.G(ctx).Infof("%s after status: %s", nextHostOrFail(i), resp.Status)
|
||||
@@ -374,6 +403,12 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||
return "", ocispec.Descriptor{}, err
|
||||
}
|
||||
|
||||
// Check for error status code
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
defer resp.Body.Close()
|
||||
return "", ocispec.Descriptor{}, unexpectedResponseErr(resp)
|
||||
}
|
||||
|
||||
bodyReader := countingReader{reader: resp.Body}
|
||||
|
||||
contentType = getManifestMediaType(resp)
|
||||
@@ -571,11 +606,13 @@ func (r *request) addQuery(key, value string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
const namespaceQueryArg = "ns"
|
||||
|
||||
func (r *request) addNamespace(ns string) error {
|
||||
if !r.host.isProxy(ns) {
|
||||
return nil
|
||||
}
|
||||
return r.addQuery("ns", ns)
|
||||
return r.addQuery(namespaceQueryArg, ns)
|
||||
}
|
||||
|
||||
type request struct {
|
||||
@@ -594,8 +631,7 @@ func (r *request) clone() *request {
|
||||
}
|
||||
|
||||
func (r *request) do(ctx context.Context) (*http.Response, error) {
|
||||
u := r.host.Scheme + "://" + r.host.Host + r.path
|
||||
req, err := http.NewRequestWithContext(ctx, r.method, u, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, r.method, r.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -616,7 +652,7 @@ func (r *request) do(ctx context.Context) (*http.Response, error) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", u))
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", r.sanitizedURL()))
|
||||
log.G(ctx).WithFields(requestFields(req)).Debug("do request")
|
||||
if err := r.authorize(ctx, req); err != nil {
|
||||
return nil, fmt.Errorf("failed to authorize: %w", err)
|
||||
@@ -653,7 +689,7 @@ type doChecks func(r *request, resp *http.Response) error
|
||||
func withErrorCheck(r *request, resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("content at %v not found: %w", r.String(), errdefs.ErrNotFound)
|
||||
return fmt.Errorf("content at %v not found: %w", r.sanitizedURL(), errdefs.ErrNotFound)
|
||||
}
|
||||
|
||||
return unexpectedResponseErr(resp)
|
||||
@@ -694,8 +730,11 @@ func withOffsetCheck(offset, parallelism int64) doChecks {
|
||||
}
|
||||
}
|
||||
|
||||
const maxAttempts = 5
|
||||
|
||||
func (r *request) doWithRetries(ctx context.Context, lastHost bool, checks ...doChecks) (resp *http.Response, err error) {
|
||||
resp, err = r.doWithRetriesInner(ctx, nil, lastHost)
|
||||
attempts := maxAttempts
|
||||
resp, err = r.doWithRetriesInner(ctx, nil, &attempts, lastHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -713,8 +752,8 @@ func (r *request) doWithRetries(ctx context.Context, lastHost bool, checks ...do
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (r *request) doWithRetriesInner(ctx context.Context, responses []*http.Response, lastHost bool) (*http.Response, error) {
|
||||
resp, err := r.do(ctx)
|
||||
func (r *request) doWithRetriesInner(ctx context.Context, responses []*http.Response, attempts *int, lastHost bool) (*http.Response, error) {
|
||||
resp, err := r.doWithTransportRetries(ctx, attempts, lastHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -725,17 +764,61 @@ func (r *request) doWithRetriesInner(ctx context.Context, responses []*http.Resp
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
if retry {
|
||||
if retry && *attempts > 0 {
|
||||
resp.Body.Close()
|
||||
return r.doWithRetriesInner(ctx, responses, lastHost)
|
||||
return r.doWithRetriesInner(ctx, responses, attempts, lastHost)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (r *request) retryRequest(ctx context.Context, responses []*http.Response, lastHost bool) (bool, error) {
|
||||
if len(responses) > 5 {
|
||||
return false, nil
|
||||
// doWithTransportRetries calls r.do, retrying on transient transport errors
|
||||
// (e.g. response header timeouts). Retries are only attempted on the last host
|
||||
// to match the response-status retry policy for 5xx errors and preserve mirror
|
||||
// fallback semantics. Context cancellation stops retries immediately.
|
||||
func (r *request) doWithTransportRetries(ctx context.Context, attempts *int, lastHost bool) (*http.Response, error) {
|
||||
for *attempts > 0 {
|
||||
resp, err := r.do(ctx)
|
||||
*attempts--
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !lastHost || !isTransientTransportErr(err) {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if *attempts == 0 {
|
||||
return nil, err
|
||||
}
|
||||
log.G(ctx).WithError(err).WithField("attempt", maxAttempts-*attempts).Debug("transient transport error, retrying")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// isTransientTransportErr reports whether err is a transport-level error worth
|
||||
// retrying. context.Canceled and context.DeadlineExceeded are not considered
|
||||
// transient.
|
||||
func isTransientTransportErr(err error) bool {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return false
|
||||
}
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return true
|
||||
}
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *request) retryRequest(ctx context.Context, responses []*http.Response, lastHost bool) (bool, error) {
|
||||
last := responses[len(responses)-1]
|
||||
switch last.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
@@ -776,6 +859,40 @@ func (r *request) String() string {
|
||||
return r.host.Scheme + "://" + r.host.Host + r.path
|
||||
}
|
||||
|
||||
// sanitizedURL returns the request URL with query parameters and auth (if any)
|
||||
// sanitized. It is intended for errors and logging, and similar to [internal/cri/util.sanitizeURL].
|
||||
//
|
||||
// [internal/cri/util.sanitizeURL]: https://github.com/containerd/containerd/blob/v2.2.1/internal/cri/util/sanitize.go#L53-L75
|
||||
func (r *request) sanitizedURL() string {
|
||||
rawURL := r.String()
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
// URL parsing failed; return original (malformed URLs shouldn't leak tokens)
|
||||
return rawURL
|
||||
}
|
||||
|
||||
if parsed.RawQuery == "" {
|
||||
// Fast path: no query arguments to sanitize.
|
||||
return parsed.Redacted()
|
||||
}
|
||||
|
||||
query := parsed.Query()
|
||||
for k := range query {
|
||||
if k == namespaceQueryArg {
|
||||
// preserve namespace query arguments
|
||||
continue
|
||||
}
|
||||
for i := range query[k] {
|
||||
if query[k][i] != "" {
|
||||
query[k][i] = "REDACTED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsed.RawQuery = query.Encode()
|
||||
return parsed.Redacted()
|
||||
}
|
||||
|
||||
func (r *request) setMediaType(mediatype string) {
|
||||
if mediatype == "" {
|
||||
r.header.Set("Accept", "*/*")
|
||||
@@ -789,7 +906,7 @@ func (r *request) setOffset(offset int64) {
|
||||
}
|
||||
|
||||
func requestFields(req *http.Request) log.Fields {
|
||||
fields := map[string]interface{}{
|
||||
fields := map[string]any{
|
||||
"request.method": req.Method,
|
||||
}
|
||||
for k, vals := range req.Header {
|
||||
@@ -810,7 +927,7 @@ func requestFields(req *http.Request) log.Fields {
|
||||
}
|
||||
|
||||
func responseFields(resp *http.Response) log.Fields {
|
||||
fields := map[string]interface{}{
|
||||
fields := map[string]any{
|
||||
"response.status": resp.Status,
|
||||
}
|
||||
for k, vals := range resp.Header {
|
||||
|
||||
+26
-4
@@ -55,9 +55,27 @@ func WithMediaTypeKeyPrefix(ctx context.Context, mediaType, prefix string) conte
|
||||
return context.WithValue(ctx, refKeyPrefix{}, values)
|
||||
}
|
||||
|
||||
// MakeRefKey returns a unique reference for the descriptor. This reference can be
|
||||
// used to lookup ongoing processes related to the descriptor. This function
|
||||
// may look to the context to namespace the reference appropriately.
|
||||
// MakeRefKey returns a stable ingest reference for desc.
|
||||
//
|
||||
// The returned key is used as a content-store reference to correlate ongoing
|
||||
// fetch and push operations for the same descriptor. The key is derived from
|
||||
// the descriptor digest and, when present, the
|
||||
// [ocispec.AnnotationRefName] annotation.
|
||||
//
|
||||
// By default, the key is prefixed according to the descriptor media type:
|
||||
//
|
||||
// - "manifest-" for manifest media types recognized by [images.IsManifestType]
|
||||
// - "index-" for index media types recognized by [images.IsIndexType]
|
||||
// - "layer-" for layer media types recognized by [images.IsLayerType]
|
||||
// - "config-" for config media types recognized by [images.IsKnownConfig]
|
||||
// - "attestation-" for attestation media types recognized by [images.IsAttestationType]
|
||||
//
|
||||
// Additional exact media type mappings may be provided through
|
||||
// [WithMediaTypeKeyPrefix]. A context-provided mapping takes precedence over the
|
||||
// built-in classification.
|
||||
//
|
||||
// If the media type is not recognized and no context override exists,
|
||||
// MakeRefKey falls back to the "unknown-" prefix.
|
||||
func MakeRefKey(ctx context.Context, desc ocispec.Descriptor) string {
|
||||
key := desc.Digest.String()
|
||||
if desc.Annotations != nil {
|
||||
@@ -85,7 +103,11 @@ func MakeRefKey(ctx context.Context, desc ocispec.Descriptor) string {
|
||||
case images.IsAttestationType(desc.MediaType):
|
||||
return "attestation-" + key
|
||||
default:
|
||||
log.G(ctx).Warnf("reference for unknown type: %s", desc.MediaType)
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"digest": desc.Digest,
|
||||
"mediatype": desc.MediaType,
|
||||
"artifactType": desc.ArtifactType,
|
||||
}).Debug("using generic reference key prefix for unclassified descriptor")
|
||||
return "unknown-" + key
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
type Transferrer interface {
|
||||
Transfer(ctx context.Context, source interface{}, destination interface{}, opts ...Opt) error
|
||||
Transfer(ctx context.Context, source any, destination any, opts ...Opt) error
|
||||
}
|
||||
|
||||
type ImageResolver interface {
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -61,7 +61,7 @@ var (
|
||||
|
||||
var (
|
||||
bufioReader32KPool = &sync.Pool{
|
||||
New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) },
|
||||
New: func() any { return bufio.NewReaderSize(nil, 32*1024) },
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -281,7 +281,7 @@ func (pe parseError) Error() string {
|
||||
return fmt.Sprintf("[%s]: %v", pe.input, pe.msg)
|
||||
}
|
||||
|
||||
func (p *parser) mkerr(pos int, format string, args ...interface{}) error {
|
||||
func (p *parser) mkerr(pos int, format string, args ...any) error {
|
||||
return fmt.Errorf("parse error: %w", parseError{
|
||||
input: p.input,
|
||||
pos: pos,
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string,
|
||||
err = errQuoteSyntax
|
||||
return
|
||||
}
|
||||
for j := 0; j < 2; j++ { // one digit already; two more
|
||||
for j := range 2 { // one digit already; two more
|
||||
x := rune(s[j]) - '0'
|
||||
if x < 0 || x > 7 {
|
||||
err = errQuoteSyntax
|
||||
|
||||
Generated
Vendored
+17
-20
@@ -14,28 +14,25 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"context"
|
||||
|
||||
"github.com/containerd/containerd/v2/pkg/namespaces"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var Compare = cmp.FilterValues(
|
||||
func(x, y interface{}) bool {
|
||||
_, xok := x.(proto.Message)
|
||||
_, yok := y.(proto.Message)
|
||||
return xok && yok
|
||||
},
|
||||
cmp.Comparer(func(x, y interface{}) bool {
|
||||
vx, ok := x.(proto.Message)
|
||||
if !ok {
|
||||
return false
|
||||
// WithNamespace adds containerd namespace attribute to spans when available.
|
||||
// It is best-effort: if namespace is not present in the context, it does nothing.
|
||||
func WithNamespace(ctx context.Context) SpanOpt {
|
||||
return func(config *StartConfig) {
|
||||
ns, err := namespaces.NamespaceRequired(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
vy, ok := y.(proto.Message)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return proto.Equal(vx, vy)
|
||||
}),
|
||||
)
|
||||
config.spanOpts = append(config.spanOpts,
|
||||
trace.WithAttributes(Attribute("namespace", ns)),
|
||||
)
|
||||
}
|
||||
}
|
||||
+26
-4
@@ -35,9 +35,21 @@ var allLevels = []log.Level{
|
||||
log.TraceLevel,
|
||||
}
|
||||
|
||||
type HookOpt func(*LogrusHook)
|
||||
|
||||
// NewLogrusHook creates a new logrus hook
|
||||
func NewLogrusHook() *LogrusHook {
|
||||
return &LogrusHook{}
|
||||
func NewLogrusHook(opts ...HookOpt) *LogrusHook {
|
||||
hook := &LogrusHook{}
|
||||
for _, opt := range opts {
|
||||
opt(hook)
|
||||
}
|
||||
return hook
|
||||
}
|
||||
|
||||
func WithTraceIDField(enabled bool) HookOpt {
|
||||
return func(h *LogrusHook) {
|
||||
h.enableTraceIDField = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// LogrusHook is a [logrus.Hook] which adds logrus events to active spans.
|
||||
@@ -45,7 +57,9 @@ func NewLogrusHook() *LogrusHook {
|
||||
// is a no-op.
|
||||
//
|
||||
// [logrus.Hook]: https://github.com/sirupsen/logrus/blob/v1.9.3/hooks.go#L3-L11
|
||||
type LogrusHook struct{}
|
||||
type LogrusHook struct {
|
||||
enableTraceIDField bool
|
||||
}
|
||||
|
||||
// Levels returns the logrus levels that this hook is interested in.
|
||||
func (h *LogrusHook) Levels() []log.Level {
|
||||
@@ -59,7 +73,15 @@ func (h *LogrusHook) Fire(entry *log.Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !span.IsRecording() || !span.SpanContext().IsValid() {
|
||||
if !span.SpanContext().IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if h.enableTraceIDField {
|
||||
entry.Data["trace_id"] = span.SpanContext().TraceID().String()
|
||||
}
|
||||
|
||||
if !span.IsRecording() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ type StartConfig struct {
|
||||
type SpanOpt func(config *StartConfig)
|
||||
|
||||
// WithAttribute appends attributes to a new created span.
|
||||
func WithAttribute(k string, v interface{}) SpanOpt {
|
||||
func WithAttribute(k string, v any) SpanOpt {
|
||||
return func(config *StartConfig) {
|
||||
config.spanOpts = append(config.spanOpts,
|
||||
trace.WithAttributes(Attribute(k, v)))
|
||||
|
||||
+22
-5
@@ -18,8 +18,10 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -39,7 +41,7 @@ import (
|
||||
)
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buffer := make([]byte, 1<<20)
|
||||
return &buffer
|
||||
},
|
||||
@@ -84,8 +86,18 @@ func NewStore(root string) (content.Store, error) {
|
||||
// require labels and should use `NewStore`. `NewLabeledStore` is primarily
|
||||
// useful for tests or standalone implementations.
|
||||
func NewLabeledStore(root string, ls LabelStore) (content.Store, error) {
|
||||
supported, _ := fsverity.IsSupported(root)
|
||||
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, fmt.Errorf("failed to stat %q: %w", root, err)
|
||||
}
|
||||
if err := os.MkdirAll(root, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to mkdir %q: %w", root, err)
|
||||
}
|
||||
}
|
||||
supported, err := fsverity.IsSupported(root)
|
||||
if err != nil {
|
||||
log.L.WithError(err).WithField("path", root).Warnf("failed check for fsverity support")
|
||||
}
|
||||
s := &store{
|
||||
root: root,
|
||||
ls: ls,
|
||||
@@ -533,8 +545,13 @@ func (s *store) writer(ctx context.Context, ref string, total int64, expected di
|
||||
|
||||
path, refp, data := s.ingestPaths(ref)
|
||||
|
||||
// if we get passed an expected digest, we need to use the same algorithm (sha512, etc)
|
||||
digestAlg := digest.Canonical
|
||||
if expected != "" && expected.Algorithm().Available() {
|
||||
digestAlg = expected.Algorithm()
|
||||
}
|
||||
var (
|
||||
digester = digest.Canonical.Digester()
|
||||
digester = digestAlg.Digester()
|
||||
offset int64
|
||||
startedAt time.Time
|
||||
updatedAt time.Time
|
||||
@@ -582,7 +599,7 @@ func (s *store) writer(ctx context.Context, ref string, total int64, expected di
|
||||
}
|
||||
|
||||
if total > 0 {
|
||||
if err := os.WriteFile(filepath.Join(path, "total"), []byte(fmt.Sprint(total)), 0666); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(path, "total"), fmt.Append(nil, total), 0666); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -113,6 +113,24 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
|
||||
}
|
||||
|
||||
dgst := w.digester.Digest()
|
||||
if expected != "" && expected.Algorithm() != dgst.Algorithm() && expected.Algorithm().Available() {
|
||||
// Writer was opened without a descriptor specifying the digest algorithm (but we got a non-canonical one here in commit), so we have to re-hash our now completed and closed content to compare
|
||||
start := time.Now()
|
||||
f, err := os.Open(filepath.Join(w.path, "data"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open ingest data for re-hashing to %s: %w", expected.Algorithm().String(), err)
|
||||
}
|
||||
w.digester = expected.Algorithm().Digester()
|
||||
_, err = io.Copy(w.digester.Hash(), f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to re-hash ingest data to %s: %w", expected.Algorithm().String(), err)
|
||||
}
|
||||
dgst = w.digester.Digest()
|
||||
if duration := time.Since(start); duration > 250*time.Millisecond {
|
||||
log.G(ctx).WithField("digest", dgst).WithField("duration", duration).Warnf("commit for blob required expensive re-hash")
|
||||
}
|
||||
}
|
||||
if expected != "" && expected != dgst {
|
||||
return fmt.Errorf("unexpected commit digest %s, expected %s: %w", dgst, expected, errdefs.ErrFailedPrecondition)
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -44,7 +44,7 @@ type service struct {
|
||||
var (
|
||||
empty = &ptypes.Empty{}
|
||||
bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buffer := make([]byte, 1<<20)
|
||||
return &buffer
|
||||
},
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ var (
|
||||
Package = "github.com/containerd/containerd/v2"
|
||||
|
||||
// Version holds the complete version number. Filled in at linking time.
|
||||
Version = "2.2.5+unknown"
|
||||
Version = "2.3.3+unknown"
|
||||
|
||||
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||
// the program at linking time.
|
||||
@@ -38,4 +38,4 @@ var (
|
||||
// This version is used by the main configuration as well as all plugins.
|
||||
// Any configuration less than this version which has structural changes
|
||||
// should migrate the configuration structures used by this version.
|
||||
const ConfigVersion = 3
|
||||
const ConfigVersion = 4
|
||||
|
||||
+10
-27
@@ -24,7 +24,7 @@ WHALE = "🇩"
|
||||
ONI = "👹"
|
||||
|
||||
# Project binaries.
|
||||
COMMANDS=protoc-gen-go-ttrpc protoc-gen-gogottrpc
|
||||
COMMANDS=protoc-gen-go-ttrpc
|
||||
|
||||
ifdef BUILDTAGS
|
||||
GO_BUILDTAGS = ${BUILDTAGS}
|
||||
@@ -57,7 +57,7 @@ TESTFLAGS_PARALLEL ?= 8
|
||||
# Use this to replace `go test` with, for instance, `gotestsum`
|
||||
GOTEST ?= $(GO) test
|
||||
|
||||
.PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor install-protobuf install-protobuild
|
||||
.PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor maintainer-clean
|
||||
.DEFAULT: default
|
||||
|
||||
# Forcibly set the default goal to all, in case an include above brought in a rule definition.
|
||||
@@ -65,7 +65,7 @@ GOTEST ?= $(GO) test
|
||||
|
||||
all: binaries
|
||||
|
||||
check: proto-fmt ## run all linters
|
||||
check: ## run all linters
|
||||
@echo "$(WHALE) $@"
|
||||
GOGC=75 golangci-lint run
|
||||
|
||||
@@ -78,9 +78,10 @@ generate: protos
|
||||
@echo "$(WHALE) $@"
|
||||
@PATH="${ROOTDIR}/bin:${PATH}" $(GO) generate -x ${PACKAGES}
|
||||
|
||||
protos: bin/protoc-gen-gogottrpc bin/protoc-gen-go-ttrpc ## generate protobuf
|
||||
protos: bin/protoc-gen-go-ttrpc ## generate protobuf
|
||||
@echo "$(WHALE) $@"
|
||||
@(PATH="${ROOTDIR}/bin:${PATH}" protobuild --quiet ${PACKAGES})
|
||||
(cd example && buf generate)
|
||||
buf generate
|
||||
|
||||
check-protos: protos ## check if protobufs needs to be generated again
|
||||
@echo "$(WHALE) $@"
|
||||
@@ -88,19 +89,6 @@ check-protos: protos ## check if protobufs needs to be generated again
|
||||
((git diff | cat) && \
|
||||
(echo "$(ONI) please run 'make protos' when making changes to proto files" && false))
|
||||
|
||||
check-api-descriptors: protos ## check that protobuf changes aren't present.
|
||||
@echo "$(WHALE) $@"
|
||||
@test -z "$$(git status --short | grep ".pb.txt" | tee /dev/stderr)" || \
|
||||
((git diff $$(find . -name '*.pb.txt') | cat) && \
|
||||
(echo "$(ONI) please run 'make protos' when making changes to proto files and check-in the generated descriptor file changes" && false))
|
||||
|
||||
proto-fmt: ## check format of proto files
|
||||
@echo "$(WHALE) $@"
|
||||
@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \
|
||||
(echo "$(ONI) please indent proto files with tabs only" && false)
|
||||
@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \
|
||||
(echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false)
|
||||
|
||||
build: ## build the go packages
|
||||
@echo "$(WHALE) $@"
|
||||
@$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${PACKAGES}
|
||||
@@ -117,6 +105,10 @@ benchmark: ## run benchmarks tests
|
||||
@echo "$(WHALE) $@"
|
||||
@$(GO) test ${TESTFLAGS} -bench . -run Benchmark
|
||||
|
||||
maintainer-clean:
|
||||
@echo "$(WHALE) $@"
|
||||
@find . -name '*.pb.go' -delete
|
||||
|
||||
FORCE:
|
||||
|
||||
define BUILD_BINARY
|
||||
@@ -139,15 +131,6 @@ install: ## install binaries
|
||||
@echo "$(WHALE) $@ $(BINPACKAGES)"
|
||||
@$(GO) install $(BINPACKAGES)
|
||||
|
||||
install-protobuf:
|
||||
@echo "$(WHALE) $@"
|
||||
@script/install-protobuf
|
||||
|
||||
install-protobuild:
|
||||
@echo "$(WHALE) $@"
|
||||
@$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
|
||||
@$(GO) install github.com/containerd/protobuild@14832ccc41429f5c4f81028e5af08aa233a219cf
|
||||
|
||||
coverage: ## generate coverprofiles from the unit tests, except tests that require root
|
||||
@echo "$(WHALE) $@"
|
||||
@rm -f coverage.txt
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
version = "2"
|
||||
generators = ["go"]
|
||||
|
||||
# Control protoc include paths. Below are usually some good defaults, but feel
|
||||
# free to try it without them if it works for your project.
|
||||
[includes]
|
||||
# Include paths that will be added before all others. Typically, you want to
|
||||
# treat the root of the project as an include, but this may not be necessary.
|
||||
before = ["."]
|
||||
|
||||
# Paths that will be added untouched to the end of the includes. We use
|
||||
# `/usr/local/include` to pickup the common install location of protobuf.
|
||||
# This is the default.
|
||||
after = ["/usr/local/include"]
|
||||
|
||||
# This section maps protobuf imports to Go packages. These will become
|
||||
# `-M` directives in the call to the go protobuf generator.
|
||||
[packages]
|
||||
"google/protobuf/any.proto" = "github.com/gogo/protobuf/types"
|
||||
"proto/status.proto" = "google.golang.org/genproto/googleapis/rpc/status"
|
||||
|
||||
[[overrides]]
|
||||
# enable ttrpc and disable fieldpath and grpc for the shim
|
||||
prefixes = ["github.com/containerd/ttrpc/integration/streaming"]
|
||||
generators = ["go", "go-ttrpc"]
|
||||
|
||||
[overrides.parameters.go-ttrpc]
|
||||
prefix = "TTRPC"
|
||||
-4
@@ -25,10 +25,6 @@ See the [protocol specification](./PROTOCOL.md).
|
||||
|
||||
# Usage
|
||||
|
||||
Create a gogo vanity binary (see
|
||||
[`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an
|
||||
example with the ttrpc plugin enabled.
|
||||
|
||||
It's recommended to use [`protobuild`](https://github.com/containerd/protobuild)
|
||||
to build the protobufs for this project, but this will work with protoc
|
||||
directly, if required.
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
version: v2
|
||||
plugins:
|
||||
- remote: buf.build/protocolbuffers/go:v1.28.1
|
||||
out: .
|
||||
opt:
|
||||
- paths=source_relative
|
||||
- local: bin/protoc-gen-go-ttrpc
|
||||
out: .
|
||||
opt:
|
||||
- paths=source_relative
|
||||
- prefix=TTRPC
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Generated by buf. DO NOT EDIT.
|
||||
version: v2
|
||||
deps:
|
||||
- name: buf.build/googleapis/googleapis
|
||||
commit: 004180b77378443887d3b55cabc00384
|
||||
digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
version: v2
|
||||
deps:
|
||||
# For google/rpc/status.proto
|
||||
- buf.build/googleapis/googleapis:004180b77378443887d3b55cabc00384
|
||||
modules:
|
||||
- path: .
|
||||
# example/ has own buf.yaml and buf.gen.yaml to not have
|
||||
# TTRPC prefix.
|
||||
excludes:
|
||||
- example
|
||||
+4
-4
@@ -386,7 +386,7 @@ func (c *Client) receiveLoop() error {
|
||||
|
||||
// createStream creates a new stream and registers it with the client
|
||||
// Introduce stream types for multiple or single response
|
||||
func (c *Client) createStream(flags uint8, b []byte) (*stream, error) {
|
||||
func (c *Client) createStream(flags uint8, b []byte, recvBuf int) (*stream, error) {
|
||||
// sendLock must be held across both allocation of the stream ID and sending it across the wire.
|
||||
// This ensures that new stream IDs sent on the wire are always increasing, which is a
|
||||
// requirement of the TTRPC protocol.
|
||||
@@ -417,7 +417,7 @@ func (c *Client) createStream(flags uint8, b []byte) (*stream, error) {
|
||||
default:
|
||||
}
|
||||
|
||||
s = newStream(c.nextStreamID, c)
|
||||
s = newStream(c.nextStreamID, c, recvBuf)
|
||||
c.streams[s.id] = s
|
||||
c.nextStreamID = c.nextStreamID + 2
|
||||
|
||||
@@ -517,7 +517,7 @@ func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, metho
|
||||
} else {
|
||||
flags = flagRemoteClosed
|
||||
}
|
||||
s, err := c.createStream(flags, p)
|
||||
s, err := c.createStream(flags, p, streamRecvBufferSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -536,7 +536,7 @@ func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) err
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := c.createStream(0, p)
|
||||
s, err := c.createStream(0, p, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+6
@@ -36,6 +36,12 @@ var (
|
||||
|
||||
// ErrStreamClosed is when the streaming connection is closed.
|
||||
ErrStreamClosed = errors.New("ttrpc: stream closed")
|
||||
|
||||
// ErrStreamFull is returned when a stream's receive buffer is full
|
||||
// and the message cannot be delivered without blocking the
|
||||
// connection's receive loop. This prevents a single unconsumed
|
||||
// stream from deadlocking all other streams on the same connection.
|
||||
ErrStreamFull = errors.New("ttrpc: stream buffer full")
|
||||
)
|
||||
|
||||
// OversizedMessageErr is used to indicate refusal to send an oversized message.
|
||||
|
||||
+69
-70
@@ -1,8 +1,8 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.20.1
|
||||
// source: github.com/containerd/ttrpc/request.proto
|
||||
// protoc (unknown)
|
||||
// source: request.proto
|
||||
|
||||
package ttrpc
|
||||
|
||||
@@ -36,7 +36,7 @@ type Request struct {
|
||||
func (x *Request) Reset() {
|
||||
*x = Request{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0]
|
||||
mi := &file_request_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (x *Request) String() string {
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0]
|
||||
mi := &file_request_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -62,7 +62,7 @@ func (x *Request) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{0}
|
||||
return file_request_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Request) GetService() string {
|
||||
@@ -112,7 +112,7 @@ type Response struct {
|
||||
func (x *Response) Reset() {
|
||||
*x = Response{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1]
|
||||
mi := &file_request_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func (x *Response) String() string {
|
||||
func (*Response) ProtoMessage() {}
|
||||
|
||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1]
|
||||
mi := &file_request_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -138,7 +138,7 @@ func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{1}
|
||||
return file_request_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Response) GetStatus() *status.Status {
|
||||
@@ -166,7 +166,7 @@ type StringList struct {
|
||||
func (x *StringList) Reset() {
|
||||
*x = StringList{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2]
|
||||
mi := &file_request_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (x *StringList) String() string {
|
||||
func (*StringList) ProtoMessage() {}
|
||||
|
||||
func (x *StringList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2]
|
||||
mi := &file_request_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -192,7 +192,7 @@ func (x *StringList) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StringList.ProtoReflect.Descriptor instead.
|
||||
func (*StringList) Descriptor() ([]byte, []int) {
|
||||
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{2}
|
||||
return file_request_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *StringList) GetList() []string {
|
||||
@@ -214,7 +214,7 @@ type KeyValue struct {
|
||||
func (x *KeyValue) Reset() {
|
||||
*x = KeyValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3]
|
||||
mi := &file_request_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func (x *KeyValue) String() string {
|
||||
func (*KeyValue) ProtoMessage() {}
|
||||
|
||||
func (x *KeyValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3]
|
||||
mi := &file_request_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -240,7 +240,7 @@ func (x *KeyValue) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead.
|
||||
func (*KeyValue) Descriptor() ([]byte, []int) {
|
||||
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{3}
|
||||
return file_request_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *KeyValue) GetKey() string {
|
||||
@@ -257,62 +257,61 @@ func (x *KeyValue) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_github_com_containerd_ttrpc_request_proto protoreflect.FileDescriptor
|
||||
var File_request_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_github_com_containerd_ttrpc_request_proto_rawDesc = []byte{
|
||||
0x0a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72,
|
||||
0x70, 0x63, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65,
|
||||
0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e,
|
||||
0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45,
|
||||
0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70,
|
||||
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61,
|
||||
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1d, 0x5a, 0x1b, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
var file_request_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x05, 0x74, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x72,
|
||||
0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07,
|
||||
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f,
|
||||
0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74,
|
||||
0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d,
|
||||
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x50, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b,
|
||||
0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42,
|
||||
0x1d, 0x5a, 0x1b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_github_com_containerd_ttrpc_request_proto_rawDescOnce sync.Once
|
||||
file_github_com_containerd_ttrpc_request_proto_rawDescData = file_github_com_containerd_ttrpc_request_proto_rawDesc
|
||||
file_request_proto_rawDescOnce sync.Once
|
||||
file_request_proto_rawDescData = file_request_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_github_com_containerd_ttrpc_request_proto_rawDescGZIP() []byte {
|
||||
file_github_com_containerd_ttrpc_request_proto_rawDescOnce.Do(func() {
|
||||
file_github_com_containerd_ttrpc_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_request_proto_rawDescData)
|
||||
func file_request_proto_rawDescGZIP() []byte {
|
||||
file_request_proto_rawDescOnce.Do(func() {
|
||||
file_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_request_proto_rawDescData)
|
||||
})
|
||||
return file_github_com_containerd_ttrpc_request_proto_rawDescData
|
||||
return file_request_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_github_com_containerd_ttrpc_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_github_com_containerd_ttrpc_request_proto_goTypes = []interface{}{
|
||||
var file_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_request_proto_goTypes = []interface{}{
|
||||
(*Request)(nil), // 0: ttrpc.Request
|
||||
(*Response)(nil), // 1: ttrpc.Response
|
||||
(*StringList)(nil), // 2: ttrpc.StringList
|
||||
(*KeyValue)(nil), // 3: ttrpc.KeyValue
|
||||
(*status.Status)(nil), // 4: Status
|
||||
(*status.Status)(nil), // 4: google.rpc.Status
|
||||
}
|
||||
var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{
|
||||
var file_request_proto_depIdxs = []int32{
|
||||
3, // 0: ttrpc.Request.metadata:type_name -> ttrpc.KeyValue
|
||||
4, // 1: ttrpc.Response.status:type_name -> Status
|
||||
4, // 1: ttrpc.Response.status:type_name -> google.rpc.Status
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
@@ -320,13 +319,13 @@ var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_github_com_containerd_ttrpc_request_proto_init() }
|
||||
func file_github_com_containerd_ttrpc_request_proto_init() {
|
||||
if File_github_com_containerd_ttrpc_request_proto != nil {
|
||||
func init() { file_request_proto_init() }
|
||||
func file_request_proto_init() {
|
||||
if File_request_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_github_com_containerd_ttrpc_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Request); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -338,7 +337,7 @@ func file_github_com_containerd_ttrpc_request_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_github_com_containerd_ttrpc_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Response); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -350,7 +349,7 @@ func file_github_com_containerd_ttrpc_request_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_github_com_containerd_ttrpc_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StringList); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -362,7 +361,7 @@ func file_github_com_containerd_ttrpc_request_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_github_com_containerd_ttrpc_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*KeyValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -379,18 +378,18 @@ func file_github_com_containerd_ttrpc_request_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_github_com_containerd_ttrpc_request_proto_rawDesc,
|
||||
RawDescriptor: file_request_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_github_com_containerd_ttrpc_request_proto_goTypes,
|
||||
DependencyIndexes: file_github_com_containerd_ttrpc_request_proto_depIdxs,
|
||||
MessageInfos: file_github_com_containerd_ttrpc_request_proto_msgTypes,
|
||||
GoTypes: file_request_proto_goTypes,
|
||||
DependencyIndexes: file_request_proto_depIdxs,
|
||||
MessageInfos: file_request_proto_msgTypes,
|
||||
}.Build()
|
||||
File_github_com_containerd_ttrpc_request_proto = out.File
|
||||
file_github_com_containerd_ttrpc_request_proto_rawDesc = nil
|
||||
file_github_com_containerd_ttrpc_request_proto_goTypes = nil
|
||||
file_github_com_containerd_ttrpc_request_proto_depIdxs = nil
|
||||
File_request_proto = out.File
|
||||
file_request_proto_rawDesc = nil
|
||||
file_request_proto_goTypes = nil
|
||||
file_request_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ syntax = "proto3";
|
||||
|
||||
package ttrpc;
|
||||
|
||||
import "proto/status.proto";
|
||||
import "google/rpc/status.proto";
|
||||
|
||||
option go_package = "github.com/containerd/ttrpc";
|
||||
|
||||
@@ -15,7 +15,7 @@ message Request {
|
||||
}
|
||||
|
||||
message Response {
|
||||
Status status = 1;
|
||||
google.rpc.Status status = 1;
|
||||
bytes payload = 2;
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -568,8 +568,6 @@ func (c *serverConn) run(sctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var noopFunc = func() {}
|
||||
|
||||
func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) {
|
||||
if len(req.Metadata) > 0 {
|
||||
md := MD{}
|
||||
@@ -577,11 +575,10 @@ func getRequestContext(ctx context.Context, req *Request) (retCtx context.Contex
|
||||
ctx = WithMetadata(ctx, md)
|
||||
}
|
||||
|
||||
cancel = noopFunc
|
||||
if req.TimeoutNano == 0 {
|
||||
return ctx, cancel
|
||||
// Cancellable so handlers' deferred cancel propagates to RecvMsg.
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano))
|
||||
return ctx, cancel
|
||||
return context.WithTimeout(ctx, time.Duration(req.TimeoutNano))
|
||||
}
|
||||
|
||||
+23
-1
@@ -23,6 +23,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -128,10 +129,14 @@ func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*sta
|
||||
StreamingClient: stream.StreamingClient,
|
||||
StreamingServer: stream.StreamingServer,
|
||||
}
|
||||
recvBuf := streamRecvBufferSize
|
||||
if !stream.StreamingClient {
|
||||
recvBuf = 1
|
||||
}
|
||||
sh := &streamHandler{
|
||||
ctx: ctx,
|
||||
respond: respond,
|
||||
recv: make(chan Unmarshaler, 5),
|
||||
recv: make(chan Unmarshaler, recvBuf),
|
||||
info: info,
|
||||
}
|
||||
go func() {
|
||||
@@ -158,6 +163,12 @@ func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*sta
|
||||
return nil, status.Errorf(codes.Unimplemented, "method %v", req.Method)
|
||||
}
|
||||
|
||||
// streamRecvBufferSize is the buffer size for stream recv channels. It
|
||||
// should be large enough to absorb normal bursts without hitting the
|
||||
// 1-second timeout fallback in receive/data, but small enough that
|
||||
// per-stream memory overhead stays trivial.
|
||||
const streamRecvBufferSize = 64
|
||||
|
||||
type streamHandler struct {
|
||||
ctx context.Context
|
||||
respond func(*status.Status, []byte, bool, bool) error
|
||||
@@ -184,6 +195,17 @@ func (s *streamHandler) data(unmarshal Unmarshaler) error {
|
||||
return nil
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
default:
|
||||
// If recv channel is full, wait up to a second for an item
|
||||
// to drain and unblock, otherwise return an error.
|
||||
select {
|
||||
case s.recv <- unmarshal:
|
||||
return nil
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
return ErrStreamFull
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-2
@@ -19,6 +19,7 @@ package ttrpc
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type streamID uint32
|
||||
@@ -38,11 +39,11 @@ type stream struct {
|
||||
recvClose chan struct{}
|
||||
}
|
||||
|
||||
func newStream(id streamID, send sender) *stream {
|
||||
func newStream(id streamID, send sender, recvBuf int) *stream {
|
||||
return &stream{
|
||||
id: id,
|
||||
sender: send,
|
||||
recv: make(chan *streamMessage, 1),
|
||||
recv: make(chan *streamMessage, recvBuf),
|
||||
recvClose: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@@ -63,6 +64,11 @@ func (s *stream) send(mt messageType, flags uint8, b []byte) error {
|
||||
return s.sender.send(uint32(s.id), mt, flags, b)
|
||||
}
|
||||
|
||||
// receive delivers a message to this stream from the connection receive loop.
|
||||
// If the stream's recv buffer is full, it waits up to 1 second for the
|
||||
// consumer to make progress. This keeps the receive loop moving for other
|
||||
// streams while still providing backpressure under normal operation. If the
|
||||
// timeout expires the stream is closed with ErrStreamFull.
|
||||
func (s *stream) receive(ctx context.Context, msg *streamMessage) error {
|
||||
select {
|
||||
case <-s.recvClose:
|
||||
@@ -76,6 +82,20 @@ func (s *stream) receive(ctx context.Context, msg *streamMessage) error {
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// If recv channel is full, wait up to a second for an item
|
||||
// to drain and unblock, otherwise close the stream.
|
||||
select {
|
||||
case <-s.recvClose:
|
||||
return s.recvErr
|
||||
case s.recv <- msg:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
s.closeWithError(ErrStreamFull)
|
||||
return ErrStreamFull
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package ttrpc;
|
||||
|
||||
option go_package = "github.com/containerd/ttrpc/internal";
|
||||
|
||||
message TestPayload {
|
||||
string foo = 1;
|
||||
int64 deadline = 2;
|
||||
string metadata = 3;
|
||||
}
|
||||
|
||||
message EchoPayload {
|
||||
int64 seq = 1;
|
||||
string msg = 2;
|
||||
}
|
||||
Reference in New Issue
Block a user