Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/networks/config.go
2601 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package networks
5
6
import (
7
_ "embed"
8
"errors"
9
"fmt"
10
"os"
11
"os/exec"
12
"path/filepath"
13
"sync"
14
15
"github.com/goccy/go-yaml"
16
"github.com/sirupsen/logrus"
17
18
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
19
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
20
"github.com/lima-vm/lima/v2/pkg/textutil"
21
)
22
23
//go:embed networks.TEMPLATE.yaml
24
var defaultConfigTemplate string
25
26
type defaultConfigTemplateArgs struct {
27
SocketVMNet string // "/opt/socket_vmnet/bin/socket_vmnet"
28
}
29
30
func defaultConfigBytes() ([]byte, error) {
31
var args defaultConfigTemplateArgs
32
candidates := []string{
33
"/opt/socket_vmnet/bin/socket_vmnet", // the hard-coded path before v0.14
34
"socket_vmnet",
35
"/usr/local/opt/socket_vmnet/bin/socket_vmnet", // Homebrew (Intel)
36
"/opt/homebrew/opt/socket_vmnet/bin/socket_vmnet", // Homebrew (ARM)
37
}
38
for _, candidate := range candidates {
39
if p, err := exec.LookPath(candidate); err == nil {
40
realP, evalErr := filepath.EvalSymlinks(p)
41
if evalErr != nil {
42
return nil, evalErr
43
}
44
args.SocketVMNet = realP
45
break
46
} else if errors.Is(err, exec.ErrNotFound) || errors.Is(err, os.ErrNotExist) {
47
logrus.WithError(err).Debugf("Failed to look up socket_vmnet path %q", candidate)
48
} else {
49
logrus.WithError(err).Warnf("Failed to look up socket_vmnet path %q", candidate)
50
}
51
}
52
if args.SocketVMNet == "" {
53
args.SocketVMNet = candidates[0] // the hard-coded path before v0.14
54
}
55
return textutil.ExecuteTemplate(defaultConfigTemplate, args)
56
}
57
58
func fillDefaults(cfg Config) (Config, error) {
59
usernetFound := false
60
if cfg.Networks == nil {
61
cfg.Networks = make(map[string]Network)
62
}
63
for nw := range cfg.Networks {
64
if cfg.Networks[nw].Mode == ModeUserV2 && cfg.Networks[nw].Gateway != nil {
65
usernetFound = true
66
}
67
}
68
if !usernetFound {
69
defaultCfg, err := DefaultConfig()
70
if err != nil {
71
return cfg, err
72
}
73
cfg.Networks[ModeUserV2] = defaultCfg.Networks[ModeUserV2]
74
}
75
return cfg, nil
76
}
77
78
func DefaultConfig() (Config, error) {
79
var cfg Config
80
b, err := defaultConfigBytes()
81
if err != nil {
82
return cfg, err
83
}
84
err = yaml.UnmarshalWithOptions(b, &cfg, yaml.Strict())
85
if err != nil {
86
return cfg, err
87
}
88
return cfg, nil
89
}
90
91
var cache struct {
92
sync.Once
93
cfg Config
94
err error
95
}
96
97
func ConfigFile() (string, error) {
98
cfgDir, err := dirnames.LimaConfigDir()
99
if err != nil {
100
return "", err
101
}
102
return filepath.Join(cfgDir, filenames.NetworksConfig), nil
103
}
104
105
// loadCache loads the _config/networks.yaml file into the cache.
106
func loadCache() {
107
cache.Do(func() {
108
var cfgFile string
109
cfgFile, cache.err = ConfigFile()
110
if cache.err != nil {
111
return
112
}
113
_, cache.err = os.Stat(cfgFile)
114
if cache.err != nil {
115
if !errors.Is(cache.err, os.ErrNotExist) {
116
return
117
}
118
cfgDir := filepath.Dir(cfgFile)
119
cache.err = os.MkdirAll(cfgDir, 0o755)
120
if cache.err != nil {
121
cache.err = fmt.Errorf("could not create %q directory: %w", cfgDir, cache.err)
122
return
123
}
124
var b []byte
125
b, cache.err = defaultConfigBytes()
126
if cache.err != nil {
127
return
128
}
129
cache.err = os.WriteFile(cfgFile, b, 0o644)
130
if cache.err != nil {
131
return
132
}
133
}
134
var b []byte
135
b, cache.err = os.ReadFile(cfgFile)
136
if cache.err != nil {
137
return
138
}
139
cache.err = yaml.Unmarshal(b, &cache.cfg)
140
if cache.err != nil {
141
cache.err = fmt.Errorf("cannot parse %q: %w", cfgFile, cache.err)
142
return
143
}
144
var strictCfg Config
145
if strictErr := yaml.UnmarshalWithOptions(b, &strictCfg, yaml.Strict()); strictErr != nil {
146
// Allow non-existing YAML fields, as a cfg created with Lima < v0.22 contains `vdeSwitch` and `vdeVMNet`.
147
// These fields were removed in Lima v0.22.
148
logrus.WithError(strictErr).Warn("Non-strict YAML is deprecated and will be unsupported in a future version of Lima: " + cfgFile)
149
}
150
cache.cfg, cache.err = fillDefaults(cache.cfg)
151
if cache.err != nil {
152
cache.err = fmt.Errorf("cannot fill default %q: %w", cfgFile, cache.err)
153
}
154
})
155
}
156
157
// LoadConfig returns the network cfg from the _config/networks.yaml file.
158
func LoadConfig() (Config, error) {
159
loadCache()
160
return cache.cfg, cache.err
161
}
162
163
// Sock returns a socket_vmnet socket.
164
func Sock(name string) (string, error) {
165
loadCache()
166
if cache.err != nil {
167
return "", cache.err
168
}
169
if err := cache.cfg.Check(name); err != nil {
170
return "", err
171
}
172
if cache.cfg.Paths.SocketVMNet == "" {
173
return "", errors.New("socketVMNet is not set")
174
}
175
return cache.cfg.Sock(name), nil
176
}
177
178
// IsUsernet returns true if the given network name is a usernet network.
179
// It return false if the cache cannot be loaded or the network is not defined.
180
func IsUsernet(name string) bool {
181
loadCache()
182
if cache.err != nil {
183
return false
184
}
185
isUsernet, err := cache.cfg.Usernet(name)
186
if err != nil {
187
return false
188
}
189
return isUsernet
190
}
191
192