cli/pkg/release/publishers/linuxkit_test.go
Snider f2bc912ebe feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs

Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.

Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.

Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add collect, config, crypt, plugin packages and fix all lint issues

Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)

Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.

Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00

924 lines
25 KiB
Go

package publishers
import (
"bytes"
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLinuxKitPublisher_Name_Good(t *testing.T) {
t.Run("returns linuxkit", func(t *testing.T) {
p := NewLinuxKitPublisher()
assert.Equal(t, "linuxkit", p.Name())
})
}
func TestLinuxKitPublisher_ParseConfig_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("uses defaults when no extended config", func(t *testing.T) {
pubCfg := PublisherConfig{Type: "linuxkit"}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, "/project/.core/linuxkit/server.yml", cfg.Config)
assert.Equal(t, []string{"iso"}, cfg.Formats)
assert.Equal(t, []string{"linux/amd64"}, cfg.Platforms)
})
t.Run("parses extended config", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": ".core/linuxkit/custom.yml",
"formats": []any{"iso", "qcow2", "vmdk"},
"platforms": []any{"linux/amd64", "linux/arm64"},
},
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, "/project/.core/linuxkit/custom.yml", cfg.Config)
assert.Equal(t, []string{"iso", "qcow2", "vmdk"}, cfg.Formats)
assert.Equal(t, []string{"linux/amd64", "linux/arm64"}, cfg.Platforms)
})
t.Run("handles absolute config path", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": "/absolute/path/to/config.yml",
},
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, "/absolute/path/to/config.yml", cfg.Config)
})
}
func TestLinuxKitPublisher_BuildLinuxKitArgs_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("builds basic args for amd64", func(t *testing.T) {
args := p.buildLinuxKitArgs("/config/server.yml", "iso", "linuxkit-1.0.0-amd64", "/output", "amd64")
assert.Contains(t, args, "build")
assert.Contains(t, args, "--format")
assert.Contains(t, args, "iso")
assert.Contains(t, args, "--name")
assert.Contains(t, args, "linuxkit-1.0.0-amd64")
assert.Contains(t, args, "--dir")
assert.Contains(t, args, "/output")
assert.Contains(t, args, "/config/server.yml")
// Should not contain --arch for amd64 (default)
assert.NotContains(t, args, "--arch")
})
t.Run("builds args with arch for arm64", func(t *testing.T) {
args := p.buildLinuxKitArgs("/config/server.yml", "qcow2", "linuxkit-1.0.0-arm64", "/output", "arm64")
assert.Contains(t, args, "--arch")
assert.Contains(t, args, "arm64")
assert.Contains(t, args, "qcow2")
})
}
func TestLinuxKitPublisher_BuildBaseName_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("strips v prefix", func(t *testing.T) {
name := p.buildBaseName("v1.2.3")
assert.Equal(t, "linuxkit-1.2.3", name)
})
t.Run("handles version without v prefix", func(t *testing.T) {
name := p.buildBaseName("1.2.3")
assert.Equal(t, "linuxkit-1.2.3", name)
})
}
func TestLinuxKitPublisher_GetArtifactPath_Good(t *testing.T) {
p := NewLinuxKitPublisher()
tests := []struct {
name string
outputDir string
outputName string
format string
expected string
}{
{
name: "ISO format",
outputDir: "/dist/linuxkit",
outputName: "linuxkit-1.0.0-amd64",
format: "iso",
expected: "/dist/linuxkit/linuxkit-1.0.0-amd64.iso",
},
{
name: "raw format",
outputDir: "/dist/linuxkit",
outputName: "linuxkit-1.0.0-amd64",
format: "raw",
expected: "/dist/linuxkit/linuxkit-1.0.0-amd64.raw",
},
{
name: "qcow2 format",
outputDir: "/dist/linuxkit",
outputName: "linuxkit-1.0.0-arm64",
format: "qcow2",
expected: "/dist/linuxkit/linuxkit-1.0.0-arm64.qcow2",
},
{
name: "vmdk format",
outputDir: "/dist/linuxkit",
outputName: "linuxkit-1.0.0-amd64",
format: "vmdk",
expected: "/dist/linuxkit/linuxkit-1.0.0-amd64.vmdk",
},
{
name: "gcp format",
outputDir: "/dist/linuxkit",
outputName: "linuxkit-1.0.0-amd64",
format: "gcp",
expected: "/dist/linuxkit/linuxkit-1.0.0-amd64.img.tar.gz",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
path := p.getArtifactPath(tc.outputDir, tc.outputName, tc.format)
assert.Equal(t, tc.expected, path)
})
}
}
func TestLinuxKitPublisher_GetFormatExtension_Good(t *testing.T) {
p := NewLinuxKitPublisher()
tests := []struct {
format string
expected string
}{
{"iso", ".iso"},
{"raw", ".raw"},
{"qcow2", ".qcow2"},
{"vmdk", ".vmdk"},
{"vhd", ".vhd"},
{"gcp", ".img.tar.gz"},
{"aws", ".raw"},
{"unknown", ".unknown"},
}
for _, tc := range tests {
t.Run(tc.format, func(t *testing.T) {
ext := p.getFormatExtension(tc.format)
assert.Equal(t, tc.expected, ext)
})
}
}
func TestLinuxKitPublisher_Publish_Bad(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("fails when config file not found with linuxkit installed", func(t *testing.T) {
if err := validateLinuxKitCli(); err != nil {
t.Skip("skipping test: linuxkit CLI not available")
}
release := &Release{
Version: "v1.0.0",
ProjectDir: "/nonexistent",
}
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": "/nonexistent/config.yml",
},
}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err := p.Publish(context.TODO(), release, pubCfg, relCfg, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "config file not found")
})
t.Run("fails when linuxkit CLI not available", func(t *testing.T) {
if err := validateLinuxKitCli(); err == nil {
t.Skip("skipping test: linuxkit CLI is available")
}
release := &Release{
Version: "v1.0.0",
ProjectDir: "/tmp",
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err := p.Publish(context.TODO(), release, pubCfg, relCfg, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "linuxkit CLI not found")
})
t.Run("fails when repository cannot be detected and not provided", func(t *testing.T) {
if err := validateLinuxKitCli(); err != nil {
t.Skip("skipping test: linuxkit CLI not available")
}
// Create temp directory that is NOT a git repo
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
// Create a config file
configPath := filepath.Join(tmpDir, "config.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0644)
require.NoError(t, err)
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": "config.yml",
},
}
relCfg := &mockReleaseConfig{repository: ""} // Empty repository
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "could not determine repository")
})
}
func TestValidateLinuxKitCli_Good(t *testing.T) {
t.Run("returns expected error when linuxkit not installed", func(t *testing.T) {
err := validateLinuxKitCli()
if err != nil {
// LinuxKit is not installed
assert.Contains(t, err.Error(), "linuxkit CLI not found")
}
// If err is nil, linuxkit is installed - that's OK
})
}
func TestLinuxKitPublisher_Publish_WithCLI_Good(t *testing.T) {
// These tests run only when linuxkit CLI is available
if err := validateLinuxKitCli(); err != nil {
t.Skip("skipping test: linuxkit CLI not available")
}
p := NewLinuxKitPublisher()
t.Run("succeeds with dry run and valid config", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
// Create config directory and file
configDir := filepath.Join(tmpDir, ".core", "linuxkit")
err = os.MkdirAll(configDir, 0755)
require.NoError(t, err)
configPath := filepath.Join(configDir, "server.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: linuxkit/kernel:5.10\n"), 0644)
require.NoError(t, err)
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "DRY RUN: LinuxKit Build & Publish")
})
t.Run("fails with missing config file", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "config file not found")
})
t.Run("uses relCfg repository", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
configDir := filepath.Join(tmpDir, ".core", "linuxkit")
err = os.MkdirAll(configDir, 0755)
require.NoError(t, err)
configPath := filepath.Join(configDir, "server.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0644)
require.NoError(t, err)
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: "custom-owner/custom-repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "custom-owner/custom-repo")
})
t.Run("detects repository when not provided", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
// Create config file
configDir := filepath.Join(tmpDir, ".core", "linuxkit")
err = os.MkdirAll(configDir, 0755)
require.NoError(t, err)
configPath := filepath.Join(configDir, "server.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0644)
require.NoError(t, err)
// Initialize git repo
cmd := exec.Command("git", "init")
cmd.Dir = tmpDir
require.NoError(t, cmd.Run())
cmd = exec.Command("git", "remote", "add", "origin", "git@github.com:detected-owner/detected-repo.git")
cmd.Dir = tmpDir
require.NoError(t, cmd.Run())
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: ""} // Empty to trigger detection
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "detected-owner/detected-repo")
})
}
func TestLinuxKitPublisher_Publish_NilRelCfg_Good(t *testing.T) {
if err := validateLinuxKitCli(); err != nil {
t.Skip("skipping test: linuxkit CLI not available")
}
p := NewLinuxKitPublisher()
t.Run("handles nil relCfg by detecting repo", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
// Create config file
configDir := filepath.Join(tmpDir, ".core", "linuxkit")
err = os.MkdirAll(configDir, 0755)
require.NoError(t, err)
configPath := filepath.Join(configDir, "server.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0644)
require.NoError(t, err)
// Initialize git repo
cmd := exec.Command("git", "init")
cmd.Dir = tmpDir
require.NoError(t, cmd.Run())
cmd = exec.Command("git", "remote", "add", "origin", "git@github.com:nil-owner/nil-repo.git")
cmd.Dir = tmpDir
require.NoError(t, cmd.Run())
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
err = p.Publish(context.TODO(), release, pubCfg, nil, true) // nil relCfg
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "nil-owner/nil-repo")
})
}
// mockReleaseConfig implements ReleaseConfig for testing.
type mockReleaseConfig struct {
repository string
projectName string
}
func (m *mockReleaseConfig) GetRepository() string {
return m.repository
}
func (m *mockReleaseConfig) GetProjectName() string {
return m.projectName
}
func TestLinuxKitPublisher_DryRunPublish_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("outputs expected dry run information", func(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: "/project",
}
cfg := LinuxKitConfig{
Config: "/project/.core/linuxkit/server.yml",
Formats: []string{"iso", "qcow2"},
Platforms: []string{"linux/amd64", "linux/arm64"},
}
err := p.dryRunPublish(release, cfg, "owner/repo")
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "DRY RUN: LinuxKit Build & Publish")
assert.Contains(t, output, "Repository: owner/repo")
assert.Contains(t, output, "Version: v1.0.0")
assert.Contains(t, output, "Config: /project/.core/linuxkit/server.yml")
assert.Contains(t, output, "Formats: iso, qcow2")
assert.Contains(t, output, "Platforms: linux/amd64, linux/arm64")
assert.Contains(t, output, "Would execute commands:")
assert.Contains(t, output, "linuxkit build")
assert.Contains(t, output, "Would upload artifacts to release:")
assert.Contains(t, output, "linuxkit-1.0.0-amd64.iso")
assert.Contains(t, output, "linuxkit-1.0.0-amd64.qcow2")
assert.Contains(t, output, "linuxkit-1.0.0-arm64.iso")
assert.Contains(t, output, "linuxkit-1.0.0-arm64.qcow2")
assert.Contains(t, output, "END DRY RUN")
})
t.Run("shows docker format usage hint", func(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: "/project",
}
cfg := LinuxKitConfig{
Config: "/config.yml",
Formats: []string{"docker"},
Platforms: []string{"linux/amd64"},
}
err := p.dryRunPublish(release, cfg, "owner/repo")
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "linuxkit-1.0.0-amd64.docker.tar")
assert.Contains(t, output, "Usage: docker load <")
})
t.Run("handles single platform and format", func(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v2.0.0",
ProjectDir: "/project",
}
cfg := LinuxKitConfig{
Config: "/config.yml",
Formats: []string{"iso"},
Platforms: []string{"linux/amd64"},
}
err := p.dryRunPublish(release, cfg, "owner/repo")
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "linuxkit-2.0.0-amd64.iso")
assert.NotContains(t, output, "arm64")
})
}
func TestLinuxKitPublisher_GetFormatExtension_AllFormats_Good(t *testing.T) {
p := NewLinuxKitPublisher()
tests := []struct {
format string
expected string
}{
{"iso", ".iso"},
{"iso-bios", ".iso"},
{"iso-efi", ".iso"},
{"raw", ".raw"},
{"raw-bios", ".raw"},
{"raw-efi", ".raw"},
{"qcow2", ".qcow2"},
{"qcow2-bios", ".qcow2"},
{"qcow2-efi", ".qcow2"},
{"vmdk", ".vmdk"},
{"vhd", ".vhd"},
{"gcp", ".img.tar.gz"},
{"aws", ".raw"},
{"docker", ".docker.tar"},
{"tar", ".tar"},
{"kernel+initrd", "-initrd.img"},
{"custom--format", ".custom--format"},
}
for _, tc := range tests {
t.Run(tc.format, func(t *testing.T) {
ext := p.getFormatExtension(tc.format)
assert.Equal(t, tc.expected, ext)
})
}
}
func TestLinuxKitPublisher_BuildLinuxKitArgs_AllArchitectures_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("amd64 does not include arch flag", func(t *testing.T) {
args := p.buildLinuxKitArgs("/config.yml", "iso", "output--name", "/output", "amd64")
assert.Contains(t, args, "build")
assert.Contains(t, args, "--format")
assert.Contains(t, args, "iso")
assert.Contains(t, args, "--name")
assert.Contains(t, args, "output--name")
assert.Contains(t, args, "--dir")
assert.Contains(t, args, "/output")
assert.Contains(t, args, "/config.yml")
assert.NotContains(t, args, "--arch")
})
t.Run("arm64 includes arch flag", func(t *testing.T) {
args := p.buildLinuxKitArgs("/config.yml", "qcow2", "output--name", "/output", "arm64")
assert.Contains(t, args, "--arch")
assert.Contains(t, args, "arm64")
})
t.Run("other architectures include arch flag", func(t *testing.T) {
args := p.buildLinuxKitArgs("/config.yml", "raw", "output--name", "/output", "riscv64")
assert.Contains(t, args, "--arch")
assert.Contains(t, args, "riscv64")
})
}
func TestLinuxKitPublisher_ParseConfig_EdgeCases_Good(t *testing.T) {
p := NewLinuxKitPublisher()
t.Run("handles nil extended config", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: nil,
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, "/project/.core/linuxkit/server.yml", cfg.Config)
assert.Equal(t, []string{"iso"}, cfg.Formats)
assert.Equal(t, []string{"linux/amd64"}, cfg.Platforms)
})
t.Run("handles empty extended config", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{},
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, "/project/.core/linuxkit/server.yml", cfg.Config)
assert.Equal(t, []string{"iso"}, cfg.Formats)
assert.Equal(t, []string{"linux/amd64"}, cfg.Platforms)
})
t.Run("handles mixed format types in extended config", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"formats": []any{"iso", 123, "qcow2"}, // includes non-string
},
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, []string{"iso", "qcow2"}, cfg.Formats)
})
t.Run("handles mixed platform types in extended config", func(t *testing.T) {
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"platforms": []any{"linux/amd64", nil, "linux/arm64"},
},
}
cfg := p.parseConfig(pubCfg, "/project")
assert.Equal(t, []string{"linux/amd64", "linux/arm64"}, cfg.Platforms)
})
}
func TestLinuxKitPublisher_BuildBaseName_EdgeCases_Good(t *testing.T) {
p := NewLinuxKitPublisher()
tests := []struct {
name string
version string
expected string
}{
{"strips v prefix", "v1.2.3", "linuxkit-1.2.3"},
{"no v prefix", "1.2.3", "linuxkit-1.2.3"},
{"prerelease version", "v1.0.0-alpha.1", "linuxkit-1.0.0-alpha.1"},
{"build metadata", "v1.0.0+build.123", "linuxkit-1.0.0+build.123"},
{"only v", "v", "linuxkit-"},
{"empty string", "", "linuxkit-"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
name := p.buildBaseName(tc.version)
assert.Equal(t, tc.expected, name)
})
}
}
func TestLinuxKitPublisher_GetArtifactPath_AllFormats_Good(t *testing.T) {
p := NewLinuxKitPublisher()
tests := []struct {
name string
outputDir string
outputName string
format string
expected string
}{
{
name: "ISO format",
outputDir: "/dist",
outputName: "linuxkit-1.0.0-amd64",
format: "iso",
expected: "/dist/linuxkit-1.0.0-amd64.iso",
},
{
name: "ISO-BIOS format",
outputDir: "/dist",
outputName: "linuxkit-1.0.0-amd64",
format: "iso-bios",
expected: "/dist/linuxkit-1.0.0-amd64.iso",
},
{
name: "docker format",
outputDir: "/output",
outputName: "linuxkit-2.0.0-arm64",
format: "docker",
expected: "/output/linuxkit-2.0.0-arm64.docker.tar",
},
{
name: "tar format",
outputDir: "/output",
outputName: "linuxkit-1.0.0",
format: "tar",
expected: "/output/linuxkit-1.0.0.tar",
},
{
name: "kernel+initrd format",
outputDir: "/output",
outputName: "linuxkit-1.0.0",
format: "kernel+initrd",
expected: "/output/linuxkit-1.0.0-initrd.img",
},
{
name: "GCP format",
outputDir: "/output",
outputName: "linuxkit-1.0.0",
format: "gcp",
expected: "/output/linuxkit-1.0.0.img.tar.gz",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
path := p.getArtifactPath(tc.outputDir, tc.outputName, tc.format)
assert.Equal(t, tc.expected, path)
})
}
}
func TestLinuxKitPublisher_Publish_DryRun_Good(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
// Skip if linuxkit CLI is not available
if err := validateLinuxKitCli(); err != nil {
t.Skip("skipping test: linuxkit CLI not available")
}
p := NewLinuxKitPublisher()
t.Run("dry run succeeds with valid config file", func(t *testing.T) {
// Create temp directory with config file
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
configDir := filepath.Join(tmpDir, ".core", "linuxkit")
err = os.MkdirAll(configDir, 0755)
require.NoError(t, err)
configPath := filepath.Join(configDir, "server.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: linuxkit/kernel:5.10\n"), 0644)
require.NoError(t, err)
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{Type: "linuxkit"}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "DRY RUN: LinuxKit Build & Publish")
})
t.Run("dry run uses custom config path", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
customConfigPath := filepath.Join(tmpDir, "custom-config.yml")
err = os.WriteFile(customConfigPath, []byte("kernel:\n image: custom\n"), 0644)
require.NoError(t, err)
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v1.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": customConfigPath,
},
}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "custom-config.yml")
})
t.Run("dry run with multiple formats and platforms", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "linuxkit-test")
require.NoError(t, err)
defer func() { _ = os.RemoveAll(tmpDir) }()
configPath := filepath.Join(tmpDir, "config.yml")
err = os.WriteFile(configPath, []byte("kernel:\n image: test\n"), 0644)
require.NoError(t, err)
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
release := &Release{
Version: "v2.0.0",
ProjectDir: tmpDir,
}
pubCfg := PublisherConfig{
Type: "linuxkit",
Extended: map[string]any{
"config": "config.yml",
"formats": []any{"iso", "qcow2", "vmdk"},
"platforms": []any{"linux/amd64", "linux/arm64"},
},
}
relCfg := &mockReleaseConfig{repository: "owner/repo"}
err = p.Publish(context.TODO(), release, pubCfg, relCfg, true)
_ = w.Close()
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
os.Stdout = oldStdout
require.NoError(t, err)
output := buf.String()
// Check all format/platform combinations are listed
assert.Contains(t, output, "linuxkit-2.0.0-amd64.iso")
assert.Contains(t, output, "linuxkit-2.0.0-amd64.qcow2")
assert.Contains(t, output, "linuxkit-2.0.0-amd64.vmdk")
assert.Contains(t, output, "linuxkit-2.0.0-arm64.iso")
assert.Contains(t, output, "linuxkit-2.0.0-arm64.qcow2")
assert.Contains(t, output, "linuxkit-2.0.0-arm64.vmdk")
})
}