-
Notifications
You must be signed in to change notification settings - Fork 4
Attach to externally-started LS container #190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -346,7 +346,34 @@ func selectContainersToStart(ctx context.Context, rt runtime.Runtime, sink outpu | |
| output.EmitInfo(sink, "LocalStack is already running") | ||
| continue | ||
| } | ||
|
|
||
| imageRepo, _, _ := strings.Cut(c.Image, ":") | ||
| found, err := rt.FindRunningByImage(ctx, imageRepo, c.ContainerPort, c.Port) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to scan for running containers: %w", err) | ||
| } | ||
| if found != nil { | ||
| runningTag := imageTagFrom(found.Image) | ||
| configTag := c.Tag | ||
| if configTag == "" { | ||
| configTag = "latest" | ||
| } | ||
| if runningTag != configTag { | ||
| output.EmitWarning(sink, fmt.Sprintf( | ||
| "Found running LocalStack %s, config specifies %s — using the running instance", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| runningTag, configTag, | ||
|
Comment on lines
+363
to
+364
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: When using 2026.03 with CLIv1 and CLIv2 I see the warning showing up. Maybe the tag is using the commit suffix and confuses the CLI? # CLIv1 command
localstack start -s localstack:2026.03
# CLIv2 config
tag = '2026.03'> Warning: LocalStack 2026.3.0:e28f8cfa4 is already running on port 4566 (config specifies 2026.03) — using the running instance |
||
| )) | ||
| } else { | ||
| output.EmitInfo(sink, "LocalStack is already running") | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if err := ports.CheckAvailable(c.Port); err != nil { | ||
| if info, infoErr := fetchLocalStackInfo(ctx, c.Port); infoErr == nil { | ||
| emitLocalStackAlreadyRunningWarning(sink, c.Port, info.Version, c.Tag) | ||
| continue | ||
| } | ||
| emitPortInUseError(sink, c.Port) | ||
| emitEmulatorStartError(ctx, tel, c, telemetry.ErrCodePortConflict, err.Error()) | ||
| return nil, output.NewSilentError(err) | ||
|
|
@@ -356,6 +383,28 @@ func selectContainersToStart(ctx context.Context, rt runtime.Runtime, sink outpu | |
| return filtered, nil | ||
| } | ||
|
|
||
| func imageTagFrom(image string) string { | ||
| _, tag, found := strings.Cut(image, ":") | ||
| if !found || tag == "" { | ||
| return "latest" | ||
| } | ||
| return tag | ||
| } | ||
|
|
||
| func emitLocalStackAlreadyRunningWarning(sink output.Sink, port, runningVersion, configTag string) { | ||
| if configTag == "" { | ||
| configTag = "latest" | ||
| } | ||
| if runningVersion != configTag { | ||
| output.EmitWarning(sink, fmt.Sprintf( | ||
| "LocalStack %s is already running on port %s (config specifies %s) — using the running instance", | ||
| runningVersion, port, configTag, | ||
| )) | ||
| } else { | ||
| output.EmitInfo(sink, fmt.Sprintf("LocalStack %s is already running on port %s", runningVersion, port)) | ||
| } | ||
| } | ||
|
|
||
| func emitPortInUseError(sink output.Sink, port string) { | ||
| actions := []output.ErrorAction{ | ||
| {Label: "Stop existing emulator:", Value: "lstk stop"}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Not the best line to comment on, but |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import ( | |
|
|
||
| "github.com/containerd/errdefs" | ||
| "github.com/docker/docker/api/types/container" | ||
| "github.com/docker/docker/api/types/filters" | ||
| "github.com/docker/docker/api/types/image" | ||
| "github.com/docker/docker/client" | ||
| "github.com/docker/docker/pkg/stdcopy" | ||
|
|
@@ -230,7 +231,12 @@ func (d *DockerRuntime) Stop(ctx context.Context, containerName string) error { | |
| if err := d.client.ContainerStop(ctx, containerName, container.StopOptions{}); err != nil { | ||
| return err | ||
| } | ||
| return d.client.ContainerRemove(ctx, containerName, container.RemoveOptions{}) | ||
| err := d.client.ContainerRemove(ctx, containerName, container.RemoveOptions{}) | ||
| // Ignore conflict and not-found: container is gone, which is the goal. | ||
| if err != nil && !errdefs.IsConflict(err) && !errdefs.IsNotFound(err) { | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (d *DockerRuntime) Remove(ctx context.Context, containerName string) error { | ||
|
|
@@ -329,6 +335,45 @@ func (d *DockerRuntime) GetBoundPort(ctx context.Context, containerName string, | |
| return bindings[0].HostPort, nil | ||
| } | ||
|
|
||
| func (d *DockerRuntime) FindRunningByImage(ctx context.Context, imageRepo string, containerPort string, hostPort string) (*RunningContainer, error) { | ||
| list, err := d.client.ContainerList(ctx, container.ListOptions{ | ||
| Filters: filters.NewArgs(filters.Arg("status", "running")), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| portStr, proto, found := strings.Cut(containerPort, "/") | ||
| if !found { | ||
| proto = "tcp" | ||
| } | ||
| privatePort, err := strconv.ParseUint(portStr, 10, 16) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("invalid container port %q: %w", containerPort, err) | ||
| } | ||
|
|
||
| prefix := imageRepo + ":" | ||
| for _, c := range list { | ||
| if c.Image != imageRepo && !strings.HasPrefix(c.Image, prefix) { | ||
| continue | ||
| } | ||
| for _, p := range c.Ports { | ||
| if p.PrivatePort == uint16(privatePort) && p.Type == proto && strconv.Itoa(int(p.PublicPort)) == hostPort { | ||
| name := "" | ||
| if len(c.Names) > 0 { | ||
| name = strings.TrimPrefix(c.Names[0], "/") | ||
| } | ||
| return &RunningContainer{ | ||
| Name: name, | ||
| Image: c.Image, | ||
| BoundPort: hostPort, | ||
| }, nil | ||
| } | ||
| } | ||
| } | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (d *DockerRuntime) GetImageVersion(ctx context.Context, imageName string) (string, error) { | ||
| inspect, err := d.client.ImageInspect(ctx, imageName) | ||
| if err != nil { | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.




There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: Not sure if this is a good line to comment on or if the network I'm is more restrictive, but running on a different port when an instance is running on the default port, users get this error.