mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 03:53:33 +00:00
chore: use original service spec instead of deriving from container, fixes diff for scale
This commit is contained in:
@@ -3,12 +3,13 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type scaleOptions struct {
|
type scaleOptions struct {
|
||||||
@@ -81,12 +82,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
|
|||||||
|
|
||||||
// TODO: Check if all containers have the same spec. If not, prompt user to choose which one to scale.
|
// TODO: Check if all containers have the same spec. If not, prompt user to choose which one to scale.
|
||||||
// This can happen if a service deployment failed midway and some containers were not updated.
|
// This can happen if a service deployment failed midway and some containers were not updated.
|
||||||
// Derive the service spec from the first container.
|
spec := svc.Containers[0].Container.ServiceSpec
|
||||||
spec, err := svc.Containers[0].Container.ServiceSpec()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get service spec from container: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
spec.Replicas = opts.replicas
|
spec.Replicas = opts.replicas
|
||||||
deployment := clusterClient.NewDeployment(spec, nil)
|
deployment := clusterClient.NewDeployment(spec, nil)
|
||||||
plan, err := deployment.Plan(ctx)
|
plan, err := deployment.Plan(ctx)
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ type CreateContainerRequest struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// JSON serialized container.Config.
|
// JSON serialised container.Config.
|
||||||
Config []byte `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
Config []byte `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
||||||
// JSON serialized container.HostConfig.
|
// JSON serialised container.HostConfig.
|
||||||
HostConfig []byte `protobuf:"bytes,2,opt,name=host_config,json=hostConfig,proto3" json:"host_config,omitempty"`
|
HostConfig []byte `protobuf:"bytes,2,opt,name=host_config,json=hostConfig,proto3" json:"host_config,omitempty"`
|
||||||
// JSON serialized network.NetworkingConfig.
|
// JSON serialised network.NetworkingConfig.
|
||||||
NetworkConfig []byte `protobuf:"bytes,3,opt,name=network_config,json=networkConfig,proto3" json:"network_config,omitempty"`
|
NetworkConfig []byte `protobuf:"bytes,3,opt,name=network_config,json=networkConfig,proto3" json:"network_config,omitempty"`
|
||||||
// JSON serialized ocispec.Platform.
|
// JSON serialised ocispec.Platform.
|
||||||
Platform []byte `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"`
|
Platform []byte `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"`
|
||||||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ type CreateContainerResponse struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// JSON serialized container.CreateResponse.
|
// JSON serialised container.CreateResponse.
|
||||||
Response []byte `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
Response []byte `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ type InspectContainerResponse struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// JSON serialized container.InspectResponse.
|
// JSON serialised container.InspectResponse.
|
||||||
Response []byte `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
Response []byte `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ type StartContainerRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
// JSON serialized container.StartOptions.
|
// JSON serialised container.StartOptions.
|
||||||
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ type StopContainerRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
// JSON serialized container.StopOptions.
|
// JSON serialised container.StopOptions.
|
||||||
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ type ListContainersRequest struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// JSON serialized container.ListOptions.
|
// JSON serialised container.ListOptions.
|
||||||
Options []byte `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"`
|
Options []byte `protobuf:"bytes,1,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +461,7 @@ type MachineContainers struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
||||||
// JSON serialized []container.ContainerJSON.
|
// JSON serialised []container.InspectResponse.
|
||||||
Containers []byte `protobuf:"bytes,2,opt,name=containers,proto3" json:"containers,omitempty"`
|
Containers []byte `protobuf:"bytes,2,opt,name=containers,proto3" json:"containers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ type RemoveContainerRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
// JSON serialized container.RemoveOptions.
|
// JSON serialised container.RemoveOptions.
|
||||||
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +573,7 @@ type PullImageRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Image string `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"`
|
Image string `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"`
|
||||||
// JSON serialized image.PullOptions.
|
// JSON serialised image.PullOptions.
|
||||||
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +628,7 @@ type JSONMessage struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// JSON serialized jsonmessage.JSONMessage.
|
// JSON serialised jsonmessage.JSONMessage.
|
||||||
Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,7 +772,7 @@ type Image struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
||||||
// JSON serialized types.ImageInspect.
|
// JSON serialised types.ImageInspect.
|
||||||
Image []byte `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"`
|
Image []byte `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,6 +1046,223 @@ func (x *CreateServiceContainerRequest) GetContainerName() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceContainer struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// JSON serialised container.InspectResponse.
|
||||||
|
Container []byte `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||||
|
// JSON serialised api.ServiceSpec.
|
||||||
|
ServiceSpec []byte `protobuf:"bytes,2,opt,name=service_spec,json=serviceSpec,proto3" json:"service_spec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ServiceContainer) Reset() {
|
||||||
|
*x = ServiceContainer{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[19]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ServiceContainer) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ServiceContainer) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ServiceContainer) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[19]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ServiceContainer.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ServiceContainer) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_docker_proto_rawDescGZIP(), []int{19}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ServiceContainer) GetContainer() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Container
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ServiceContainer) GetServiceSpec() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceSpec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListServiceContainersRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ServiceId string `protobuf:"bytes,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"`
|
||||||
|
// JSON serialised container.ListOptions.
|
||||||
|
Options []byte `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersRequest) Reset() {
|
||||||
|
*x = ListServiceContainersRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[20]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListServiceContainersRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[20]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListServiceContainersRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListServiceContainersRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_docker_proto_rawDescGZIP(), []int{20}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersRequest) GetServiceId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersRequest) GetOptions() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Options
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListServiceContainersResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Must contain only one repeated messages field to allow broadcasting ListServiceContainers requests
|
||||||
|
// to multiple machines.
|
||||||
|
Messages []*MachineServiceContainers `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersResponse) Reset() {
|
||||||
|
*x = ListServiceContainersResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[21]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListServiceContainersResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[21]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListServiceContainersResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListServiceContainersResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_docker_proto_rawDescGZIP(), []int{21}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListServiceContainersResponse) GetMessages() []*MachineServiceContainers {
|
||||||
|
if x != nil {
|
||||||
|
return x.Messages
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MachineServiceContainers struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
||||||
|
Containers []*ServiceContainer `protobuf:"bytes,2,rep,name=containers,proto3" json:"containers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MachineServiceContainers) Reset() {
|
||||||
|
*x = MachineServiceContainers{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[22]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MachineServiceContainers) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*MachineServiceContainers) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *MachineServiceContainers) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_docker_proto_msgTypes[22]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MachineServiceContainers.ProtoReflect.Descriptor instead.
|
||||||
|
func (*MachineServiceContainers) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_docker_proto_rawDescGZIP(), []int{22}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MachineServiceContainers) GetMetadata() *Metadata {
|
||||||
|
if x != nil {
|
||||||
|
return x.Metadata
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MachineServiceContainers) GetContainers() []*ServiceContainer {
|
||||||
|
if x != nil {
|
||||||
|
return x.Containers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_internal_machine_api_pb_docker_proto protoreflect.FileDescriptor
|
var File_internal_machine_api_pb_docker_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_internal_machine_api_pb_docker_proto_rawDesc = []byte{
|
var file_internal_machine_api_pb_docker_proto_rawDesc = []byte{
|
||||||
@@ -1144,63 +1361,99 @@ 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,
|
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,
|
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,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e,
|
||||||
0x61, 0x6d, 0x65, 0x32, 0xc3, 0x06, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x4c,
|
0x61, 0x6d, 0x65, 0x22, 0x53, 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f,
|
||||||
0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,
|
||||||
0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f,
|
0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74,
|
||||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c,
|
0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x65, 0x72,
|
||||||
0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x10,
|
0x76, 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x22, 0x57, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74,
|
||||||
0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||||
0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x6f,
|
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76,
|
||||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d,
|
0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f,
|
||||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a,
|
0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12,
|
0x73, 0x22, 0x5a, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||||
0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 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, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
|
||||||
0x69, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 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, 0x12, 0x49, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43,
|
|
||||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
|
||||||
0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65,
|
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
|
||||||
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x73, 0x65, 0x12, 0x46, 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74,
|
0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01,
|
||||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f,
|
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x76, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x6e, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x65, 0x72, 0x73, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x7c, 0x0a,
|
||||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x09, 0x50, 0x75,
|
0x18, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43,
|
||||||
0x6c, 0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x75,
|
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74,
|
||||||
0x6c, 0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10,
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,
|
||||||
0x30, 0x01, 0x12, 0x43, 0x0a, 0x0c, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x61,
|
0x64, 0x61, 0x74, 0x61, 0x12, 0x35, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||||
0x67, 0x65, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74,
|
0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53,
|
||||||
0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52,
|
||||||
0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52,
|
0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x32, 0xf3, 0x07, 0x0a, 0x06,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x70, 0x65,
|
0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e,
|
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74,
|
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52,
|
||||||
0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74,
|
0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a,
|
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43,
|
||||||
0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43,
|
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49,
|
||||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43,
|
0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52,
|
||||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73,
|
||||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61,
|
0x70, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||||
0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f,
|
||||||
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x16, 0x52, 0x65,
|
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74,
|
||||||
0x6d, 0x6f, 0x76, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
0x61, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
|
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0d, 0x53,
|
||||||
0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x61,
|
||||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74,
|
0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||||
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
0x49, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x73, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
|
||||||
|
0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
|
||||||
|
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||||
|
0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0f, 0x52, 0x65,
|
||||||
|
0x6d, 0x6f, 0x76, 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, 0x12, 0x36, 0x0a, 0x09, 0x50, 0x75, 0x6c, 0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12,
|
||||||
|
0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x53, 0x4f,
|
||||||
|
0x4e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x43, 0x0a, 0x0c, 0x49, 0x6e,
|
||||||
|
0x73, 0x70, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69,
|
||||||
|
0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65,
|
||||||
|
0x63, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
|
0x55, 0x0a, 0x12, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
||||||
|
0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70,
|
||||||
|
0x65, 0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70,
|
||||||
|
0x65, 0x63, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
|
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||||
|
0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 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, 0x12, 0x4e, 0x0a, 0x17, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x2e,
|
||||||
|
0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||||
|
0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70,
|
||||||
|
0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||||
|
0x65, 0x72, 0x12, 0x5e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||||
|
0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x70,
|
||||||
|
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e,
|
||||||
|
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22,
|
||||||
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||||
|
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 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 (
|
var (
|
||||||
@@ -1215,7 +1468,7 @@ func file_internal_machine_api_pb_docker_proto_rawDescGZIP() []byte {
|
|||||||
return file_internal_machine_api_pb_docker_proto_rawDescData
|
return file_internal_machine_api_pb_docker_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_docker_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
var file_internal_machine_api_pb_docker_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||||
var file_internal_machine_api_pb_docker_proto_goTypes = []any{
|
var file_internal_machine_api_pb_docker_proto_goTypes = []any{
|
||||||
(*CreateContainerRequest)(nil), // 0: api.CreateContainerRequest
|
(*CreateContainerRequest)(nil), // 0: api.CreateContainerRequest
|
||||||
(*CreateContainerResponse)(nil), // 1: api.CreateContainerResponse
|
(*CreateContainerResponse)(nil), // 1: api.CreateContainerResponse
|
||||||
@@ -1236,43 +1489,54 @@ var file_internal_machine_api_pb_docker_proto_goTypes = []any{
|
|||||||
(*InspectRemoteImageResponse)(nil), // 16: api.InspectRemoteImageResponse
|
(*InspectRemoteImageResponse)(nil), // 16: api.InspectRemoteImageResponse
|
||||||
(*RemoteImage)(nil), // 17: api.RemoteImage
|
(*RemoteImage)(nil), // 17: api.RemoteImage
|
||||||
(*CreateServiceContainerRequest)(nil), // 18: api.CreateServiceContainerRequest
|
(*CreateServiceContainerRequest)(nil), // 18: api.CreateServiceContainerRequest
|
||||||
(*Metadata)(nil), // 19: api.Metadata
|
(*ServiceContainer)(nil), // 19: api.ServiceContainer
|
||||||
(*emptypb.Empty)(nil), // 20: google.protobuf.Empty
|
(*ListServiceContainersRequest)(nil), // 20: api.ListServiceContainersRequest
|
||||||
|
(*ListServiceContainersResponse)(nil), // 21: api.ListServiceContainersResponse
|
||||||
|
(*MachineServiceContainers)(nil), // 22: api.MachineServiceContainers
|
||||||
|
(*Metadata)(nil), // 23: api.Metadata
|
||||||
|
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_docker_proto_depIdxs = []int32{
|
var file_internal_machine_api_pb_docker_proto_depIdxs = []int32{
|
||||||
8, // 0: api.ListContainersResponse.messages:type_name -> api.MachineContainers
|
8, // 0: api.ListContainersResponse.messages:type_name -> api.MachineContainers
|
||||||
19, // 1: api.MachineContainers.metadata:type_name -> api.Metadata
|
23, // 1: api.MachineContainers.metadata:type_name -> api.Metadata
|
||||||
14, // 2: api.InspectImageResponse.messages:type_name -> api.Image
|
14, // 2: api.InspectImageResponse.messages:type_name -> api.Image
|
||||||
19, // 3: api.Image.metadata:type_name -> api.Metadata
|
23, // 3: api.Image.metadata:type_name -> api.Metadata
|
||||||
17, // 4: api.InspectRemoteImageResponse.messages:type_name -> api.RemoteImage
|
17, // 4: api.InspectRemoteImageResponse.messages:type_name -> api.RemoteImage
|
||||||
19, // 5: api.RemoteImage.metadata:type_name -> api.Metadata
|
23, // 5: api.RemoteImage.metadata:type_name -> api.Metadata
|
||||||
0, // 6: api.Docker.CreateContainer:input_type -> api.CreateContainerRequest
|
22, // 6: api.ListServiceContainersResponse.messages:type_name -> api.MachineServiceContainers
|
||||||
2, // 7: api.Docker.InspectContainer:input_type -> api.InspectContainerRequest
|
23, // 7: api.MachineServiceContainers.metadata:type_name -> api.Metadata
|
||||||
4, // 8: api.Docker.StartContainer:input_type -> api.StartContainerRequest
|
19, // 8: api.MachineServiceContainers.containers:type_name -> api.ServiceContainer
|
||||||
5, // 9: api.Docker.StopContainer:input_type -> api.StopContainerRequest
|
0, // 9: api.Docker.CreateContainer:input_type -> api.CreateContainerRequest
|
||||||
6, // 10: api.Docker.ListContainers:input_type -> api.ListContainersRequest
|
2, // 10: api.Docker.InspectContainer:input_type -> api.InspectContainerRequest
|
||||||
9, // 11: api.Docker.RemoveContainer:input_type -> api.RemoveContainerRequest
|
4, // 11: api.Docker.StartContainer:input_type -> api.StartContainerRequest
|
||||||
10, // 12: api.Docker.PullImage:input_type -> api.PullImageRequest
|
5, // 12: api.Docker.StopContainer:input_type -> api.StopContainerRequest
|
||||||
12, // 13: api.Docker.InspectImage:input_type -> api.InspectImageRequest
|
6, // 13: api.Docker.ListContainers:input_type -> api.ListContainersRequest
|
||||||
15, // 14: api.Docker.InspectRemoteImage:input_type -> api.InspectRemoteImageRequest
|
9, // 14: api.Docker.RemoveContainer:input_type -> api.RemoveContainerRequest
|
||||||
18, // 15: api.Docker.CreateServiceContainer:input_type -> api.CreateServiceContainerRequest
|
10, // 15: api.Docker.PullImage:input_type -> api.PullImageRequest
|
||||||
9, // 16: api.Docker.RemoveServiceContainer:input_type -> api.RemoveContainerRequest
|
12, // 16: api.Docker.InspectImage:input_type -> api.InspectImageRequest
|
||||||
1, // 17: api.Docker.CreateContainer:output_type -> api.CreateContainerResponse
|
15, // 17: api.Docker.InspectRemoteImage:input_type -> api.InspectRemoteImageRequest
|
||||||
3, // 18: api.Docker.InspectContainer:output_type -> api.InspectContainerResponse
|
18, // 18: api.Docker.CreateServiceContainer:input_type -> api.CreateServiceContainerRequest
|
||||||
20, // 19: api.Docker.StartContainer:output_type -> google.protobuf.Empty
|
2, // 19: api.Docker.InspectServiceContainer:input_type -> api.InspectContainerRequest
|
||||||
20, // 20: api.Docker.StopContainer:output_type -> google.protobuf.Empty
|
20, // 20: api.Docker.ListServiceContainers:input_type -> api.ListServiceContainersRequest
|
||||||
7, // 21: api.Docker.ListContainers:output_type -> api.ListContainersResponse
|
9, // 21: api.Docker.RemoveServiceContainer:input_type -> api.RemoveContainerRequest
|
||||||
20, // 22: api.Docker.RemoveContainer:output_type -> google.protobuf.Empty
|
1, // 22: api.Docker.CreateContainer:output_type -> api.CreateContainerResponse
|
||||||
11, // 23: api.Docker.PullImage:output_type -> api.JSONMessage
|
3, // 23: api.Docker.InspectContainer:output_type -> api.InspectContainerResponse
|
||||||
13, // 24: api.Docker.InspectImage:output_type -> api.InspectImageResponse
|
24, // 24: api.Docker.StartContainer:output_type -> google.protobuf.Empty
|
||||||
16, // 25: api.Docker.InspectRemoteImage:output_type -> api.InspectRemoteImageResponse
|
24, // 25: api.Docker.StopContainer:output_type -> google.protobuf.Empty
|
||||||
1, // 26: api.Docker.CreateServiceContainer:output_type -> api.CreateContainerResponse
|
7, // 26: api.Docker.ListContainers:output_type -> api.ListContainersResponse
|
||||||
20, // 27: api.Docker.RemoveServiceContainer:output_type -> google.protobuf.Empty
|
24, // 27: api.Docker.RemoveContainer:output_type -> google.protobuf.Empty
|
||||||
17, // [17:28] is the sub-list for method output_type
|
11, // 28: api.Docker.PullImage:output_type -> api.JSONMessage
|
||||||
6, // [6:17] is the sub-list for method input_type
|
13, // 29: api.Docker.InspectImage:output_type -> api.InspectImageResponse
|
||||||
6, // [6:6] is the sub-list for extension type_name
|
16, // 30: api.Docker.InspectRemoteImage:output_type -> api.InspectRemoteImageResponse
|
||||||
6, // [6:6] is the sub-list for extension extendee
|
1, // 31: api.Docker.CreateServiceContainer:output_type -> api.CreateContainerResponse
|
||||||
0, // [0:6] is the sub-list for field type_name
|
19, // 32: api.Docker.InspectServiceContainer:output_type -> api.ServiceContainer
|
||||||
|
21, // 33: api.Docker.ListServiceContainers:output_type -> api.ListServiceContainersResponse
|
||||||
|
24, // 34: api.Docker.RemoveServiceContainer:output_type -> google.protobuf.Empty
|
||||||
|
22, // [22:35] is the sub-list for method output_type
|
||||||
|
9, // [9:22] is the sub-list for method input_type
|
||||||
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
|
0, // [0:9] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_internal_machine_api_pb_docker_proto_init() }
|
func init() { file_internal_machine_api_pb_docker_proto_init() }
|
||||||
@@ -1510,6 +1774,54 @@ func file_internal_machine_api_pb_docker_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_internal_machine_api_pb_docker_proto_msgTypes[19].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ServiceContainer); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_internal_machine_api_pb_docker_proto_msgTypes[20].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ListServiceContainersRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_internal_machine_api_pb_docker_proto_msgTypes[21].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ListServiceContainersResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_internal_machine_api_pb_docker_proto_msgTypes[22].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*MachineServiceContainers); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -1517,7 +1829,7 @@ func file_internal_machine_api_pb_docker_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_docker_proto_rawDesc,
|
RawDescriptor: file_internal_machine_api_pb_docker_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 19,
|
NumMessages: 23,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,23 +21,25 @@ service Docker {
|
|||||||
rpc InspectRemoteImage(InspectRemoteImageRequest) returns (InspectRemoteImageResponse);
|
rpc InspectRemoteImage(InspectRemoteImageRequest) returns (InspectRemoteImageResponse);
|
||||||
|
|
||||||
rpc CreateServiceContainer(CreateServiceContainerRequest) returns (CreateContainerResponse);
|
rpc CreateServiceContainer(CreateServiceContainerRequest) returns (CreateContainerResponse);
|
||||||
|
rpc InspectServiceContainer(InspectContainerRequest) returns (ServiceContainer);
|
||||||
|
rpc ListServiceContainers(ListServiceContainersRequest) returns (ListServiceContainersResponse);
|
||||||
rpc RemoveServiceContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
rpc RemoveServiceContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateContainerRequest {
|
message CreateContainerRequest {
|
||||||
// JSON serialized container.Config.
|
// JSON serialised container.Config.
|
||||||
bytes config = 1;
|
bytes config = 1;
|
||||||
// JSON serialized container.HostConfig.
|
// JSON serialised container.HostConfig.
|
||||||
bytes host_config = 2;
|
bytes host_config = 2;
|
||||||
// JSON serialized network.NetworkingConfig.
|
// JSON serialised network.NetworkingConfig.
|
||||||
bytes network_config = 3;
|
bytes network_config = 3;
|
||||||
// JSON serialized ocispec.Platform.
|
// JSON serialised ocispec.Platform.
|
||||||
bytes platform = 4;
|
bytes platform = 4;
|
||||||
string name = 5;
|
string name = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateContainerResponse {
|
message CreateContainerResponse {
|
||||||
// JSON serialized container.CreateResponse.
|
// JSON serialised container.CreateResponse.
|
||||||
bytes response = 1;
|
bytes response = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,24 +48,24 @@ message InspectContainerRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message InspectContainerResponse {
|
message InspectContainerResponse {
|
||||||
// JSON serialized container.InspectResponse.
|
// JSON serialised container.InspectResponse.
|
||||||
bytes response = 1;
|
bytes response = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartContainerRequest {
|
message StartContainerRequest {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
// JSON serialized container.StartOptions.
|
// JSON serialised container.StartOptions.
|
||||||
bytes options = 2;
|
bytes options = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StopContainerRequest {
|
message StopContainerRequest {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
// JSON serialized container.StopOptions.
|
// JSON serialised container.StopOptions.
|
||||||
bytes options = 2;
|
bytes options = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListContainersRequest {
|
message ListContainersRequest {
|
||||||
// JSON serialized container.ListOptions.
|
// JSON serialised container.ListOptions.
|
||||||
bytes options = 1;
|
bytes options = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,24 +76,24 @@ message ListContainersResponse {
|
|||||||
|
|
||||||
message MachineContainers {
|
message MachineContainers {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
// JSON serialized []container.ContainerJSON.
|
// JSON serialised []container.InspectResponse.
|
||||||
bytes containers = 2;
|
bytes containers = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RemoveContainerRequest {
|
message RemoveContainerRequest {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
// JSON serialized container.RemoveOptions.
|
// JSON serialised container.RemoveOptions.
|
||||||
bytes options = 2;
|
bytes options = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PullImageRequest {
|
message PullImageRequest {
|
||||||
string image = 1;
|
string image = 1;
|
||||||
// JSON serialized image.PullOptions.
|
// JSON serialised image.PullOptions.
|
||||||
bytes options = 2;
|
bytes options = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message JSONMessage {
|
message JSONMessage {
|
||||||
// JSON serialized jsonmessage.JSONMessage.
|
// JSON serialised jsonmessage.JSONMessage.
|
||||||
bytes message = 1;
|
bytes message = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ message InspectImageResponse {
|
|||||||
|
|
||||||
message Image {
|
message Image {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
// JSON serialized types.ImageInspect.
|
// JSON serialised types.ImageInspect.
|
||||||
bytes image = 2;
|
bytes image = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,3 +135,27 @@ message CreateServiceContainerRequest {
|
|||||||
bytes service_spec = 2;
|
bytes service_spec = 2;
|
||||||
string container_name = 3;
|
string container_name = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ServiceContainer {
|
||||||
|
// JSON serialised container.InspectResponse.
|
||||||
|
bytes container = 1;
|
||||||
|
// JSON serialised api.ServiceSpec.
|
||||||
|
bytes service_spec = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListServiceContainersRequest {
|
||||||
|
string service_id = 1;
|
||||||
|
// JSON serialised container.ListOptions.
|
||||||
|
bytes options = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListServiceContainersResponse {
|
||||||
|
// Must contain only one repeated messages field to allow broadcasting ListServiceContainers requests
|
||||||
|
// to multiple machines.
|
||||||
|
repeated MachineServiceContainers messages = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MachineServiceContainers {
|
||||||
|
Metadata metadata = 1;
|
||||||
|
repeated ServiceContainer containers = 2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const (
|
|||||||
Docker_InspectImage_FullMethodName = "/api.Docker/InspectImage"
|
Docker_InspectImage_FullMethodName = "/api.Docker/InspectImage"
|
||||||
Docker_InspectRemoteImage_FullMethodName = "/api.Docker/InspectRemoteImage"
|
Docker_InspectRemoteImage_FullMethodName = "/api.Docker/InspectRemoteImage"
|
||||||
Docker_CreateServiceContainer_FullMethodName = "/api.Docker/CreateServiceContainer"
|
Docker_CreateServiceContainer_FullMethodName = "/api.Docker/CreateServiceContainer"
|
||||||
|
Docker_InspectServiceContainer_FullMethodName = "/api.Docker/InspectServiceContainer"
|
||||||
|
Docker_ListServiceContainers_FullMethodName = "/api.Docker/ListServiceContainers"
|
||||||
Docker_RemoveServiceContainer_FullMethodName = "/api.Docker/RemoveServiceContainer"
|
Docker_RemoveServiceContainer_FullMethodName = "/api.Docker/RemoveServiceContainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ type DockerClient interface {
|
|||||||
// Docker auth credentials if necessary.
|
// Docker auth credentials if necessary.
|
||||||
InspectRemoteImage(ctx context.Context, in *InspectRemoteImageRequest, opts ...grpc.CallOption) (*InspectRemoteImageResponse, error)
|
InspectRemoteImage(ctx context.Context, in *InspectRemoteImageRequest, opts ...grpc.CallOption) (*InspectRemoteImageResponse, error)
|
||||||
CreateServiceContainer(ctx context.Context, in *CreateServiceContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
|
CreateServiceContainer(ctx context.Context, in *CreateServiceContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
|
||||||
|
InspectServiceContainer(ctx context.Context, in *InspectContainerRequest, opts ...grpc.CallOption) (*ServiceContainer, error)
|
||||||
|
ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error)
|
||||||
RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +173,26 @@ func (c *dockerClient) CreateServiceContainer(ctx context.Context, in *CreateSer
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *dockerClient) InspectServiceContainer(ctx context.Context, in *InspectContainerRequest, opts ...grpc.CallOption) (*ServiceContainer, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ServiceContainer)
|
||||||
|
err := c.cc.Invoke(ctx, Docker_InspectServiceContainer_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dockerClient) ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListServiceContainersResponse)
|
||||||
|
err := c.cc.Invoke(ctx, Docker_ListServiceContainers_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *dockerClient) RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
func (c *dockerClient) RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(emptypb.Empty)
|
out := new(emptypb.Empty)
|
||||||
@@ -195,6 +219,8 @@ type DockerServer interface {
|
|||||||
// Docker auth credentials if necessary.
|
// Docker auth credentials if necessary.
|
||||||
InspectRemoteImage(context.Context, *InspectRemoteImageRequest) (*InspectRemoteImageResponse, error)
|
InspectRemoteImage(context.Context, *InspectRemoteImageRequest) (*InspectRemoteImageResponse, error)
|
||||||
CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error)
|
CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error)
|
||||||
|
InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error)
|
||||||
|
ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error)
|
||||||
RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
||||||
mustEmbedUnimplementedDockerServer()
|
mustEmbedUnimplementedDockerServer()
|
||||||
}
|
}
|
||||||
@@ -236,6 +262,12 @@ func (UnimplementedDockerServer) InspectRemoteImage(context.Context, *InspectRem
|
|||||||
func (UnimplementedDockerServer) CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error) {
|
func (UnimplementedDockerServer) CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateServiceContainer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateServiceContainer not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDockerServer) InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method InspectServiceContainer not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDockerServer) ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListServiceContainers not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDockerServer) RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error) {
|
func (UnimplementedDockerServer) RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveServiceContainer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RemoveServiceContainer not implemented")
|
||||||
}
|
}
|
||||||
@@ -433,6 +465,42 @@ func _Docker_CreateServiceContainer_Handler(srv interface{}, ctx context.Context
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Docker_InspectServiceContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(InspectContainerRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DockerServer).InspectServiceContainer(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Docker_InspectServiceContainer_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DockerServer).InspectServiceContainer(ctx, req.(*InspectContainerRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Docker_ListServiceContainers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListServiceContainersRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DockerServer).ListServiceContainers(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Docker_ListServiceContainers_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DockerServer).ListServiceContainers(ctx, req.(*ListServiceContainersRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Docker_RemoveServiceContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Docker_RemoveServiceContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(RemoveContainerRequest)
|
in := new(RemoveContainerRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -494,6 +562,14 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "CreateServiceContainer",
|
MethodName: "CreateServiceContainer",
|
||||||
Handler: _Docker_CreateServiceContainer_Handler,
|
Handler: _Docker_CreateServiceContainer_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "InspectServiceContainer",
|
||||||
|
Handler: _Docker_InspectServiceContainer_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListServiceContainers",
|
||||||
|
Handler: _Docker_ListServiceContainers_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "RemoveServiceContainer",
|
MethodName: "RemoveServiceContainer",
|
||||||
Handler: _Docker_RemoveServiceContainer_Handler,
|
Handler: _Docker_RemoveServiceContainer_Handler,
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"maps"
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateConfig(containers []api.Container, verifyResponse string) (*caddy.Config, error) {
|
func GenerateConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) {
|
||||||
// Maps hostnames to lists of upstreams (container IP:port pairs).
|
// Maps hostnames to lists of upstreams (container IP:port pairs).
|
||||||
httpHostUpstreams := make(map[string][]string)
|
httpHostUpstreams := make(map[string][]string)
|
||||||
httpsHostUpstreams := make(map[string][]string)
|
httpsHostUpstreams := make(map[string][]string)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package caddyfile
|
package caddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
@@ -8,20 +11,18 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateConfig(t *testing.T) {
|
func TestGenerateConfig(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
containers []api.Container
|
containers []api.ServiceContainer
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty containers",
|
name: "empty containers",
|
||||||
containers: []api.Container{},
|
containers: []api.ServiceContainer{},
|
||||||
want: `{
|
want: `{
|
||||||
"servers": {
|
"servers": {
|
||||||
"http": {
|
"http": {
|
||||||
@@ -45,7 +46,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: "HTTP container",
|
name: "HTTP container",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: `{
|
||||||
@@ -79,7 +80,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "load balancing multiple containers",
|
name: "load balancing multiple containers",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||||
newContainer("10.210.0.3", "app.example.com:8080/http"),
|
newContainer("10.210.0.3", "app.example.com:8080/http"),
|
||||||
},
|
},
|
||||||
@@ -117,7 +118,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "HTTPS container",
|
name: "HTTPS container",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "secure.example.com:8000/https"),
|
newContainer("10.210.0.2", "secure.example.com:8000/https"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: `{
|
||||||
@@ -153,7 +154,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mixed HTTP and HTTPS",
|
name: "mixed HTTP and HTTPS",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2",
|
newContainer("10.210.0.2",
|
||||||
"app.example.com:8080/http",
|
"app.example.com:8080/http",
|
||||||
"web.example.com:8000/http"),
|
"web.example.com:8000/http"),
|
||||||
@@ -227,7 +228,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "container without uncloud network ignored",
|
name: "container without uncloud network ignored",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainerWithoutNetwork("ignored.example.com:8080/http"),
|
newContainerWithoutNetwork("ignored.example.com:8080/http"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: `{
|
||||||
@@ -252,7 +253,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "container with invalid port ignored",
|
name: "container with invalid port ignored",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "invalid-port"),
|
newContainer("10.210.0.2", "invalid-port"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: `{
|
||||||
@@ -277,7 +278,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "containers with unsupported protocols ignored",
|
name: "containers with unsupported protocols ignored",
|
||||||
containers: []api.Container{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "5000/tcp"),
|
newContainer("10.210.0.2", "5000/tcp"),
|
||||||
newContainer("10.210.0.3", "5000/udp"),
|
newContainer("10.210.0.3", "5000/udp"),
|
||||||
newContainer("10.210.0.4", "80:8080/tcp@host"),
|
newContainer("10.210.0.4", "80:8080/tcp@host"),
|
||||||
@@ -322,10 +323,9 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContainer(ip string, ports ...string) api.Container {
|
func newContainer(ip string, ports ...string) api.ServiceContainer {
|
||||||
portsLabel := strings.Join(ports, ",")
|
portsLabel := strings.Join(ports, ",")
|
||||||
return api.Container{
|
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
||||||
ContainerJSON: types.ContainerJSON{
|
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{},
|
ContainerJSONBase: &types.ContainerJSONBase{},
|
||||||
NetworkSettings: &types.NetworkSettings{
|
NetworkSettings: &types.NetworkSettings{
|
||||||
Networks: map[string]*network.EndpointSettings{
|
Networks: map[string]*network.EndpointSettings{
|
||||||
@@ -339,14 +339,12 @@ func newContainer(ip string, ports ...string) api.Container {
|
|||||||
api.LabelServicePorts: portsLabel,
|
api.LabelServicePorts: portsLabel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContainerWithoutNetwork(ports ...string) api.Container {
|
func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
||||||
portsLabel := strings.Join(ports, ",")
|
portsLabel := strings.Join(ports, ",")
|
||||||
return api.Container{
|
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
||||||
ContainerJSON: types.ContainerJSON{
|
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{},
|
ContainerJSONBase: &types.ContainerJSONBase{},
|
||||||
NetworkSettings: &types.NetworkSettings{
|
NetworkSettings: &types.NetworkSettings{
|
||||||
Networks: map[string]*network.EndpointSettings{
|
Networks: map[string]*network.EndpointSettings{
|
||||||
@@ -360,6 +358,5 @@ func newContainerWithoutNetwork(ports ...string) api.Container {
|
|||||||
api.LabelServicePorts: portsLabel,
|
api.LabelServicePorts: portsLabel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -89,15 +90,20 @@ func (c *Controller) Run(ctx context.Context) error {
|
|||||||
// filterAvailableContainers filters out containers from this machine that are likely unavailable. The availability
|
// filterAvailableContainers filters out containers from this machine that are likely unavailable. The availability
|
||||||
// is determined by the cluster membership state of the machine that the container is running on.
|
// is determined by the cluster membership state of the machine that the container is running on.
|
||||||
// TODO: implement machine membership check using Corrossion Admin client.
|
// TODO: implement machine membership check using Corrossion Admin client.
|
||||||
func (c *Controller) filterAvailableContainers(containerRecords []store.ContainerRecord) ([]api.Container, error) {
|
func (c *Controller) filterAvailableContainers(
|
||||||
containers := make([]api.Container, len(containerRecords))
|
containerRecords []store.ContainerRecord,
|
||||||
|
) ([]api.ServiceContainer, error) {
|
||||||
|
containers := make([]api.ServiceContainer, len(containerRecords))
|
||||||
for i, cr := range containerRecords {
|
for i, cr := range containerRecords {
|
||||||
containers[i] = cr.Container
|
containers[i] = api.ServiceContainer{
|
||||||
|
Container: cr.Container,
|
||||||
|
// TODO: restore ServiceSpec from the container record once it's saved in the store.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) generateConfig(containers []api.Container) error {
|
func (c *Controller) generateConfig(containers []api.ServiceContainer) error {
|
||||||
config, err := GenerateConfig(containers, c.verifyResponse)
|
config, err := GenerateConfig(containers, c.verifyResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -369,6 +369,75 @@ func (c *Client) CreateServiceContainer(
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InspectServiceContainer returns the container information and service specification that was used to create the
|
||||||
|
// container with the given ID.
|
||||||
|
func (c *Client) InspectServiceContainer(ctx context.Context, id string) (api.ServiceContainer, error) {
|
||||||
|
var resp api.ServiceContainer
|
||||||
|
|
||||||
|
grpcResp, err := c.grpcClient.InspectServiceContainer(ctx, &pb.InspectContainerRequest{Id: id})
|
||||||
|
if err != nil {
|
||||||
|
if status.Convert(err).Code() == codes.NotFound {
|
||||||
|
return resp, errdefs.NotFound(err)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(grpcResp.Container, &resp.Container); err != nil {
|
||||||
|
return resp, fmt.Errorf("unmarshal container: %w", err)
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(grpcResp.ServiceSpec, &resp.ServiceSpec); err != nil {
|
||||||
|
return resp, fmt.Errorf("unmarshal service spec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MachineServiceContainers struct {
|
||||||
|
Metadata *pb.Metadata
|
||||||
|
Containers []api.ServiceContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServiceContainers returns all containers on requested machines that belong to the service with the given
|
||||||
|
// name or ID. If serviceNameOrID is empty, all service containers are returned.
|
||||||
|
func (c *Client) ListServiceContainers(
|
||||||
|
ctx context.Context, serviceNameOrID string, opts container.ListOptions,
|
||||||
|
) ([]MachineServiceContainers, error) {
|
||||||
|
optsBytes, err := json.Marshal(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.grpcClient.ListServiceContainers(ctx, &pb.ListServiceContainersRequest{
|
||||||
|
ServiceId: serviceNameOrID,
|
||||||
|
Options: optsBytes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
machineContainers := make([]MachineServiceContainers, len(resp.Messages))
|
||||||
|
for i, msg := range resp.Messages {
|
||||||
|
machineContainers[i].Metadata = msg.Metadata
|
||||||
|
if msg.Metadata != nil && msg.Metadata.Error != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
containers := make([]api.ServiceContainer, len(msg.Containers))
|
||||||
|
for j, sc := range msg.Containers {
|
||||||
|
if err = json.Unmarshal(sc.Container, &containers[j].Container); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal container: %w", err)
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(sc.ServiceSpec, &containers[j].ServiceSpec); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal service spec: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
machineContainers[i].Containers = containers
|
||||||
|
}
|
||||||
|
|
||||||
|
return machineContainers, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
|
// 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.
|
// 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 {
|
func (c *Client) RemoveServiceContainer(ctx context.Context, id string, opts container.RemoveOptions) error {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -455,6 +456,125 @@ func (s *Server) CreateServiceContainer(
|
|||||||
return &pb.CreateContainerResponse{Response: respBytes}, nil
|
return &pb.CreateContainerResponse{Response: respBytes}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InspectServiceContainer returns the container information and service specification that was used to create the
|
||||||
|
// container with the given ID.
|
||||||
|
func (s *Server) InspectServiceContainer(
|
||||||
|
ctx context.Context, req *pb.InspectContainerRequest,
|
||||||
|
) (*pb.ServiceContainer, error) {
|
||||||
|
ctr, err := s.client.ContainerInspect(ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
return nil, status.Errorf(codes.NotFound, err.Error())
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrBytes, err := json.Marshal(ctr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "marshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var specBytes []byte
|
||||||
|
err = s.db.QueryRowContext(ctx, `SELECT service_spec FROM containers WHERE id = $1`, ctr.ID).Scan(&specBytes)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "service spec not found for container: '%s'", ctr.ID)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Internal, "get service spec for container '%s' from machine database: %v",
|
||||||
|
ctr.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ServiceContainer{
|
||||||
|
Container: ctrBytes,
|
||||||
|
ServiceSpec: specBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServiceContainers returns all containers that belong to the service with the given name or ID.
|
||||||
|
// If req.ServiceId is empty, all service containers are returned.
|
||||||
|
func (s *Server) ListServiceContainers(
|
||||||
|
ctx context.Context, req *pb.ListServiceContainersRequest,
|
||||||
|
) (*pb.ListServiceContainersResponse, error) {
|
||||||
|
var opts container.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
|
||||||
|
} else {
|
||||||
|
opts.Filters = filters.NewArgs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only uncloud-managed containers that belong to some service.
|
||||||
|
opts.Filters.Add("label", api.LabelServiceID)
|
||||||
|
opts.Filters.Add("label", api.LabelManaged)
|
||||||
|
|
||||||
|
containerSummaries, err := s.client.ContainerList(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
containers := make([]*pb.ServiceContainer, 0, len(containerSummaries))
|
||||||
|
for _, cs := range containerSummaries {
|
||||||
|
if req.ServiceId != "" &&
|
||||||
|
cs.Labels[api.LabelServiceID] != req.ServiceId && cs.Labels[api.LabelServiceName] != req.ServiceId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr, err := s.client.ContainerInspect(ctx, cs.ID)
|
||||||
|
if err != nil {
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
// The listed container may have been removed while we were inspecting other containers.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Internal, "inspect container %s: %v", cs.ID, err)
|
||||||
|
}
|
||||||
|
ctrBytes, err := json.Marshal(ctr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "marshal container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var specBytes []byte
|
||||||
|
err = s.db.QueryRowContext(ctx, `SELECT service_spec FROM containers WHERE id = $1`, ctr.ID).Scan(&specBytes)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
// If this happens, there is a bug in the code, or someone manually removed the container from the DB,
|
||||||
|
// or created a managed container out of band.
|
||||||
|
slog.Error("Service container not found in machine database.", "id", ctr.ID)
|
||||||
|
// Just ignore such a container to not fail the list operation as it's not easily recoverable.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Internal, "get service spec for container '%s' from machine database: %v",
|
||||||
|
ctr.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containers = append(containers, &pb.ServiceContainer{
|
||||||
|
Container: ctrBytes,
|
||||||
|
ServiceSpec: specBytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListServiceContainersResponse{
|
||||||
|
Messages: []*pb.MachineServiceContainers{
|
||||||
|
{
|
||||||
|
Containers: containers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
|
// 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
|
// The difference between this method and RemoveContainer is that it also removes the container from the machine
|
||||||
// database.
|
// database.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/siderolabs/grpc-proxy/proxy"
|
"github.com/siderolabs/grpc-proxy/proxy"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -751,10 +752,11 @@ func (m *Machine) InspectService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctr := api.ServiceContainer{Container: records[0].Container}
|
||||||
svc := &pb.Service{
|
svc := &pb.Service{
|
||||||
Id: records[0].Container.ServiceID(),
|
Id: ctr.ServiceID(),
|
||||||
Name: records[0].Container.ServiceName(),
|
Name: ctr.ServiceName(),
|
||||||
Mode: records[0].Container.ServiceMode(),
|
Mode: ctr.ServiceMode(),
|
||||||
Containers: containers,
|
Containers: containers,
|
||||||
}
|
}
|
||||||
return &pb.InspectServiceResponse{Service: svc}, nil
|
return &pb.InspectServiceResponse{Service: svc}, nil
|
||||||
|
|||||||
+6
-5
@@ -3,10 +3,11 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
@@ -21,10 +22,10 @@ type ContainerClient interface {
|
|||||||
CreateContainer(
|
CreateContainer(
|
||||||
ctx context.Context, serviceID string, spec ServiceSpec, machineID string,
|
ctx context.Context, serviceID string, spec ServiceSpec, machineID string,
|
||||||
) (container.CreateResponse, error)
|
) (container.CreateResponse, error)
|
||||||
InspectContainer(ctx context.Context, serviceID, containerID string) (MachineContainer, error)
|
InspectContainer(ctx context.Context, serviceNameOrID, containerNameOrID string) (MachineServiceContainer, error)
|
||||||
RemoveContainer(ctx context.Context, serviceID, containerID string, opts container.RemoveOptions) error
|
RemoveContainer(ctx context.Context, serviceNameOrID, containerNameOrID string, opts container.RemoveOptions) error
|
||||||
StartContainer(ctx context.Context, serviceID, containerID string) error
|
StartContainer(ctx context.Context, serviceNameOrID, containerNameOrID string) error
|
||||||
StopContainer(ctx context.Context, serviceID, containerID string, opts container.StopOptions) error
|
StopContainer(ctx context.Context, serviceNameOrID, containerNameOrID string, opts container.StopOptions) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSClient interface {
|
type DNSClient interface {
|
||||||
|
|||||||
+68
-70
@@ -1,11 +1,13 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,73 +23,6 @@ type Container struct {
|
|||||||
types.ContainerJSON
|
types.ContainerJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameWithoutSlash returns the container name without the leading slash.
|
|
||||||
// TODO: modify Name in original ContainerJSON structure when inspecting a Docker container and get rid of this method.
|
|
||||||
func (c *Container) NameWithoutSlash() string {
|
|
||||||
return c.Name[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceID returns the ID of the service this container belongs to.
|
|
||||||
func (c *Container) ServiceID() string {
|
|
||||||
return c.Config.Labels[LabelServiceID]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceName returns the name of the service this container belongs to.
|
|
||||||
func (c *Container) ServiceName() string {
|
|
||||||
return c.Config.Labels[LabelServiceName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceMode returns the replication mode of the service this container belongs to.
|
|
||||||
func (c *Container) ServiceMode() string {
|
|
||||||
return c.Config.Labels[LabelServiceMode]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServicePorts returns the ports this container publishes as part of its service.
|
|
||||||
func (c *Container) ServicePorts() ([]PortSpec, error) {
|
|
||||||
encoded, ok := c.Config.Labels[LabelServicePorts]
|
|
||||||
if !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(encoded) == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
publishPorts := strings.Split(encoded, ",")
|
|
||||||
ports := make([]PortSpec, len(publishPorts))
|
|
||||||
for i, p := range publishPorts {
|
|
||||||
port, err := ParsePortSpec(strings.TrimSpace(p))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ports[i] = port
|
|
||||||
}
|
|
||||||
|
|
||||||
return ports, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceSpec constructs a service spec from the container's configuration.
|
|
||||||
func (c *Container) ServiceSpec() (ServiceSpec, error) {
|
|
||||||
ports, err := c.ServicePorts()
|
|
||||||
if err != nil {
|
|
||||||
return ServiceSpec{}, fmt.Errorf("get service ports: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: many properties on the container such as Config.Cmd or Config.Entrypoint are populated when the container
|
|
||||||
// is created. Figure out how to get a spec that is equal to the initial spec.
|
|
||||||
return ServiceSpec{
|
|
||||||
Container: ContainerSpec{
|
|
||||||
Command: c.Config.Cmd,
|
|
||||||
Entrypoint: c.Config.Entrypoint,
|
|
||||||
Image: c.Config.Image,
|
|
||||||
Init: c.HostConfig.Init,
|
|
||||||
Volumes: c.HostConfig.Binds,
|
|
||||||
},
|
|
||||||
Mode: c.ServiceMode(),
|
|
||||||
Name: c.ServiceName(),
|
|
||||||
Ports: ports,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Healthy determines if the container is running and healthy.
|
// Healthy determines if the container is running and healthy.
|
||||||
// A running container with no health check configured is considered healthy.
|
// A running container with no health check configured is considered healthy.
|
||||||
func (c *Container) Healthy() bool {
|
func (c *Container) Healthy() bool {
|
||||||
@@ -156,8 +91,71 @@ func (c *Container) HumanState() (string, error) {
|
|||||||
c.State.ExitCode, units.HumanDuration(time.Now().UTC().Sub(finishedAt))), nil
|
c.State.ExitCode, units.HumanDuration(time.Now().UTC().Sub(finishedAt))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) UnmarshalJSON(data []byte) error {
|
||||||
|
// A temporary type that's identical to Container but doesn't have the UnmarshalJSON method.
|
||||||
|
type ContainerAlias Container
|
||||||
|
|
||||||
|
var temp ContainerAlias
|
||||||
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = Container(temp)
|
||||||
|
c.Name = strings.TrimPrefix(c.Name, "/")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceContainer struct {
|
||||||
|
Container
|
||||||
|
ServiceSpec ServiceSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
type MachineContainer struct {
|
||||||
|
MachineID string
|
||||||
|
Container Container
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceID returns the ID of the service this container belongs to.
|
||||||
|
func (c *ServiceContainer) ServiceID() string {
|
||||||
|
return c.Config.Labels[LabelServiceID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceName returns the name of the service this container belongs to.
|
||||||
|
func (c *ServiceContainer) ServiceName() string {
|
||||||
|
return c.Config.Labels[LabelServiceName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceMode returns the replication mode of the service this container belongs to.
|
||||||
|
func (c *ServiceContainer) ServiceMode() string {
|
||||||
|
return c.Config.Labels[LabelServiceMode]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServicePorts returns the ports this container publishes as part of its service.
|
||||||
|
func (c *ServiceContainer) ServicePorts() ([]PortSpec, error) {
|
||||||
|
encoded, ok := c.Config.Labels[LabelServicePorts]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(encoded) == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
publishPorts := strings.Split(encoded, ",")
|
||||||
|
ports := make([]PortSpec, len(publishPorts))
|
||||||
|
for i, p := range publishPorts {
|
||||||
|
port, err := ParsePortSpec(strings.TrimSpace(p))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ports[i] = port
|
||||||
|
}
|
||||||
|
|
||||||
|
return ports, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ConflictingServicePorts returns a list of service ports that conflict with the given ports.
|
// ConflictingServicePorts returns a list of service ports that conflict with the given ports.
|
||||||
func (c *Container) ConflictingServicePorts(ports []PortSpec) ([]PortSpec, error) {
|
func (c *ServiceContainer) ConflictingServicePorts(ports []PortSpec) ([]PortSpec, error) {
|
||||||
svcPorts, err := c.ServicePorts()
|
svcPorts, err := c.ServicePorts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get service ports: %w", err)
|
return nil, fmt.Errorf("get service ports: %w", err)
|
||||||
|
|||||||
@@ -1,60 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"net/netip"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainer_ServiceSpec(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
init := true
|
|
||||||
ctr := &Container{ContainerJSON: types.ContainerJSON{
|
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{
|
|
||||||
HostConfig: &container.HostConfig{
|
|
||||||
Binds: []string{"/host/path:/container/path"},
|
|
||||||
Init: &init,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Config: &container.Config{
|
|
||||||
Cmd: []string{"/app/server"},
|
|
||||||
Image: "app:latest",
|
|
||||||
Labels: map[string]string{
|
|
||||||
LabelServiceID: "test-service-id",
|
|
||||||
LabelServiceName: "test-service-name",
|
|
||||||
LabelServicePorts: "app.example.com:8000/https",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
|
|
||||||
expectedSpec := ServiceSpec{
|
|
||||||
Container: ContainerSpec{
|
|
||||||
Command: []string{"/app/server"},
|
|
||||||
Image: "app:latest",
|
|
||||||
Init: &init,
|
|
||||||
Volumes: []string{"/host/path:/container/path"},
|
|
||||||
},
|
|
||||||
Name: "test-service-name",
|
|
||||||
Ports: []PortSpec{
|
|
||||||
{
|
|
||||||
Hostname: "app.example.com",
|
|
||||||
ContainerPort: 8000,
|
|
||||||
Protocol: ProtocolHTTPS,
|
|
||||||
Mode: PortModeIngress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
spec, err := ctr.ServiceSpec()
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, reflect.DeepEqual(spec, expectedSpec))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_Healthy(t *testing.T) {
|
func TestContainer_Healthy(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -325,13 +280,13 @@ func TestContainer_ConflictingServicePorts(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ctr := &Container{ContainerJSON: types.ContainerJSON{
|
ctr := &ServiceContainer{Container: Container{ContainerJSON: types.ContainerJSON{
|
||||||
Config: &container.Config{
|
Config: &container.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
LabelServicePorts: tt.containerPorts,
|
LabelServicePorts: tt.containerPorts,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}}
|
||||||
|
|
||||||
got, err := ctr.ConflictingServicePorts(tt.checkPorts)
|
got, err := ctr.ConflictingServicePorts(tt.checkPorts)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
|
|||||||
+11
-10
@@ -5,12 +5,13 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/distribution/reference"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"maps"
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -206,12 +207,12 @@ type Service struct {
|
|||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Mode string
|
Mode string
|
||||||
Containers []MachineContainer
|
Containers []MachineServiceContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
type MachineContainer struct {
|
type MachineServiceContainer struct {
|
||||||
MachineID string
|
MachineID string
|
||||||
Container Container
|
Container ServiceContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoints returns the exposed HTTP and HTTPS endpoints of the service.
|
// Endpoints returns the exposed HTTP and HTTPS endpoints of the service.
|
||||||
@@ -261,7 +262,7 @@ func (s *Service) Endpoints() []string {
|
|||||||
|
|
||||||
func ServiceFromProto(s *pb.Service) (Service, error) {
|
func ServiceFromProto(s *pb.Service) (Service, error) {
|
||||||
var err error
|
var err error
|
||||||
containers := make([]MachineContainer, len(s.Containers))
|
containers := make([]MachineServiceContainer, len(s.Containers))
|
||||||
for i, sc := range s.Containers {
|
for i, sc := range s.Containers {
|
||||||
containers[i], err = machineContainerFromProto(sc)
|
containers[i], err = machineContainerFromProto(sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -277,14 +278,14 @@ func ServiceFromProto(s *pb.Service) (Service, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func machineContainerFromProto(sc *pb.Service_Container) (MachineContainer, error) {
|
func machineContainerFromProto(sc *pb.Service_Container) (MachineServiceContainer, error) {
|
||||||
var c Container
|
var c Container
|
||||||
if err := json.Unmarshal(sc.Container, &c); err != nil {
|
if err := json.Unmarshal(sc.Container, &c); err != nil {
|
||||||
return MachineContainer{}, fmt.Errorf("unmarshal container: %w", err)
|
return MachineServiceContainer{}, fmt.Errorf("unmarshal container: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return MachineContainer{
|
return MachineServiceContainer{
|
||||||
MachineID: sc.MachineId,
|
MachineID: sc.MachineId,
|
||||||
Container: c,
|
Container: ServiceContainer{Container: c},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a client for the machine API.
|
// Client is a client for the machine API.
|
||||||
|
|||||||
+17
-20
@@ -190,29 +190,26 @@ func toPullProgressEvent(jm jsonmessage.JSONMessage) *progress.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InspectContainer returns the information about the specified container within the service.
|
// InspectContainer returns the information about the specified container within the service.
|
||||||
func (cli *Client) InspectContainer(ctx context.Context, serviceID, containerID string) (api.MachineContainer, error) {
|
func (cli *Client) InspectContainer(
|
||||||
var ctr api.MachineContainer
|
ctx context.Context, serviceNameOrID, containerNameOrID string,
|
||||||
|
) (api.MachineServiceContainer, error) {
|
||||||
svc, err := cli.InspectService(ctx, serviceID)
|
svc, err := cli.InspectService(ctx, serviceNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctr, fmt.Errorf("inspect service: %w", err)
|
return api.MachineServiceContainer{}, fmt.Errorf("inspect service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range svc.Containers {
|
for _, c := range svc.Containers {
|
||||||
if c.Container.ID == containerID || c.Container.NameWithoutSlash() == containerID {
|
if c.Container.ID == containerNameOrID || c.Container.Name == containerNameOrID {
|
||||||
ctr = c
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ctr.MachineID == "" {
|
|
||||||
return ctr, api.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctr, nil
|
return api.MachineServiceContainer{}, api.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartContainer starts the specified container within the service.
|
// StartContainer starts the specified container within the service.
|
||||||
func (cli *Client) StartContainer(ctx context.Context, serviceID, containerID string) error {
|
func (cli *Client) StartContainer(ctx context.Context, serviceNameOrID, containerNameOrID string) error {
|
||||||
ctr, err := cli.InspectContainer(ctx, serviceID, containerID)
|
ctr, err := cli.InspectContainer(ctx, serviceNameOrID, containerNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -224,7 +221,7 @@ func (cli *Client) StartContainer(ctx context.Context, serviceID, containerID st
|
|||||||
ctx = proxyToMachine(ctx, machine.Machine)
|
ctx = proxyToMachine(ctx, machine.Machine)
|
||||||
|
|
||||||
pw := progress.ContextWriter(ctx)
|
pw := progress.ContextWriter(ctx)
|
||||||
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.NameWithoutSlash(), machine.Machine.Name)
|
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.Name, machine.Machine.Name)
|
||||||
|
|
||||||
pw.Event(progress.StartingEvent(eventID))
|
pw.Event(progress.StartingEvent(eventID))
|
||||||
if err = cli.Docker.StartContainer(ctx, ctr.Container.ID, container.StartOptions{}); err != nil {
|
if err = cli.Docker.StartContainer(ctx, ctr.Container.ID, container.StartOptions{}); err != nil {
|
||||||
@@ -237,9 +234,9 @@ func (cli *Client) StartContainer(ctx context.Context, serviceID, containerID st
|
|||||||
|
|
||||||
// StopContainer stops the specified container within the service.
|
// StopContainer stops the specified container within the service.
|
||||||
func (cli *Client) StopContainer(
|
func (cli *Client) StopContainer(
|
||||||
ctx context.Context, serviceID, containerID string, opts container.StopOptions,
|
ctx context.Context, serviceNameOrID, containerNameOrID string, opts container.StopOptions,
|
||||||
) error {
|
) error {
|
||||||
ctr, err := cli.InspectContainer(ctx, serviceID, containerID)
|
ctr, err := cli.InspectContainer(ctx, serviceNameOrID, containerNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -251,7 +248,7 @@ func (cli *Client) StopContainer(
|
|||||||
ctx = proxyToMachine(ctx, machine.Machine)
|
ctx = proxyToMachine(ctx, machine.Machine)
|
||||||
|
|
||||||
pw := progress.ContextWriter(ctx)
|
pw := progress.ContextWriter(ctx)
|
||||||
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.NameWithoutSlash(), machine.Machine.Name)
|
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.Name, machine.Machine.Name)
|
||||||
|
|
||||||
pw.Event(progress.StoppingEvent(eventID))
|
pw.Event(progress.StoppingEvent(eventID))
|
||||||
if err = cli.Docker.StopContainer(ctx, ctr.Container.ID, opts); err != nil {
|
if err = cli.Docker.StopContainer(ctx, ctr.Container.ID, opts); err != nil {
|
||||||
@@ -264,9 +261,9 @@ func (cli *Client) StopContainer(
|
|||||||
|
|
||||||
// RemoveContainer removes the specified container within the service.
|
// RemoveContainer removes the specified container within the service.
|
||||||
func (cli *Client) RemoveContainer(
|
func (cli *Client) RemoveContainer(
|
||||||
ctx context.Context, serviceID, containerNameOrID string, opts container.RemoveOptions,
|
ctx context.Context, serviceNameOrID, containerNameOrID string, opts container.RemoveOptions,
|
||||||
) error {
|
) error {
|
||||||
ctr, err := cli.InspectContainer(ctx, serviceID, containerNameOrID)
|
ctr, err := cli.InspectContainer(ctx, serviceNameOrID, containerNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -278,7 +275,7 @@ func (cli *Client) RemoveContainer(
|
|||||||
ctx = proxyToMachine(ctx, machine.Machine)
|
ctx = proxyToMachine(ctx, machine.Machine)
|
||||||
|
|
||||||
pw := progress.ContextWriter(ctx)
|
pw := progress.ContextWriter(ctx)
|
||||||
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.NameWithoutSlash(), machine.Machine.Name)
|
eventID := fmt.Sprintf("Container %s on %s", ctr.Container.Name, machine.Machine.Name)
|
||||||
|
|
||||||
pw.Event(progress.RemovingEvent(eventID))
|
pw.Event(progress.RemovingEvent(eventID))
|
||||||
if err = cli.Docker.RemoveServiceContainer(ctx, ctr.Container.ID, opts); err != nil {
|
if err = cli.Docker.RemoveServiceContainer(ctx, ctr.Container.ID, opts); err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package deploy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +12,8 @@ const ContainerUpToDate ContainerSpecStatus = "up-to-date"
|
|||||||
const ContainerNeedsUpdate ContainerSpecStatus = "needs-update"
|
const ContainerNeedsUpdate ContainerSpecStatus = "needs-update"
|
||||||
const ContainerNeedsRecreate ContainerSpecStatus = "needs-recreate"
|
const ContainerNeedsRecreate ContainerSpecStatus = "needs-recreate"
|
||||||
|
|
||||||
func CompareContainerToSpec(ctr api.Container, spec api.ServiceSpec) (ContainerSpecStatus, error) {
|
func CompareContainerToSpec(ctr api.ServiceContainer, spec api.ServiceSpec) (ContainerSpecStatus, error) {
|
||||||
|
// TODO: replace the hash comparison with a more detailed comparison of ctr.ServiceSpec and spec.
|
||||||
specHash, err := spec.ImmutableHash()
|
specHash, err := spec.ImmutableHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("calculate immutable hash for service spec: %w", err)
|
return "", fmt.Errorf("calculate immutable hash for service spec: %w", err)
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package deploy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"math/rand/v2"
|
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Strategy defines how a service should be deployed or updated. Different implementations can provide various
|
// Strategy defines how a service should be deployed or updated. Different implementations can provide various
|
||||||
@@ -89,7 +90,7 @@ func (s *RollingStrategy) planReplicated(
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Organise existing containers by machine.
|
// Organise existing containers by machine.
|
||||||
containersOnMachine := make(map[string][]api.Container)
|
containersOnMachine := make(map[string][]api.ServiceContainer)
|
||||||
upToDateContainersOnMachine := make(map[string]int)
|
upToDateContainersOnMachine := make(map[string]int)
|
||||||
containerSpecStatuses := make(map[string]ContainerSpecStatus)
|
containerSpecStatuses := make(map[string]ContainerSpecStatus)
|
||||||
if svc != nil {
|
if svc != nil {
|
||||||
@@ -111,7 +112,7 @@ func (s *RollingStrategy) planReplicated(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort containers such that running containers with the desired spec are first.
|
// Sort containers such that running containers with the desired spec are first.
|
||||||
slices.SortFunc(svc.Containers, func(c1, c2 api.MachineContainer) int {
|
slices.SortFunc(svc.Containers, func(c1, c2 api.MachineServiceContainer) int {
|
||||||
if status, ok := containerSpecStatuses[c1.Container.ID]; ok && status == ContainerUpToDate {
|
if status, ok := containerSpecStatuses[c1.Container.ID]; ok && status == ContainerUpToDate {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@@ -222,7 +223,7 @@ func (s *RollingStrategy) planGlobal(
|
|||||||
// Map machineID to service containers on that machine. For the global mode, there should be at most one
|
// Map machineID to service containers on that machine. For the global mode, there should be at most one
|
||||||
// container per machine but we use a slice to handle multiple containers that may exist due to a bug
|
// container per machine but we use a slice to handle multiple containers that may exist due to a bug
|
||||||
// or interruption in the previous deployment.
|
// or interruption in the previous deployment.
|
||||||
containersOnMachine := make(map[string][]api.MachineContainer)
|
containersOnMachine := make(map[string][]api.MachineServiceContainer)
|
||||||
if svc != nil {
|
if svc != nil {
|
||||||
for _, c := range svc.Containers {
|
for _, c := range svc.Containers {
|
||||||
containersOnMachine[c.MachineID] = append(containersOnMachine[c.MachineID], c)
|
containersOnMachine[c.MachineID] = append(containersOnMachine[c.MachineID], c)
|
||||||
@@ -271,7 +272,7 @@ func (s *RollingStrategy) planGlobal(
|
|||||||
// It ensures exactly one container with the desired spec is running on the machine by creating a new container and
|
// It ensures exactly one container with the desired spec is running on the machine by creating a new container and
|
||||||
// removing old ones. If there is a host port conflict, it stops the old container before starting a new one.
|
// removing old ones. If there is a host port conflict, it stops the old container before starting a new one.
|
||||||
func reconcileGlobalContainer(
|
func reconcileGlobalContainer(
|
||||||
containers []api.MachineContainer, spec api.ServiceSpec, serviceID, machineID string,
|
containers []api.MachineServiceContainer, spec api.ServiceSpec, serviceID, machineID string,
|
||||||
) ([]Operation, error) {
|
) ([]Operation, error) {
|
||||||
var ops []Operation
|
var ops []Operation
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ func (cli *Client) ServiceOperationNameResolver(ctx context.Context, svc api.Ser
|
|||||||
}
|
}
|
||||||
containerNames := make(map[string]string, len(svc.Containers))
|
containerNames := make(map[string]string, len(svc.Containers))
|
||||||
for _, c := range svc.Containers {
|
for _, c := range svc.Containers {
|
||||||
containerNames[c.Container.ID] = c.Container.NameWithoutSlash()
|
containerNames[c.Container.ID] = c.Container.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewNameResolver(machineNames, containerNames), nil
|
return NewNameResolver(machineNames, containerNames), nil
|
||||||
|
|||||||
+22
-35
@@ -4,17 +4,18 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RunServiceResponse struct {
|
type RunServiceResponse struct {
|
||||||
@@ -64,8 +65,8 @@ func (cli *Client) RunService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InspectService returns detailed information about a service and its containers.
|
// InspectService returns detailed information about a service and its containers.
|
||||||
// The id parameter can be either a service ID or name.
|
// The nameOrID parameter can be either a service name or ID.
|
||||||
func (cli *Client) InspectService(ctx context.Context, id string) (api.Service, error) {
|
func (cli *Client) InspectService(ctx context.Context, nameOrID string) (api.Service, error) {
|
||||||
var svc api.Service
|
var svc api.Service
|
||||||
|
|
||||||
machines, err := cli.ListMachines(ctx)
|
machines, err := cli.ListMachines(ctx)
|
||||||
@@ -87,22 +88,16 @@ func (cli *Client) InspectService(ctx context.Context, id string) (api.Service,
|
|||||||
}
|
}
|
||||||
listCtx := metadata.NewOutgoingContext(ctx, md)
|
listCtx := metadata.NewOutgoingContext(ctx, md)
|
||||||
|
|
||||||
// List only uncloud-managed containers that belong to some service.
|
// List all service containers including stopped ones.
|
||||||
opts := container.ListOptions{
|
opts := container.ListOptions{All: true}
|
||||||
All: true,
|
machineContainers, err := cli.Docker.ListServiceContainers(listCtx, nameOrID, opts)
|
||||||
Filters: filters.NewArgs(
|
|
||||||
filters.Arg("label", api.LabelServiceID),
|
|
||||||
filters.Arg("label", api.LabelManaged),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
machineContainers, err := cli.Docker.ListContainers(listCtx, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return svc, fmt.Errorf("list containers: %w", err)
|
return svc, fmt.Errorf("list containers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all containers on all machines that belong to the specified service.
|
// Collect all containers on all machines that belong to the specified service.
|
||||||
foundByID := false
|
foundByID := false
|
||||||
var containers []api.MachineContainer
|
var containers []api.MachineServiceContainer
|
||||||
for _, mc := range machineContainers {
|
for _, mc := range machineContainers {
|
||||||
// Metadata can be nil if the request was broadcasted to only one machine.
|
// Metadata can be nil if the request was broadcasted to only one machine.
|
||||||
if mc.Metadata == nil && len(machineContainers) > 1 {
|
if mc.Metadata == nil && len(machineContainers) > 1 {
|
||||||
@@ -130,15 +125,14 @@ func (cli *Client) InspectService(ctx context.Context, id string) (api.Service,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range mc.Containers {
|
for _, ctr := range mc.Containers {
|
||||||
ctr := api.Container{ContainerJSON: c}
|
if ctr.ServiceID() == nameOrID || ctr.ServiceName() == nameOrID {
|
||||||
if ctr.ServiceID() == id || ctr.ServiceName() == id {
|
containers = append(containers, api.MachineServiceContainer{
|
||||||
containers = append(containers, api.MachineContainer{
|
|
||||||
MachineID: machineID,
|
MachineID: machineID,
|
||||||
Container: ctr,
|
Container: ctr,
|
||||||
})
|
})
|
||||||
|
|
||||||
if ctr.ServiceID() == id {
|
if ctr.ServiceID() == nameOrID {
|
||||||
foundByID = true
|
foundByID = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,15 +147,15 @@ func (cli *Client) InspectService(ctx context.Context, id string) (api.Service,
|
|||||||
// may not prevent this), or a service name might match another service's ID. In these cases, matching by ID takes
|
// may not prevent this), or a service name might match another service's ID. In these cases, matching by ID takes
|
||||||
// priority over matching by name.
|
// priority over matching by name.
|
||||||
if foundByID {
|
if foundByID {
|
||||||
containers = slices.DeleteFunc(containers, func(mc api.MachineContainer) bool {
|
containers = slices.DeleteFunc(containers, func(mc api.MachineServiceContainer) bool {
|
||||||
return mc.Container.ServiceID() != id
|
return mc.Container.ServiceID() != nameOrID
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Matched only by name but there could be multiple services with the same name.
|
// Matched only by name but there could be multiple services with the same name.
|
||||||
serviceID := containers[0].Container.ServiceID()
|
serviceID := containers[0].Container.ServiceID()
|
||||||
for _, mc := range containers[1:] {
|
for _, mc := range containers[1:] {
|
||||||
if mc.Container.ServiceID() != serviceID {
|
if mc.Container.ServiceID() != serviceID {
|
||||||
return svc, fmt.Errorf("multiple services found with name '%s', use the service ID instead", id)
|
return svc, fmt.Errorf("multiple services found with name '%s', use the service ID instead", nameOrID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,15 +267,9 @@ func (cli *Client) ListServices(ctx context.Context) ([]api.Service, error) {
|
|||||||
}
|
}
|
||||||
listCtx := metadata.NewOutgoingContext(ctx, md)
|
listCtx := metadata.NewOutgoingContext(ctx, md)
|
||||||
|
|
||||||
// List only uncloud-managed containers that belong to some service.
|
// List all containers including stopped ones.
|
||||||
opts := container.ListOptions{
|
opts := container.ListOptions{All: true}
|
||||||
All: true,
|
machineContainers, err := cli.Docker.ListServiceContainers(listCtx, "", opts)
|
||||||
Filters: filters.NewArgs(
|
|
||||||
filters.Arg("label", api.LabelServiceID),
|
|
||||||
filters.Arg("label", api.LabelManaged),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
machineContainers, err := cli.Docker.ListContainers(listCtx, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list containers: %w", err)
|
return nil, fmt.Errorf("list containers: %w", err)
|
||||||
}
|
}
|
||||||
@@ -292,13 +280,12 @@ func (cli *Client) ListServices(ctx context.Context) ([]api.Service, error) {
|
|||||||
for _, mc := range machineContainers {
|
for _, mc := range machineContainers {
|
||||||
if mc.Metadata != nil && mc.Metadata.Error != "" {
|
if mc.Metadata != nil && mc.Metadata.Error != "" {
|
||||||
// TODO: return failed machines in the response.
|
// TODO: return failed machines in the response.
|
||||||
fmt.Printf("WARNING: failed to list containers on machine '%s': %s\n",
|
fmt.Fprintf(os.Stderr, "WARNING: failed to list containers on machine '%s': %s\n",
|
||||||
mc.Metadata.Machine, mc.Metadata.Error)
|
mc.Metadata.Machine, mc.Metadata.Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range mc.Containers {
|
for _, ctr := range mc.Containers {
|
||||||
ctr := api.Container{ContainerJSON: c}
|
|
||||||
if _, ok := servicesByID[ctr.ServiceID()]; ok {
|
if _, ok := servicesByID[ctr.ServiceID()]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -1,12 +1,13 @@
|
|||||||
package e2e
|
package e2e
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertServiceMatchesSpec(t *testing.T, svc api.Service, spec api.ServiceSpec) {
|
func assertServiceMatchesSpec(t *testing.T, svc api.Service, spec api.ServiceSpec) {
|
||||||
@@ -24,15 +25,15 @@ func assertServiceMatchesSpec(t *testing.T, svc api.Service, spec api.ServiceSpe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertContainerMatchesSpec(t *testing.T, ctr api.Container, spec api.ServiceSpec) {
|
func assertContainerMatchesSpec(t *testing.T, ctr api.ServiceContainer, spec api.ServiceSpec) {
|
||||||
status, err := deploy.CompareContainerToSpec(ctr, spec)
|
status, err := deploy.CompareContainerToSpec(ctr, spec)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, deploy.ContainerUpToDate, status)
|
assert.Equal(t, deploy.ContainerUpToDate, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serviceContainersByMachine returns a map of machine ID to service containers on that machine.
|
// serviceContainersByMachine returns a map of machine ID to service containers on that machine.
|
||||||
func serviceContainersByMachine(t *testing.T, svc api.Service) map[string][]api.Container {
|
func serviceContainersByMachine(t *testing.T, svc api.Service) map[string][]api.ServiceContainer {
|
||||||
containers := make(map[string][]api.Container)
|
containers := make(map[string][]api.ServiceContainer)
|
||||||
for _, c := range svc.Containers {
|
for _, c := range svc.Containers {
|
||||||
containers[c.MachineID] = append(containers[c.MachineID], c.Container)
|
containers[c.MachineID] = append(containers[c.MachineID], c.Container)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package e2e
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
@@ -14,9 +18,6 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newServiceID() string {
|
func newServiceID() string {
|
||||||
@@ -246,7 +247,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
assert.NotEqual(t, oldContainerID, ctr.Container.ID,
|
assert.NotEqual(t, oldContainerID, ctr.Container.ID,
|
||||||
"Container on machine %s should have been updated", machine.Machine.Name)
|
"Container on machine %s should have been updated", machine.Machine.Name)
|
||||||
|
|
||||||
svcSpec, err := ctr.Container.ServiceSpec()
|
svcSpec := ctr.Container.ServiceSpec
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, svcSpec.Container.Init)
|
assert.NotNil(t, svcSpec.Container.Init)
|
||||||
assert.True(t, *svcSpec.Container.Init,
|
assert.True(t, *svcSpec.Container.Init,
|
||||||
@@ -279,7 +280,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
|
|
||||||
// Verify all containers are updated with a published port.
|
// Verify all containers are updated with a published port.
|
||||||
for _, ctr := range svc.Containers {
|
for _, ctr := range svc.Containers {
|
||||||
svcSpec, err := ctr.Container.ServiceSpec()
|
svcSpec := ctr.Container.ServiceSpec
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Nil(t, svcSpec.Container.Init,
|
assert.Nil(t, svcSpec.Container.Init,
|
||||||
"Container on machine %s should have init disabled", ctr.MachineID)
|
"Container on machine %s should have init disabled", ctr.MachineID)
|
||||||
@@ -382,7 +383,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
assert.Len(t, svc.Containers, 2)
|
assert.Len(t, svc.Containers, 2)
|
||||||
|
|
||||||
// Existing container ctr0 on machine #0 should be left unchanged.
|
// Existing container ctr0 on machine #0 should be left unchanged.
|
||||||
var ctr2 api.MachineContainer
|
var ctr2 api.MachineServiceContainer
|
||||||
if ctr0.Container.ID == svc.Containers[0].Container.ID {
|
if ctr0.Container.ID == svc.Containers[0].Container.ID {
|
||||||
ctr2 = svc.Containers[1]
|
ctr2 = svc.Containers[1]
|
||||||
} else {
|
} else {
|
||||||
@@ -652,7 +653,7 @@ func TestServiceLifecycle(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ctr := mc.Container
|
ctr := mc.Container
|
||||||
|
|
||||||
assert.True(t, strings.HasPrefix(ctr.Name, "/container-spec-defaults-"))
|
assert.True(t, strings.HasPrefix(ctr.Name, "container-spec-defaults-"))
|
||||||
assert.Equal(t, "portainer/pause:latest", ctr.Config.Image)
|
assert.Equal(t, "portainer/pause:latest", ctr.Config.Image)
|
||||||
|
|
||||||
// Verify default settings.
|
// Verify default settings.
|
||||||
@@ -727,7 +728,7 @@ func TestServiceLifecycle(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ctr := mc.Container
|
ctr := mc.Container
|
||||||
|
|
||||||
assert.True(t, strings.HasPrefix(ctr.Name, "/container-spec-full-"))
|
assert.True(t, strings.HasPrefix(ctr.Name, "container-spec-full-"))
|
||||||
assert.Equal(t, "portainer/pause:latest", ctr.Config.Image)
|
assert.Equal(t, "portainer/pause:latest", ctr.Config.Image)
|
||||||
|
|
||||||
assert.EqualValues(t, spec.Container.Command, ctr.Config.Cmd)
|
assert.EqualValues(t, spec.Container.Command, ctr.Config.Cmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user