Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/templatestore/templatestore.go
2649 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package templatestore
5
6
import (
7
"cmp"
8
"errors"
9
"fmt"
10
"io/fs"
11
"os"
12
"path/filepath"
13
"slices"
14
"strings"
15
"unicode"
16
17
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
18
"github.com/lima-vm/lima/v2/pkg/usrlocal"
19
)
20
21
type Template struct {
22
Name string `json:"name"`
23
Location string `json:"location"`
24
}
25
26
func templatesPaths() ([]string, error) {
27
if tmplPath := os.Getenv("LIMA_TEMPLATES_PATH"); tmplPath != "" {
28
return strings.Split(tmplPath, string(filepath.ListSeparator)), nil
29
}
30
limaTemplatesDir, err := dirnames.LimaTemplatesDir()
31
if err != nil {
32
return nil, err
33
}
34
shareLimaDirs, err := usrlocal.ShareLima()
35
if err != nil {
36
return nil, err
37
}
38
res := []string{limaTemplatesDir}
39
for _, shareLimaDir := range shareLimaDirs {
40
res = append(res, filepath.Join(shareLimaDir, "templates"))
41
}
42
return res, nil
43
}
44
45
// Read searches for template `name` in all template directories and returns the
46
// contents of the first one found. Template names cannot contain the substring ".."
47
// to make sure they don't reference files outside the template directories. We are
48
// not using securejoin.SecureJoin because the actual template may be a symlink to a
49
// directory elsewhere (e.g. when installed by Homebrew).
50
func Read(name string) ([]byte, error) {
51
doubleDot := ".."
52
if strings.Contains(name, doubleDot) {
53
return nil, fmt.Errorf("template name %q must not contain %q", name, doubleDot)
54
}
55
paths, err := templatesPaths()
56
if err != nil {
57
return nil, err
58
}
59
ext := filepath.Ext(name)
60
// Append .yaml extension if name doesn't have an extension, or if it starts with a digit.
61
// So "docker.sh" would remain unchanged but "ubuntu-24.04" becomes "ubuntu-24.04.yaml".
62
if len(ext) < 2 || unicode.IsDigit(rune(ext[1])) {
63
name += ".yaml"
64
}
65
for _, templatesDir := range paths {
66
// Normalize filePath for error messages because template names always use forward slashes
67
filePath := filepath.Clean(filepath.Join(templatesDir, name))
68
if b, err := os.ReadFile(filePath); !errors.Is(err, os.ErrNotExist) {
69
return b, err
70
}
71
}
72
return nil, fmt.Errorf("template %q not found", name)
73
}
74
75
const Default = "default"
76
77
// Templates returns a list of Template structures containing the Name and Location for each template.
78
// It searches all template directories, but only the first template of a given name is recorded.
79
// Only non-hidden files with a ".yaml" file extension are considered templates.
80
// The final result is sorted alphabetically by template name.
81
func Templates() ([]Template, error) {
82
paths, err := templatesPaths()
83
if err != nil {
84
return nil, err
85
}
86
87
templates := make(map[string]string)
88
for _, templatesDir := range paths {
89
if _, err := os.Stat(templatesDir); os.IsNotExist(err) {
90
continue
91
}
92
walkDirFn := func(p string, _ fs.DirEntry, err error) error {
93
if err != nil {
94
return err
95
}
96
base := filepath.Base(p)
97
if strings.HasPrefix(base, ".") || !strings.HasSuffix(base, ".yaml") {
98
return nil
99
}
100
// Name is like "default", "debian", "deprecated/centos-7", ...
101
name := strings.TrimSuffix(strings.TrimPrefix(p, templatesDir+"/"), ".yaml")
102
if _, ok := templates[name]; !ok {
103
templates[name] = p
104
}
105
return nil
106
}
107
if err = filepath.WalkDir(templatesDir, walkDirFn); err != nil {
108
return nil, err
109
}
110
}
111
var res []Template
112
for name, loc := range templates {
113
res = append(res, Template{Name: name, Location: loc})
114
}
115
slices.SortFunc(res, func(i, j Template) int { return cmp.Compare(i.Name, j.Name) })
116
return res, nil
117
}
118
119