Skip to content
5 changes: 3 additions & 2 deletions agent/internal/agent/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,22 @@ func (a *Agent) RequestStatusReport(reason string) {
}

func (a *Agent) reportStatus(reason string) {
startedAt := time.Now()
report := a.BuildStatusReport(true)
reportedDeploymentErrorCount := len(report.DeploymentErrors)
completed, active := a.SnapshotWorkStatus()
serverlessTransitions := a.SnapshotServerlessTransitions()
response, err := a.Client.ReportStatus(report, completed, active, serverlessTransitions)
if err != nil {
log.Printf("[status] failed to report (%s): %v", reason, err)
log.Printf("[status] failed to report (%s) latency=%s: %v", reason, time.Since(startedAt).Round(time.Millisecond), err)
return
}
a.ClearReportedDeploymentErrors(reportedDeploymentErrorCount)
a.AcknowledgeServerlessTransitions(response.ServerlessTransitionResults, len(serverlessTransitions))
a.AcknowledgeWorkResults(response.AcceptedWorkItemResults, response.RejectedWorkItemResults)
a.LogRejectedActiveWorkItems(response.RejectedActiveWorkItems)
a.AcceptLeasedWorkItems(response.WorkItems)
log.Printf("[status] reported (%s)", reason)
log.Printf("[status] reported (%s) latency=%s", reason, time.Since(startedAt).Round(time.Millisecond))
}

func nextStatusReportDelay() time.Duration {
Expand Down
85 changes: 48 additions & 37 deletions agent/internal/container/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,47 @@ func Deploy(config *DeployConfig) (*DeployResult, error) {
logFunc("stdout", fmt.Sprintf("Created volume directory: %s", vm.HostPath))
}

args := buildPodmanRunArgs(config, image)

logFunc("stdout", fmt.Sprintf("Starting container: %s", config.Name))

runCmd := exec.Command("podman", args...)
output, err := runCmd.CombinedOutput()
if err != nil {
logFunc("stderr", fmt.Sprintf("Start failed: %s", string(output)))
return nil, fmt.Errorf("failed to run container: %s: %w", string(output), err)
}

containerID := strings.TrimSpace(string(output))
logFunc("stdout", fmt.Sprintf("Container started: %s", containerID))

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

logFunc("stdout", "Verifying container is running...")
err = retry.WithBackoff(ctx, retry.DeployBackoff, func() (bool, error) {
running, err := IsContainerRunning(containerID)
if err != nil {
return false, err
}
return running, nil
})

if err != nil {
logsCmd := exec.Command("podman", "logs", "--tail", "50", containerID)
logsOutput, _ := logsCmd.CombinedOutput()
logFunc("stderr", fmt.Sprintf("Container failed to stay running. Logs:\n%s", string(logsOutput)))
return nil, fmt.Errorf("container failed to stay running after start: %w", err)
}

logFunc("stdout", "Container verified running")

return &DeployResult{
ContainerID: containerID,
}, nil
}

