Set restart policy after readiness
- commit
- 7132cc965ca94f4d02c45838a340d0e0731b34ab
- author
- brice · · 2026-05-05 17:51 UTC
- parent
- 86ffd72
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +143 -73
M internal/usecase/container/service.go +82 -58
| @@ -275,19 +275,18 @@ func (s *Service) buildContainerConfig(in containerConfigInput) *domain.Containe | |||
| 275 | 275 | } | |
| 276 | 276 | ||
| 277 | 277 | containerConfig := &domain.ContainerConfig{ | |
| 278 | - | Image: in.ImageRef, | |
| 279 | - | Name: containerName, | |
| 280 | - | Ports: ports, | |
| 281 | - | Env: in.EnvVars, | |
| 282 | - | Volumes: in.Volumes, | |
| 283 | - | NetworkMode: in.NetworkName, | |
| 284 | - | Hostname: in.Domain, | |
| 285 | - | Labels: labels, | |
| 286 | - | AutoRemove: false, | |
| 287 | - | RestartPolicy: domain.RestartPolicyAlways, | |
| 288 | - | MemoryLimit: cfg.DefaultMemoryLimit, | |
| 289 | - | NanoCPUs: cfg.DefaultNanoCPUs, | |
| 290 | - | PidsLimit: cfg.DefaultPidsLimit, | |
| 278 | + | Image: in.ImageRef, | |
| 279 | + | Name: containerName, | |
| 280 | + | Ports: ports, | |
| 281 | + | Env: in.EnvVars, | |
| 282 | + | Volumes: in.Volumes, | |
| 283 | + | NetworkMode: in.NetworkName, | |
| 284 | + | Hostname: in.Domain, | |
| 285 | + | Labels: labels, | |
| 286 | + | AutoRemove: false, | |
| 287 | + | MemoryLimit: cfg.DefaultMemoryLimit, | |
| 288 | + | NanoCPUs: cfg.DefaultNanoCPUs, | |
| 289 | + | PidsLimit: cfg.DefaultPidsLimit, | |
| 291 | 290 | } | |
| 292 | 291 | applySecurityProfile(containerConfig, cfg) | |
| 293 | 292 | return containerConfig | |
| @@ -655,6 +654,10 @@ func (s *Service) createStartedContainer(ctx context.Context, route domain.Route | |||
| 655 | 654 | return nil, deployErr | |
| 656 | 655 | } | |
| 657 | 656 | } | |
| 657 | + | if err := s.runtime.EnsureContainerRestartPolicy(ctx, newContainer.ID, domain.RestartPolicyAlways); err != nil { | |
| 658 | + | s.cleanupFailedContainer(ctx, newContainer.ID) | |
| 659 | + | return nil, log.WrapErr(err, "failed to set container restart policy") | |
| 660 | + | } | |
| 658 | 661 | ||
| 659 | 662 | inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID) | |
| 660 | 663 | if err != nil { | |
| @@ -1418,13 +1421,13 @@ func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) err | |||
| 1418 | 1421 | return log.WrapErr(err, "failed to list containers for restart policy migration") | |
| 1419 | 1422 | } | |
| 1420 | 1423 | ||
| 1421 | - | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |
| 1422 | - | return nil | |
| 1424 | + | return s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |
| 1423 | 1425 | } | |
| 1424 | 1426 | ||
| 1425 | - | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) { | |
| 1427 | + | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) error { | |
| 1426 | 1428 | log := zerowrap.FromCtx(ctx) | |
| 1427 | 1429 | ||
| 1430 | + | var errs []error | |
| 1428 | 1431 | for _, c := range allContainers { | |
| 1429 | 1432 | if c.Labels == nil || c.Labels[domain.LabelManaged] != "true" { | |
| 1430 | 1433 | continue | |
| @@ -1434,8 +1437,10 @@ func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, all | |||
| 1434 | 1437 | Str(zerowrap.FieldEntityID, c.ID). | |
| 1435 | 1438 | Str("container_name", c.Name). | |
| 1436 | 1439 | Msg("failed to ensure managed container restart policy") | |
| 1440 | + | errs = append(errs, fmt.Errorf("container %s: %w", c.ID, err)) | |
| 1437 | 1441 | } | |
| 1438 | 1442 | } | |
| 1443 | + | return errors.Join(errs...) | |
| 1439 | 1444 | } | |
| 1440 | 1445 | ||
| 1441 | 1446 | // SyncContainers synchronizes containers with runtime state. | |
| @@ -2616,85 +2621,104 @@ func (s *Service) deployAttachedService(ctx context.Context, ownerDomain, servic | |||
| 2616 | 2621 | ||
| 2617 | 2622 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | |
| 2618 | 2623 | ||
| 2619 | - | // Ensure image (canonical ref is returned; internal pulls may use the local registry). | |
| 2620 | - | imageRef, err := s.buildValidatedImageRef(ctx, serviceImage) | |
| 2624 | + | config, err := s.buildAttachmentContainerConfig(ctx, ownerDomain, serviceName, serviceImage, containerName, networkName) | |
| 2621 | 2625 | if err != nil { | |
| 2622 | 2626 | return err | |
| 2623 | 2627 | } | |
| 2624 | - | actualImageRef, err := s.ensureImage(ctx, imageRef) | |
| 2628 | + | ||
| 2629 | + | container, err := s.runtime.CreateContainer(ctx, config) | |
| 2625 | 2630 | if err != nil { | |
| 2631 | + | return log.WrapErr(err, "failed to create attachment container") | |
| 2632 | + | } | |
| 2633 | + | ||
| 2634 | + | // Start container | |
| 2635 | + | if err := s.runtime.StartContainer(ctx, container.ID); err != nil { | |
| 2636 | + | s.runtime.RemoveContainer(ctx, container.ID, true) | |
| 2637 | + | return log.WrapErr(err, "failed to start attachment container") | |
| 2638 | + | } | |
| 2639 | + | ||
| 2640 | + | // Wait for attachment to be ready before proceeding | |
| 2641 | + | if err := s.waitForAttachmentReady(ctx, container.ID, config); err != nil { | |
| 2642 | + | log.WrapErr(err, "attachment readiness check failed, cleaning up") | |
| 2643 | + | if stopErr := s.runtime.StopContainer(ctx, container.ID); stopErr != nil { | |
| 2644 | + | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, container.ID).Msg("failed to stop unready attachment") | |
| 2645 | + | } | |
| 2646 | + | s.runtime.RemoveContainer(ctx, container.ID, true) | |
| 2647 | + | return fmt.Errorf("attachment %q not ready: %w", serviceName, err) | |
| 2648 | + | } | |
| 2649 | + | if err := s.ensureAttachmentRestartPolicy(ctx, container.ID); err != nil { | |
| 2626 | 2650 | return err | |
| 2627 | 2651 | } | |
| 2628 | 2652 | ||
| 2653 | + | // Track attachment | |
| 2654 | + | s.mu.Lock() | |
| 2655 | + | s.attachments[ownerDomain] = append(s.attachments[ownerDomain], container.ID) | |
| 2656 | + | s.mu.Unlock() | |
| 2657 | + | ||
| 2658 | + | // Start log collection for attachment | |
| 2659 | + | s.startLogCollection(ctx, container.ID, containerName) | |
| 2660 | + | ||
| 2661 | + | log.Info().Str(zerowrap.FieldEntityID, container.ID).Msg("attachment deployed successfully") | |
| 2662 | + | return nil | |
| 2663 | + | } | |
| 2664 | + | ||
| 2665 | + | func (s *Service) buildAttachmentContainerConfig(ctx context.Context, ownerDomain, serviceName, serviceImage, containerName, networkName string) (*domain.ContainerConfig, error) { | |
| 2666 | + | imageRef, err := s.buildValidatedImageRef(ctx, serviceImage) | |
| 2667 | + | if err != nil { | |
| 2668 | + | return nil, err | |
| 2669 | + | } | |
| 2670 | + | actualImageRef, err := s.ensureImage(ctx, imageRef) | |
| 2671 | + | if err != nil { | |
| 2672 | + | return nil, err | |
| 2673 | + | } | |
| 2674 | + | ||
| 2629 | 2675 | exposedPorts := s.attachmentExposedPorts(ctx, actualImageRef) | |
| 2630 | 2676 | volumes, err := s.attachmentVolumes(ctx, containerName, actualImageRef) | |
| 2631 | 2677 | if err != nil { | |
| 2632 | - | return err | |
| 2678 | + | return nil, err | |
| 2633 | 2679 | } | |
| 2634 | 2680 | envVars, err := s.attachmentEnv(ctx, containerName, actualImageRef) | |
| 2635 | 2681 | if err != nil { | |
| 2636 | - | return err | |
| 2682 | + | return nil, err | |
| 2637 | 2683 | } | |
| 2638 | - | envHash := hashEnvironment(envVars) | |
| 2639 | 2684 | ||
| 2640 | 2685 | s.mu.RLock() | |
| 2641 | 2686 | cfg := s.config | |
| 2642 | 2687 | s.mu.RUnlock() | |
| 2643 | 2688 | ||
| 2644 | - | // Create container with attachment labels (use actual ref, track original in labels) | |
| 2645 | 2689 | config := &domain.ContainerConfig{ | |
| 2646 | 2690 | Image: actualImageRef, | |
| 2647 | 2691 | Name: containerName, | |
| 2648 | - | Hostname: serviceName, // Internal DNS: postgres, redis, etc. | |
| 2692 | + | Hostname: serviceName, | |
| 2649 | 2693 | Aliases: []string{serviceName}, | |
| 2650 | 2694 | Ports: exposedPorts, | |
| 2651 | 2695 | Env: envVars, | |
| 2652 | 2696 | Volumes: volumes, | |
| 2653 | - | NetworkMode: networkName, // Same network as main app | |
| 2697 | + | NetworkMode: networkName, | |
| 2654 | 2698 | Labels: map[string]string{ | |
| 2655 | 2699 | domain.LabelManaged: "true", | |
| 2656 | 2700 | domain.LabelAttachment: "true", | |
| 2657 | 2701 | domain.LabelAttachedTo: ownerDomain, | |
| 2658 | - | domain.LabelEnvHash: envHash, | |
| 2702 | + | domain.LabelEnvHash: hashEnvironment(envVars), | |
| 2659 | 2703 | domain.LabelImage: serviceImage, | |
| 2660 | 2704 | }, | |
| 2661 | - | MemoryLimit: cfg.DefaultMemoryLimit, | |
| 2662 | - | NanoCPUs: cfg.DefaultNanoCPUs, | |
| 2663 | - | PidsLimit: cfg.DefaultPidsLimit, | |
| 2664 | - | RestartPolicy: domain.RestartPolicyAlways, | |
| 2705 | + | MemoryLimit: cfg.DefaultMemoryLimit, | |
| 2706 | + | NanoCPUs: cfg.DefaultNanoCPUs, | |
| 2707 | + | PidsLimit: cfg.DefaultPidsLimit, | |
| 2665 | 2708 | } | |
| 2666 | 2709 | applySecurityProfile(config, cfg) | |
| 2710 | + | return config, nil | |
| 2711 | + | } | |
| 2667 | 2712 | ||
| 2668 | - | container, err := s.runtime.CreateContainer(ctx, config) | |
| 2669 | - | if err != nil { | |
| 2670 | - | return log.WrapErr(err, "failed to create attachment container") | |
| 2671 | - | } | |
| 2672 | - | ||
| 2673 | - | // Start container | |
| 2674 | - | if err := s.runtime.StartContainer(ctx, container.ID); err != nil { | |
| 2675 | - | s.runtime.RemoveContainer(ctx, container.ID, true) | |
| 2676 | - | return log.WrapErr(err, "failed to start attachment container") | |
| 2677 | - | } | |
| 2678 | - | ||
| 2679 | - | // Wait for attachment to be ready before proceeding | |
| 2680 | - | if err := s.waitForAttachmentReady(ctx, container.ID, config); err != nil { | |
| 2681 | - | log.WrapErr(err, "attachment readiness check failed, cleaning up") | |
| 2682 | - | if stopErr := s.runtime.StopContainer(ctx, container.ID); stopErr != nil { | |
| 2683 | - | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, container.ID).Msg("failed to stop unready attachment") | |
| 2713 | + | func (s *Service) ensureAttachmentRestartPolicy(ctx context.Context, containerID string) error { | |
| 2714 | + | log := zerowrap.FromCtx(ctx) | |
| 2715 | + | if err := s.runtime.EnsureContainerRestartPolicy(ctx, containerID, domain.RestartPolicyAlways); err != nil { | |
| 2716 | + | if stopErr := s.runtime.StopContainer(ctx, containerID); stopErr != nil { | |
| 2717 | + | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, containerID).Msg("failed to stop attachment after restart policy update failure") | |
| 2684 | 2718 | } | |
| 2685 | - | s.runtime.RemoveContainer(ctx, container.ID, true) | |
| 2686 | - | return fmt.Errorf("attachment %q not ready: %w", serviceName, err) | |
| 2719 | + | s.runtime.RemoveContainer(ctx, containerID, true) | |
| 2720 | + | return log.WrapErr(err, "failed to set attachment restart policy") | |
| 2687 | 2721 | } | |
| 2688 | - | ||
| 2689 | - | // Track attachment | |
| 2690 | - | s.mu.Lock() | |
| 2691 | - | s.attachments[ownerDomain] = append(s.attachments[ownerDomain], container.ID) | |
| 2692 | - | s.mu.Unlock() | |
| 2693 | - | ||
| 2694 | - | // Start log collection for attachment | |
| 2695 | - | s.startLogCollection(ctx, container.ID, containerName) | |
| 2696 | - | ||
| 2697 | - | log.Info().Str(zerowrap.FieldEntityID, container.ID).Msg("attachment deployed successfully") | |
| 2698 | 2722 | return nil | |
| 2699 | 2723 | } | |
| 2700 | 2724 | ||
| @@ -275,19 +275,18 @@ func (s *Service) buildContainerConfig(in containerConfigInput) *domain.Containe | |||||
| 275 | } | 275 | } | ||
| 276 | 276 | ||||
| 277 | containerConfig := &domain.ContainerConfig{ | 277 | containerConfig := &domain.ContainerConfig{ | ||
| 278 | - | Image: in.ImageRef, | 278 | + | Image: in.ImageRef, |
| 279 | - | Name: containerName, | 279 | + | Name: containerName, |
| 280 | - | Ports: ports, | 280 | + | Ports: ports, |
| 281 | - | Env: in.EnvVars, | 281 | + | Env: in.EnvVars, |
| 282 | - | Volumes: in.Volumes, | 282 | + | Volumes: in.Volumes, |
| 283 | - | NetworkMode: in.NetworkName, | 283 | + | NetworkMode: in.NetworkName, |
| 284 | - | Hostname: in.Domain, | 284 | + | Hostname: in.Domain, |
| 285 | - | Labels: labels, | 285 | + | Labels: labels, |
| 286 | - | AutoRemove: false, | 286 | + | AutoRemove: false, |
| 287 | - | RestartPolicy: domain.RestartPolicyAlways, | 287 | + | MemoryLimit: cfg.DefaultMemoryLimit, |
| 288 | - | MemoryLimit: cfg.DefaultMemoryLimit, | 288 | + | NanoCPUs: cfg.DefaultNanoCPUs, |
| 289 | - | NanoCPUs: cfg.DefaultNanoCPUs, | 289 | + | PidsLimit: cfg.DefaultPidsLimit, |
| 290 | - | PidsLimit: cfg.DefaultPidsLimit, | |||
| 291 | } | 290 | } | ||
| 292 | applySecurityProfile(containerConfig, cfg) | 291 | applySecurityProfile(containerConfig, cfg) | ||
| 293 | return containerConfig | 292 | return containerConfig | ||
| @@ -655,6 +654,10 @@ func (s *Service) createStartedContainer(ctx context.Context, route domain.Route | |||||
| 655 | return nil, deployErr | 654 | return nil, deployErr | ||
| 656 | } | 655 | } | ||
| 657 | } | 656 | } | ||
| 657 | + | if err := s.runtime.EnsureContainerRestartPolicy(ctx, newContainer.ID, domain.RestartPolicyAlways); err != nil { | |||
| 658 | + | s.cleanupFailedContainer(ctx, newContainer.ID) | |||
| 659 | + | return nil, log.WrapErr(err, "failed to set container restart policy") | |||
| 660 | + | } | |||
| 658 | 661 | ||||
| 659 | inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID) | 662 | inspected, err := s.runtime.InspectContainer(ctx, newContainer.ID) | ||
| 660 | if err != nil { | 663 | if err != nil { | ||
| @@ -1418,13 +1421,13 @@ func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) err | |||||
| 1418 | return log.WrapErr(err, "failed to list containers for restart policy migration") | 1421 | return log.WrapErr(err, "failed to list containers for restart policy migration") | ||
| 1419 | } | 1422 | } | ||
| 1420 | 1423 | ||||
| 1421 | - | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | 1424 | + | return s.ensureManagedContainerRestartPolicies(ctx, allContainers) |
| 1422 | - | return nil | |||
| 1423 | } | 1425 | } | ||
| 1424 | 1426 | ||||
| 1425 | - | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) { | 1427 | + | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) error { |
| 1426 | log := zerowrap.FromCtx(ctx) | 1428 | log := zerowrap.FromCtx(ctx) | ||
| 1427 | 1429 | ||||
| 1430 | + | var errs []error | |||
| 1428 | for _, c := range allContainers { | 1431 | for _, c := range allContainers { | ||
| 1429 | if c.Labels == nil || c.Labels[domain.LabelManaged] != "true" { | 1432 | if c.Labels == nil || c.Labels[domain.LabelManaged] != "true" { | ||
| 1430 | continue | 1433 | continue | ||
| @@ -1434,8 +1437,10 @@ func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, all | |||||
| 1434 | Str(zerowrap.FieldEntityID, c.ID). | 1437 | Str(zerowrap.FieldEntityID, c.ID). | ||
| 1435 | Str("container_name", c.Name). | 1438 | Str("container_name", c.Name). | ||
| 1436 | Msg("failed to ensure managed container restart policy") | 1439 | Msg("failed to ensure managed container restart policy") | ||
| 1440 | + | errs = append(errs, fmt.Errorf("container %s: %w", c.ID, err)) | |||
| 1437 | } | 1441 | } | ||
| 1438 | } | 1442 | } | ||
| 1443 | + | return errors.Join(errs...) | |||
| 1439 | } | 1444 | } | ||
| 1440 | 1445 | ||||
| 1441 | // SyncContainers synchronizes containers with runtime state. | 1446 | // SyncContainers synchronizes containers with runtime state. | ||
| @@ -2616,85 +2621,104 @@ func (s *Service) deployAttachedService(ctx context.Context, ownerDomain, servic | |||||
| 2616 | 2621 | ||||
| 2617 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | 2622 | log.Info().Str(zerowrap.FieldService, serviceImage).Msg("deploying attached service") | ||
| 2618 | 2623 | ||||
| 2619 | - | // Ensure image (canonical ref is returned; internal pulls may use the local registry). | 2624 | + | config, err := s.buildAttachmentContainerConfig(ctx, ownerDomain, serviceName, serviceImage, containerName, networkName) |
| 2620 | - | imageRef, err := s.buildValidatedImageRef(ctx, serviceImage) | |||
| 2621 | if err != nil { | 2625 | if err != nil { | ||
| 2622 | return err | 2626 | return err | ||
| 2623 | } | 2627 | } | ||
| 2624 | - | actualImageRef, err := s.ensureImage(ctx, imageRef) | 2628 | + | |
| 2629 | + | container, err := s.runtime.CreateContainer(ctx, config) | |||
| 2625 | if err != nil { | 2630 | if err != nil { | ||
| 2631 | + | return log.WrapErr(err, "failed to create attachment container") | |||
| 2632 | + | } | |||
| 2633 | + | ||||
| 2634 | + | // Start container | |||
| 2635 | + | if err := s.runtime.StartContainer(ctx, container.ID); err != nil { | |||
| 2636 | + | s.runtime.RemoveContainer(ctx, container.ID, true) | |||
| 2637 | + | return log.WrapErr(err, "failed to start attachment container") | |||
| 2638 | + | } | |||
| 2639 | + | ||||
| 2640 | + | // Wait for attachment to be ready before proceeding | |||
| 2641 | + | if err := s.waitForAttachmentReady(ctx, container.ID, config); err != nil { | |||
| 2642 | + | log.WrapErr(err, "attachment readiness check failed, cleaning up") | |||
| 2643 | + | if stopErr := s.runtime.StopContainer(ctx, container.ID); stopErr != nil { | |||
| 2644 | + | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, container.ID).Msg("failed to stop unready attachment") | |||
| 2645 | + | } | |||
| 2646 | + | s.runtime.RemoveContainer(ctx, container.ID, true) | |||
| 2647 | + | return fmt.Errorf("attachment %q not ready: %w", serviceName, err) | |||
| 2648 | + | } | |||
| 2649 | + | if err := s.ensureAttachmentRestartPolicy(ctx, container.ID); err != nil { | |||
| 2626 | return err | 2650 | return err | ||
| 2627 | } | 2651 | } | ||
| 2628 | 2652 | ||||
| 2653 | + | // Track attachment | |||
| 2654 | + | s.mu.Lock() | |||
| 2655 | + | s.attachments[ownerDomain] = append(s.attachments[ownerDomain], container.ID) | |||
| 2656 | + | s.mu.Unlock() | |||
| 2657 | + | ||||
| 2658 | + | // Start log collection for attachment | |||
| 2659 | + | s.startLogCollection(ctx, container.ID, containerName) | |||
| 2660 | + | ||||
| 2661 | + | log.Info().Str(zerowrap.FieldEntityID, container.ID).Msg("attachment deployed successfully") | |||
| 2662 | + | return nil | |||
| 2663 | + | } | |||
| 2664 | + | ||||
| 2665 | + | func (s *Service) buildAttachmentContainerConfig(ctx context.Context, ownerDomain, serviceName, serviceImage, containerName, networkName string) (*domain.ContainerConfig, error) { | |||
| 2666 | + | imageRef, err := s.buildValidatedImageRef(ctx, serviceImage) | |||
| 2667 | + | if err != nil { | |||
| 2668 | + | return nil, err | |||
| 2669 | + | } | |||
| 2670 | + | actualImageRef, err := s.ensureImage(ctx, imageRef) | |||
| 2671 | + | if err != nil { | |||
| 2672 | + | return nil, err | |||
| 2673 | + | } | |||
| 2674 | + | ||||
| 2629 | exposedPorts := s.attachmentExposedPorts(ctx, actualImageRef) | 2675 | exposedPorts := s.attachmentExposedPorts(ctx, actualImageRef) | ||
| 2630 | volumes, err := s.attachmentVolumes(ctx, containerName, actualImageRef) | 2676 | volumes, err := s.attachmentVolumes(ctx, containerName, actualImageRef) | ||
| 2631 | if err != nil { | 2677 | if err != nil { | ||
| 2632 | - | return err | 2678 | + | return nil, err |
| 2633 | } | 2679 | } | ||
| 2634 | envVars, err := s.attachmentEnv(ctx, containerName, actualImageRef) | 2680 | envVars, err := s.attachmentEnv(ctx, containerName, actualImageRef) | ||
| 2635 | if err != nil { | 2681 | if err != nil { | ||
| 2636 | - | return err | 2682 | + | return nil, err |
| 2637 | } | 2683 | } | ||
| 2638 | - | envHash := hashEnvironment(envVars) | |||
| 2639 | 2684 | ||||
| 2640 | s.mu.RLock() | 2685 | s.mu.RLock() | ||
| 2641 | cfg := s.config | 2686 | cfg := s.config | ||
| 2642 | s.mu.RUnlock() | 2687 | s.mu.RUnlock() | ||
| 2643 | 2688 | ||||
| 2644 | - | // Create container with attachment labels (use actual ref, track original in labels) | |||
| 2645 | config := &domain.ContainerConfig{ | 2689 | config := &domain.ContainerConfig{ | ||
| 2646 | Image: actualImageRef, | 2690 | Image: actualImageRef, | ||
| 2647 | Name: containerName, | 2691 | Name: containerName, | ||
| 2648 | - | Hostname: serviceName, // Internal DNS: postgres, redis, etc. | 2692 | + | Hostname: serviceName, |
| 2649 | Aliases: []string{serviceName}, | 2693 | Aliases: []string{serviceName}, | ||
| 2650 | Ports: exposedPorts, | 2694 | Ports: exposedPorts, | ||
| 2651 | Env: envVars, | 2695 | Env: envVars, | ||
| 2652 | Volumes: volumes, | 2696 | Volumes: volumes, | ||
| 2653 | - | NetworkMode: networkName, // Same network as main app | 2697 | + | NetworkMode: networkName, |
| 2654 | Labels: map[string]string{ | 2698 | Labels: map[string]string{ | ||
| 2655 | domain.LabelManaged: "true", | 2699 | domain.LabelManaged: "true", | ||
| 2656 | domain.LabelAttachment: "true", | 2700 | domain.LabelAttachment: "true", | ||
| 2657 | domain.LabelAttachedTo: ownerDomain, | 2701 | domain.LabelAttachedTo: ownerDomain, | ||
| 2658 | - | domain.LabelEnvHash: envHash, | 2702 | + | domain.LabelEnvHash: hashEnvironment(envVars), |
| 2659 | domain.LabelImage: serviceImage, | 2703 | domain.LabelImage: serviceImage, | ||
| 2660 | }, | 2704 | }, | ||
| 2661 | - | MemoryLimit: cfg.DefaultMemoryLimit, | 2705 | + | MemoryLimit: cfg.DefaultMemoryLimit, |
| 2662 | - | NanoCPUs: cfg.DefaultNanoCPUs, | 2706 | + | NanoCPUs: cfg.DefaultNanoCPUs, |
| 2663 | - | PidsLimit: cfg.DefaultPidsLimit, | 2707 | + | PidsLimit: cfg.DefaultPidsLimit, |
| 2664 | - | RestartPolicy: domain.RestartPolicyAlways, | |||
| 2665 | } | 2708 | } | ||
| 2666 | applySecurityProfile(config, cfg) | 2709 | applySecurityProfile(config, cfg) | ||
| 2710 | + | return config, nil | |||
| 2711 | + | } | |||
| 2667 | 2712 | ||||
| 2668 | - | container, err := s.runtime.CreateContainer(ctx, config) | 2713 | + | func (s *Service) ensureAttachmentRestartPolicy(ctx context.Context, containerID string) error { |
| 2669 | - | if err != nil { | 2714 | + | log := zerowrap.FromCtx(ctx) |
| 2670 | - | return log.WrapErr(err, "failed to create attachment container") | 2715 | + | if err := s.runtime.EnsureContainerRestartPolicy(ctx, containerID, domain.RestartPolicyAlways); err != nil { |
| 2671 | - | } | 2716 | + | if stopErr := s.runtime.StopContainer(ctx, containerID); stopErr != nil { |
| 2672 | - | 2717 | + | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, containerID).Msg("failed to stop attachment after restart policy update failure") | |
| 2673 | - | // Start container | |||
| 2674 | - | if err := s.runtime.StartContainer(ctx, container.ID); err != nil { | |||
| 2675 | - | s.runtime.RemoveContainer(ctx, container.ID, true) | |||
| 2676 | - | return log.WrapErr(err, "failed to start attachment container") | |||
| 2677 | - | } | |||
| 2678 | - | ||||
| 2679 | - | // Wait for attachment to be ready before proceeding | |||
| 2680 | - | if err := s.waitForAttachmentReady(ctx, container.ID, config); err != nil { | |||
| 2681 | - | log.WrapErr(err, "attachment readiness check failed, cleaning up") | |||
| 2682 | - | if stopErr := s.runtime.StopContainer(ctx, container.ID); stopErr != nil { | |||
| 2683 | - | log.Warn().Err(stopErr).Str(zerowrap.FieldEntityID, container.ID).Msg("failed to stop unready attachment") | |||
| 2684 | } | 2718 | } | ||
| 2685 | - | s.runtime.RemoveContainer(ctx, container.ID, true) | 2719 | + | s.runtime.RemoveContainer(ctx, containerID, true) |
| 2686 | - | return fmt.Errorf("attachment %q not ready: %w", serviceName, err) | 2720 | + | return log.WrapErr(err, "failed to set attachment restart policy") |
| 2687 | } | 2721 | } | ||
| 2688 | - | ||||
| 2689 | - | // Track attachment | |||
| 2690 | - | s.mu.Lock() | |||
| 2691 | - | s.attachments[ownerDomain] = append(s.attachments[ownerDomain], container.ID) | |||
| 2692 | - | s.mu.Unlock() | |||
| 2693 | - | ||||
| 2694 | - | // Start log collection for attachment | |||
| 2695 | - | s.startLogCollection(ctx, container.ID, containerName) | |||
| 2696 | - | ||||
| 2697 | - | log.Info().Str(zerowrap.FieldEntityID, container.ID).Msg("attachment deployed successfully") | |||
| 2698 | return nil | 2722 | return nil | ||
| 2699 | } | 2723 | } | ||
| 2700 | 2724 | ||||
M internal/usecase/container/service_test.go +61 -15
| @@ -57,6 +57,11 @@ func testMinDelayConfig() Config { | |||
| 57 | 57 | } | |
| 58 | 58 | } | |
| 59 | 59 | ||
| 60 | + | func allowRestartPolicyEnsure(t *testing.T, runtime *mocks.MockContainerRuntime) { | |
| 61 | + | t.Helper() | |
| 62 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 63 | + | } | |
| 64 | + | ||
| 60 | 65 | func setupMetricsTest(t *testing.T) (*telemetry.Metrics, *sdkmetric.ManualReader) { | |
| 61 | 66 | t.Helper() | |
| 62 | 67 | ||
| @@ -255,8 +260,6 @@ func TestService_SyncContainers_ManagedContainersMetricTracksDelta(t *testing.T) | |||
| 255 | 260 | ctx := testContext() | |
| 256 | 261 | metrics, reader := setupMetricsTest(t) | |
| 257 | 262 | svc.SetMetrics(metrics) | |
| 258 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 259 | - | ||
| 260 | 263 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | |
| 261 | 264 | { | |
| 262 | 265 | ID: "container-1", | |
| @@ -379,6 +382,8 @@ func TestService_Deploy_Success(t *testing.T) { | |||
| 379 | 382 | // Wait for ready: IsContainerRunning (first check returns true) + verify after delay | |
| 380 | 383 | runtime.EXPECT().IsContainerRunning(mock.Anything, "container-123").Return(true, nil).Times(2) | |
| 381 | 384 | ||
| 385 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "container-123", domain.RestartPolicyAlways).Return(nil).Once() | |
| 386 | + | ||
| 382 | 387 | // Re-inspect after start | |
| 383 | 388 | runningContainer := &domain.Container{ | |
| 384 | 389 | ID: "container-123", | |
| @@ -407,6 +412,7 @@ func TestService_Deploy_Success(t *testing.T) { | |||
| 407 | 412 | ||
| 408 | 413 | func TestService_Deploy_ReadinessRecoveryWindow_AllowsTransientFlap(t *testing.T) { | |
| 409 | 414 | runtime := mocks.NewMockContainerRuntime(t) | |
| 415 | + | allowRestartPolicyEnsure(t, runtime) | |
| 410 | 416 | envLoader := mocks.NewMockEnvLoader(t) | |
| 411 | 417 | eventBus := mocks.NewMockEventPublisher(t) | |
| 412 | 418 | ||
| @@ -473,6 +479,7 @@ func TestService_Deploy_ReadinessRecoveryWindow_AllowsTransientFlap(t *testing.T | |||
| 473 | 479 | ||
| 474 | 480 | func TestService_Deploy_ImagePullFailure(t *testing.T) { | |
| 475 | 481 | runtime := mocks.NewMockContainerRuntime(t) | |
| 482 | + | allowRestartPolicyEnsure(t, runtime) | |
| 476 | 483 | envLoader := mocks.NewMockEnvLoader(t) | |
| 477 | 484 | eventBus := mocks.NewMockEventPublisher(t) | |
| 478 | 485 | ||
| @@ -515,6 +522,7 @@ func TestIsPullFailure_ErrImagePullFailed(t *testing.T) { | |||
| 515 | 522 | ||
| 516 | 523 | func TestService_Deploy_DoesNotMisclassifyUnauthorizedEnvLoadFailure(t *testing.T) { | |
| 517 | 524 | runtime := mocks.NewMockContainerRuntime(t) | |
| 525 | + | allowRestartPolicyEnsure(t, runtime) | |
| 518 | 526 | envLoader := mocks.NewMockEnvLoader(t) | |
| 519 | 527 | eventBus := mocks.NewMockEventPublisher(t) | |
| 520 | 528 | ||
| @@ -555,6 +563,7 @@ func TestService_CaptureRecentContainerLogs_SkipsCanceledContext(t *testing.T) { | |||
| 555 | 563 | ||
| 556 | 564 | func TestService_Deploy_ReadinessFailureReturnsDeployFailureWithLogs(t *testing.T) { | |
| 557 | 565 | runtime := mocks.NewMockContainerRuntime(t) | |
| 566 | + | allowRestartPolicyEnsure(t, runtime) | |
| 558 | 567 | envLoader := mocks.NewMockEnvLoader(t) | |
| 559 | 568 | eventBus := mocks.NewMockEventPublisher(t) | |
| 560 | 569 | ||
| @@ -614,6 +623,7 @@ func TestService_Deploy_ReadinessFailureReturnsDeployFailureWithLogs(t *testing. | |||
| 614 | 623 | ||
| 615 | 624 | func TestService_Deploy_StrictHealthModeWithoutHealthcheckReturnsHint(t *testing.T) { | |
| 616 | 625 | runtime := mocks.NewMockContainerRuntime(t) | |
| 626 | + | allowRestartPolicyEnsure(t, runtime) | |
| 617 | 627 | envLoader := mocks.NewMockEnvLoader(t) | |
| 618 | 628 | eventBus := mocks.NewMockEventPublisher(t) | |
| 619 | 629 | ||
| @@ -725,6 +735,7 @@ func TestService_PullImage_InternalRetriesOnConnectionRefused(t *testing.T) { | |||
| 725 | 735 | ||
| 726 | 736 | func TestService_Deploy_ReplacesExistingContainer(t *testing.T) { | |
| 727 | 737 | runtime := mocks.NewMockContainerRuntime(t) | |
| 738 | + | allowRestartPolicyEnsure(t, runtime) | |
| 728 | 739 | envLoader := mocks.NewMockEnvLoader(t) | |
| 729 | 740 | eventBus := mocks.NewMockEventPublisher(t) | |
| 730 | 741 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -808,6 +819,7 @@ func TestService_Deploy_ReplacesExistingContainer(t *testing.T) { | |||
| 808 | 819 | // from killing the real active container after a Gordon restart. | |
| 809 | 820 | func TestService_Deploy_ResolvesExistingFromRuntime_WhenMemoryStale(t *testing.T) { | |
| 810 | 821 | runtime := mocks.NewMockContainerRuntime(t) | |
| 822 | + | allowRestartPolicyEnsure(t, runtime) | |
| 811 | 823 | envLoader := mocks.NewMockEnvLoader(t) | |
| 812 | 824 | eventBus := mocks.NewMockEventPublisher(t) | |
| 813 | 825 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -907,6 +919,7 @@ func TestService_Deploy_ResolvesExistingFromRuntime_WhenMemoryStale(t *testing.T | |||
| 907 | 919 | ||
| 908 | 920 | func TestService_Deploy_SkipRedundantDeploy_GetImageIDError(t *testing.T) { | |
| 909 | 921 | runtime := mocks.NewMockContainerRuntime(t) | |
| 922 | + | allowRestartPolicyEnsure(t, runtime) | |
| 910 | 923 | envLoader := mocks.NewMockEnvLoader(t) | |
| 911 | 924 | eventBus := mocks.NewMockEventPublisher(t) | |
| 912 | 925 | ||
| @@ -969,6 +982,7 @@ func TestService_Deploy_SkipRedundantDeploy_GetImageIDError(t *testing.T) { | |||
| 969 | 982 | ||
| 970 | 983 | func TestService_Deploy_WithNetworkIsolation(t *testing.T) { | |
| 971 | 984 | runtime := mocks.NewMockContainerRuntime(t) | |
| 985 | + | allowRestartPolicyEnsure(t, runtime) | |
| 972 | 986 | envLoader := mocks.NewMockEnvLoader(t) | |
| 973 | 987 | eventBus := mocks.NewMockEventPublisher(t) | |
| 974 | 988 | ||
| @@ -1027,6 +1041,7 @@ func TestService_Deploy_WithNetworkIsolation(t *testing.T) { | |||
| 1027 | 1041 | ||
| 1028 | 1042 | func TestService_Deploy_WithVolumeAutoCreate(t *testing.T) { | |
| 1029 | 1043 | runtime := mocks.NewMockContainerRuntime(t) | |
| 1044 | + | allowRestartPolicyEnsure(t, runtime) | |
| 1030 | 1045 | envLoader := mocks.NewMockEnvLoader(t) | |
| 1031 | 1046 | eventBus := mocks.NewMockEventPublisher(t) | |
| 1032 | 1047 | ||
| @@ -1295,7 +1310,7 @@ func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsA | |||
| 1295 | 1310 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | |
| 1296 | 1311 | } | |
| 1297 | 1312 | ||
| 1298 | - | func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testing.T) { | |
| 1313 | + | func TestService_EnsureManagedContainerRestartPolicies_ContinuesAndReturnsError(t *testing.T) { | |
| 1299 | 1314 | runtime := mocks.NewMockContainerRuntime(t) | |
| 1300 | 1315 | envLoader := mocks.NewMockEnvLoader(t) | |
| 1301 | 1316 | eventBus := mocks.NewMockEventPublisher(t) | |
| @@ -1311,12 +1326,23 @@ func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testin | |||
| 1311 | 1326 | domain.LabelDomain: "app.example.com", | |
| 1312 | 1327 | }, | |
| 1313 | 1328 | } | |
| 1329 | + | routeContainer2 := &domain.Container{ | |
| 1330 | + | ID: "route-2", | |
| 1331 | + | Name: "gordon-api.example.com", | |
| 1332 | + | Labels: map[string]string{ | |
| 1333 | + | domain.LabelManaged: "true", | |
| 1334 | + | domain.LabelDomain: "api.example.com", | |
| 1335 | + | }, | |
| 1336 | + | } | |
| 1314 | 1337 | ||
| 1315 | - | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once() | |
| 1338 | + | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer, routeContainer2}, nil).Once() | |
| 1316 | 1339 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | |
| 1340 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-2", domain.RestartPolicyAlways).Return(nil).Once() | |
| 1317 | 1341 | err := svc.EnsureManagedContainerRestartPolicies(ctx) | |
| 1318 | 1342 | ||
| 1319 | - | require.NoError(t, err) | |
| 1343 | + | require.Error(t, err) | |
| 1344 | + | assert.Contains(t, err.Error(), "route-1") | |
| 1345 | + | assert.Contains(t, err.Error(), "update failed") | |
| 1320 | 1346 | } | |
| 1321 | 1347 | ||
| 1322 | 1348 | func TestService_SyncContainers(t *testing.T) { | |
| @@ -1327,7 +1353,6 @@ func TestService_SyncContainers(t *testing.T) { | |||
| 1327 | 1353 | svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil) | |
| 1328 | 1354 | ctx := testContext() | |
| 1329 | 1355 | ||
| 1330 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 1331 | 1356 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | |
| 1332 | 1357 | { | |
| 1333 | 1358 | ID: "container-1", | |
| @@ -1535,8 +1560,6 @@ func TestService_Restart_WithAttachments(t *testing.T) { | |||
| 1535 | 1560 | }, | |
| 1536 | 1561 | }, | |
| 1537 | 1562 | }, nil).Maybe() | |
| 1538 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 1539 | - | ||
| 1540 | 1563 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | |
| 1541 | 1564 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-1").Return(nil) | |
| 1542 | 1565 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-2").Return(fmt.Errorf("boom")) | |
| @@ -1612,8 +1635,6 @@ func TestService_Restart_WithAttachments_ErrorsWhenPartiallyDeployed(t *testing. | |||
| 1612 | 1635 | }, | |
| 1613 | 1636 | }, | |
| 1614 | 1637 | }, nil).Maybe() | |
| 1615 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 1616 | - | ||
| 1617 | 1638 | err := svc.Restart(testContext(), "test.example.com", true) | |
| 1618 | 1639 | ||
| 1619 | 1640 | assert.Error(t, err) | |
| @@ -1659,8 +1680,6 @@ func TestService_Restart_WithAttachments_SucceedsWhenAllDeployed(t *testing.T) { | |||
| 1659 | 1680 | }, | |
| 1660 | 1681 | }, | |
| 1661 | 1682 | }, nil).Maybe() | |
| 1662 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |
| 1663 | - | ||
| 1664 | 1683 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | |
| 1665 | 1684 | runtime.EXPECT().RestartContainer(mock.Anything, "attach-456").Return(nil) | |
| 1666 | 1685 | ||
| @@ -2036,6 +2055,7 @@ func TestRewriteToLocalRegistry(t *testing.T) { | |||
| 2036 | 2055 | ||
| 2037 | 2056 | func TestService_Deploy_InternalDeployForcesPull(t *testing.T) { | |
| 2038 | 2057 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2058 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2039 | 2059 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2040 | 2060 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2041 | 2061 | ||
| @@ -2116,6 +2136,7 @@ func TestService_Deploy_InternalDeployForcesPull(t *testing.T) { | |||
| 2116 | 2136 | ||
| 2117 | 2137 | func TestService_AutoStart_StartsNewContainers(t *testing.T) { | |
| 2118 | 2138 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2139 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2119 | 2140 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2120 | 2141 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2121 | 2142 | ||
| @@ -2165,6 +2186,7 @@ func TestService_AutoStart_StartsNewContainers(t *testing.T) { | |||
| 2165 | 2186 | ||
| 2166 | 2187 | func TestService_AutoStart_SkipsExistingContainers(t *testing.T) { | |
| 2167 | 2188 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2189 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2168 | 2190 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2169 | 2191 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2170 | 2192 | ||
| @@ -2214,6 +2236,7 @@ func TestService_AutoStart_SkipsExistingContainers(t *testing.T) { | |||
| 2214 | 2236 | ||
| 2215 | 2237 | func TestService_AutoStart_HandlesDeployErrors(t *testing.T) { | |
| 2216 | 2238 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2239 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2217 | 2240 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2218 | 2241 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2219 | 2242 | ||
| @@ -2242,6 +2265,7 @@ func TestService_AutoStart_HandlesDeployErrors(t *testing.T) { | |||
| 2242 | 2265 | ||
| 2243 | 2266 | func TestService_AutoStart_UsesInternalDeployContext(t *testing.T) { | |
| 2244 | 2267 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2268 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2245 | 2269 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2246 | 2270 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2247 | 2271 | ||
| @@ -2294,6 +2318,7 @@ func TestService_AutoStart_UsesInternalDeployContext(t *testing.T) { | |||
| 2294 | 2318 | // until the new container is ready and traffic is switched. | |
| 2295 | 2319 | func TestService_Deploy_OrphanCleanupSkipsTrackedContainer(t *testing.T) { | |
| 2296 | 2320 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2321 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2297 | 2322 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2298 | 2323 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2299 | 2324 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -2390,6 +2415,7 @@ func TestService_Deploy_OrphanCleanupSkipsTrackedContainer(t *testing.T) { | |||
| 2390 | 2415 | // Running canonical containers are preserved (see NeverKillsRunningCanonical test). | |
| 2391 | 2416 | func TestService_Deploy_OrphanCleanupRemovesTrueOrphans(t *testing.T) { | |
| 2392 | 2417 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2418 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2393 | 2419 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2394 | 2420 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2395 | 2421 | ||
| @@ -2470,6 +2496,7 @@ func TestService_Deploy_OrphanCleanupRemovesTrueOrphans(t *testing.T) { | |||
| 2470 | 2496 | ||
| 2471 | 2497 | func TestService_Deploy_OrphanCleanupRemovesStaleNewContainer(t *testing.T) { | |
| 2472 | 2498 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2499 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2473 | 2500 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2474 | 2501 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2475 | 2502 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -2542,6 +2569,7 @@ func TestService_Deploy_OrphanCleanupRemovesStaleNewContainer(t *testing.T) { | |||
| 2542 | 2569 | ||
| 2543 | 2570 | func TestService_Deploy_TrackedTempContainerUsesAlternateTempName(t *testing.T) { | |
| 2544 | 2571 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2572 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2545 | 2573 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2546 | 2574 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2547 | 2575 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -2731,6 +2759,7 @@ func TestService_Deploy_ConcurrentSameDomain(t *testing.T) { | |||
| 2731 | 2759 | // Verify that concurrent Deploy calls for the same domain are serialized | |
| 2732 | 2760 | // and both succeed without container name conflicts. | |
| 2733 | 2761 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2762 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2734 | 2763 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2735 | 2764 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2736 | 2765 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -2841,6 +2870,7 @@ func TestService_Deploy_ConcurrentSameDomain(t *testing.T) { | |||
| 2841 | 2870 | func TestService_Deploy_ContextCancellation(t *testing.T) { | |
| 2842 | 2871 | // Verify that deploy lock acquisition respects context cancellation. | |
| 2843 | 2872 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2873 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2844 | 2874 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2845 | 2875 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2846 | 2876 | ||
| @@ -2871,6 +2901,7 @@ func TestService_Deploy_ContextCancellation(t *testing.T) { | |||
| 2871 | 2901 | // BEFORE the old container is stopped, preventing 503 errors during deployment. | |
| 2872 | 2902 | func TestService_Deploy_CacheInvalidationBeforeOldContainerStop(t *testing.T) { | |
| 2873 | 2903 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2904 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2874 | 2905 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2875 | 2906 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2876 | 2907 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -2951,6 +2982,7 @@ func TestService_Deploy_CacheInvalidationBeforeOldContainerStop(t *testing.T) { | |||
| 2951 | 2982 | // when no cache invalidator is set (e.g., in tests or minimal configurations). | |
| 2952 | 2983 | func TestService_Deploy_NilCacheInvalidator(t *testing.T) { | |
| 2953 | 2984 | runtime := mocks.NewMockContainerRuntime(t) | |
| 2985 | + | allowRestartPolicyEnsure(t, runtime) | |
| 2954 | 2986 | envLoader := mocks.NewMockEnvLoader(t) | |
| 2955 | 2987 | eventBus := mocks.NewMockEventPublisher(t) | |
| 2956 | 2988 | ||
| @@ -3000,6 +3032,7 @@ func TestService_Deploy_NilCacheInvalidator(t *testing.T) { | |||
| 3000 | 3032 | ||
| 3001 | 3033 | func TestService_Deploy_SkipsRedundantDeploy(t *testing.T) { | |
| 3002 | 3034 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3035 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3003 | 3036 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3004 | 3037 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3005 | 3038 | ||
| @@ -3060,6 +3093,7 @@ func TestService_Deploy_SkipsRedundantDeploy(t *testing.T) { | |||
| 3060 | 3093 | ||
| 3061 | 3094 | func TestService_Deploy_SkipsRedundantDeploy_WhenTrackedImageIDMissing(t *testing.T) { | |
| 3062 | 3095 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3096 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3063 | 3097 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3064 | 3098 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3065 | 3099 | ||
| @@ -3121,6 +3155,7 @@ func TestService_Deploy_SkipsRedundantDeploy_WhenTrackedImageIDMissing(t *testin | |||
| 3121 | 3155 | ||
| 3122 | 3156 | func TestService_Deploy_DoesNotSkipWhenImageIDDiffers(t *testing.T) { | |
| 3123 | 3157 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3158 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3124 | 3159 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3125 | 3160 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3126 | 3161 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -3223,6 +3258,7 @@ func TestSyncContainersRebuildsAttachmentMap(t *testing.T) { | |||
| 3223 | 3258 | ||
| 3224 | 3259 | func TestService_Deploy_SkipRedundantDeploy_ContainerNotRunning(t *testing.T) { | |
| 3225 | 3260 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3261 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3226 | 3262 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3227 | 3263 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3228 | 3264 | ||
| @@ -3286,6 +3322,7 @@ func TestService_Deploy_SkipRedundantDeploy_ContainerNotRunning(t *testing.T) { | |||
| 3286 | 3322 | ||
| 3287 | 3323 | func TestService_Deploy_DoesNotSkip_WhenEnvHashDiffers(t *testing.T) { | |
| 3288 | 3324 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3325 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3289 | 3326 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3290 | 3327 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3291 | 3328 | ||
| @@ -3344,6 +3381,7 @@ func TestService_Deploy_DoesNotSkip_WhenEnvHashDiffers(t *testing.T) { | |||
| 3344 | 3381 | ||
| 3345 | 3382 | func TestService_Deploy_DoesNotSkip_WhenEnvHashMissing(t *testing.T) { | |
| 3346 | 3383 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3384 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3347 | 3385 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3348 | 3386 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3349 | 3387 | ||
| @@ -3470,6 +3508,7 @@ func TestService_attachmentEnvDrifted_ReturnsTrueWhenHashMissing(t *testing.T) { | |||
| 3470 | 3508 | // disallows creating a container with the same name as an existing running container. | |
| 3471 | 3509 | func TestService_Deploy_OrphanCleanup_NeverKillsRunningCanonical(t *testing.T) { | |
| 3472 | 3510 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3511 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3473 | 3512 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3474 | 3513 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3475 | 3514 | ||
| @@ -3566,6 +3605,7 @@ func TestService_Deploy_OrphanCleanup_NeverKillsRunningCanonical(t *testing.T) { | |||
| 3566 | 3605 | // and the failed new container is cleaned up. | |
| 3567 | 3606 | func TestService_Deploy_RollbackOnPostSwitchCrash(t *testing.T) { | |
| 3568 | 3607 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3608 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3569 | 3609 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3570 | 3610 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3571 | 3611 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -3660,6 +3700,7 @@ func TestService_Deploy_RollbackOnPostSwitchCrash(t *testing.T) { | |||
| 3660 | 3700 | // container is finalized (stopped and removed). | |
| 3661 | 3701 | func TestService_Deploy_StabilizationSuccess(t *testing.T) { | |
| 3662 | 3702 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3703 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3663 | 3704 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3664 | 3705 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3665 | 3706 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -3748,6 +3789,7 @@ func TestService_Deploy_StabilizationSuccess(t *testing.T) { | |||
| 3748 | 3789 | // Uses testify's NotBefore() to enforce mock call ordering. | |
| 3749 | 3790 | func TestService_Deploy_ZeroDowntime_OldNeverStoppedBeforeNewReady(t *testing.T) { | |
| 3750 | 3791 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3792 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3751 | 3793 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3752 | 3794 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3753 | 3795 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | |
| @@ -3844,6 +3886,7 @@ func TestService_Deploy_ZeroDowntime_OldNeverStoppedBeforeNewReady(t *testing.T) | |||
| 3844 | 3886 | // the old container remains as the tracked container. | |
| 3845 | 3887 | func TestService_Deploy_ZeroDowntime_ReadinessFailure_OldUntouched(t *testing.T) { | |
| 3846 | 3888 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3889 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3847 | 3890 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3848 | 3891 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3849 | 3892 | ||
| @@ -3934,6 +3977,7 @@ func TestService_Deploy_ZeroDowntime_ReadinessFailure_OldUntouched(t *testing.T) | |||
| 3934 | 3977 | // finalize/drain logic being triggered. | |
| 3935 | 3978 | func TestService_Deploy_ZeroDowntime_NoExisting_FirstDeploy(t *testing.T) { | |
| 3936 | 3979 | runtime := mocks.NewMockContainerRuntime(t) | |
| 3980 | + | allowRestartPolicyEnsure(t, runtime) | |
| 3937 | 3981 | envLoader := mocks.NewMockEnvLoader(t) | |
| 3938 | 3982 | eventBus := mocks.NewMockEventPublisher(t) | |
| 3939 | 3983 | ||
| @@ -4010,6 +4054,7 @@ func TestService_Deploy_ZeroDowntime_NoExisting_FirstDeploy(t *testing.T) { | |||
| 4010 | 4054 | ||
| 4011 | 4055 | func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) { | |
| 4012 | 4056 | runtime := mocks.NewMockContainerRuntime(t) | |
| 4057 | + | allowRestartPolicyEnsure(t, runtime) | |
| 4013 | 4058 | envLoader := mocks.NewMockEnvLoader(t) | |
| 4014 | 4059 | eventBus := mocks.NewMockEventPublisher(t) | |
| 4015 | 4060 | ||
| @@ -4267,7 +4312,7 @@ func TestDeployAttachedService_SetsAliasOnContainerConfig(t *testing.T) { | |||
| 4267 | 4312 | assert.Equal(t, []string{"postgres"}, cfg.Aliases, "attachment container must have network aliases for DNS resolution") | |
| 4268 | 4313 | assert.Equal(t, "postgres", cfg.Hostname) | |
| 4269 | 4314 | assert.Equal(t, networkName, cfg.NetworkMode) | |
| 4270 | - | assert.Equal(t, domain.RestartPolicyAlways, cfg.RestartPolicy) | |
| 4315 | + | assert.Empty(t, cfg.RestartPolicy) | |
| 4271 | 4316 | }). | |
| 4272 | 4317 | Return(&domain.Container{ID: "pg-container-1", Name: containerName}, nil) | |
| 4273 | 4318 | ||
| @@ -4280,6 +4325,7 @@ func TestDeployAttachedService_SetsAliasOnContainerConfig(t *testing.T) { | |||
| 4280 | 4325 | runtime.EXPECT().GetContainerHealthStatus(mock.Anything, "pg-container-1").Return("", false, nil) | |
| 4281 | 4326 | // TCP probe: resolve endpoint fails -> falls back to delay | |
| 4282 | 4327 | runtime.EXPECT().GetContainerNetworkInfo(mock.Anything, "pg-container-1").Return("", 0, errors.New("no network")) | |
| 4328 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "pg-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 4283 | 4329 | ||
| 4284 | 4330 | err := svc.deployAttachedService(ctx, ownerDomain, serviceImage, networkName) | |
| 4285 | 4331 | require.NoError(t, err) | |
| @@ -4405,10 +4451,10 @@ func TestService_BuildContainerConfig_CompatSecurityProfileDefault(t *testing.T) | |||
| 4405 | 4451 | assert.Nil(t, cfg.CapAdd) | |
| 4406 | 4452 | } | |
| 4407 | 4453 | ||
| 4408 | - | func TestService_BuildContainerConfig_SetsRestartPolicyAlways(t *testing.T) { | |
| 4454 | + | func TestService_BuildContainerConfig_OmitsRestartPolicyBeforeReadiness(t *testing.T) { | |
| 4409 | 4455 | svc := NewService(nil, nil, nil, nil, Config{}, nil) | |
| 4410 | 4456 | ||
| 4411 | 4457 | cfg := svc.buildContainerConfig(containerConfigInput{Domain: "app.example.com", Image: "app:latest", ImageRef: "app:latest"}) | |
| 4412 | 4458 | ||
| 4413 | - | assert.Equal(t, domain.RestartPolicyAlways, cfg.RestartPolicy) | |
| 4459 | + | assert.Empty(t, cfg.RestartPolicy) | |
| 4414 | 4460 | } | |
| @@ -57,6 +57,11 @@ func testMinDelayConfig() Config { | |||||
| 57 | } | 57 | } | ||
| 58 | } | 58 | } | ||
| 59 | 59 | ||||
| 60 | + | func allowRestartPolicyEnsure(t *testing.T, runtime *mocks.MockContainerRuntime) { | |||
| 61 | + | t.Helper() | |||
| 62 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 63 | + | } | |||
| 64 | + | ||||
| 60 | func setupMetricsTest(t *testing.T) (*telemetry.Metrics, *sdkmetric.ManualReader) { | 65 | func setupMetricsTest(t *testing.T) (*telemetry.Metrics, *sdkmetric.ManualReader) { | ||
| 61 | t.Helper() | 66 | t.Helper() | ||
| 62 | 67 | ||||
| @@ -255,8 +260,6 @@ func TestService_SyncContainers_ManagedContainersMetricTracksDelta(t *testing.T) | |||||
| 255 | ctx := testContext() | 260 | ctx := testContext() | ||
| 256 | metrics, reader := setupMetricsTest(t) | 261 | metrics, reader := setupMetricsTest(t) | ||
| 257 | svc.SetMetrics(metrics) | 262 | svc.SetMetrics(metrics) | ||
| 258 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 259 | - | ||||
| 260 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | 263 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | ||
| 261 | { | 264 | { | ||
| 262 | ID: "container-1", | 265 | ID: "container-1", | ||
| @@ -379,6 +382,8 @@ func TestService_Deploy_Success(t *testing.T) { | |||||
| 379 | // Wait for ready: IsContainerRunning (first check returns true) + verify after delay | 382 | // Wait for ready: IsContainerRunning (first check returns true) + verify after delay | ||
| 380 | runtime.EXPECT().IsContainerRunning(mock.Anything, "container-123").Return(true, nil).Times(2) | 383 | runtime.EXPECT().IsContainerRunning(mock.Anything, "container-123").Return(true, nil).Times(2) | ||
| 381 | 384 | ||||
| 385 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "container-123", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 386 | + | ||||
| 382 | // Re-inspect after start | 387 | // Re-inspect after start | ||
| 383 | runningContainer := &domain.Container{ | 388 | runningContainer := &domain.Container{ | ||
| 384 | ID: "container-123", | 389 | ID: "container-123", | ||
| @@ -407,6 +412,7 @@ func TestService_Deploy_Success(t *testing.T) { | |||||
| 407 | 412 | ||||
| 408 | func TestService_Deploy_ReadinessRecoveryWindow_AllowsTransientFlap(t *testing.T) { | 413 | func TestService_Deploy_ReadinessRecoveryWindow_AllowsTransientFlap(t *testing.T) { | ||
| 409 | runtime := mocks.NewMockContainerRuntime(t) | 414 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 415 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 410 | envLoader := mocks.NewMockEnvLoader(t) | 416 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 411 | eventBus := mocks.NewMockEventPublisher(t) | 417 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 412 | 418 | ||||
| @@ -473,6 +479,7 @@ func TestService_Deploy_ReadinessRecoveryWindow_AllowsTransientFlap(t *testing.T | |||||
| 473 | 479 | ||||
| 474 | func TestService_Deploy_ImagePullFailure(t *testing.T) { | 480 | func TestService_Deploy_ImagePullFailure(t *testing.T) { | ||
| 475 | runtime := mocks.NewMockContainerRuntime(t) | 481 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 482 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 476 | envLoader := mocks.NewMockEnvLoader(t) | 483 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 477 | eventBus := mocks.NewMockEventPublisher(t) | 484 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 478 | 485 | ||||
| @@ -515,6 +522,7 @@ func TestIsPullFailure_ErrImagePullFailed(t *testing.T) { | |||||
| 515 | 522 | ||||
| 516 | func TestService_Deploy_DoesNotMisclassifyUnauthorizedEnvLoadFailure(t *testing.T) { | 523 | func TestService_Deploy_DoesNotMisclassifyUnauthorizedEnvLoadFailure(t *testing.T) { | ||
| 517 | runtime := mocks.NewMockContainerRuntime(t) | 524 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 525 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 518 | envLoader := mocks.NewMockEnvLoader(t) | 526 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 519 | eventBus := mocks.NewMockEventPublisher(t) | 527 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 520 | 528 | ||||
| @@ -555,6 +563,7 @@ func TestService_CaptureRecentContainerLogs_SkipsCanceledContext(t *testing.T) { | |||||
| 555 | 563 | ||||
| 556 | func TestService_Deploy_ReadinessFailureReturnsDeployFailureWithLogs(t *testing.T) { | 564 | func TestService_Deploy_ReadinessFailureReturnsDeployFailureWithLogs(t *testing.T) { | ||
| 557 | runtime := mocks.NewMockContainerRuntime(t) | 565 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 566 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 558 | envLoader := mocks.NewMockEnvLoader(t) | 567 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 559 | eventBus := mocks.NewMockEventPublisher(t) | 568 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 560 | 569 | ||||
| @@ -614,6 +623,7 @@ func TestService_Deploy_ReadinessFailureReturnsDeployFailureWithLogs(t *testing. | |||||
| 614 | 623 | ||||
| 615 | func TestService_Deploy_StrictHealthModeWithoutHealthcheckReturnsHint(t *testing.T) { | 624 | func TestService_Deploy_StrictHealthModeWithoutHealthcheckReturnsHint(t *testing.T) { | ||
| 616 | runtime := mocks.NewMockContainerRuntime(t) | 625 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 626 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 617 | envLoader := mocks.NewMockEnvLoader(t) | 627 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 618 | eventBus := mocks.NewMockEventPublisher(t) | 628 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 619 | 629 | ||||
| @@ -725,6 +735,7 @@ func TestService_PullImage_InternalRetriesOnConnectionRefused(t *testing.T) { | |||||
| 725 | 735 | ||||
| 726 | func TestService_Deploy_ReplacesExistingContainer(t *testing.T) { | 736 | func TestService_Deploy_ReplacesExistingContainer(t *testing.T) { | ||
| 727 | runtime := mocks.NewMockContainerRuntime(t) | 737 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 738 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 728 | envLoader := mocks.NewMockEnvLoader(t) | 739 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 729 | eventBus := mocks.NewMockEventPublisher(t) | 740 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 730 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 741 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -808,6 +819,7 @@ func TestService_Deploy_ReplacesExistingContainer(t *testing.T) { | |||||
| 808 | // from killing the real active container after a Gordon restart. | 819 | // from killing the real active container after a Gordon restart. | ||
| 809 | func TestService_Deploy_ResolvesExistingFromRuntime_WhenMemoryStale(t *testing.T) { | 820 | func TestService_Deploy_ResolvesExistingFromRuntime_WhenMemoryStale(t *testing.T) { | ||
| 810 | runtime := mocks.NewMockContainerRuntime(t) | 821 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 822 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 811 | envLoader := mocks.NewMockEnvLoader(t) | 823 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 812 | eventBus := mocks.NewMockEventPublisher(t) | 824 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 813 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 825 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -907,6 +919,7 @@ func TestService_Deploy_ResolvesExistingFromRuntime_WhenMemoryStale(t *testing.T | |||||
| 907 | 919 | ||||
| 908 | func TestService_Deploy_SkipRedundantDeploy_GetImageIDError(t *testing.T) { | 920 | func TestService_Deploy_SkipRedundantDeploy_GetImageIDError(t *testing.T) { | ||
| 909 | runtime := mocks.NewMockContainerRuntime(t) | 921 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 922 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 910 | envLoader := mocks.NewMockEnvLoader(t) | 923 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 911 | eventBus := mocks.NewMockEventPublisher(t) | 924 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 912 | 925 | ||||
| @@ -969,6 +982,7 @@ func TestService_Deploy_SkipRedundantDeploy_GetImageIDError(t *testing.T) { | |||||
| 969 | 982 | ||||
| 970 | func TestService_Deploy_WithNetworkIsolation(t *testing.T) { | 983 | func TestService_Deploy_WithNetworkIsolation(t *testing.T) { | ||
| 971 | runtime := mocks.NewMockContainerRuntime(t) | 984 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 985 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 972 | envLoader := mocks.NewMockEnvLoader(t) | 986 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 973 | eventBus := mocks.NewMockEventPublisher(t) | 987 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 974 | 988 | ||||
| @@ -1027,6 +1041,7 @@ func TestService_Deploy_WithNetworkIsolation(t *testing.T) { | |||||
| 1027 | 1041 | ||||
| 1028 | func TestService_Deploy_WithVolumeAutoCreate(t *testing.T) { | 1042 | func TestService_Deploy_WithVolumeAutoCreate(t *testing.T) { | ||
| 1029 | runtime := mocks.NewMockContainerRuntime(t) | 1043 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 1044 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 1030 | envLoader := mocks.NewMockEnvLoader(t) | 1045 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 1031 | eventBus := mocks.NewMockEventPublisher(t) | 1046 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 1032 | 1047 | ||||
| @@ -1295,7 +1310,7 @@ func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsA | |||||
| 1295 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | 1310 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | ||
| 1296 | } | 1311 | } | ||
| 1297 | 1312 | ||||
| 1298 | - | func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testing.T) { | 1313 | + | func TestService_EnsureManagedContainerRestartPolicies_ContinuesAndReturnsError(t *testing.T) { |
| 1299 | runtime := mocks.NewMockContainerRuntime(t) | 1314 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 1300 | envLoader := mocks.NewMockEnvLoader(t) | 1315 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 1301 | eventBus := mocks.NewMockEventPublisher(t) | 1316 | eventBus := mocks.NewMockEventPublisher(t) | ||
| @@ -1311,12 +1326,23 @@ func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testin | |||||
| 1311 | domain.LabelDomain: "app.example.com", | 1326 | domain.LabelDomain: "app.example.com", | ||
| 1312 | }, | 1327 | }, | ||
| 1313 | } | 1328 | } | ||
| 1329 | + | routeContainer2 := &domain.Container{ | |||
| 1330 | + | ID: "route-2", | |||
| 1331 | + | Name: "gordon-api.example.com", | |||
| 1332 | + | Labels: map[string]string{ | |||
| 1333 | + | domain.LabelManaged: "true", | |||
| 1334 | + | domain.LabelDomain: "api.example.com", | |||
| 1335 | + | }, | |||
| 1336 | + | } | |||
| 1314 | 1337 | ||||
| 1315 | - | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once() | 1338 | + | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer, routeContainer2}, nil).Once() |
| 1316 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | 1339 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | ||
| 1340 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-2", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 1317 | err := svc.EnsureManagedContainerRestartPolicies(ctx) | 1341 | err := svc.EnsureManagedContainerRestartPolicies(ctx) | ||
| 1318 | 1342 | ||||
| 1319 | - | require.NoError(t, err) | 1343 | + | require.Error(t, err) |
| 1344 | + | assert.Contains(t, err.Error(), "route-1") | |||
| 1345 | + | assert.Contains(t, err.Error(), "update failed") | |||
| 1320 | } | 1346 | } | ||
| 1321 | 1347 | ||||
| 1322 | func TestService_SyncContainers(t *testing.T) { | 1348 | func TestService_SyncContainers(t *testing.T) { | ||
| @@ -1327,7 +1353,6 @@ func TestService_SyncContainers(t *testing.T) { | |||||
| 1327 | svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil) | 1353 | svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil) | ||
| 1328 | ctx := testContext() | 1354 | ctx := testContext() | ||
| 1329 | 1355 | ||||
| 1330 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 1331 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | 1356 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{ | ||
| 1332 | { | 1357 | { | ||
| 1333 | ID: "container-1", | 1358 | ID: "container-1", | ||
| @@ -1535,8 +1560,6 @@ func TestService_Restart_WithAttachments(t *testing.T) { | |||||
| 1535 | }, | 1560 | }, | ||
| 1536 | }, | 1561 | }, | ||
| 1537 | }, nil).Maybe() | 1562 | }, nil).Maybe() | ||
| 1538 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 1539 | - | ||||
| 1540 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | 1563 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | ||
| 1541 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-1").Return(nil) | 1564 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-1").Return(nil) | ||
| 1542 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-2").Return(fmt.Errorf("boom")) | 1565 | runtime.EXPECT().RestartContainer(mock.Anything, "container-attach-2").Return(fmt.Errorf("boom")) | ||
| @@ -1612,8 +1635,6 @@ func TestService_Restart_WithAttachments_ErrorsWhenPartiallyDeployed(t *testing. | |||||
| 1612 | }, | 1635 | }, | ||
| 1613 | }, | 1636 | }, | ||
| 1614 | }, nil).Maybe() | 1637 | }, nil).Maybe() | ||
| 1615 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 1616 | - | ||||
| 1617 | err := svc.Restart(testContext(), "test.example.com", true) | 1638 | err := svc.Restart(testContext(), "test.example.com", true) | ||
| 1618 | 1639 | ||||
| 1619 | assert.Error(t, err) | 1640 | assert.Error(t, err) | ||
| @@ -1659,8 +1680,6 @@ func TestService_Restart_WithAttachments_SucceedsWhenAllDeployed(t *testing.T) { | |||||
| 1659 | }, | 1680 | }, | ||
| 1660 | }, | 1681 | }, | ||
| 1661 | }, nil).Maybe() | 1682 | }, nil).Maybe() | ||
| 1662 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, mock.Anything, domain.RestartPolicyAlways).Return(nil).Maybe() | |||
| 1663 | - | ||||
| 1664 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | 1683 | runtime.EXPECT().RestartContainer(mock.Anything, "container-123").Return(nil) | ||
| 1665 | runtime.EXPECT().RestartContainer(mock.Anything, "attach-456").Return(nil) | 1684 | runtime.EXPECT().RestartContainer(mock.Anything, "attach-456").Return(nil) | ||
| 1666 | 1685 | ||||
| @@ -2036,6 +2055,7 @@ func TestRewriteToLocalRegistry(t *testing.T) { | |||||
| 2036 | 2055 | ||||
| 2037 | func TestService_Deploy_InternalDeployForcesPull(t *testing.T) { | 2056 | func TestService_Deploy_InternalDeployForcesPull(t *testing.T) { | ||
| 2038 | runtime := mocks.NewMockContainerRuntime(t) | 2057 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2058 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2039 | envLoader := mocks.NewMockEnvLoader(t) | 2059 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2040 | eventBus := mocks.NewMockEventPublisher(t) | 2060 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2041 | 2061 | ||||
| @@ -2116,6 +2136,7 @@ func TestService_Deploy_InternalDeployForcesPull(t *testing.T) { | |||||
| 2116 | 2136 | ||||
| 2117 | func TestService_AutoStart_StartsNewContainers(t *testing.T) { | 2137 | func TestService_AutoStart_StartsNewContainers(t *testing.T) { | ||
| 2118 | runtime := mocks.NewMockContainerRuntime(t) | 2138 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2139 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2119 | envLoader := mocks.NewMockEnvLoader(t) | 2140 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2120 | eventBus := mocks.NewMockEventPublisher(t) | 2141 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2121 | 2142 | ||||
| @@ -2165,6 +2186,7 @@ func TestService_AutoStart_StartsNewContainers(t *testing.T) { | |||||
| 2165 | 2186 | ||||
| 2166 | func TestService_AutoStart_SkipsExistingContainers(t *testing.T) { | 2187 | func TestService_AutoStart_SkipsExistingContainers(t *testing.T) { | ||
| 2167 | runtime := mocks.NewMockContainerRuntime(t) | 2188 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2189 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2168 | envLoader := mocks.NewMockEnvLoader(t) | 2190 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2169 | eventBus := mocks.NewMockEventPublisher(t) | 2191 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2170 | 2192 | ||||
| @@ -2214,6 +2236,7 @@ func TestService_AutoStart_SkipsExistingContainers(t *testing.T) { | |||||
| 2214 | 2236 | ||||
| 2215 | func TestService_AutoStart_HandlesDeployErrors(t *testing.T) { | 2237 | func TestService_AutoStart_HandlesDeployErrors(t *testing.T) { | ||
| 2216 | runtime := mocks.NewMockContainerRuntime(t) | 2238 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2239 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2217 | envLoader := mocks.NewMockEnvLoader(t) | 2240 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2218 | eventBus := mocks.NewMockEventPublisher(t) | 2241 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2219 | 2242 | ||||
| @@ -2242,6 +2265,7 @@ func TestService_AutoStart_HandlesDeployErrors(t *testing.T) { | |||||
| 2242 | 2265 | ||||
| 2243 | func TestService_AutoStart_UsesInternalDeployContext(t *testing.T) { | 2266 | func TestService_AutoStart_UsesInternalDeployContext(t *testing.T) { | ||
| 2244 | runtime := mocks.NewMockContainerRuntime(t) | 2267 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2268 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2245 | envLoader := mocks.NewMockEnvLoader(t) | 2269 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2246 | eventBus := mocks.NewMockEventPublisher(t) | 2270 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2247 | 2271 | ||||
| @@ -2294,6 +2318,7 @@ func TestService_AutoStart_UsesInternalDeployContext(t *testing.T) { | |||||
| 2294 | // until the new container is ready and traffic is switched. | 2318 | // until the new container is ready and traffic is switched. | ||
| 2295 | func TestService_Deploy_OrphanCleanupSkipsTrackedContainer(t *testing.T) { | 2319 | func TestService_Deploy_OrphanCleanupSkipsTrackedContainer(t *testing.T) { | ||
| 2296 | runtime := mocks.NewMockContainerRuntime(t) | 2320 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2321 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2297 | envLoader := mocks.NewMockEnvLoader(t) | 2322 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2298 | eventBus := mocks.NewMockEventPublisher(t) | 2323 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2299 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 2324 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -2390,6 +2415,7 @@ func TestService_Deploy_OrphanCleanupSkipsTrackedContainer(t *testing.T) { | |||||
| 2390 | // Running canonical containers are preserved (see NeverKillsRunningCanonical test). | 2415 | // Running canonical containers are preserved (see NeverKillsRunningCanonical test). | ||
| 2391 | func TestService_Deploy_OrphanCleanupRemovesTrueOrphans(t *testing.T) { | 2416 | func TestService_Deploy_OrphanCleanupRemovesTrueOrphans(t *testing.T) { | ||
| 2392 | runtime := mocks.NewMockContainerRuntime(t) | 2417 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2418 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2393 | envLoader := mocks.NewMockEnvLoader(t) | 2419 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2394 | eventBus := mocks.NewMockEventPublisher(t) | 2420 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2395 | 2421 | ||||
| @@ -2470,6 +2496,7 @@ func TestService_Deploy_OrphanCleanupRemovesTrueOrphans(t *testing.T) { | |||||
| 2470 | 2496 | ||||
| 2471 | func TestService_Deploy_OrphanCleanupRemovesStaleNewContainer(t *testing.T) { | 2497 | func TestService_Deploy_OrphanCleanupRemovesStaleNewContainer(t *testing.T) { | ||
| 2472 | runtime := mocks.NewMockContainerRuntime(t) | 2498 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2499 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2473 | envLoader := mocks.NewMockEnvLoader(t) | 2500 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2474 | eventBus := mocks.NewMockEventPublisher(t) | 2501 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2475 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 2502 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -2542,6 +2569,7 @@ func TestService_Deploy_OrphanCleanupRemovesStaleNewContainer(t *testing.T) { | |||||
| 2542 | 2569 | ||||
| 2543 | func TestService_Deploy_TrackedTempContainerUsesAlternateTempName(t *testing.T) { | 2570 | func TestService_Deploy_TrackedTempContainerUsesAlternateTempName(t *testing.T) { | ||
| 2544 | runtime := mocks.NewMockContainerRuntime(t) | 2571 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2572 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2545 | envLoader := mocks.NewMockEnvLoader(t) | 2573 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2546 | eventBus := mocks.NewMockEventPublisher(t) | 2574 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2547 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 2575 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -2731,6 +2759,7 @@ func TestService_Deploy_ConcurrentSameDomain(t *testing.T) { | |||||
| 2731 | // Verify that concurrent Deploy calls for the same domain are serialized | 2759 | // Verify that concurrent Deploy calls for the same domain are serialized | ||
| 2732 | // and both succeed without container name conflicts. | 2760 | // and both succeed without container name conflicts. | ||
| 2733 | runtime := mocks.NewMockContainerRuntime(t) | 2761 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2762 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2734 | envLoader := mocks.NewMockEnvLoader(t) | 2763 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2735 | eventBus := mocks.NewMockEventPublisher(t) | 2764 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2736 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 2765 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -2841,6 +2870,7 @@ func TestService_Deploy_ConcurrentSameDomain(t *testing.T) { | |||||
| 2841 | func TestService_Deploy_ContextCancellation(t *testing.T) { | 2870 | func TestService_Deploy_ContextCancellation(t *testing.T) { | ||
| 2842 | // Verify that deploy lock acquisition respects context cancellation. | 2871 | // Verify that deploy lock acquisition respects context cancellation. | ||
| 2843 | runtime := mocks.NewMockContainerRuntime(t) | 2872 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2873 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2844 | envLoader := mocks.NewMockEnvLoader(t) | 2874 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2845 | eventBus := mocks.NewMockEventPublisher(t) | 2875 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2846 | 2876 | ||||
| @@ -2871,6 +2901,7 @@ func TestService_Deploy_ContextCancellation(t *testing.T) { | |||||
| 2871 | // BEFORE the old container is stopped, preventing 503 errors during deployment. | 2901 | // BEFORE the old container is stopped, preventing 503 errors during deployment. | ||
| 2872 | func TestService_Deploy_CacheInvalidationBeforeOldContainerStop(t *testing.T) { | 2902 | func TestService_Deploy_CacheInvalidationBeforeOldContainerStop(t *testing.T) { | ||
| 2873 | runtime := mocks.NewMockContainerRuntime(t) | 2903 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2904 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2874 | envLoader := mocks.NewMockEnvLoader(t) | 2905 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2875 | eventBus := mocks.NewMockEventPublisher(t) | 2906 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2876 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 2907 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -2951,6 +2982,7 @@ func TestService_Deploy_CacheInvalidationBeforeOldContainerStop(t *testing.T) { | |||||
| 2951 | // when no cache invalidator is set (e.g., in tests or minimal configurations). | 2982 | // when no cache invalidator is set (e.g., in tests or minimal configurations). | ||
| 2952 | func TestService_Deploy_NilCacheInvalidator(t *testing.T) { | 2983 | func TestService_Deploy_NilCacheInvalidator(t *testing.T) { | ||
| 2953 | runtime := mocks.NewMockContainerRuntime(t) | 2984 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 2985 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 2954 | envLoader := mocks.NewMockEnvLoader(t) | 2986 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 2955 | eventBus := mocks.NewMockEventPublisher(t) | 2987 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 2956 | 2988 | ||||
| @@ -3000,6 +3032,7 @@ func TestService_Deploy_NilCacheInvalidator(t *testing.T) { | |||||
| 3000 | 3032 | ||||
| 3001 | func TestService_Deploy_SkipsRedundantDeploy(t *testing.T) { | 3033 | func TestService_Deploy_SkipsRedundantDeploy(t *testing.T) { | ||
| 3002 | runtime := mocks.NewMockContainerRuntime(t) | 3034 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3035 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3003 | envLoader := mocks.NewMockEnvLoader(t) | 3036 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3004 | eventBus := mocks.NewMockEventPublisher(t) | 3037 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3005 | 3038 | ||||
| @@ -3060,6 +3093,7 @@ func TestService_Deploy_SkipsRedundantDeploy(t *testing.T) { | |||||
| 3060 | 3093 | ||||
| 3061 | func TestService_Deploy_SkipsRedundantDeploy_WhenTrackedImageIDMissing(t *testing.T) { | 3094 | func TestService_Deploy_SkipsRedundantDeploy_WhenTrackedImageIDMissing(t *testing.T) { | ||
| 3062 | runtime := mocks.NewMockContainerRuntime(t) | 3095 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3096 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3063 | envLoader := mocks.NewMockEnvLoader(t) | 3097 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3064 | eventBus := mocks.NewMockEventPublisher(t) | 3098 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3065 | 3099 | ||||
| @@ -3121,6 +3155,7 @@ func TestService_Deploy_SkipsRedundantDeploy_WhenTrackedImageIDMissing(t *testin | |||||
| 3121 | 3155 | ||||
| 3122 | func TestService_Deploy_DoesNotSkipWhenImageIDDiffers(t *testing.T) { | 3156 | func TestService_Deploy_DoesNotSkipWhenImageIDDiffers(t *testing.T) { | ||
| 3123 | runtime := mocks.NewMockContainerRuntime(t) | 3157 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3158 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3124 | envLoader := mocks.NewMockEnvLoader(t) | 3159 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3125 | eventBus := mocks.NewMockEventPublisher(t) | 3160 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3126 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 3161 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -3223,6 +3258,7 @@ func TestSyncContainersRebuildsAttachmentMap(t *testing.T) { | |||||
| 3223 | 3258 | ||||
| 3224 | func TestService_Deploy_SkipRedundantDeploy_ContainerNotRunning(t *testing.T) { | 3259 | func TestService_Deploy_SkipRedundantDeploy_ContainerNotRunning(t *testing.T) { | ||
| 3225 | runtime := mocks.NewMockContainerRuntime(t) | 3260 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3261 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3226 | envLoader := mocks.NewMockEnvLoader(t) | 3262 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3227 | eventBus := mocks.NewMockEventPublisher(t) | 3263 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3228 | 3264 | ||||
| @@ -3286,6 +3322,7 @@ func TestService_Deploy_SkipRedundantDeploy_ContainerNotRunning(t *testing.T) { | |||||
| 3286 | 3322 | ||||
| 3287 | func TestService_Deploy_DoesNotSkip_WhenEnvHashDiffers(t *testing.T) { | 3323 | func TestService_Deploy_DoesNotSkip_WhenEnvHashDiffers(t *testing.T) { | ||
| 3288 | runtime := mocks.NewMockContainerRuntime(t) | 3324 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3325 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3289 | envLoader := mocks.NewMockEnvLoader(t) | 3326 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3290 | eventBus := mocks.NewMockEventPublisher(t) | 3327 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3291 | 3328 | ||||
| @@ -3344,6 +3381,7 @@ func TestService_Deploy_DoesNotSkip_WhenEnvHashDiffers(t *testing.T) { | |||||
| 3344 | 3381 | ||||
| 3345 | func TestService_Deploy_DoesNotSkip_WhenEnvHashMissing(t *testing.T) { | 3382 | func TestService_Deploy_DoesNotSkip_WhenEnvHashMissing(t *testing.T) { | ||
| 3346 | runtime := mocks.NewMockContainerRuntime(t) | 3383 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3384 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3347 | envLoader := mocks.NewMockEnvLoader(t) | 3385 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3348 | eventBus := mocks.NewMockEventPublisher(t) | 3386 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3349 | 3387 | ||||
| @@ -3470,6 +3508,7 @@ func TestService_attachmentEnvDrifted_ReturnsTrueWhenHashMissing(t *testing.T) { | |||||
| 3470 | // disallows creating a container with the same name as an existing running container. | 3508 | // disallows creating a container with the same name as an existing running container. | ||
| 3471 | func TestService_Deploy_OrphanCleanup_NeverKillsRunningCanonical(t *testing.T) { | 3509 | func TestService_Deploy_OrphanCleanup_NeverKillsRunningCanonical(t *testing.T) { | ||
| 3472 | runtime := mocks.NewMockContainerRuntime(t) | 3510 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3511 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3473 | envLoader := mocks.NewMockEnvLoader(t) | 3512 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3474 | eventBus := mocks.NewMockEventPublisher(t) | 3513 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3475 | 3514 | ||||
| @@ -3566,6 +3605,7 @@ func TestService_Deploy_OrphanCleanup_NeverKillsRunningCanonical(t *testing.T) { | |||||
| 3566 | // and the failed new container is cleaned up. | 3605 | // and the failed new container is cleaned up. | ||
| 3567 | func TestService_Deploy_RollbackOnPostSwitchCrash(t *testing.T) { | 3606 | func TestService_Deploy_RollbackOnPostSwitchCrash(t *testing.T) { | ||
| 3568 | runtime := mocks.NewMockContainerRuntime(t) | 3607 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3608 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3569 | envLoader := mocks.NewMockEnvLoader(t) | 3609 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3570 | eventBus := mocks.NewMockEventPublisher(t) | 3610 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3571 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 3611 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -3660,6 +3700,7 @@ func TestService_Deploy_RollbackOnPostSwitchCrash(t *testing.T) { | |||||
| 3660 | // container is finalized (stopped and removed). | 3700 | // container is finalized (stopped and removed). | ||
| 3661 | func TestService_Deploy_StabilizationSuccess(t *testing.T) { | 3701 | func TestService_Deploy_StabilizationSuccess(t *testing.T) { | ||
| 3662 | runtime := mocks.NewMockContainerRuntime(t) | 3702 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3703 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3663 | envLoader := mocks.NewMockEnvLoader(t) | 3704 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3664 | eventBus := mocks.NewMockEventPublisher(t) | 3705 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3665 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 3706 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -3748,6 +3789,7 @@ func TestService_Deploy_StabilizationSuccess(t *testing.T) { | |||||
| 3748 | // Uses testify's NotBefore() to enforce mock call ordering. | 3789 | // Uses testify's NotBefore() to enforce mock call ordering. | ||
| 3749 | func TestService_Deploy_ZeroDowntime_OldNeverStoppedBeforeNewReady(t *testing.T) { | 3790 | func TestService_Deploy_ZeroDowntime_OldNeverStoppedBeforeNewReady(t *testing.T) { | ||
| 3750 | runtime := mocks.NewMockContainerRuntime(t) | 3791 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3792 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3751 | envLoader := mocks.NewMockEnvLoader(t) | 3793 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3752 | eventBus := mocks.NewMockEventPublisher(t) | 3794 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3753 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | 3795 | cacheInvalidator := mocks.NewMockProxyCacheInvalidator(t) | ||
| @@ -3844,6 +3886,7 @@ func TestService_Deploy_ZeroDowntime_OldNeverStoppedBeforeNewReady(t *testing.T) | |||||
| 3844 | // the old container remains as the tracked container. | 3886 | // the old container remains as the tracked container. | ||
| 3845 | func TestService_Deploy_ZeroDowntime_ReadinessFailure_OldUntouched(t *testing.T) { | 3887 | func TestService_Deploy_ZeroDowntime_ReadinessFailure_OldUntouched(t *testing.T) { | ||
| 3846 | runtime := mocks.NewMockContainerRuntime(t) | 3888 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3889 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3847 | envLoader := mocks.NewMockEnvLoader(t) | 3890 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3848 | eventBus := mocks.NewMockEventPublisher(t) | 3891 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3849 | 3892 | ||||
| @@ -3934,6 +3977,7 @@ func TestService_Deploy_ZeroDowntime_ReadinessFailure_OldUntouched(t *testing.T) | |||||
| 3934 | // finalize/drain logic being triggered. | 3977 | // finalize/drain logic being triggered. | ||
| 3935 | func TestService_Deploy_ZeroDowntime_NoExisting_FirstDeploy(t *testing.T) { | 3978 | func TestService_Deploy_ZeroDowntime_NoExisting_FirstDeploy(t *testing.T) { | ||
| 3936 | runtime := mocks.NewMockContainerRuntime(t) | 3979 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 3980 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 3937 | envLoader := mocks.NewMockEnvLoader(t) | 3981 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 3938 | eventBus := mocks.NewMockEventPublisher(t) | 3982 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 3939 | 3983 | ||||
| @@ -4010,6 +4054,7 @@ func TestService_Deploy_ZeroDowntime_NoExisting_FirstDeploy(t *testing.T) { | |||||
| 4010 | 4054 | ||||
| 4011 | func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) { | 4055 | func TestService_Deploy_PropagatesImageLabelsToContainerConfig(t *testing.T) { | ||
| 4012 | runtime := mocks.NewMockContainerRuntime(t) | 4056 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 4057 | + | allowRestartPolicyEnsure(t, runtime) | |||
| 4013 | envLoader := mocks.NewMockEnvLoader(t) | 4058 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 4014 | eventBus := mocks.NewMockEventPublisher(t) | 4059 | eventBus := mocks.NewMockEventPublisher(t) | ||
| 4015 | 4060 | ||||
| @@ -4267,7 +4312,7 @@ func TestDeployAttachedService_SetsAliasOnContainerConfig(t *testing.T) { | |||||
| 4267 | assert.Equal(t, []string{"postgres"}, cfg.Aliases, "attachment container must have network aliases for DNS resolution") | 4312 | assert.Equal(t, []string{"postgres"}, cfg.Aliases, "attachment container must have network aliases for DNS resolution") | ||
| 4268 | assert.Equal(t, "postgres", cfg.Hostname) | 4313 | assert.Equal(t, "postgres", cfg.Hostname) | ||
| 4269 | assert.Equal(t, networkName, cfg.NetworkMode) | 4314 | assert.Equal(t, networkName, cfg.NetworkMode) | ||
| 4270 | - | assert.Equal(t, domain.RestartPolicyAlways, cfg.RestartPolicy) | 4315 | + | assert.Empty(t, cfg.RestartPolicy) |
| 4271 | }). | 4316 | }). | ||
| 4272 | Return(&domain.Container{ID: "pg-container-1", Name: containerName}, nil) | 4317 | Return(&domain.Container{ID: "pg-container-1", Name: containerName}, nil) | ||
| 4273 | 4318 | ||||
| @@ -4280,6 +4325,7 @@ func TestDeployAttachedService_SetsAliasOnContainerConfig(t *testing.T) { | |||||
| 4280 | runtime.EXPECT().GetContainerHealthStatus(mock.Anything, "pg-container-1").Return("", false, nil) | 4325 | runtime.EXPECT().GetContainerHealthStatus(mock.Anything, "pg-container-1").Return("", false, nil) | ||
| 4281 | // TCP probe: resolve endpoint fails -> falls back to delay | 4326 | // TCP probe: resolve endpoint fails -> falls back to delay | ||
| 4282 | runtime.EXPECT().GetContainerNetworkInfo(mock.Anything, "pg-container-1").Return("", 0, errors.New("no network")) | 4327 | runtime.EXPECT().GetContainerNetworkInfo(mock.Anything, "pg-container-1").Return("", 0, errors.New("no network")) | ||
| 4328 | + | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "pg-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 4283 | 4329 | ||||
| 4284 | err := svc.deployAttachedService(ctx, ownerDomain, serviceImage, networkName) | 4330 | err := svc.deployAttachedService(ctx, ownerDomain, serviceImage, networkName) | ||
| 4285 | require.NoError(t, err) | 4331 | require.NoError(t, err) | ||
| @@ -4405,10 +4451,10 @@ func TestService_BuildContainerConfig_CompatSecurityProfileDefault(t *testing.T) | |||||
| 4405 | assert.Nil(t, cfg.CapAdd) | 4451 | assert.Nil(t, cfg.CapAdd) | ||
| 4406 | } | 4452 | } | ||
| 4407 | 4453 | ||||
| 4408 | - | func TestService_BuildContainerConfig_SetsRestartPolicyAlways(t *testing.T) { | 4454 | + | func TestService_BuildContainerConfig_OmitsRestartPolicyBeforeReadiness(t *testing.T) { |
| 4409 | svc := NewService(nil, nil, nil, nil, Config{}, nil) | 4455 | svc := NewService(nil, nil, nil, nil, Config{}, nil) | ||
| 4410 | 4456 | ||||
| 4411 | cfg := svc.buildContainerConfig(containerConfigInput{Domain: "app.example.com", Image: "app:latest", ImageRef: "app:latest"}) | 4457 | cfg := svc.buildContainerConfig(containerConfigInput{Domain: "app.example.com", Image: "app:latest", ImageRef: "app:latest"}) | ||
| 4412 | 4458 | ||||
| 4413 | - | assert.Equal(t, domain.RestartPolicyAlways, cfg.RestartPolicy) | 4459 | + | assert.Empty(t, cfg.RestartPolicy) |
| 4414 | } | 4460 | } | ||