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
2 changes: 1 addition & 1 deletion cmd/config/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (cmd *ApplyCmd) inspectRunningContainer(
}

containerDetails := &details[0]
if !strings.EqualFold(containerDetails.State.Status, pkgworkspace.ContainerStatusRunning) {
if containerDetails.State.Status != devcconfig.ContainerStatusRunning {
return nil, fmt.Errorf(
"container %s is not running (status: %s)",
cmd.Container,
Expand Down
7 changes: 4 additions & 3 deletions cmd/internal/agentworkspace/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package agentworkspace
import (
"context"
"fmt"
"strings"

"github.com/devsy-org/devsy/cmd/flags"
"github.com/devsy-org/devsy/pkg/agent"
"github.com/devsy-org/devsy/pkg/client"
config2 "github.com/devsy-org/devsy/pkg/devcontainer/config"
cliflags "github.com/devsy-org/devsy/pkg/flags"
"github.com/devsy-org/devsy/pkg/flags/names"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -66,10 +66,11 @@ func (cmd *StatusCmd) Run(ctx context.Context) error {
}

// is running?
if strings.ToLower(containerDetails.State.Status) == "running" {
switch containerDetails.State.Status {
case config2.ContainerStatusRunning:
fmt.Print(client.StatusRunning)
return nil
} else if strings.ToLower(containerDetails.State.Status) == "exited" {
case config2.ContainerStatusExited:
fmt.Print(client.StatusStopped)
return nil
}
Expand Down
5 changes: 2 additions & 3 deletions cmd/internal/container_tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/devsy-org/devsy/pkg/agent"
pkgconfig "github.com/devsy-org/devsy/pkg/config"
"github.com/devsy-org/devsy/pkg/devcontainer"
"github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/encoding"
cliflags "github.com/devsy-org/devsy/pkg/flags"
"github.com/devsy-org/devsy/pkg/flags/names"
Expand All @@ -24,8 +25,6 @@ import (
"github.com/spf13/cobra"
)

const containerStatusRunning = "running"

// containerRootUser is the user to use when running commands inside the container that
// require root privileges.
const containerRootUser = "root"
Expand Down Expand Up @@ -166,7 +165,7 @@ func startDevContainer(
return fmt.Errorf("find devcontainer: %w", err)
}

if containerDetails == nil || containerDetails.State.Status != containerStatusRunning {
if containerDetails == nil || containerDetails.State.Status != config.ContainerStatusRunning {
if err := startContainer(ctx, runner, workspaceConfig); err != nil {
return fmt.Errorf("start container: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/internal/container_tunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestStartDevContainer_RunningContainerWithoutResultRestarts(t *testing.T) {
upErr := errors.New("boom")
runner := stubRunner{
findResult: &config.ContainerDetails{
State: config.ContainerDetailsState{Status: containerStatusRunning},
State: config.ContainerDetailsState{Status: config.ContainerStatusRunning},
},
commandErr: errors.New("cat: no such file"), // hasDevContainerResult -> false
upErr: upErr,
Expand All @@ -147,7 +147,7 @@ func TestStartDevContainer_RunningContainerWithoutResultRestarts(t *testing.T) {
func TestStartDevContainer_RunningContainerWithResultIsNoOp(t *testing.T) {
runner := stubRunner{
findResult: &config.ContainerDetails{
State: config.ContainerDetailsState{Status: containerStatusRunning},
State: config.ContainerDetailsState{Status: config.ContainerStatusRunning},
},
commandErr: nil, // hasDevContainerResult -> true
upErr: errors.New("Up must not be called on the happy path"),
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/runusercommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (cmd *RunUserCommandsCmd) inspectRunningContainer(
}

containerDetails := &details[0]
if !strings.EqualFold(containerDetails.State.Status, workspace2.ContainerStatusRunning) {
if containerDetails.State.Status != devcconfig.ContainerStatusRunning {
errMsg := fmt.Sprintf(
"container %s is not running (status: %s)",
cmd.ContainerID,
Expand Down
11 changes: 6 additions & 5 deletions cmd/pro/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
loftclientset "github.com/devsy-org/api/pkg/clientset/versioned"
proflags "github.com/devsy-org/devsy/cmd/pro/flags"
"github.com/devsy-org/devsy/pkg/config"
devcconfig "github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/hash"
"github.com/devsy-org/devsy/pkg/log"
"github.com/devsy-org/devsy/pkg/machineid"
Expand Down Expand Up @@ -665,8 +666,8 @@ func (cmd *StartCmd) successDocker(ctx context.Context, containerID string) erro
containerDetails, err := cmd.inspectContainer(ctx, containerID)
if err != nil {
return false, fmt.Errorf("inspect loft container: %w", err)
} else if strings.ToLower(containerDetails.State.Status) == "exited" ||
strings.ToLower(containerDetails.State.Status) == "dead" {
} else if containerDetails.State.Status == devcconfig.ContainerStatusExited ||
containerDetails.State.Status == devcconfig.ContainerStatusDead {
logs, _ := cmd.logsContainer(ctx, containerID)
return false, fmt.Errorf(
"container failed (status: %s):\n %s",
Expand Down Expand Up @@ -919,7 +920,7 @@ func (cmd *StartCmd) resolveRunningContainer(
switch {
case err != nil:
return "", err
case onlyRunning && strings.ToLower(containerState.State.Status) != "running":
case onlyRunning && containerState.State.Status != devcconfig.ContainerStatusRunning:
err = cmd.removeContainer(ctx, containerID)
if err != nil {
return "", err
Expand Down Expand Up @@ -2144,8 +2145,8 @@ type ContainerDetailsConfig struct {
}

type ContainerDetailsState struct {
Status string `json:"Status,omitempty"`
StartedAt string `json:"StartedAt,omitempty"`
Status devcconfig.ContainerStatus `json:"Status,omitempty"`
StartedAt string `json:"StartedAt,omitempty"`
}

func WrapCommandError(stdout []byte, err error) error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/workspace/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func (cmd *ExecCmd) runWithContainerID(ctx context.Context, args []string) error
}

containerDetails := &details[0]
if !strings.EqualFold(containerDetails.State.Status, workspace2.ContainerStatusRunning) {
if containerDetails.State.Status != devcconfig.ContainerStatusRunning {
return fmt.Errorf(
"container %s is not running (status: %s)",
cmd.ContainerID,
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/delivery/local_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (d *LocalDockerDelivery) DeliverPreStart(ctx context.Context, opts PreStart
if opts.RunOptions.Env == nil {
opts.RunOptions.Env = make(map[string]string)
}
opts.RunOptions.Env["DEVSY_AGENT_PATH"] = volumeMountPath + "/" + binaryName()
opts.RunOptions.Env[pkgconfig.EnvAgentPath] = volumeMountPath + "/" + binaryName()

return nil
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/apple/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ func (h *AppleHelper) WaitContainerRunning(ctx context.Context, id string) error
if len(details) == 0 {
return false, nil
}
switch strings.ToLower(details[0].State.Status) {
case stateRunning:
switch details[0].State.Status {
case config.ContainerStatusRunning:
return true, nil
case stateExited:
case config.ContainerStatusExited:
return false, fmt.Errorf("container %s exited before reaching running state", id)
default:
return false, nil
Expand Down Expand Up @@ -249,7 +249,7 @@ func (h *AppleHelper) FindContainerByID(
return nil, err
}
for i := range details {
if strings.ToLower(details[i].State.Status) != "removing" {
if details[i].State.Status != config.ContainerStatusRemoving {
return &details[i], nil
}
}
Expand Down Expand Up @@ -357,7 +357,7 @@ func (h *AppleHelper) SystemRunning(ctx context.Context) bool {
if err != nil {
return false
}
return strings.Contains(strings.ToLower(string(out)), stateRunning)
return strings.Contains(strings.ToLower(string(out)), string(config.ContainerStatusRunning))
}

func (h *AppleHelper) buildCmd(ctx context.Context, args ...string) *exec.Cmd {
Expand Down
8 changes: 6 additions & 2 deletions pkg/apple/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ func (p *probe) findRunning(label string) *config.ContainerDetails {
if found == nil {
p.t.Fatal("FindDevContainer returned nil for a running labelled container")
}
if found.State.Status != stateRunning {
p.t.Errorf("found.State.Status = %q, want %q", found.State.Status, stateRunning)
if found.State.Status != config.ContainerStatusRunning {
p.t.Errorf(
"found.State.Status = %q, want %q",
found.State.Status,
config.ContainerStatusRunning,
)
}
if found.Config.Labels["devsy.e2e"] != "1" {
p.t.Errorf("label devsy.e2e = %q, want 1", found.Config.Labels["devsy.e2e"])
Expand Down
13 changes: 6 additions & 7 deletions pkg/apple/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
)

const (
stateRunning = "running"
stateExited = "exited"
mountTypeBind = "bind"
archUnknown = "unknown" // placeholder arch in Apple's multi-arch image index
)
Expand Down Expand Up @@ -130,12 +128,13 @@ func (c containerInspect) toContainerDetails() config.ContainerDetails {
}
}

func normalizeState(state string) string {
s := strings.ToLower(strings.TrimSpace(state))
// normalizeState maps the runner's status vocabulary onto the shared Docker
// vocabulary: "stopped" means the container ran and exited, matching what
// terminal-state checks expect.
func normalizeState(state string) config.ContainerStatus {
s := config.ToContainerStatus(strings.TrimSpace(state))
if s == "stopped" {
// Docker uses "exited" for a container that ran and stopped; the
// runner's terminal-state checks key off that vocabulary.
return stateExited
return config.ContainerStatusExited
}
return s
}
Expand Down
17 changes: 11 additions & 6 deletions pkg/apple/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package apple
import (
"encoding/json"
"testing"

"github.com/devsy-org/devsy/pkg/devcontainer/config"
)

// containerInspectFixture is verbatim output from `container inspect` on
Expand Down Expand Up @@ -46,7 +48,7 @@ func TestContainerInspectMapping(t *testing.T) {
name, got, want string
}{
{"ID", got.ID, "devsy-probe"},
{"State.Status", got.State.Status, stateRunning},
{"State.Status", string(got.State.Status), string(config.ContainerStatusRunning)},
{"StartedAt", got.State.StartedAt, "2026-07-25T01:57:25Z"},
{"WorkingDir", got.Config.WorkingDir, "/"},
{"User", got.Config.User, "0"},
Expand All @@ -69,11 +71,14 @@ func TestContainerInspectMapping(t *testing.T) {
}

func TestNormalizeState(t *testing.T) {
cases := []struct{ in, want string }{
{"running", stateRunning},
{"Running", stateRunning},
{"stopped", stateExited},
{"Stopped", stateExited},
cases := []struct {
in string
want config.ContainerStatus
}{
{"running", config.ContainerStatusRunning},
{"Running", config.ContainerStatusRunning},
{"stopped", config.ContainerStatusExited},
{"Stopped", config.ContainerStatusExited},
}
for _, c := range cases {
if got := normalizeState(c.in); got != c.want {
Expand Down
6 changes: 3 additions & 3 deletions pkg/compose/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ func (h *ComposeHelper) FindDevContainer(
return nil, err
}

for _, details := range containerDetails {
if details.State.Status != "removing" {
return &details, nil
for i := range containerDetails {
if containerDetails[i].State.Status != config.ContainerStatusRemoving {
return &containerDetails[i], nil
}
}

Expand Down
11 changes: 7 additions & 4 deletions pkg/config/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ const (
// EnvAgentPreferDownload forces agent binary download even if a local copy exists.
EnvAgentPreferDownload = "DEVSY_AGENT_PREFER_DOWNLOAD"

// EnvOS is set to the host operating system (runtime.GOOS).
// EnvAgentPath is the path to the agent binary inside the workspace
// container, set by agent delivery so the container entrypoint can locate
// it (defaults to /usr/local/bin/devsy).
EnvAgentPath = "DEVSY_AGENT_PATH"

// EnvOS is set to the host operating system.
EnvOS = "DEVSY_OS"

// EnvArch is set to the host architecture (runtime.GOARCH).
// EnvArch is set to the host architecture.
EnvArch = "DEVSY_ARCH"

// EnvLogLevel is set to the current log level.
Expand Down Expand Up @@ -121,8 +126,6 @@ const (
// EnvProviderPrefix is the prefix for provider-specific option env vars (append provider name + "_").
EnvProviderPrefix = EnvPrefix + "PROVIDER_"

// --- Provider-scoped env vars (set when running provider commands) ---.

// EnvProviderWorkspaceID is the workspace identifier passed to providers.
EnvProviderWorkspaceID = "WORKSPACE_ID"

Expand Down
3 changes: 1 addition & 2 deletions pkg/devcontainer/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const (
FeaturesBuildOverrideFilePrefix = "docker-compose.devcontainer.build"
FeaturesStartOverrideFilePrefix = "docker-compose.devcontainer.containerFeatures"

containerStatusRunning = "running"
composeProjectNameFlag = "--project-name"
)

Expand Down Expand Up @@ -328,7 +327,7 @@ func (r *runner) ensureComposeContainer(
}

// container already exists and is running, nothing to do
if containerDetails != nil && containerDetails.State.Status == containerStatusRunning &&
if containerDetails != nil && containerDetails.State.Status == config.ContainerStatusRunning &&
!options.Recreate {
return containerDetails, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/devcontainer/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ func TestBuildOverrideEntrypointAppendsUserEntrypoint(t *testing.T) {
func TestBuildOverrideEntrypointKeepsDefaultEntrypointReachable(t *testing.T) {
script := buildOverrideEntrypoint(&config.MergedDevContainerConfig{}, nil)
body := script[2]
if !strings.Contains(body, "devsy internal agent container daemon") {
if !strings.Contains(body, "internal agent container daemon") {
t.Errorf("expected default entrypoint invocation in script, got %q", body)
}
}
Expand Down
46 changes: 42 additions & 4 deletions pkg/devcontainer/config/container_details.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package config

import (
"encoding/json"
"strings"
)

type ImageDetails struct {
ID string
Config ImageDetailsConfig
Expand Down Expand Up @@ -43,8 +48,41 @@ type ContainerDetailsConfig struct {
}

type ContainerDetailsState struct {
Status string `json:"Status,omitempty"`
StartedAt string `json:"StartedAt,omitempty"`
ExitCode int `json:"ExitCode,omitempty"`
Error string `json:"Error,omitempty"`
Status ContainerStatus `json:"Status,omitempty"`
StartedAt string `json:"StartedAt,omitempty"`
ExitCode int `json:"ExitCode,omitempty"`
Error string `json:"Error,omitempty"`
}

// UnmarshalJSON decodes inspect output and normalizes Status, so the field is
// always canonical regardless of the runtime's casing.
func (s *ContainerDetailsState) UnmarshalJSON(data []byte) error {
type alias ContainerDetailsState
var raw alias
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
raw.Status = ToContainerStatus(string(raw.Status))
*s = ContainerDetailsState(raw)
return nil
}

// ContainerStatus is a normalized container state string (`State.Status` from
// `docker inspect` and friends), comparable against the ContainerStatus*
// constants.
type ContainerStatus string

const (
ContainerStatusRunning ContainerStatus = "running"
ContainerStatusExited ContainerStatus = "exited"
ContainerStatusCreated ContainerStatus = "created"
ContainerStatusPaused ContainerStatus = "paused"
ContainerStatusRestarting ContainerStatus = "restarting"
ContainerStatusDead ContainerStatus = "dead"
ContainerStatusRemoving ContainerStatus = "removing"
)

// ToContainerStatus normalizes a raw status string for comparison.
func ToContainerStatus(s string) ContainerStatus {
return ContainerStatus(strings.ToLower(s))
}
Loading
Loading