chore(volumes): gRPC methods for managing Docker volumes

This commit is contained in:
Pavel Sviderski
2025-04-08 16:33:35 +10:00
parent 9100c22e7f
commit 13dea92311
5 changed files with 932 additions and 164 deletions
+70
View File
@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/jsonmessage"
regtypes "github.com/google/go-containerregistry/pkg/v1/types"
@@ -341,6 +342,75 @@ func parseRemoteImageMessage(msg *pb.RemoteImage) (api.MachineRemoteImage, error
return mri, nil
}
// CreateVolume creates a new volume with the given options.
func (c *Client) CreateVolume(ctx context.Context, opts volume.CreateOptions) (volume.Volume, error) {
var vol volume.Volume
optsBytes, err := json.Marshal(opts)
if err != nil {
return vol, fmt.Errorf("marshal options: %w", err)
}
resp, err := c.grpcClient.CreateVolume(ctx, &pb.CreateVolumeRequest{Options: optsBytes})
if err != nil {
return vol, err
}
if err = json.Unmarshal(resp.Volume, &vol); err != nil {
return vol, fmt.Errorf("unmarshal volume: %w", err)
}
return vol, nil
}
// MachineVolumes represents a volume list response from a machine.
type MachineVolumes struct {
Metadata *pb.Metadata
Response volume.ListResponse
}
// ListVolumes returns a list of all volumes matching the filter.
func (c *Client) ListVolumes(ctx context.Context, opts volume.ListOptions) ([]MachineVolumes, error) {
optsBytes, err := json.Marshal(opts)
if err != nil {
return nil, fmt.Errorf("marshal options: %w", err)
}
resp, err := c.grpcClient.ListVolumes(ctx, &pb.ListVolumesRequest{Options: optsBytes})
if err != nil {
return nil, err
}
machineVolumes := make([]MachineVolumes, len(resp.Messages))
for i, msg := range resp.Messages {
machineVolumes[i].Metadata = msg.Metadata
if msg.Metadata != nil && msg.Metadata.Error != "" {
continue
}
if err = json.Unmarshal(msg.Response, &machineVolumes[i].Response); err != nil {
return nil, fmt.Errorf("unmarshal response: %w", err)
}
}
return machineVolumes, nil
}
// RemoveVolume removes a volume with the given ID.
func (c *Client) RemoveVolume(ctx context.Context, id string, force bool) error {
_, err := c.grpcClient.RemoveVolume(ctx, &pb.RemoveVolumeRequest{
Id: id,
Force: force,
})
if err != nil {
if status.Convert(err).Code() == codes.NotFound {
return errdefs.NotFound(err)
}
}
return err
}
// CreateServiceContainer creates a new container for the service with the given specifications.
func (c *Client) CreateServiceContainer(
ctx context.Context, serviceID string, spec api.ServiceSpec, containerName string,
+124
View File
@@ -18,6 +18,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/google/go-containerregistry/pkg/authn"
@@ -329,6 +330,80 @@ func (s *Server) InspectRemoteImage(
}, nil
}
// CreateVolume creates a new volume with the given options.
func (s *Server) CreateVolume(ctx context.Context, req *pb.CreateVolumeRequest) (*pb.CreateVolumeResponse, error) {
var opts volume.CreateOptions
if err := json.Unmarshal(req.Options, &opts); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "unmarshal options: %v", err)
}
vol, err := s.client.VolumeCreate(ctx, opts)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
volBytes, err := json.Marshal(vol)
if err != nil {
return nil, status.Errorf(codes.Internal, "marshal volume: %v", err)
}
return &pb.CreateVolumeResponse{Volume: volBytes}, nil
}
// ListVolumes returns a list of all volumes matching the filter.
func (s *Server) ListVolumes(ctx context.Context, req *pb.ListVolumesRequest) (*pb.ListVolumesResponse, error) {
var opts volume.ListOptions
if len(req.Options) > 0 {
if err := json.Unmarshal(req.Options, &opts); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "unmarshal options: %v", err)
}
// Handle filters separately because they implement custom JSON unmarshalling.
var raw map[string]json.RawMessage
if err := json.Unmarshal(req.Options, &raw); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "unmarshal options to raw map: %v", err)
}
if filtersBytes, ok := raw["Filters"]; ok {
args, err := filters.FromJSON(string(filtersBytes))
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "unmarshal filters: %v", err)
}
opts.Filters = args
}
}
resp, err := s.client.VolumeList(ctx, opts)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
respBytes, err := json.Marshal(resp)
if err != nil {
return nil, status.Errorf(codes.Internal, "marshal response: %v", err)
}
return &pb.ListVolumesResponse{
Messages: []*pb.MachineVolumes{
{
Response: respBytes,
},
},
}, nil
}
// RemoveVolume removes a volume with the given ID.
func (s *Server) RemoveVolume(ctx context.Context, req *pb.RemoveVolumeRequest) (*emptypb.Empty, error) {
if err := s.client.VolumeRemove(ctx, req.Id, req.Force); err != nil {
if client.IsErrNotFound(err) {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, err.Error())
}
return &emptypb.Empty{}, nil
}
// CreateServiceContainer creates a new container for the service with the given specifications.
func (s *Server) CreateServiceContainer(
ctx context.Context, req *pb.CreateServiceContainerRequest,
@@ -451,6 +526,55 @@ func (s *Server) CreateServiceContainer(
return &pb.CreateContainerResponse{Response: respBytes}, nil
}
//func toMounts(volumes []api.VolumeSpec) ([]mount.Mount, error) {
// mounts := make([]mount.Mount, 0, len(volumes))
// for _, vol := range volumes {
// m := mount.Mount{
// Type: mount.Type(vol.Type),
// Source: vol.Source,
// Target: vol.Target,
// ReadOnly: vol.ReadOnly,
// }
//
// // Set type-specific options.
// switch vol.Type {
// case api.VolumeTypeBind:
// if vol.BindOptions != nil {
// m.BindOptions = &mount.BindOptions{
// Propagation: vol.BindOptions.Propagation,
// NonRecursive: false,
// }
// if vol.BindOptions.CreateHostPath {
// m.BindOptions.CreateMountpoint = true
// }
// // Handle SELinux options if specified
// if vol.BindOptions.SELinux == api.SELinuxShared {
// m.BindOptions.Propagation = mount.PropagationShared
// } else if vol.BindOptions.SELinux == api.SELinuxUnshared {
// m.BindOptions.Propagation = mount.PropagationPrivate
// }
// }
// case api.VolumeTypeVolume:
// if vol.VolumeOptions != nil {
// m.VolumeOptions = &mount.VolumeOptions{
// NoCopy: vol.VolumeOptions.NoCopy,
// Labels: vol.VolumeOptions.Labels,
// DriverConfig: vol.VolumeOptions.Driver,
// Subpath: vol.VolumeOptions.Subpath,
// }
// }
// case api.VolumeTypeTmpfs:
// m.TmpfsOptions = vol.TmpfsOptions
// default:
// return nil, fmt.Errorf("invalid volume type: '%s' (must be one of %s, %s, %s)",
// vol.Type, api.VolumeTypeBind, api.VolumeTypeVolume, api.VolumeTypeTmpfs)
// }
//
// mounts = append(mounts, m)
// }
// return mounts
//}
// InspectServiceContainer returns the container information and service specification that was used to create the
// container with the given ID.
func (s *Server) InspectServiceContainer(