func buildPodmanRunArgs(config *DeployConfig, image string) []string {
args := []string{
"run", "-d",
"--name", config.Name,
Expand All @@ -117,6 +158,12 @@ func Deploy(config *DeployConfig) (*DeployResult, error) {

if config.IPAddress != "" {
args = append(args, "--network", NetworkName, "--ip", config.IPAddress)
if config.PublishLocalPorts {
for _, pm := range config.PortMappings {
portMapping := fmt.Sprintf("127.0.0.1:%d:%d", pm.HostPort, pm.ContainerPort)
args = append(args, "-p", portMapping)
}
}
} else {
for _, pm := range config.PortMappings {
portMapping := fmt.Sprintf("%s:%d:%d", config.WireGuardIP, pm.HostPort, pm.ContainerPort)
Expand Down Expand Up @@ -154,43 +201,7 @@ func Deploy(config *DeployConfig) (*DeployResult, error) {
} else {
args = append(args, image)
}

logFunc("stdout", fmt.Sprintf("Starting container: %s", config.Name))

runCmd := exec.Command("podman", args...)
output, err := runCmd.CombinedOutput()
if err != nil {
logFunc("stderr", fmt.Sprintf("Start failed: %s", string(output)))
return nil, fmt.Errorf("failed to run container: %s: %w", string(output), err)
}

containerID := strings.TrimSpace(string(output))
logFunc("stdout", fmt.Sprintf("Container started: %s", containerID))

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

logFunc("stdout", "Verifying container is running...")
err = retry.WithBackoff(ctx, retry.DeployBackoff, func() (bool, error) {
running, err := IsContainerRunning(containerID)
if err != nil {
return false, err
}
return running, nil
})

if err != nil {
logsCmd := exec.Command("podman", "logs", "--tail", "50", containerID)
logsOutput, _ := logsCmd.CombinedOutput()
logFunc("stderr", fmt.Sprintf("Container failed to stay running. Logs:\n%s", string(logsOutput)))
return nil, fmt.Errorf("container failed to stay running after start: %w", err)
}

logFunc("stdout", "Container verified running")

return &DeployResult{
ContainerID: containerID,
}, nil
return args
}

func Stop(containerID string) error {
Expand Down
45 changes: 45 additions & 0 deletions agent/internal/container/runtime_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package container

import (
"slices"
"testing"
)

func TestBuildPodmanRunArgsPublishesLoopbackPortsWithStaticIP(t *testing.T) {
args := buildPodmanRunArgs(&DeployConfig{
Name: "svc-dep",
Image: "docker.io/library/nginx:latest",
ServiceID: "svc",
ServiceName: "api",
DeploymentID: "dep",
IPAddress: "10.200.1.2",
PublishLocalPorts: true,
PortMappings: []PortMapping{
{ContainerPort: 80, HostPort: 30080},
},
}, "docker.io/library/nginx:latest")

for _, want := range []string{"--network", NetworkName, "--ip", "10.200.1.2", "-p", "127.0.0.1:30080:80"} {
if !slices.Contains(args, want) {
t.Fatalf("args missing %q: %+v", want, args)
}
}
}

func TestBuildPodmanRunArgsDoesNotPublishStaticIPPortsByDefault(t *testing.T) {
args := buildPodmanRunArgs(&DeployConfig{
Name: "svc-dep",
Image: "docker.io/library/nginx:latest",
ServiceID: "svc",
ServiceName: "api",
DeploymentID: "dep",
IPAddress: "10.200.1.2",
PortMappings: []PortMapping{
{ContainerPort: 80, HostPort: 30080},
},
}, "docker.io/library/nginx:latest")

if slices.Contains(args, "-p") {
t.Fatalf("args unexpectedly publish ports: %+v", args)
}
}
31 changes: 16 additions & 15 deletions agent/internal/container/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,22 @@ type VolumeMount struct {
type BuildLogFunc func(stream string, message string)

type DeployConfig struct {
Name string
Image string
ServiceID string
ServiceName string
DeploymentID string
WireGuardIP string
IPAddress string
PortMappings []PortMapping
HealthCheck *HealthCheck
Env map[string]string
VolumeMounts []VolumeMount
StartCommand string
CPULimit *float64
MemoryLimitMb *int
LogFunc BuildLogFunc
Name string
Image string
ServiceID string
ServiceName string
DeploymentID string
WireGuardIP string
IPAddress string
PortMappings []PortMapping
PublishLocalPorts bool
HealthCheck *HealthCheck
Env map[string]string
VolumeMounts []VolumeMount
StartCommand string
CPULimit *float64
MemoryLimitMb *int
LogFunc BuildLogFunc
}

type DeployResult struct {
Expand Down
1 change: 1 addition & 0 deletions agent/internal/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type ExpectedContainer struct {
Image string `json:"image"`
IPAddress string `json:"ipAddress"`
Ports []PortMapping `json:"ports"`
PublishLocalPorts bool `json:"publishLocalPorts"`
Env map[string]string `json:"env"`
StartCommand string `json:"startCommand"`
HealthCheck *HealthCheck `json:"healthCheck"`
Expand Down
29 changes: 15 additions & 14 deletions agent/internal/reconcile/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,21 @@ func (r *Reconciler) Deploy(exp agenthttp.ExpectedContainer) error {
}

_, err := container.Deploy(&container.DeployConfig{
Name: exp.Name,
Image: exp.Image,
ServiceID: exp.ServiceID,
ServiceName: exp.ServiceName,
DeploymentID: exp.DeploymentID,
IPAddress: exp.IPAddress,
PortMappings: portMappings,
HealthCheck: healthCheck,
Env: decryptedEnv,
VolumeMounts: volumeMounts,
StartCommand: exp.StartCommand,
CPULimit: exp.ResourceCPULimit,
MemoryLimitMb: exp.ResourceMemoryLimitMb,
LogFunc: func(stream, message string) { log.Printf("[deploy:%s] %s", stream, message) },
Name: exp.Name,
Image: exp.Image,
ServiceID: exp.ServiceID,
ServiceName: exp.ServiceName,
DeploymentID: exp.DeploymentID,
IPAddress: exp.IPAddress,
PortMappings: portMappings,
PublishLocalPorts: exp.PublishLocalPorts,
HealthCheck: healthCheck,
Env: decryptedEnv,
VolumeMounts: volumeMounts,
StartCommand: exp.StartCommand,
CPULimit: exp.ResourceCPULimit,
MemoryLimitMb: exp.ResourceMemoryLimitMb,
LogFunc: func(stream, message string) { log.Printf("[deploy:%s] %s", stream, message) },
})

return err
Expand Down
Loading
Loading