feat(volumes): create and mount data volumes when running service with 'uc run' command

This commit is contained in:
Pavel Sviderski
2025-04-11 21:28:36 +10:00
parent 87331a9265
commit f8ebd94a79
5 changed files with 325 additions and 111 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ func (s *ContainerSpec) SetDefaults() ContainerSpec {
func (s *ContainerSpec) Validate() error {
if _, err := reference.ParseDockerRef(s.Image); err != nil {
return fmt.Errorf("invalid image: %w", err)
return fmt.Errorf("invalid image '%s': %w", s.Image, err)
}
for _, m := range s.VolumeMounts {
+2 -2
View File
@@ -61,8 +61,8 @@ func (cli *Client) CreateContainer(
return resp, fmt.Errorf("unsupported pull policy: '%s'", spec.Container.PullPolicy)
}
// Not found error is expected if the image is missing.
if !dockerclient.IsErrNotFound(err) {
// NotFound (No such image) error is expected if the image is missing.
if !dockerclient.IsErrNotFound(err) || !strings.Contains(err.Error(), "No such image") {
return resp, err
}
+22 -19
View File
@@ -67,25 +67,9 @@ func (r *ServiceSpecResolver) resolveServiceName(spec *api.ServiceSpec) error {
return nil
}
// Generate a random service name from the image when not provided.
img, err := reference.ParseDockerRef(spec.Container.Image)
if err != nil {
return fmt.Errorf("invalid image: %w", err)
}
// Get the image name without the repository and tag/digest parts.
imageName := reference.FamiliarName(img)
// Get the last part of the image name (path), e.g. "nginx" from "bitnami/nginx".
if i := strings.LastIndex(imageName, "/"); i != -1 {
imageName = imageName[i+1:]
}
// Append a random suffix to the image name to generate an optimistically unique service name.
suffix, err := secret.RandomAlphaNumeric(4)
if err != nil {
return fmt.Errorf("generate random suffix: %w", err)
}
spec.Name = fmt.Sprintf("%s-%s", imageName, suffix)
return nil
var err error
spec.Name, err = GenerateServiceName(spec.Container.Image)
return err
}
// expandIngressPorts processes HTTP(S) ingress ports in a service spec by:
@@ -142,6 +126,25 @@ func (r *ServiceSpecResolver) resolveImageDigest(spec *api.ServiceSpec) error {
return nil
}
func GenerateServiceName(image string) (string, error) {
img, err := reference.ParseDockerRef(image)
if err != nil {
return "", fmt.Errorf("invalid image '%s': %w", image, err)
}
// Get the image name without the repository and tag/digest parts.
imageName := reference.FamiliarName(img)
// Get the last part of the image name (path), e.g. "nginx" from "bitnami/nginx".
if i := strings.LastIndex(imageName, "/"); i != -1 {
imageName = imageName[i+1:]
}
// Append a random suffix to the image name to generate an optimistically unique service name.
suffix, err := secret.RandomAlphaNumeric(4)
if err != nil {
return "", fmt.Errorf("generate random suffix: %w", err)
}
return fmt.Sprintf("%s-%s", imageName, suffix), nil
}
type ImageResolverClient interface {
api.ImageClient
api.MachineClient
+4 -14
View File
@@ -8,7 +8,6 @@ import (
"slices"
"sync"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/api/types/container"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api"
@@ -44,22 +43,13 @@ func (cli *Client) RunService(
}
deployment := cli.NewDeployment(spec, &deploy.RollingStrategy{MachineFilter: filter})
plan, err := deployment.Plan(ctx)
plan, err := deployment.Run(ctx)
if err != nil {
return resp, fmt.Errorf("plan deployment: %w", err)
return resp, err
}
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
_, err = deployment.Run(ctx)
if err != nil {
return err
}
resp.ID = plan.ServiceID
resp.Name = plan.ServiceName
return nil
}, cli.progressOut(), fmt.Sprintf("Running service %s (%s mode)", plan.ServiceName, spec.Mode))
resp.ID = plan.ServiceID
resp.Name = plan.ServiceName
return resp, err
}