diff --git a/pkg/runtime/podman/create.go b/pkg/runtime/podman/create.go index 0c8f45b..c749629 100644 --- a/pkg/runtime/podman/create.go +++ b/pkg/runtime/podman/create.go @@ -365,9 +365,24 @@ func findFreePorts(n int) ([]int, error) { return ports, nil } -// renderPodYAML renders the embedded pod YAML template with the given data. -func renderPodYAML(data podTemplateData) ([]byte, error) { - tmpl, err := template.New("pod").Parse(string(pods.OnecliPodYAML)) +// loadPodYAMLTemplate returns the pod YAML template to use for rendering. +// If the user has placed a custom template at /config/onecli-pod.yaml +// that file is read and returned; otherwise the embedded default is returned. +func (p *podmanRuntime) loadPodYAMLTemplate() ([]byte, error) { + customPath := filepath.Join(p.storageDir, "config", "onecli-pod.yaml") + content, err := os.ReadFile(customPath) + if err == nil { + return content, nil + } + if !os.IsNotExist(err) { + return nil, fmt.Errorf("failed to read custom pod template %s: %w", customPath, err) + } + return pods.OnecliPodYAML, nil +} + +// renderPodYAML renders the given pod YAML template with the given data. +func renderPodYAML(tmplContent []byte, data podTemplateData) ([]byte, error) { + tmpl, err := template.New("pod").Parse(string(tmplContent)) if err != nil { return nil, fmt.Errorf("failed to parse pod template: %w", err) } @@ -530,7 +545,7 @@ func (p *podmanRuntime) Create(ctx context.Context, params runtime.CreateParams) return runtime.RuntimeInfo{}, fmt.Errorf("failed to create temp pod directory: %w", err) } tmpYAMLPath := filepath.Join(tmpPodDir, podYAMLFile) - if err := writePodYAMLFile(tmpYAMLPath, tmplData); err != nil { + if err := p.writePodYAMLFile(tmpYAMLPath, tmplData); err != nil { return runtime.RuntimeInfo{}, err } @@ -790,8 +805,14 @@ func writeApprovalHandlerFiles(dir string) error { } // writePodYAMLFile renders and writes the pod YAML template to the given path. -func writePodYAMLFile(path string, data podTemplateData) error { - content, err := renderPodYAML(data) +// It uses a custom template from /config/onecli-pod.yaml when present, +// falling back to the embedded default. +func (p *podmanRuntime) writePodYAMLFile(path string, data podTemplateData) error { + tmplContent, err := p.loadPodYAMLTemplate() + if err != nil { + return err + } + content, err := renderPodYAML(tmplContent, data) if err != nil { return err } diff --git a/pkg/runtime/podman/create_test.go b/pkg/runtime/podman/create_test.go index 6ba3a71..6a257d5 100644 --- a/pkg/runtime/podman/create_test.go +++ b/pkg/runtime/podman/create_test.go @@ -36,6 +36,7 @@ import ( "github.com/openkaiden/kdn/pkg/runtime" "github.com/openkaiden/kdn/pkg/runtime/podman/config" "github.com/openkaiden/kdn/pkg/runtime/podman/exec" + "github.com/openkaiden/kdn/pkg/runtime/podman/pods" "github.com/openkaiden/kdn/pkg/steplogger" ) @@ -2229,7 +2230,7 @@ func TestRenderPodYAML_Ports(t *testing.T) { }, } - rendered, err := renderPodYAML(data) + rendered, err := renderPodYAML(pods.OnecliPodYAML, data) if err != nil { t.Fatalf("renderPodYAML() failed: %v", err) } @@ -2266,7 +2267,7 @@ func TestRenderPodYAML_Ports(t *testing.T) { ApprovalHandlerDir: "/tmp/approval", } - rendered, err := renderPodYAML(data) + rendered, err := renderPodYAML(pods.OnecliPodYAML, data) if err != nil { t.Fatalf("renderPodYAML() failed: %v", err) } diff --git a/pkg/runtime/podman/podman.go b/pkg/runtime/podman/podman.go index d1de2da..5c35bdc 100644 --- a/pkg/runtime/podman/podman.go +++ b/pkg/runtime/podman/podman.go @@ -174,14 +174,14 @@ func (p *podmanRuntime) podNamePath(containerID string) string { } // writePodFiles writes the per-workspace pod YAML and pod name file. -// The YAML is rendered from the embedded template using the supplied data. +// The YAML is rendered from the user-provided template (if present) or the embedded default. func (p *podmanRuntime) writePodFiles(containerID string, data podTemplateData) error { dir := p.podDir(containerID) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create pod directory: %w", err) } - if err := writePodYAMLFile(p.podYAMLPath(containerID), data); err != nil { + if err := p.writePodYAMLFile(p.podYAMLPath(containerID), data); err != nil { return err } if err := os.WriteFile(p.podNamePath(containerID), []byte(data.Name), 0644); err != nil { diff --git a/pkg/runtime/podman/podman_test.go b/pkg/runtime/podman/podman_test.go index d5229b6..01f5c40 100644 --- a/pkg/runtime/podman/podman_test.go +++ b/pkg/runtime/podman/podman_test.go @@ -24,6 +24,7 @@ import ( workspace "github.com/openkaiden/kdn-api/workspace-configuration/go" "github.com/openkaiden/kdn/pkg/runtime/podman/config" "github.com/openkaiden/kdn/pkg/runtime/podman/exec" + "github.com/openkaiden/kdn/pkg/runtime/podman/pods" "github.com/openkaiden/kdn/pkg/system" ) @@ -293,7 +294,7 @@ func TestRenderPodYAML(t *testing.T) { ApprovalHandlerDir: "/tmp/approval-handler/my-project", } - result, err := renderPodYAML(data) + result, err := renderPodYAML(pods.OnecliPodYAML, data) if err != nil { t.Fatalf("renderPodYAML() failed: %v", err) } @@ -350,7 +351,7 @@ func TestRenderPodYAML(t *testing.T) { ApprovalHandlerDir: "/tmp/approval-handler/test", } - result, err := renderPodYAML(data) + result, err := renderPodYAML(pods.OnecliPodYAML, data) if err != nil { t.Fatalf("renderPodYAML() failed: %v", err) } @@ -363,6 +364,49 @@ func TestRenderPodYAML(t *testing.T) { }) } +func TestLoadPodYAMLTemplate(t *testing.T) { + t.Parallel() + + t.Run("returns embedded template when no custom file exists", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + p := &podmanRuntime{storageDir: storageDir} + + content, err := p.loadPodYAMLTemplate() + if err != nil { + t.Fatalf("loadPodYAMLTemplate() failed: %v", err) + } + if string(content) != string(pods.OnecliPodYAML) { + t.Error("Expected embedded template to be returned when no custom file exists") + } + }) + + t.Run("returns custom template when file exists in config dir", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + configDir := filepath.Join(storageDir, "config") + if err := os.MkdirAll(configDir, 0755); err != nil { + t.Fatalf("failed to create config dir: %v", err) + } + customTemplate := []byte("custom-pod-template: {{.Name}}") + if err := os.WriteFile(filepath.Join(configDir, "onecli-pod.yaml"), customTemplate, 0644); err != nil { + t.Fatalf("failed to write custom template: %v", err) + } + + p := &podmanRuntime{storageDir: storageDir} + + content, err := p.loadPodYAMLTemplate() + if err != nil { + t.Fatalf("loadPodYAMLTemplate() failed: %v", err) + } + if string(content) != string(customTemplate) { + t.Errorf("Expected custom template to be returned, got: %s", content) + } + }) +} + func TestFindFreePorts(t *testing.T) { t.Parallel()