Skip to content
Merged
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
51 changes: 51 additions & 0 deletions pkg/agent/delivery/delivery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package delivery

import (
"context"
"fmt"
"io"

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

type DeliveryPhase int

const (
PhasePreStart DeliveryPhase = iota
PhasePostStart
)

func (p DeliveryPhase) String() string {
switch p {
case PhasePreStart:
return "pre-start"
case PhasePostStart:
return "post-start"
default:
return fmt.Sprintf("unknown(%d)", int(p))
}
}

type BinarySourceFunc func(ctx context.Context, arch string) (io.ReadCloser, error)

type PreStartOptions struct {
WorkspaceID string
RunOptions *driver.RunOptions
BinarySource BinarySourceFunc
Arch string
}

type PostStartOptions struct {
WorkspaceID string
ContainerDetails *config.ContainerDetails
BinarySource BinarySourceFunc
Arch string
}

type AgentDelivery interface {
Phase() DeliveryPhase
DeliverPreStart(ctx context.Context, opts PreStartOptions) error
DeliverPostStart(ctx context.Context, opts PostStartOptions) error
Cleanup(ctx context.Context, workspaceID string) error
}
114 changes: 114 additions & 0 deletions pkg/agent/delivery/delivery_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//go:build integration

package delivery

import (
"context"
"io"
"os/exec"
"strings"
"testing"

"github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/driver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func testBinarySource(_ context.Context, _ string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("#!/bin/sh\necho hello\n")), nil
}

func dockerAvailable() bool {
cmd := exec.Command("docker", "info")
return cmd.Run() == nil
}

func TestLocalDockerDelivery_Integration(t *testing.T) {
if !dockerAvailable() {
t.Skip("docker not available")
}

ctx := context.Background()
d := &LocalDockerDelivery{DockerCommand: "docker"}
workspaceID := "delivery-test-integration"

runOpts := &driver.RunOptions{
Mounts: []*config.Mount{},
Env: map[string]string{},
}
opts := PreStartOptions{
WorkspaceID: workspaceID,
RunOptions: runOpts,
BinarySource: testBinarySource,
Arch: "amd64",
}

err := d.DeliverPreStart(ctx, opts)
require.NoError(t, err)

assert.Len(t, runOpts.Mounts, 1)
assert.Equal(t, "devsy-agent-"+workspaceID, runOpts.Mounts[0].Source)
assert.Equal(t, volumeMountPath, runOpts.Mounts[0].Target)
assert.Equal(t, "volume", runOpts.Mounts[0].Type)

// Verify volume exists
out, err := exec.CommandContext(ctx, "docker", "volume", "inspect", "devsy-agent-"+workspaceID).
CombinedOutput()
require.NoError(t, err, "volume should exist: %s", string(out))

// Verify binary is in the volume
out, err = exec.CommandContext(ctx, "docker", "run", "--rm",
"-v", "devsy-agent-"+workspaceID+":"+volumeMountPath,
"busybox:latest", "ls", "-la", volumeMountPath+"/devsy",
).CombinedOutput()
require.NoError(t, err, "binary should exist in volume: %s", string(out))
assert.Contains(t, string(out), "rwx")

// Cleanup
err = d.Cleanup(ctx, workspaceID)
require.NoError(t, err)

// Verify volume removed
out, _ = exec.CommandContext(ctx, "docker", "volume", "inspect", "devsy-agent-"+workspaceID).
CombinedOutput()
assert.Contains(t, string(out), "No such volume")
}

func TestRemoteDockerDelivery_Integration(t *testing.T) {
if !dockerAvailable() {
t.Skip("docker not available")
}

ctx := context.Background()
containerID := "delivery-test-remote"

// Create a test container
out, err := exec.CommandContext(ctx, "docker", "run", "-d",
"--name", containerID,
"busybox:latest", "sleep", "60",
).CombinedOutput()
require.NoError(t, err, "failed to create test container: %s", string(out))
defer func() {
_ = exec.CommandContext(ctx, "docker", "rm", "-f", containerID).Run()
}()

d := &RemoteDockerDelivery{
DockerCommand: "docker",
ContainerID: containerID,
}

err = d.DeliverPostStart(ctx, PostStartOptions{
WorkspaceID: "test-workspace",
BinarySource: testBinarySource,
Arch: "amd64",
})
require.NoError(t, err)

// Verify binary exists in container
out, err = exec.CommandContext(ctx, "docker", "exec", containerID,
"ls", "-la", "/usr/local/bin/devsy",
).CombinedOutput()
require.NoError(t, err, "binary should exist in container: %s", string(out))
assert.Contains(t, string(out), "rwx")
}
125 changes: 125 additions & 0 deletions pkg/agent/delivery/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package delivery

import (
"context"
"fmt"
"io"
"os"

"github.com/devsy-org/devsy/pkg/inject"
"github.com/devsy-org/devsy/pkg/log"
"github.com/devsy-org/devsy/pkg/provider"
)

type FactoryOptions struct {
WorkspaceConfig *provider.AgentWorkspaceInfo
WorkspaceID string
DockerCommand string
DockerEnv []string
IsRemoteDocker bool
ContainerID string
ExecFunc inject.ExecFunc
}

func NewAgentDelivery(opts FactoryOptions) AgentDelivery {
driverType := opts.WorkspaceConfig.Agent.Driver

switch {
case driverType == provider.CustomDriver:
log.Debugf("using legacy shell delivery for custom driver")
return &LegacyShellDelivery{
ExecFunc: opts.ExecFunc,
DownloadURL: "",
}

case opts.IsRemoteDocker:
log.Debugf("using remote docker delivery (docker cp)")
return &RemoteDockerDelivery{
DockerCommand: opts.DockerCommand,
Environment: opts.DockerEnv,
ContainerID: opts.ContainerID,
}

case driverType == "" || driverType == provider.DockerDriver:
if isDockerLocal(opts.DockerCommand) {
log.Debugf("using local docker delivery (named volume)")
return &LocalDockerDelivery{
DockerCommand: opts.DockerCommand,
Environment: opts.DockerEnv,
}
}
log.Debugf("using remote docker delivery for non-local docker daemon")
return &RemoteDockerDelivery{
DockerCommand: opts.DockerCommand,
Environment: opts.DockerEnv,
ContainerID: opts.ContainerID,
}

default:
log.Debugf("using legacy shell delivery for driver: %s", driverType)
return &LegacyShellDelivery{
ExecFunc: opts.ExecFunc,
DownloadURL: "",
}
}
}

func isDockerLocal(_ string) bool {
envHost := os.Getenv("DOCKER_HOST")
return envHost == "" || isLocalDockerHost(envHost)
}

func isLocalDockerHost(host string) bool {
if host == "" {
return true
}
hasPrefix := func(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
return hasPrefix(host, "unix://") || hasPrefix(host, "npipe://")
}

// CommandFunc adapts a driver's command function to inject.ExecFunc.
func CommandFunc(
driverCmd func(
ctx context.Context,
workspaceID, user, command string,
stdin io.Reader, stdout io.Writer, stderr io.Writer,
) error,
workspaceID string,
) inject.ExecFunc {
return func(
ctx context.Context,
command string,
stdin io.Reader, stdout io.Writer, stderr io.Writer,
) error {
return driverCmd(ctx, workspaceID, "root", command, stdin, stdout, stderr)
}
}

// Deliver calls the appropriate delivery method based on the strategy's phase.
func Deliver(
ctx context.Context,
strategy AgentDelivery,
preOpts *PreStartOptions,
postOpts *PostStartOptions,
) error {
switch strategy.Phase() {
case PhasePreStart:
if preOpts == nil {
return fmt.Errorf(
"pre-start options required for %s delivery", strategy.Phase(),
)
}
return strategy.DeliverPreStart(ctx, *preOpts)
case PhasePostStart:
if postOpts == nil {
return fmt.Errorf(
"post-start options required for %s delivery", strategy.Phase(),
)
}
return strategy.DeliverPostStart(ctx, *postOpts)
default:
return fmt.Errorf("unknown delivery phase: %s", strategy.Phase())
}
}
Loading
Loading