chore: add k8s and docker as service to agent and update logic for ansible deploy
ci-agent / build (push) Failing after 2m35s
ci-agent / build (push) Failing after 2m35s
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/config"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource"
|
||||
)
|
||||
|
||||
var _ logsource.LogSource = new(DockerLogSource)
|
||||
|
||||
// DockerLogSource reads logs from a Docker container via `docker logs -f`.
|
||||
type DockerLogSource struct {
|
||||
cmd *exec.Cmd
|
||||
stdout io.ReadCloser
|
||||
stdoutscanner *bufio.Scanner
|
||||
}
|
||||
|
||||
// ReadLine implements logsource.LogSource.
|
||||
func (d *DockerLogSource) ReadLine() (string, error) {
|
||||
if d.stdoutscanner.Scan() {
|
||||
return d.stdoutscanner.Text(), nil
|
||||
} else {
|
||||
if d.stdoutscanner.Err() == nil {
|
||||
return "", fmt.Errorf("%w: %s", logsource.ErrDead, io.EOF)
|
||||
}
|
||||
return "", d.stdoutscanner.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements logsource.LogSource.
|
||||
func (d *DockerLogSource) Close() error {
|
||||
_ = d.cmd.Process.Signal(syscall.SIGTERM)
|
||||
return d.cmd.Wait()
|
||||
}
|
||||
|
||||
// New creates a Docker log source for the given container.
|
||||
// The container name is taken from cfg.Path (if set) or cfg.Name.
|
||||
func New(cfg config.ServiceConfig) (*DockerLogSource, error) {
|
||||
containerName := cfg.Name
|
||||
if cfg.Path != nil && *cfg.Path != "" {
|
||||
containerName = *cfg.Path
|
||||
}
|
||||
|
||||
// docker logs -f --tail=0 --no-color <container_name>
|
||||
// -f : follow new logs
|
||||
// --tail=0 : skip existing logs
|
||||
// --no-color: strip color codes for clean output
|
||||
cmd := exec.Command("docker", "logs", "-f", "--tail=0", "--no-color", containerName) //nolint:gosec
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stdout pipe for docker logs: %w", err)
|
||||
}
|
||||
|
||||
// Also capture stderr since docker logs merges stdout and stderr from the container
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stderr pipe for docker logs: %w", err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start docker logs for container %q: %w", containerName, err)
|
||||
}
|
||||
|
||||
// Use MultiReader to merge stdout and stderr
|
||||
// Docker logs outputs container stdout+stderr to its own stdout, but we also
|
||||
// capture the docker CLI's stderr separately in case of errors (e.g. container not found)
|
||||
stdoutscanner := bufio.NewScanner(stdout)
|
||||
|
||||
// Start a goroutine to consume stderr (we don't send docker CLI stderr as logs,
|
||||
// but we need to prevent the pipe from filling up)
|
||||
go func() {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
_, err := stderr.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &DockerLogSource{cmd, stdout, stdoutscanner}, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/config"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource"
|
||||
)
|
||||
|
||||
var _ logsource.LogSource = new(KubernetesLogSource)
|
||||
|
||||
// KubernetesLogSource reads logs from a Kubernetes pod via `kubectl logs -f`.
|
||||
type KubernetesLogSource struct {
|
||||
cmd *exec.Cmd
|
||||
stdout io.ReadCloser
|
||||
stdoutscanner *bufio.Scanner
|
||||
}
|
||||
|
||||
// ReadLine implements logsource.LogSource.
|
||||
func (k *KubernetesLogSource) ReadLine() (string, error) {
|
||||
if k.stdoutscanner.Scan() {
|
||||
return k.stdoutscanner.Text(), nil
|
||||
} else {
|
||||
if k.stdoutscanner.Err() == nil {
|
||||
return "", fmt.Errorf("%w: %s", logsource.ErrDead, io.EOF)
|
||||
}
|
||||
return "", k.stdoutscanner.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements logsource.LogSource.
|
||||
func (k *KubernetesLogSource) Close() error {
|
||||
_ = k.cmd.Process.Signal(syscall.SIGTERM)
|
||||
return k.cmd.Wait()
|
||||
}
|
||||
|
||||
// New creates a Kubernetes log source for the given pod.
|
||||
// The pod identifier is taken from cfg.Path in the format "namespace/podname".
|
||||
// If no namespace is specified (just "podname"), "default" namespace is used.
|
||||
// If cfg.Path is nil or empty, cfg.Name is used as the pod name with "default" namespace.
|
||||
func New(cfg config.ServiceConfig) (*KubernetesLogSource, error) {
|
||||
podName := cfg.Name
|
||||
namespace := "default"
|
||||
|
||||
if cfg.Path != nil && *cfg.Path != "" {
|
||||
parts := strings.SplitN(*cfg.Path, "/", 2)
|
||||
if len(parts) == 2 {
|
||||
namespace = parts[0]
|
||||
podName = parts[1]
|
||||
} else {
|
||||
podName = parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
// kubectl logs -f <pod> -n <namespace> --tail=0 --no-color
|
||||
// -f : follow new logs
|
||||
// --tail=0 : skip existing logs
|
||||
// --no-color: strip color codes for clean output
|
||||
cmd := exec.Command("kubectl", "logs", "-f", podName, "-n", namespace, "--tail=0", "--no-color") //nolint:gosec
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stdout pipe for kubectl logs: %w", err)
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stderr pipe for kubectl logs: %w", err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start kubectl logs for pod %q (ns: %q): %w", podName, namespace, err)
|
||||
}
|
||||
|
||||
stdoutscanner := bufio.NewScanner(stdout)
|
||||
|
||||
// Consume stderr to prevent pipe from filling up
|
||||
go func() {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
_, err := stderr.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &KubernetesLogSource{cmd, stdout, stdoutscanner}, nil
|
||||
}
|
||||
@@ -14,8 +14,10 @@ import (
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/config"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logger"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource/docker"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource/file"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource/journald"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource/kubernetes"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/mtls"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/registration"
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/proto/proto"
|
||||
@@ -147,6 +149,16 @@ func main() {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file source %q: %w", svc.Name, err)
|
||||
}
|
||||
case "docker":
|
||||
src, err = docker.New(svc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create docker source for container %q: %w", svc.Name, err)
|
||||
}
|
||||
case "kubernetes":
|
||||
src, err = kubernetes.New(svc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create kubernetes source for pod %q: %w", svc.Name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown log source type %q for service %q", svc.Type, svc.Name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user