chore: handle RemoveContainer server-side that deletes container from machine db

This commit is contained in:
Pavel Sviderski
2025-03-29 20:06:49 +10:00
parent 615022c9ac
commit 0975cbab66
6 changed files with 126 additions and 23 deletions
+25 -18
View File
@@ -1144,7 +1144,7 @@ var file_internal_machine_api_pb_docker_proto_rawDesc = []byte{
0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x25, 0x0a, 0x0e,
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e,
0x61, 0x6d, 0x65, 0x32, 0xf4, 0x05, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x4c,
0x61, 0x6d, 0x65, 0x32, 0xc3, 0x06, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x4c,
0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f,
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c,
@@ -1191,11 +1191,16 @@ var file_internal_machine_api_pb_docker_proto_rawDesc = []byte{
0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74,
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72,
0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x16, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1251,18 +1256,20 @@ var file_internal_machine_api_pb_docker_proto_depIdxs = []int32{
12, // 13: api.Docker.InspectImage:input_type -> api.InspectImageRequest
15, // 14: api.Docker.InspectRemoteImage:input_type -> api.InspectRemoteImageRequest
18, // 15: api.Docker.CreateServiceContainer:input_type -> api.CreateServiceContainerRequest
1, // 16: api.Docker.CreateContainer:output_type -> api.CreateContainerResponse
3, // 17: api.Docker.InspectContainer:output_type -> api.InspectContainerResponse
20, // 18: api.Docker.StartContainer:output_type -> google.protobuf.Empty
20, // 19: api.Docker.StopContainer:output_type -> google.protobuf.Empty
7, // 20: api.Docker.ListContainers:output_type -> api.ListContainersResponse
20, // 21: api.Docker.RemoveContainer:output_type -> google.protobuf.Empty
11, // 22: api.Docker.PullImage:output_type -> api.JSONMessage
13, // 23: api.Docker.InspectImage:output_type -> api.InspectImageResponse
16, // 24: api.Docker.InspectRemoteImage:output_type -> api.InspectRemoteImageResponse
1, // 25: api.Docker.CreateServiceContainer:output_type -> api.CreateContainerResponse
16, // [16:26] is the sub-list for method output_type
6, // [6:16] is the sub-list for method input_type
9, // 16: api.Docker.RemoveServiceContainer:input_type -> api.RemoveContainerRequest
1, // 17: api.Docker.CreateContainer:output_type -> api.CreateContainerResponse
3, // 18: api.Docker.InspectContainer:output_type -> api.InspectContainerResponse
20, // 19: api.Docker.StartContainer:output_type -> google.protobuf.Empty
20, // 20: api.Docker.StopContainer:output_type -> google.protobuf.Empty
7, // 21: api.Docker.ListContainers:output_type -> api.ListContainersResponse
20, // 22: api.Docker.RemoveContainer:output_type -> google.protobuf.Empty
11, // 23: api.Docker.PullImage:output_type -> api.JSONMessage
13, // 24: api.Docker.InspectImage:output_type -> api.InspectImageResponse
16, // 25: api.Docker.InspectRemoteImage:output_type -> api.InspectRemoteImageResponse
1, // 26: api.Docker.CreateServiceContainer:output_type -> api.CreateContainerResponse
20, // 27: api.Docker.RemoveServiceContainer:output_type -> google.protobuf.Empty
17, // [17:28] is the sub-list for method output_type
6, // [6:17] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
+1
View File
@@ -21,6 +21,7 @@ service Docker {
rpc InspectRemoteImage(InspectRemoteImageRequest) returns (InspectRemoteImageResponse);
rpc CreateServiceContainer(CreateServiceContainerRequest) returns (CreateContainerResponse);
rpc RemoveServiceContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
}
message CreateContainerRequest {
+38
View File
@@ -30,6 +30,7 @@ const (
Docker_InspectImage_FullMethodName = "/api.Docker/InspectImage"
Docker_InspectRemoteImage_FullMethodName = "/api.Docker/InspectRemoteImage"
Docker_CreateServiceContainer_FullMethodName = "/api.Docker/CreateServiceContainer"
Docker_RemoveServiceContainer_FullMethodName = "/api.Docker/RemoveServiceContainer"
)
// DockerClient is the client API for Docker service.
@@ -48,6 +49,7 @@ type DockerClient interface {
// Docker auth credentials if necessary.
InspectRemoteImage(ctx context.Context, in *InspectRemoteImageRequest, opts ...grpc.CallOption) (*InspectRemoteImageResponse, error)
CreateServiceContainer(ctx context.Context, in *CreateServiceContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}
type dockerClient struct {
@@ -167,6 +169,16 @@ func (c *dockerClient) CreateServiceContainer(ctx context.Context, in *CreateSer
return out, nil
}
func (c *dockerClient) RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Docker_RemoveServiceContainer_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// DockerServer is the server API for Docker service.
// All implementations must embed UnimplementedDockerServer
// for forward compatibility.
@@ -183,6 +195,7 @@ type DockerServer interface {
// Docker auth credentials if necessary.
InspectRemoteImage(context.Context, *InspectRemoteImageRequest) (*InspectRemoteImageResponse, error)
CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error)
RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
mustEmbedUnimplementedDockerServer()
}
@@ -223,6 +236,9 @@ func (UnimplementedDockerServer) InspectRemoteImage(context.Context, *InspectRem
func (UnimplementedDockerServer) CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateServiceContainer not implemented")
}
func (UnimplementedDockerServer) RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveServiceContainer not implemented")
}
func (UnimplementedDockerServer) mustEmbedUnimplementedDockerServer() {}
func (UnimplementedDockerServer) testEmbeddedByValue() {}
@@ -417,6 +433,24 @@ func _Docker_CreateServiceContainer_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler)
}
func _Docker_RemoveServiceContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveContainerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DockerServer).RemoveServiceContainer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Docker_RemoveServiceContainer_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DockerServer).RemoveServiceContainer(ctx, req.(*RemoveContainerRequest))
}
return interceptor(ctx, in, info, handler)
}
// Docker_ServiceDesc is the grpc.ServiceDesc for Docker service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -460,6 +494,10 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreateServiceContainer",
Handler: _Docker_CreateServiceContainer_Handler,
},
{
MethodName: "RemoveServiceContainer",
Handler: _Docker_RemoveServiceContainer_Handler,
},
},
Streams: []grpc.StreamDesc{
{
+22 -1
View File
@@ -5,6 +5,8 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"github.com/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@@ -18,7 +20,6 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"io"
)
// Client is a gRPC client for the Docker service that provides a similar interface to the Docker HTTP client.
@@ -367,3 +368,23 @@ func (c *Client) CreateServiceContainer(
}
return resp, nil
}
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
// A service container is a container that has been created with CreateServiceContainer.
func (c *Client) RemoveServiceContainer(ctx context.Context, id string, opts container.RemoveOptions) error {
optsBytes, err := json.Marshal(opts)
if err != nil {
return fmt.Errorf("marshal options: %w", err)
}
_, err = c.grpcClient.RemoveServiceContainer(ctx, &pb.RemoveContainerRequest{
Id: id,
Options: optsBytes,
})
if err != nil {
if status.Convert(err).Code() == codes.NotFound {
return errdefs.NotFound(err)
}
}
return err
}
+35
View File
@@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"regexp"
"strconv"
"strings"
@@ -32,6 +34,8 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)
var fullDockerIDRegex = regexp.MustCompile(`^[a-f0-9]{64}$`)
// Server implements the gRPC Docker service that proxies requests to the Docker daemon.
type Server struct {
pb.UnimplementedDockerServer
@@ -450,3 +454,34 @@ func (s *Server) CreateServiceContainer(
return &pb.CreateContainerResponse{Response: respBytes}, nil
}
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
// The difference between this method and RemoveContainer is that it also removes the container from the machine
// database.
func (s *Server) RemoveServiceContainer(ctx context.Context, req *pb.RemoveContainerRequest) (*emptypb.Empty, error) {
ctrID := req.Id
// If the ID is not a full Docker ID, inspect the container to get its full ID.
if !fullDockerIDRegex.MatchString(req.Id) {
ctr, err := s.client.ContainerInspect(ctx, req.Id)
if err != nil {
if client.IsErrNotFound(err) {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, err.Error())
}
ctrID = ctr.ID
}
resp, err := s.RemoveContainer(ctx, req)
if err != nil {
return nil, err
}
if _, err = s.db.ExecContext(ctx, `DELETE FROM containers WHERE id = $1`, ctrID); err != nil {
slog.Error("Failed to remove container from machine database.", "err", err, "id", ctrID)
// Do not return an error because the container has already been removed from the Docker daemon.
// The orphaned db record will be ignored and eventually cleaned up by the garbage collector.
}
return resp, nil
}