mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-08-19 08:47:03 +00:00
* [refactoring] simplify method and make it more efficient
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* [WIP] Introduce `logs` command to collects logs from various places
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Handle globs properly and merge default logs with user provided ones
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Change default logs location to be the current directory
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Skip new field in the schema tests
TODO: Update the schema and re-enable
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Remove test focus
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Add more default services
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Don't try to run journactl on non systemd distros
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Add more files (for openrc)
c6fdf6ee67/pkg/bundled/cloudconfigs/09_openrc_services.yaml (L52)
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Use standard library for globbing
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
* Capture all files under `/var/log`
because there is also k3s.log (maybe also k0s) etc. Better have them all
than missing some.
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
---------
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
615 lines
23 KiB
Go
615 lines
23 KiB
Go
package agent
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"path/filepath"
|
|
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
|
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
|
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
|
"github.com/kairos-io/kairos-sdk/types"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/twpayne/go-vfs/v5/vfst"
|
|
)
|
|
|
|
var _ = Describe("Logs Command", Label("logs", "cmd"), func() {
|
|
var (
|
|
fs *vfst.TestFS
|
|
cleanup func()
|
|
err error
|
|
logger types.KairosLogger
|
|
runner *v1mock.FakeRunner
|
|
)
|
|
|
|
// Helper function to create a mock systemctl file for systemd detection
|
|
createMockSystemctl := func() {
|
|
err := fsutils.MkdirAll(fs, "/usr/bin", constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = fs.WriteFile("/usr/bin/systemctl", []byte("#!/bin/sh\necho 'mock systemctl'"), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
BeforeEach(func() {
|
|
fs, cleanup, err = vfst.NewTestFS(nil)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
logger = types.NewNullLogger()
|
|
runner = v1mock.NewFakeRunner()
|
|
|
|
// Create mock systemctl for systemd detection in all tests
|
|
createMockSystemctl()
|
|
})
|
|
|
|
AfterEach(func() {
|
|
cleanup()
|
|
})
|
|
|
|
Describe("LogsCollector", func() {
|
|
var collector *LogsCollector
|
|
|
|
BeforeEach(func() {
|
|
cfg := config.NewConfig(
|
|
config.WithFs(fs),
|
|
config.WithLogger(logger),
|
|
config.WithRunner(runner),
|
|
)
|
|
collector = NewLogsCollector(cfg)
|
|
})
|
|
|
|
It("should collect journal logs", func() {
|
|
// Mock journalctl command output
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" && len(args) > 0 && args[0] == "-u" {
|
|
service := args[1]
|
|
return []byte("journal logs for " + service), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"myservice"},
|
|
Files: []string{},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(result).ToNot(BeNil())
|
|
|
|
// Verify journalctl was called for both default and user-defined services
|
|
// Default services: kairos-agent, kairos-installer, kairos-webui, cos-setup-boot, cos-setup-fs, cos-setup-network, cos-setup-reconcile, k3s, k3s-agent, k0scontroller, k0sworker
|
|
// User service: myservice
|
|
Expect(runner.CmdsMatch([][]string{
|
|
{"journalctl", "-u", "kairos-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-installer", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-webui", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-boot", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-fs", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-network", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-reconcile", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0scontroller", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0sworker", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "myservice", "--no-pager", "-o", "cat"},
|
|
})).To(BeNil())
|
|
|
|
// Verify that both default and user-defined services are in the result
|
|
Expect(result.Journal).To(HaveKey("kairos-agent"))
|
|
Expect(result.Journal).To(HaveKey("kairos-installer"))
|
|
Expect(result.Journal).To(HaveKey("kairos-webui"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-boot"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-fs"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-network"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-reconcile"))
|
|
Expect(result.Journal).To(HaveKey("k3s"))
|
|
Expect(result.Journal).To(HaveKey("k3s-agent"))
|
|
Expect(result.Journal).To(HaveKey("k0scontroller"))
|
|
Expect(result.Journal).To(HaveKey("k0sworker"))
|
|
Expect(result.Journal).To(HaveKey("myservice"))
|
|
Expect(result.Journal).To(HaveLen(12))
|
|
})
|
|
|
|
It("should collect file logs with globbing", func() {
|
|
// Create test files
|
|
testFiles := []string{
|
|
"/var/log/test1.log",
|
|
"/var/log/test2.log",
|
|
"/var/log/subdir/test3.log",
|
|
"/var/log/kairos/agent.log", // Default pattern file
|
|
}
|
|
|
|
for _, file := range testFiles {
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(file), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = fs.WriteFile(file, []byte("log content for "+file), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{},
|
|
Files: []string{"/var/log/*.log", "/var/log/subdir/*.log"},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(result).ToNot(BeNil())
|
|
|
|
// Verify files were collected (both default and user-defined patterns)
|
|
// Default pattern: /var/log/kairos/* (matches agent.log)
|
|
// User patterns: /var/log/*.log, /var/log/subdir/*.log
|
|
Expect(result.Files).To(HaveLen(4))
|
|
Expect(result.Files).To(HaveKey("/var/log/test1.log"))
|
|
Expect(result.Files).To(HaveKey("/var/log/test2.log"))
|
|
Expect(result.Files).To(HaveKey("/var/log/subdir/test3.log"))
|
|
Expect(result.Files).To(HaveKey("/var/log/kairos/agent.log"))
|
|
})
|
|
|
|
It("should handle nested directories correctly", func() {
|
|
// Create test files with same basename in different directories
|
|
testFiles := []string{
|
|
"/var/log/test.log",
|
|
"/var/log/subdir/test.log", // Same basename, different directory
|
|
"/var/log/kairos/agent.log", // Default pattern file
|
|
}
|
|
|
|
for _, file := range testFiles {
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(file), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = fs.WriteFile(file, []byte("log content for "+file), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{},
|
|
Files: []string{"/var/log/test.log", "/var/log/subdir/test.log"},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(result).ToNot(BeNil())
|
|
|
|
// Verify both files are collected with full paths as keys (including default pattern)
|
|
Expect(result.Files).To(HaveKey("/var/log/test.log"))
|
|
Expect(result.Files).To(HaveKey("/var/log/subdir/test.log"))
|
|
Expect(result.Files).To(HaveKey("/var/log/kairos/agent.log"))
|
|
Expect(result.Files).To(HaveLen(3))
|
|
|
|
// Create tarball
|
|
tarballPath := "/tmp/logs.tar.gz"
|
|
err = collector.CreateTarball(result, tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Read and verify tarball contents
|
|
tarballData, err := fs.ReadFile(tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Extract and verify tarball structure
|
|
gzr, err := gzip.NewReader(bytes.NewReader(tarballData))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer gzr.Close()
|
|
|
|
tr := tar.NewReader(gzr)
|
|
files := make(map[string]bool)
|
|
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
files[header.Name] = true
|
|
}
|
|
|
|
// Verify that all files are preserved with their full directory structure
|
|
Expect(files).To(HaveKey("files/var/log/test.log"))
|
|
Expect(files).To(HaveKey("files/var/log/subdir/test.log"))
|
|
Expect(files).To(HaveKey("files/var/log/kairos/agent.log"))
|
|
Expect(files).To(HaveLen(3))
|
|
})
|
|
|
|
It("should create tarball with proper structure", func() {
|
|
// Mock journalctl output
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" {
|
|
return []byte("journal logs content"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Create test file
|
|
testFile := "/var/log/test.log"
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(testFile), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = fs.WriteFile(testFile, []byte("file log content"), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"myservice"},
|
|
Files: []string{testFile},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Create tarball
|
|
tarballPath := "/tmp/logs.tar.gz"
|
|
err = collector.CreateTarball(result, tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Verify tarball exists and has correct structure
|
|
exists, err := fsutils.Exists(fs, tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(exists).To(BeTrue())
|
|
|
|
// Read and verify tarball contents
|
|
tarballData, err := fs.ReadFile(tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Extract and verify tarball structure
|
|
gzr, err := gzip.NewReader(bytes.NewReader(tarballData))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer gzr.Close()
|
|
|
|
tr := tar.NewReader(gzr)
|
|
files := make(map[string]bool)
|
|
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
files[header.Name] = true
|
|
}
|
|
|
|
// Verify expected structure (both default and user-defined)
|
|
// Default services: kairos-agent, kairos-installer, kairos-webui, cos-setup-boot, cos-setup-fs, cos-setup-network, cos-setup-reconcile, k3s, k3s-agent, k0scontroller, k0sworker
|
|
// User service: myservice
|
|
// Default files: /var/log/kairos-*.log (if exists)
|
|
// User file: /var/log/test.log
|
|
Expect(files).To(HaveKey("journal/kairos-agent.log"))
|
|
Expect(files).To(HaveKey("journal/kairos-installer.log"))
|
|
Expect(files).To(HaveKey("journal/kairos-webui.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-boot.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-fs.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-network.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-reconcile.log"))
|
|
Expect(files).To(HaveKey("journal/k3s.log"))
|
|
Expect(files).To(HaveKey("journal/k3s-agent.log"))
|
|
Expect(files).To(HaveKey("journal/k0scontroller.log"))
|
|
Expect(files).To(HaveKey("journal/k0sworker.log"))
|
|
Expect(files).To(HaveKey("journal/myservice.log"))
|
|
Expect(files).To(HaveKey("files/var/log/test.log"))
|
|
})
|
|
|
|
It("should handle missing files gracefully", func() {
|
|
// Create a default pattern file to ensure it's collected
|
|
defaultFile := "/var/log/kairos/agent.log"
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(defaultFile), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = fs.WriteFile(defaultFile, []byte("default log content"), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{},
|
|
Files: []string{"/var/log/nonexistent.log"},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// Should have collected the default pattern file even though user file doesn't exist
|
|
Expect(result.Files).To(HaveLen(1))
|
|
Expect(result.Files).To(HaveKey("/var/log/kairos/agent.log"))
|
|
})
|
|
|
|
It("should handle journal service errors gracefully", func() {
|
|
// Mock journalctl to return no entries for non-existent service
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" && len(args) > 1 && args[1] == "nonexistentservice" {
|
|
return []byte("-- No entries --"), nil
|
|
}
|
|
if command == "journalctl" {
|
|
return []byte("journal logs content"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"nonexistentservice"},
|
|
Files: []string{},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// Should have collected from default services but not the non-existent user service
|
|
Expect(result.Journal).To(HaveLen(11))
|
|
Expect(result.Journal).To(HaveKey("kairos-agent"))
|
|
Expect(result.Journal).To(HaveKey("kairos-installer"))
|
|
Expect(result.Journal).To(HaveKey("kairos-webui"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-boot"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-fs"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-network"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-reconcile"))
|
|
Expect(result.Journal).To(HaveKey("k3s"))
|
|
Expect(result.Journal).To(HaveKey("k3s-agent"))
|
|
Expect(result.Journal).To(HaveKey("k0scontroller"))
|
|
Expect(result.Journal).To(HaveKey("k0sworker"))
|
|
Expect(result.Journal).ToNot(HaveKey("nonexistentservice"))
|
|
})
|
|
|
|
It("should handle empty journal output gracefully", func() {
|
|
// Mock journalctl to return empty output for user service, normal for defaults
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" && len(args) > 1 && args[1] == "emptyservice" {
|
|
return []byte{}, nil
|
|
}
|
|
if command == "journalctl" {
|
|
return []byte("journal logs content"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"emptyservice"},
|
|
Files: []string{},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// Should have collected from default services but not the empty user service
|
|
Expect(result.Journal).To(HaveLen(11))
|
|
Expect(result.Journal).To(HaveKey("kairos-agent"))
|
|
Expect(result.Journal).To(HaveKey("kairos-installer"))
|
|
Expect(result.Journal).To(HaveKey("kairos-webui"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-boot"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-fs"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-network"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-reconcile"))
|
|
Expect(result.Journal).To(HaveKey("k3s"))
|
|
Expect(result.Journal).To(HaveKey("k3s-agent"))
|
|
Expect(result.Journal).To(HaveKey("k0scontroller"))
|
|
Expect(result.Journal).To(HaveKey("k0sworker"))
|
|
Expect(result.Journal).ToNot(HaveKey("emptyservice"))
|
|
})
|
|
|
|
It("should not create tarball files for services with no journal entries", func() {
|
|
// Mock journalctl to return no entries for one service and content for another
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" && len(args) > 1 && args[1] == "existingservice" {
|
|
return []byte("journal logs content"), nil
|
|
}
|
|
if command == "journalctl" && len(args) > 1 && args[1] == "nonexistentservice" {
|
|
return []byte("-- No entries --"), nil
|
|
}
|
|
if command == "journalctl" {
|
|
return []byte("default journal logs"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Set logs config in the main config
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"existingservice", "nonexistentservice"},
|
|
Files: []string{},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// Should have collected from default services and the existing user service
|
|
Expect(result.Journal).To(HaveLen(12))
|
|
Expect(result.Journal).To(HaveKey("kairos-agent"))
|
|
Expect(result.Journal).To(HaveKey("kairos-installer"))
|
|
Expect(result.Journal).To(HaveKey("kairos-webui"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-boot"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-fs"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-network"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-reconcile"))
|
|
Expect(result.Journal).To(HaveKey("k3s"))
|
|
Expect(result.Journal).To(HaveKey("k3s-agent"))
|
|
Expect(result.Journal).To(HaveKey("k0scontroller"))
|
|
Expect(result.Journal).To(HaveKey("k0sworker"))
|
|
Expect(result.Journal).To(HaveKey("existingservice"))
|
|
Expect(result.Journal).ToNot(HaveKey("nonexistentservice"))
|
|
|
|
// Create tarball and verify contents
|
|
tarballPath := "/tmp/logs.tar.gz"
|
|
err = collector.CreateTarball(result, tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Read and verify tarball contents
|
|
tarballData, err := fs.ReadFile(tarballPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Extract and verify tarball structure
|
|
gzr, err := gzip.NewReader(bytes.NewReader(tarballData))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer gzr.Close()
|
|
|
|
tr := tar.NewReader(gzr)
|
|
files := make(map[string]bool)
|
|
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
Expect(err).ToNot(HaveOccurred())
|
|
files[header.Name] = true
|
|
}
|
|
|
|
// Verify that default services and existing user service files are created
|
|
Expect(files).To(HaveKey("journal/kairos-agent.log"))
|
|
Expect(files).To(HaveKey("journal/kairos-installer.log"))
|
|
Expect(files).To(HaveKey("journal/kairos-webui.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-boot.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-fs.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-network.log"))
|
|
Expect(files).To(HaveKey("journal/cos-setup-reconcile.log"))
|
|
Expect(files).To(HaveKey("journal/k3s.log"))
|
|
Expect(files).To(HaveKey("journal/k3s-agent.log"))
|
|
Expect(files).To(HaveKey("journal/k0scontroller.log"))
|
|
Expect(files).To(HaveKey("journal/k0sworker.log"))
|
|
Expect(files).To(HaveKey("journal/existingservice.log"))
|
|
Expect(files).ToNot(HaveKey("journal/nonexistentservice.log"))
|
|
Expect(files).To(HaveLen(12))
|
|
})
|
|
|
|
It("should use default log sources when no config provided", func() {
|
|
// Mock journalctl output
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" {
|
|
return []byte("default journal logs"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Don't set logs config - should use defaults
|
|
collector.config.Logs = nil
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(result).ToNot(BeNil())
|
|
|
|
// Should have collected from default services
|
|
Expect(runner.CmdsMatch([][]string{
|
|
{"journalctl", "-u", "kairos-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-installer", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-webui", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-boot", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-fs", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-network", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-reconcile", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0scontroller", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0sworker", "--no-pager", "-o", "cat"},
|
|
})).To(BeNil())
|
|
})
|
|
|
|
It("should merge user logs config with defaults", func() {
|
|
// Mock journalctl output
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" && len(args) > 0 && args[0] == "-u" {
|
|
service := args[1]
|
|
return []byte("journal logs for " + service), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Set logs config in the main config with user-defined services
|
|
collector.config.Logs = &config.LogsConfig{
|
|
Journal: []string{"myservice", "myotherservice"},
|
|
Files: []string{"/var/log/mycustom.log"},
|
|
}
|
|
|
|
result, err := collector.Collect()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(result).ToNot(BeNil())
|
|
|
|
// Verify that both default and user-defined services were collected
|
|
// Default services: kairos-agent, kairos-installer, kairos-webui, cos-setup-boot, cos-setup-fs, cos-setup-network, cos-setup-reconcile, k3s, k3s-agent, k0scontroller, k0sworker
|
|
// User services: myservice, myotherservice
|
|
Expect(runner.CmdsMatch([][]string{
|
|
{"journalctl", "-u", "kairos-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-installer", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "kairos-webui", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-boot", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-fs", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-network", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "cos-setup-reconcile", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k3s-agent", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0scontroller", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "k0sworker", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "myservice", "--no-pager", "-o", "cat"},
|
|
{"journalctl", "-u", "myotherservice", "--no-pager", "-o", "cat"},
|
|
})).To(BeNil())
|
|
|
|
// Verify that both default and user-defined files are in the result
|
|
Expect(result.Journal).To(HaveKey("kairos-agent"))
|
|
Expect(result.Journal).To(HaveKey("kairos-installer"))
|
|
Expect(result.Journal).To(HaveKey("kairos-webui"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-boot"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-fs"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-network"))
|
|
Expect(result.Journal).To(HaveKey("cos-setup-reconcile"))
|
|
Expect(result.Journal).To(HaveKey("k3s"))
|
|
Expect(result.Journal).To(HaveKey("k3s-agent"))
|
|
Expect(result.Journal).To(HaveKey("k0scontroller"))
|
|
Expect(result.Journal).To(HaveKey("k0sworker"))
|
|
Expect(result.Journal).To(HaveKey("myservice"))
|
|
Expect(result.Journal).To(HaveKey("myotherservice"))
|
|
Expect(result.Journal).To(HaveLen(13))
|
|
})
|
|
})
|
|
|
|
Describe("CLI Command", func() {
|
|
It("should execute logs command successfully", func() {
|
|
// Mock journalctl output
|
|
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
|
if command == "journalctl" {
|
|
return []byte("journal logs content"), nil
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Create test config file
|
|
configContent := `
|
|
logs:
|
|
journal:
|
|
- myservice
|
|
files:
|
|
- /var/log/test.log
|
|
`
|
|
configPath := "/etc/kairos/config.yaml"
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(configPath), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
err = fs.WriteFile(configPath, []byte(configContent), constants.FilePerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Create test log file
|
|
testFile := "/var/log/test.log"
|
|
err = fsutils.MkdirAll(fs, filepath.Dir(testFile), constants.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Execute logs command
|
|
outputPath := "/tmp/logs.tar.gz"
|
|
err = ExecuteLogsCommand(fs, logger, runner, outputPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Verify output file was created
|
|
exists, err := fsutils.Exists(fs, outputPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(exists).To(BeTrue())
|
|
})
|
|
|
|
It("should handle missing config gracefully", func() {
|
|
// Execute logs command without config
|
|
outputPath := "/tmp/logs.tar.gz"
|
|
err := ExecuteLogsCommand(fs, logger, runner, outputPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Should still create output with default sources
|
|
exists, err := fsutils.Exists(fs, outputPath)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(exists).To(BeTrue())
|
|
})
|
|
})
|
|
})
|