|
| 1 | +package internal |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "io" |
| 7 | + "os" |
| 8 | + |
| 9 | + "github.com/docker/docker/api/types/container" |
| 10 | + "github.com/docker/docker/api/types/filters" |
| 11 | + "github.com/docker/docker/client" |
| 12 | +) |
| 13 | + |
| 14 | +// Inspect incldues the logic for the inspect command |
| 15 | +func Inspect(ctx context.Context, serviceName, portName string) error { |
| 16 | + client, err := newDockerClient() |
| 17 | + if err != nil { |
| 18 | + return fmt.Errorf("failed to create docker client: %w", err) |
| 19 | + } |
| 20 | + |
| 21 | + serviceID, portNum, err := retrieveContainerDetails(client, serviceName, portName) |
| 22 | + if err != nil { |
| 23 | + return fmt.Errorf("failed to retrieve container details: %w", err) |
| 24 | + } |
| 25 | + |
| 26 | + return runTcpFlow(ctx, client, serviceID, portNum) |
| 27 | +} |
| 28 | + |
| 29 | +func retrieveContainerDetails(client *client.Client, serviceName, portName string) (string, string, error) { |
| 30 | + // Get the service by name |
| 31 | + containers, err := client.ContainerList(context.Background(), container.ListOptions{ |
| 32 | + Filters: filters.NewArgs(filters.Arg("label", "service="+serviceName)), |
| 33 | + All: true, |
| 34 | + }) |
| 35 | + if err != nil { |
| 36 | + return "", "", fmt.Errorf("error getting container list: %w", err) |
| 37 | + } |
| 38 | + |
| 39 | + size := len(containers) |
| 40 | + if size == 0 { |
| 41 | + return "", "", fmt.Errorf("no containers found for service %s", serviceName) |
| 42 | + } else if size > 1 { |
| 43 | + return "", "", fmt.Errorf("multiple containers found for service %s", serviceName) |
| 44 | + } |
| 45 | + |
| 46 | + container := containers[0] |
| 47 | + |
| 48 | + // Get the container details to find the port mapping in the labels as port.<name> |
| 49 | + containerDetails, err := client.ContainerInspect(context.Background(), container.ID) |
| 50 | + if err != nil { |
| 51 | + return "", "", fmt.Errorf("error inspecting container %s: %w", container.ID, err) |
| 52 | + } |
| 53 | + |
| 54 | + // Check if the port name is in the labels |
| 55 | + portLabel := fmt.Sprintf("port.%s", portName) |
| 56 | + portNum, ok := containerDetails.Config.Labels[portLabel] |
| 57 | + if !ok { |
| 58 | + return "", "", fmt.Errorf("port %s not found in container %s", portName, container.ID) |
| 59 | + } |
| 60 | + |
| 61 | + return container.ID, portNum, nil |
| 62 | +} |
| 63 | + |
| 64 | +func runTcpFlow(ctx context.Context, client *client.Client, containerID, portName string) error { |
| 65 | + // Create container config for tcpflow |
| 66 | + config := &container.Config{ |
| 67 | + Image: "appropriate/tcpflow:latest", |
| 68 | + Cmd: []string{"-c", "-p", "-i", "eth0", "port", portName}, |
| 69 | + Tty: true, |
| 70 | + AttachStdout: true, |
| 71 | + AttachStderr: true, |
| 72 | + } |
| 73 | + |
| 74 | + // Host config with network mode and capabilities |
| 75 | + hostConfig := &container.HostConfig{ |
| 76 | + NetworkMode: container.NetworkMode("container:" + containerID), |
| 77 | + CapAdd: []string{"NET_ADMIN"}, |
| 78 | + } |
| 79 | + |
| 80 | + // Create the container |
| 81 | + resp, err := client.ContainerCreate(ctx, config, hostConfig, nil, nil, "") |
| 82 | + if err != nil { |
| 83 | + return fmt.Errorf("failed to create container: %w", err) |
| 84 | + } |
| 85 | + |
| 86 | + // Start the container |
| 87 | + if err := client.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { |
| 88 | + return fmt.Errorf("failed to start container: %w", err) |
| 89 | + } |
| 90 | + |
| 91 | + // Get container logs and stream them |
| 92 | + logOptions := container.LogsOptions{ |
| 93 | + ShowStdout: true, |
| 94 | + ShowStderr: true, |
| 95 | + Follow: true, |
| 96 | + Timestamps: false, |
| 97 | + } |
| 98 | + |
| 99 | + logs, err := client.ContainerLogs(ctx, resp.ID, logOptions) |
| 100 | + if err != nil { |
| 101 | + return fmt.Errorf("failed to get container logs: %w", err) |
| 102 | + } |
| 103 | + defer logs.Close() |
| 104 | + |
| 105 | + // Start copying logs to stdout |
| 106 | + go func() { |
| 107 | + _, err := io.Copy(os.Stdout, logs) |
| 108 | + if err != nil && err != io.EOF { |
| 109 | + fmt.Fprintf(os.Stderr, "Error copying logs: %v\n", err) |
| 110 | + } |
| 111 | + }() |
| 112 | + |
| 113 | + // Wait for interrupt signal |
| 114 | + <-ctx.Done() |
| 115 | + |
| 116 | + // Cleanup: stop and remove the container |
| 117 | + timeout := 5 |
| 118 | + if err := client.ContainerStop(context.Background(), resp.ID, container.StopOptions{Timeout: &timeout}); err != nil { |
| 119 | + fmt.Fprintf(os.Stderr, "Error stopping container: %v\n", err) |
| 120 | + } |
| 121 | + |
| 122 | + if err := client.ContainerRemove(context.Background(), resp.ID, container.RemoveOptions{Force: true}); err != nil { |
| 123 | + fmt.Fprintf(os.Stderr, "Error removing container: %v\n", err) |
| 124 | + } |
| 125 | + |
| 126 | + return nil |
| 127 | +} |
0 commit comments