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
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