Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions internal/container/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, opts Start

tel := opts.Telemetry

hostEnv := filterHostEnv(os.Environ())

containers := make([]runtime.ContainerConfig, len(opts.Containers))
for i, c := range opts.Containers {
image, err := c.Image()
Expand Down Expand Up @@ -128,6 +130,8 @@ func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, opts Start
"MAIN_CONTAINER_NAME="+containerName,
)

env = append(env, hostEnv...)

var binds []runtime.BindMount
if socketPath := rt.SocketPath(); socketPath != "" {
binds = append(binds, runtime.BindMount{HostPath: socketPath, ContainerPath: "/var/run/docker.sock"})
Expand Down Expand Up @@ -445,6 +449,20 @@ func awaitStartup(ctx context.Context, rt runtime.Runtime, sink output.Sink, con
}
}

// filterHostEnv returns the subset of host environment entries that should be
// forwarded to the emulator container. It keeps CI and LOCALSTACK_* variables
// but explicitly drops LOCALSTACK_AUTH_TOKEN so the host value cannot overwrite
// the token resolved by lstk (which may come from the keyring).
func filterHostEnv(envList []string) []string {
var out []string
for _, e := range envList {
if strings.HasPrefix(e, "CI=") || (strings.HasPrefix(e, "LOCALSTACK_") && !strings.HasPrefix(e, "LOCALSTACK_AUTH_TOKEN=")) {
out = append(out, e)
}
}
return out
}

func hasDuplicateContainerTypes(containers []config.ContainerConfig) bool {
seen := make(map[config.EmulatorType]bool)
for _, c := range containers {
Expand Down
32 changes: 22 additions & 10 deletions internal/container/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,26 @@ func TestEmitPostStartPointers_WithoutWebApp(t *testing.T) {
assert.Contains(t, got, "> Tip:")
}

func TestServicePortRange_ReturnsExpectedPorts(t *testing.T) {
ports := servicePortRange()

require.Len(t, ports, 51)
assert.Equal(t, "443", ports[0].ContainerPort)
assert.Equal(t, "443", ports[0].HostPort)
assert.Equal(t, "4510", ports[1].ContainerPort)
assert.Equal(t, "4510", ports[1].HostPort)
assert.Equal(t, "4559", ports[50].ContainerPort)
assert.Equal(t, "4559", ports[50].HostPort)
func TestFilterHostEnv(t *testing.T) {
input := []string{
"CI=true",
"LOCALSTACK_DISABLE_EVENTS=1",
"LOCALSTACK_API_ENDPOINT=https://example.test",
"LOCALSTACK_AUTH_TOKEN=host-token",
"PATH=/usr/bin",
"HOME=/home/user",
"CI_PIPELINE=foo",
}

got := filterHostEnv(input)

assert.Contains(t, got, "CI=true")
assert.Contains(t, got, "LOCALSTACK_DISABLE_EVENTS=1")
assert.Contains(t, got, "LOCALSTACK_API_ENDPOINT=https://example.test")
assert.NotContains(t, got, "LOCALSTACK_AUTH_TOKEN=host-token",
"host LOCALSTACK_AUTH_TOKEN must be filtered so it cannot overwrite the lstk-resolved token")
assert.NotContains(t, got, "PATH=/usr/bin")
assert.NotContains(t, got, "HOME=/home/user")
assert.NotContains(t, got, "CI_PIPELINE=foo", "only exact CI= must be forwarded, not CI_*")
}

44 changes: 36 additions & 8 deletions test/integration/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,32 @@ func TestStartCommandSetsUpContainerCorrectly(t *testing.T) {
})
}

// containerEnvToMap converts a Docker container's []string env to a map.
func containerEnvToMap(envList []string) map[string]string {
m := make(map[string]string, len(envList))
for _, e := range envList {
k, v, _ := strings.Cut(e, "=")
m[k] = v
}
return m
func TestStartCommandPassesCIAndLocalStackEnvVars(t *testing.T) {
requireDocker(t)
_ = env.Require(t, env.AuthToken)

cleanup()
t.Cleanup(cleanup)

mockServer := createMockLicenseServer(true)
defer mockServer.Close()

ctx := testContext(t)
_, stderr, err := runLstk(t, ctx, "", env.With(env.APIEndpoint, mockServer.URL).
With(env.CI, "true").
With(env.DisableEvents, "1"),
"start")
require.NoError(t, err, "lstk start failed: %s", stderr)
requireExitCode(t, 0, err)

inspect, err := dockerClient.ContainerInspect(ctx, containerName)
require.NoError(t, err, "failed to inspect container")
require.True(t, inspect.State.Running)

envVars := containerEnvToMap(inspect.Config.Env)
assert.Equal(t, "true", envVars["CI"])
assert.Equal(t, "1", envVars["LOCALSTACK_DISABLE_EVENTS"])
assert.NotEmpty(t, envVars["LOCALSTACK_AUTH_TOKEN"])
}

// hasBindTarget checks if any bind mount targets the given container path.
Expand All @@ -247,6 +265,16 @@ func hasBindTarget(binds []string, containerPath string) bool {
return false
}

// containerEnvToMap converts a Docker container's []string env to a map.
func containerEnvToMap(envList []string) map[string]string {
m := make(map[string]string, len(envList))
for _, e := range envList {
k, v, _ := strings.Cut(e, "=")
m[k] = v
}
return m
}

func cleanup() {
ctx := context.Background()
_ = dockerClient.ContainerStop(ctx, containerName, container.StopOptions{})
Expand Down