mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
feat(pre-deploy): add support for pre-deploy hook containers in service management, sync *all* containers to store
This commit is contained in:
@@ -420,34 +420,6 @@ func (c *Client) RemoveVolume(ctx context.Context, id string, force bool) error
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateServiceContainer creates a new container for the service with the given specifications.
|
||||
func (c *Client) CreateServiceContainer(
|
||||
ctx context.Context, serviceID string, spec api.ServiceSpec, containerName string,
|
||||
) (container.CreateResponse, error) {
|
||||
var resp container.CreateResponse
|
||||
|
||||
specBytes, err := json.Marshal(spec)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("marshal service spec: %w", err)
|
||||
}
|
||||
grpcResp, err := c.GRPCClient.CreateServiceContainer(ctx, &pb.CreateServiceContainerRequest{
|
||||
ServiceId: serviceID,
|
||||
ServiceSpec: specBytes,
|
||||
ContainerName: containerName,
|
||||
})
|
||||
if err != nil {
|
||||
if status.Convert(err).Code() == codes.NotFound {
|
||||
return resp, errdefs.NotFound(err)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(grpcResp.Response, &resp); err != nil {
|
||||
return resp, fmt.Errorf("unmarshal gRPC response: %w", err)
|
||||
}
|
||||
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) {
|
||||
@@ -474,10 +446,13 @@ func (c *Client) InspectServiceContainer(ctx context.Context, id string) (api.Se
|
||||
type MachineServiceContainers struct {
|
||||
Metadata *pb.Metadata
|
||||
Containers []api.ServiceContainer
|
||||
// HookContainers are one-shot containers for deployment hooks (e.g. pre-deploy).
|
||||
HookContainers []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.
|
||||
// Set opts.All to true to include hook containers.
|
||||
func (c *Client) ListServiceContainers(
|
||||
ctx context.Context, serviceNameOrID string, opts container.ListOptions,
|
||||
) ([]MachineServiceContainers, error) {
|
||||
@@ -501,22 +476,33 @@ func (c *Client) ListServiceContainers(
|
||||
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, err = serviceContainersFromProto(msg.Containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
machineContainers[i].HookContainers, err = serviceContainersFromProto(msg.HookContainers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
machineContainers[i].Containers = containers
|
||||
}
|
||||
|
||||
return machineContainers, nil
|
||||
}
|
||||
|
||||
// serviceContainersFromProto converts a slice of protobuf service containers to api.ServiceContainer.
|
||||
func serviceContainersFromProto(pbContainers []*pb.ServiceContainer) ([]api.ServiceContainer, error) {
|
||||
containers := make([]api.ServiceContainer, len(pbContainers))
|
||||
for i, sc := range pbContainers {
|
||||
if err := json.Unmarshal(sc.Container, &containers[i].Container); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal container: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(sc.ServiceSpec, &containers[i].ServiceSpec); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal service spec: %w", err)
|
||||
}
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
|
||||
// A service container is a container that has been created with CreateServiceContainer.
|
||||
func (c *Client) RemoveServiceContainer(ctx context.Context, id string, opts container.RemoveOptions) error {
|
||||
|
||||
@@ -152,12 +152,16 @@ func (c *Controller) syncContainersToStore(ctx context.Context) error {
|
||||
return fmt.Errorf("list containers from store: %w", err)
|
||||
}
|
||||
|
||||
containers, err := c.service.ListServiceContainers(ctx, "", container.ListOptions{})
|
||||
// List containers for all services, including stopped ones and deployment hooks.
|
||||
result, err := c.service.ListServiceContainers(ctx, "", container.ListOptions{All: true})
|
||||
if err != nil {
|
||||
// TODO: mark all containers as outdated in the store.
|
||||
return fmt.Errorf("list service containers: %w", err)
|
||||
}
|
||||
|
||||
// Sync both regular and one-off hook containers to the store.
|
||||
containers := append(result.Containers, result.HookContainers...)
|
||||
|
||||
// Delete containers from the store that are no longer present in the Docker daemon.
|
||||
var deleteIDs []string
|
||||
for _, sc := range storeContainers {
|
||||
|
||||
@@ -664,6 +664,39 @@ func (s *Server) CreateServiceContainer(
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the container configuration that doesn't make sense for the pre-deploy hook and apply its overrides.
|
||||
if spec.PreDeploy != nil && req.ContainerType == pb.CreateServiceContainerRequest_PRE_DEPLOY {
|
||||
config.Labels = map[string]string{
|
||||
api.LabelServiceID: req.ServiceId,
|
||||
api.LabelServiceName: spec.Name,
|
||||
api.LabelHook: api.LabelHookPreDeploy,
|
||||
api.LabelManaged: "",
|
||||
}
|
||||
config.Healthcheck = &container.HealthConfig{
|
||||
Test: []string{"NONE"},
|
||||
}
|
||||
hostConfig.PortBindings = nil
|
||||
hostConfig.RestartPolicy = container.RestartPolicy{
|
||||
Name: container.RestartPolicyDisabled,
|
||||
}
|
||||
|
||||
// Apply the pre-deploy hook overrides.
|
||||
config.Cmd = spec.PreDeploy.Command
|
||||
|
||||
for k, v := range spec.PreDeploy.Env {
|
||||
envVars[k] = v
|
||||
}
|
||||
envVars["UNCLOUD_HOOK_PRE_DEPLOY"] = "true"
|
||||
config.Env = envVars.ToSlice()
|
||||
|
||||
if spec.PreDeploy.Privileged != nil {
|
||||
hostConfig.Privileged = *spec.PreDeploy.Privileged
|
||||
}
|
||||
if spec.PreDeploy.User != "" {
|
||||
config.User = spec.PreDeploy.User
|
||||
}
|
||||
}
|
||||
|
||||
networkConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
NetworkName: {},
|
||||
@@ -1009,37 +1042,49 @@ func (s *Server) ListServiceContainers(
|
||||
}
|
||||
}
|
||||
|
||||
containers, err := s.service.ListServiceContainers(ctx, req.ServiceId, opts)
|
||||
result, err := s.service.ListServiceContainers(ctx, req.ServiceId, opts)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
// Convert to protobuf format.
|
||||
pbContainers, err := serviceContainersToProto(result.Containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pbHookContainers, err := serviceContainersToProto(result.HookContainers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.ListServiceContainersResponse{
|
||||
Messages: []*pb.MachineServiceContainers{
|
||||
{
|
||||
Containers: pbContainers,
|
||||
HookContainers: pbHookContainers,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// serviceContainersToProto converts a slice of service containers to protobuf format.
|
||||
func serviceContainersToProto(containers []api.ServiceContainer) ([]*pb.ServiceContainer, error) {
|
||||
pbContainers := make([]*pb.ServiceContainer, 0, len(containers))
|
||||
for _, ctr := range containers {
|
||||
ctrBytes, err := json.Marshal(ctr.Container)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "marshal container: %v", err)
|
||||
}
|
||||
|
||||
specBytes, err := json.Marshal(ctr.ServiceSpec)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "marshal service spec: %v", err)
|
||||
}
|
||||
|
||||
pbContainers = append(pbContainers, &pb.ServiceContainer{
|
||||
Container: ctrBytes,
|
||||
ServiceSpec: specBytes,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.ListServiceContainersResponse{
|
||||
Messages: []*pb.MachineServiceContainers{
|
||||
{
|
||||
Containers: pbContainers,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
return pbContainers, nil
|
||||
}
|
||||
|
||||
// RemoveServiceContainer stops (kills after grace period) and removes a service container with the given ID.
|
||||
|
||||
@@ -74,11 +74,20 @@ func (s *Service) InspectServiceContainer(ctx context.Context, nameOrID string)
|
||||
return serviceCtr, nil
|
||||
}
|
||||
|
||||
// ListServiceContainersResult holds the result of listing service containers, split into regular
|
||||
// service containers and one-off hook containers.
|
||||
type ListServiceContainersResult struct {
|
||||
Containers []api.ServiceContainer
|
||||
HookContainers []api.ServiceContainer
|
||||
}
|
||||
|
||||
// ListServiceContainers lists Docker containers that belong to the service with the given name or ID.
|
||||
// If serviceIDOrName is empty, all service containers are returned. The opts parameter allows additional filtering.
|
||||
func (s *Service) ListServiceContainers(
|
||||
ctx context.Context, serviceNameOrID string, opts container.ListOptions,
|
||||
) ([]api.ServiceContainer, error) {
|
||||
) (ListServiceContainersResult, error) {
|
||||
var result ListServiceContainersResult
|
||||
|
||||
if opts.Filters.Len() == 0 {
|
||||
opts.Filters = filters.NewArgs()
|
||||
}
|
||||
@@ -88,10 +97,9 @@ func (s *Service) ListServiceContainers(
|
||||
|
||||
containerSummaries, err := s.Client.ContainerList(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return result, err
|
||||
}
|
||||
|
||||
var containers []api.ServiceContainer
|
||||
for _, cs := range containerSummaries {
|
||||
// Filter by service name or ID if provided.
|
||||
if serviceNameOrID != "" &&
|
||||
@@ -106,10 +114,15 @@ func (s *Service) ListServiceContainers(
|
||||
slog.Error("Failed to inspect service container.", "service", serviceNameOrID, "id", cs.ID, "err", err)
|
||||
continue
|
||||
}
|
||||
containers = append(containers, ctr)
|
||||
|
||||
if ctr.IsHook() {
|
||||
result.HookContainers = append(result.HookContainers, ctr)
|
||||
} else {
|
||||
result.Containers = append(result.Containers, ctr)
|
||||
}
|
||||
}
|
||||
|
||||
return containers, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IsContainerdImageStoreEnabled checks if Docker is configured to use the containerd image store:
|
||||
|
||||
Reference in New Issue
Block a user