Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions internal/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,11 +469,7 @@ func scaffoldInitWorkload(configPath, templateRef string, vars *config.TemplateV
if templatePath == "" || strings.TrimSpace(vars.ManifestPath) == "" {
return nil, nil
}
target := vars.ManifestPath
if !filepath.IsAbs(target) {
target = filepath.Join(config.ManifestDir(configPath), target)
}
target = filepath.Clean(target)
target := resolveInitScaffoldFilePath(configPath, vars.ManifestPath)
if _, err := os.Stat(target); err == nil && !force {
return nil, nil
}
Expand Down Expand Up @@ -512,10 +508,7 @@ func scaffoldInitTemplateFiles(configPath, templateRef string, meta *config.Temp
if target == "" {
return nil, fmt.Errorf("template file path %q rendered empty", pathTemplate)
}
if !filepath.IsAbs(target) {
target = filepath.Join(config.ManifestDir(configPath), target)
}
target = filepath.Clean(target)
target = resolveInitScaffoldFilePath(configPath, target)
if _, err := os.Stat(target); err == nil && !force {
continue
}
Expand All @@ -539,6 +532,22 @@ func scaffoldInitTemplateFiles(configPath, templateRef string, meta *config.Temp
return wrote, nil
}

func resolveInitScaffoldFilePath(configPath, path string) string {
target := strings.TrimSpace(path)
if filepath.IsAbs(target) {
return filepath.Clean(target)
}
if isFolderConfigPath(configPath) && pathStartsWithDotOkdev(target) {
return filepath.Clean(filepath.Join(config.RootDir(configPath), target))
}
return filepath.Clean(filepath.Join(config.ManifestDir(configPath), target))
}

func pathStartsWithDotOkdev(path string) bool {
cleaned := filepath.Clean(strings.TrimSpace(path))
return cleaned == ".okdev" || strings.HasPrefix(cleaned, ".okdev"+string(filepath.Separator))
}

func usesBuiltinBasicTemplate(ref string) bool {
return strings.TrimSpace(ref) == "" || strings.TrimSpace(ref) == "basic"
}
Expand Down
91 changes: 91 additions & 0 deletions internal/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,25 @@ func TestInitScaffoldsPyTorchJobManifest(t *testing.T) {
}
}

func TestInitScaffoldsPyTorchJobManifestWithFolderConfigAndDotOkdevPath(t *testing.T) {
tmp := t.TempDir()
opts := &Options{ConfigPath: filepath.Join(tmp, ".okdev", "okdev.yaml")}
cmd := newInitCmd(opts)
cmd.SetArgs([]string{"--yes", "--workload", "pytorchjob", "--manifest-path", ".okdev/pytorchjob.yaml"})
cmd.SetIn(strings.NewReader(""))

if err := cmd.Execute(); err != nil {
t.Fatalf("init execute: %v", err)
}

if _, err := os.Stat(filepath.Join(tmp, ".okdev", "pytorchjob.yaml")); err != nil {
t.Fatalf("expected manifest beside folder config, got err=%v", err)
}
if _, err := os.Stat(filepath.Join(tmp, ".okdev", ".okdev", "pytorchjob.yaml")); !os.IsNotExist(err) {
t.Fatalf("expected duplicate .okdev manifest path to be absent, err=%v", err)
}
}

func TestInitManifestPreservedWithoutForce(t *testing.T) {
tmp := t.TempDir()
okdevDir := filepath.Join(tmp, ".okdev")
Expand Down Expand Up @@ -759,6 +778,78 @@ spec:
}
}

func TestInitCustomTemplateRendersDeclaredFileWithFolderConfigAndDotOkdevPath(t *testing.T) {
tmp := t.TempDir()
tmplDir := filepath.Join(tmp, ".okdev", "templates")
if err := os.MkdirAll(filepath.Join(tmplDir, "manifests"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tmplDir, "pytorch.yaml.tmpl"), []byte(`---
name: pytorch
files:
- path: "{{ .ManifestPath }}"
template: manifests/pytorchjob.yaml.tmpl
---
apiVersion: okdev.io/v1alpha1
kind: DevEnvironment
metadata:
name: {{ .Name }}
spec:
namespace: {{ .Namespace }}
sync:
paths:
- "{{ .SyncLocal }}:{{ .SyncRemote }}"
ssh:
user: root
sidecar:
image: ghcr.io/acmore/okdev:edge
workload:
type: {{ .WorkloadType }}
manifestPath: {{ .ManifestPath }}
inject:
- path: "spec.pytorchReplicaSpecs.Master.template"
- path: "spec.pytorchReplicaSpecs.Worker.template"
`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(tmplDir, "manifests", "pytorchjob.yaml.tmpl"), []byte(`apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: {{ .Name }}
spec:
pytorchReplicaSpecs:
Master:
template:
spec:
containers:
- name: dev
image: ubuntu:22.04
Worker:
template:
spec:
containers:
- name: dev
image: ubuntu:22.04
`), 0o644); err != nil {
t.Fatal(err)
}

opts := &Options{ConfigPath: filepath.Join(tmp, ".okdev", "okdev.yaml")}
cmd := newInitCmd(opts)
cmd.SetArgs([]string{"--yes", "--template", "pytorch", "--workload", "pytorchjob", "--manifest-path", ".okdev/pytorchjob.yaml"})
cmd.SetIn(strings.NewReader(""))

if err := cmd.Execute(); err != nil {
t.Fatalf("init execute: %v", err)
}
if _, err := os.Stat(filepath.Join(tmp, ".okdev", "pytorchjob.yaml")); err != nil {
t.Fatalf("expected companion manifest beside folder config, got err=%v", err)
}
if _, err := os.Stat(filepath.Join(tmp, ".okdev", ".okdev", "pytorchjob.yaml")); !os.IsNotExist(err) {
t.Fatalf("expected duplicate .okdev companion path to be absent, err=%v", err)
}
}

func TestInitProjectTemplateShadowingBasicIsNotTreatedAsBuiltin(t *testing.T) {
tmp := t.TempDir()
tmplDir := filepath.Join(tmp, ".okdev", "templates")
Expand Down
Loading