go-build/pkg/release/publishers/linuxkit.go
Snider febe858942 fix(ax): replace banned os imports and add usage example comments
- Remove `os` import from internal/ax/ax.go; replace os.Getwd() with
  syscall.Getwd(), os.MkdirAll() with coreio.Local.EnsureDir(), and
  os.Chmod() with syscall.Chmod()
- Remove `os` import from pkg/sdk/generators/typescript_test.go;
  replace os.PathListSeparator and os.Getenv() with core.Env("PS")
  and core.Env("PATH")
- Replace all "Usage example: call/declare ... from integrating code"
  placeholder comments with concrete code examples across 45 files
  covering build, release, sdk, signing, publishers, builders, and cmd

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 18:33:36 +01:00

323 lines
9.6 KiB
Go

// Package publishers provides release publishing implementations.
package publishers
import (
"context"
"dappco.re/go/core"
"dappco.re/go/core/build/internal/ax"
coreerr "dappco.re/go/core/log"
)
// LinuxKitConfig holds configuration for the LinuxKit publisher.
//
// cfg := publishers.LinuxKitConfig{Config: ".core/node.yaml", Formats: []string{"iso", "qcow2"}}
type LinuxKitConfig struct {
// Config is the path to the LinuxKit YAML configuration file.
Config string `yaml:"config"`
// Formats are the output formats to build.
// Supported: iso, iso-bios, iso-efi, raw, raw-bios, raw-efi,
// qcow2, qcow2-bios, qcow2-efi, vmdk, vhd, gcp, aws,
// docker (tarball for `docker load`), tar, kernel+initrd
Formats []string `yaml:"formats"`
// Platforms are the target platforms (linux/amd64, linux/arm64).
Platforms []string `yaml:"platforms"`
}
// LinuxKitPublisher builds and publishes LinuxKit images.
//
// pub := publishers.NewLinuxKitPublisher()
type LinuxKitPublisher struct{}
// NewLinuxKitPublisher creates a new LinuxKit publisher.
//
// pub := publishers.NewLinuxKitPublisher()
func NewLinuxKitPublisher() *LinuxKitPublisher {
return &LinuxKitPublisher{}
}
// Name returns the publisher's identifier.
//
// name := pub.Name() // → "linuxkit"
func (p *LinuxKitPublisher) Name() string {
return "linuxkit"
}
// Publish builds LinuxKit images and uploads them to the GitHub release.
//
// err := pub.Publish(ctx, rel, pubCfg, relCfg, false)
func (p *LinuxKitPublisher) Publish(ctx context.Context, release *Release, pubCfg PublisherConfig, relCfg ReleaseConfig, dryRun bool) error {
linuxkitCommand, err := resolveLinuxKitCli()
if err != nil {
return err
}
// Parse LinuxKit-specific config from publisher config
lkCfg := p.parseConfig(pubCfg, release.ProjectDir)
// Validate config file exists
if release.FS == nil {
return coreerr.E("linuxkit.Publish", "release filesystem (FS) is nil", nil)
}
if !release.FS.Exists(lkCfg.Config) {
return coreerr.E("linuxkit.Publish", "config file not found: "+lkCfg.Config, nil)
}
// Determine repository for artifact upload
repo := ""
if relCfg != nil {
repo = relCfg.GetRepository()
}
if repo == "" {
detectedRepo, err := detectRepository(ctx, release.ProjectDir)
if err != nil {
return coreerr.E("linuxkit.Publish", "could not determine repository", err)
}
repo = detectedRepo
}
if dryRun {
return p.dryRunPublish(release, lkCfg, repo)
}
return p.executePublish(ctx, release, lkCfg, repo, linuxkitCommand)
}
// parseConfig extracts LinuxKit-specific configuration.
func (p *LinuxKitPublisher) parseConfig(pubCfg PublisherConfig, projectDir string) LinuxKitConfig {
cfg := LinuxKitConfig{
Config: ax.Join(projectDir, ".core", "linuxkit", "server.yml"),
Formats: []string{"iso"},
Platforms: []string{"linux/amd64"},
}
// Override from extended config if present
if ext, ok := pubCfg.Extended.(map[string]any); ok {
if configPath, ok := ext["config"].(string); ok && configPath != "" {
if ax.IsAbs(configPath) {
cfg.Config = configPath
} else {
cfg.Config = ax.Join(projectDir, configPath)
}
}
if formats, ok := ext["formats"].([]any); ok && len(formats) > 0 {
cfg.Formats = make([]string, 0, len(formats))
for _, f := range formats {
if s, ok := f.(string); ok {
cfg.Formats = append(cfg.Formats, s)
}
}
}
if platforms, ok := ext["platforms"].([]any); ok && len(platforms) > 0 {
cfg.Platforms = make([]string, 0, len(platforms))
for _, p := range platforms {
if s, ok := p.(string); ok {
cfg.Platforms = append(cfg.Platforms, s)
}
}
}
}
return cfg
}
// dryRunPublish shows what would be done without actually building.
func (p *LinuxKitPublisher) dryRunPublish(release *Release, cfg LinuxKitConfig, repo string) error {
publisherPrintln()
publisherPrintln("=== DRY RUN: LinuxKit Build & Publish ===")
publisherPrintln()
publisherPrint("Repository: %s", repo)
publisherPrint("Version: %s", release.Version)
publisherPrint("Config: %s", cfg.Config)
publisherPrint("Formats: %s", core.Join(", ", cfg.Formats...))
publisherPrint("Platforms: %s", core.Join(", ", cfg.Platforms...))
publisherPrintln()
outputDir := ax.Join(release.ProjectDir, "dist", "linuxkit")
baseName := p.buildBaseName(release.Version)
publisherPrintln("Would execute commands:")
for _, platform := range cfg.Platforms {
parts := core.Split(platform, "/")
arch := "amd64"
if len(parts) == 2 {
arch = parts[1]
}
for _, format := range cfg.Formats {
outputName := core.Sprintf("%s-%s", baseName, arch)
args := p.buildLinuxKitArgs(cfg.Config, format, outputName, outputDir, arch)
publisherPrint(" linuxkit %s", core.Join(" ", args...))
}
}
publisherPrintln()
publisherPrintln("Would upload artifacts to release:")
for _, platform := range cfg.Platforms {
parts := core.Split(platform, "/")
arch := "amd64"
if len(parts) == 2 {
arch = parts[1]
}
for _, format := range cfg.Formats {
outputName := core.Sprintf("%s-%s", baseName, arch)
artifactPath := p.getArtifactPath(outputDir, outputName, format)
publisherPrint(" - %s", ax.Base(artifactPath))
if format == "docker" {
publisherPrint(" Usage: docker load < %s", ax.Base(artifactPath))
}
}
}
publisherPrintln()
publisherPrintln("=== END DRY RUN ===")
return nil
}
// executePublish builds LinuxKit images and uploads them.
func (p *LinuxKitPublisher) executePublish(ctx context.Context, release *Release, cfg LinuxKitConfig, repo, linuxkitCommand string) error {
outputDir := ax.Join(release.ProjectDir, "dist", "linuxkit")
// Create output directory
if err := release.FS.EnsureDir(outputDir); err != nil {
return coreerr.E("linuxkit.Publish", "failed to create output directory", err)
}
baseName := p.buildBaseName(release.Version)
var artifacts []string
// Build for each platform and format
for _, platform := range cfg.Platforms {
parts := core.Split(platform, "/")
arch := "amd64"
if len(parts) == 2 {
arch = parts[1]
}
for _, format := range cfg.Formats {
outputName := core.Sprintf("%s-%s", baseName, arch)
// Build the image
args := p.buildLinuxKitArgs(cfg.Config, format, outputName, outputDir, arch)
publisherPrint("Building LinuxKit image: %s (%s)", outputName, format)
if err := publisherRun(ctx, release.ProjectDir, nil, linuxkitCommand, args...); err != nil {
return coreerr.E("linuxkit.Publish", "build failed for "+platform+"/"+format, err)
}
// Track artifact for upload
artifactPath := p.getArtifactPath(outputDir, outputName, format)
artifacts = append(artifacts, artifactPath)
}
}
// Upload artifacts to GitHub release
for _, artifactPath := range artifacts {
if !release.FS.Exists(artifactPath) {
return coreerr.E("linuxkit.Publish", "artifact not found after build: "+artifactPath, nil)
}
if err := UploadArtifact(ctx, repo, release.Version, artifactPath); err != nil {
return coreerr.E("linuxkit.Publish", "failed to upload "+ax.Base(artifactPath), err)
}
// Print helpful usage info for docker format
if core.HasSuffix(artifactPath, ".docker.tar") {
publisherPrint(" Load with: docker load < %s", ax.Base(artifactPath))
}
}
return nil
}
// buildBaseName creates the base name for output files.
func (p *LinuxKitPublisher) buildBaseName(version string) string {
// Strip leading 'v' if present for cleaner filenames
name := core.TrimPrefix(version, "v")
return core.Sprintf("linuxkit-%s", name)
}
// buildLinuxKitArgs builds the arguments for linuxkit build command.
func (p *LinuxKitPublisher) buildLinuxKitArgs(configPath, format, outputName, outputDir, arch string) []string {
args := []string{"build"}
// Output format
args = append(args, "--format", format)
// Output name
args = append(args, "--name", outputName)
// Output directory
args = append(args, "--dir", outputDir)
// Architecture (if not amd64)
if arch != "amd64" {
args = append(args, "--arch", arch)
}
// Config file
args = append(args, configPath)
return args
}
// getArtifactPath returns the expected path of the built artifact.
func (p *LinuxKitPublisher) getArtifactPath(outputDir, outputName, format string) string {
ext := p.getFormatExtension(format)
return ax.Join(outputDir, outputName+ext)
}
// getFormatExtension returns the file extension for a LinuxKit output format.
func (p *LinuxKitPublisher) getFormatExtension(format string) string {
switch format {
case "iso", "iso-bios", "iso-efi":
return ".iso"
case "raw", "raw-bios", "raw-efi":
return ".raw"
case "qcow2", "qcow2-bios", "qcow2-efi":
return ".qcow2"
case "vmdk":
return ".vmdk"
case "vhd":
return ".vhd"
case "gcp":
return ".img.tar.gz"
case "aws":
return ".raw"
case "docker":
// Docker format outputs a tarball that can be loaded with `docker load`
return ".docker.tar"
case "tar":
return ".tar"
case "kernel+initrd":
return "-initrd.img"
default:
return "." + format
}
}
// resolveLinuxKitCli returns the executable path for the linuxkit CLI.
func resolveLinuxKitCli(paths ...string) (string, error) {
if len(paths) == 0 {
paths = []string{
"/usr/local/bin/linuxkit",
"/opt/homebrew/bin/linuxkit",
}
}
command, err := ax.ResolveCommand("linuxkit", paths...)
if err != nil {
return "", coreerr.E("linuxkit.resolveLinuxKitCli", "linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit", err)
}
return command, nil
}
// validateLinuxKitCli checks if the linuxkit CLI is available.
func validateLinuxKitCli() error {
if _, err := resolveLinuxKitCli(); err != nil {
return coreerr.E("linuxkit.validateLinuxKitCli", "linuxkit CLI not found. Install it from https://github.com/linuxkit/linuxkit", err)
}
return nil
}