Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/network.go
2613 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"encoding/json"
8
"errors"
9
"fmt"
10
"maps"
11
"net"
12
"os"
13
"slices"
14
"strings"
15
"text/tabwriter"
16
17
"github.com/sirupsen/logrus"
18
"github.com/spf13/cobra"
19
20
"github.com/lima-vm/lima/v2/pkg/networks"
21
"github.com/lima-vm/lima/v2/pkg/yqutil"
22
)
23
24
const networkExample = ` List all networks:
25
$ limactl network list
26
27
Create a network:
28
$ limactl network create foo --gateway 192.168.42.1/24
29
30
Connect VM instances to the newly created network:
31
$ limactl create --network lima:foo --name vm1
32
$ limactl create --network lima:foo --name vm2
33
34
Delete a network:
35
$ limactl network delete --force foo
36
`
37
38
const networkCreateExample = ` Create a network:
39
$ limactl network create foo --gateway 192.168.42.1/24
40
41
Connect VM instances to the newly created network:
42
$ limactl create --network lima:foo --name vm1
43
$ limactl create --network lima:foo --name vm2
44
`
45
46
func newNetworkCommand() *cobra.Command {
47
networkCommand := &cobra.Command{
48
Use: "network",
49
Short: "Lima network management",
50
Example: networkExample,
51
GroupID: advancedCommand,
52
}
53
networkCommand.AddCommand(
54
newNetworkListCommand(),
55
newNetworkCreateCommand(),
56
newNetworkDeleteCommand(),
57
)
58
return networkCommand
59
}
60
61
func newNetworkListCommand() *cobra.Command {
62
cmd := &cobra.Command{
63
Use: "list",
64
Short: "List networks",
65
Example: ` List all networks:
66
$ limactl network list
67
68
List networks in JSON format:
69
$ limactl network list --json
70
`,
71
Aliases: []string{"ls"},
72
Args: WrapArgsError(cobra.ArbitraryArgs),
73
RunE: networkListAction,
74
ValidArgsFunction: networkBashComplete,
75
}
76
flags := cmd.Flags()
77
flags.Bool("json", false, "JSONify output")
78
return cmd
79
}
80
81
func networkListAction(cmd *cobra.Command, args []string) error {
82
flags := cmd.Flags()
83
jsonFormat, err := flags.GetBool("json")
84
if err != nil {
85
return err
86
}
87
88
config, err := networks.LoadConfig()
89
if err != nil {
90
return err
91
}
92
93
allNetworks := slices.Sorted(maps.Keys(config.Networks))
94
95
networks := []string{}
96
if len(args) > 0 {
97
for _, arg := range args {
98
matches := nameMatches(arg, allNetworks)
99
if len(matches) > 0 {
100
networks = append(networks, matches...)
101
} else {
102
logrus.Warnf("No network matching %v found.", arg)
103
}
104
}
105
} else {
106
networks = allNetworks
107
}
108
109
if jsonFormat {
110
w := cmd.OutOrStdout()
111
for _, name := range networks {
112
nw, ok := config.Networks[name]
113
if !ok {
114
logrus.Errorf("network %q does not exist", nw)
115
continue
116
}
117
j, err := json.Marshal(nw)
118
if err != nil {
119
return err
120
}
121
fmt.Fprintln(w, string(j))
122
}
123
return nil
124
}
125
126
w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
127
fmt.Fprintln(w, "NAME\tMODE\tGATEWAY\tINTERFACE")
128
for _, name := range networks {
129
nw, ok := config.Networks[name]
130
if !ok {
131
logrus.Errorf("network %q does not exist", nw)
132
continue
133
}
134
gwStr := "-"
135
if nw.Gateway != nil {
136
gw := net.IPNet{
137
IP: nw.Gateway,
138
Mask: net.IPMask(nw.NetMask),
139
}
140
gwStr = gw.String()
141
}
142
intfStr := "-"
143
if nw.Interface != "" {
144
intfStr = nw.Interface
145
}
146
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, nw.Mode, gwStr, intfStr)
147
}
148
return w.Flush()
149
}
150
151
func newNetworkCreateCommand() *cobra.Command {
152
cmd := &cobra.Command{
153
Use: "create NETWORK",
154
Short: "Create a Lima network",
155
Example: networkCreateExample,
156
Args: WrapArgsError(cobra.ExactArgs(1)),
157
RunE: networkCreateAction,
158
}
159
flags := cmd.Flags()
160
flags.String("mode", networks.ModeUserV2, "mode")
161
_ = cmd.RegisterFlagCompletionFunc("mode", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
162
return networks.Modes, cobra.ShellCompDirectiveNoFileComp
163
})
164
flags.String("gateway", "", "gateway, e.g., \"192.168.42.1/24\"")
165
flags.String("interface", "", "interface for bridged mode")
166
_ = cmd.RegisterFlagCompletionFunc("interface", bashFlagCompleteNetworkInterfaceNames)
167
return cmd
168
}
169
170
func networkCreateAction(cmd *cobra.Command, args []string) error {
171
name := args[0]
172
// LoadConfig ensures existence of networks.yaml
173
config, err := networks.LoadConfig()
174
if err != nil {
175
return err
176
}
177
if _, ok := config.Networks[name]; ok {
178
return fmt.Errorf("network %q already exists", name)
179
}
180
181
flags := cmd.Flags()
182
mode, err := flags.GetString("mode")
183
if err != nil {
184
return err
185
}
186
187
gateway, err := flags.GetString("gateway")
188
if err != nil {
189
return err
190
}
191
192
intf, err := flags.GetString("interface")
193
if err != nil {
194
return err
195
}
196
197
switch mode {
198
case networks.ModeBridged:
199
if gateway != "" {
200
return fmt.Errorf("network mode %q does not support specifying gateway", mode)
201
}
202
if intf == "" {
203
return fmt.Errorf("network mode %q requires specifying interface", mode)
204
}
205
yq := fmt.Sprintf(`.networks.%q = {"mode":%q,"interface":%q}`, name, mode, intf)
206
return networkApplyYQ(yq)
207
default:
208
if gateway == "" {
209
return fmt.Errorf("network mode %q requires specifying gateway", mode)
210
}
211
if intf != "" {
212
return fmt.Errorf("network mode %q does not support specifying interface", mode)
213
}
214
if !strings.Contains(gateway, "/") {
215
gateway += "/24"
216
}
217
gwIP, gwMask, err := net.ParseCIDR(gateway)
218
if err != nil {
219
return fmt.Errorf("failed to parse CIDR %q: %w", gateway, err)
220
}
221
if gwIP.IsUnspecified() || gwIP.IsLoopback() {
222
return fmt.Errorf("invalid IP address: %v", gwIP)
223
}
224
gwMaskStr := "255.255.255.0"
225
if gwMask != nil {
226
gwMaskStr = net.IP(gwMask.Mask).String()
227
}
228
// TODO: check IP range collision
229
230
yq := fmt.Sprintf(`.networks.%q = {"mode":%q,"gateway":%q,"netmask":%q,"interface":%q}`, name, mode, gwIP.String(), gwMaskStr, intf)
231
return networkApplyYQ(yq)
232
}
233
}
234
235
func networkApplyYQ(yq string) error {
236
filePath, err := networks.ConfigFile()
237
if err != nil {
238
return err
239
}
240
yContent, err := os.ReadFile(filePath)
241
if err != nil {
242
return err
243
}
244
yBytes, err := yqutil.EvaluateExpression(yq, yContent)
245
if err != nil {
246
return err
247
}
248
if err := os.WriteFile(filePath, yBytes, 0o644); err != nil {
249
return err
250
}
251
return nil
252
}
253
254
func newNetworkDeleteCommand() *cobra.Command {
255
cmd := &cobra.Command{
256
Use: "delete NETWORK [NETWORK, ...]",
257
Short: "Delete one or more Lima networks",
258
Example: ` Delete a network:
259
$ limactl network delete --force foo
260
261
Delete multiple networks:
262
$ limactl network delete --force foo bar
263
`,
264
Aliases: []string{"remove", "rm"},
265
Args: WrapArgsError(cobra.MinimumNArgs(1)),
266
RunE: networkDeleteAction,
267
ValidArgsFunction: networkBashComplete,
268
}
269
flags := cmd.Flags()
270
flags.BoolP("force", "f", false, "Force delete (currently always required)")
271
return cmd
272
}
273
274
func networkDeleteAction(cmd *cobra.Command, args []string) error {
275
flags := cmd.Flags()
276
force, err := flags.GetBool("force")
277
if err != nil {
278
return err
279
}
280
if !force {
281
return errors.New("`limactl network delete` currently always requires `--force`")
282
// Because the command currently does not check whether the network being removed is in use
283
}
284
285
networks := make([]string, len(args))
286
for i, name := range args {
287
networks[i] = fmt.Sprintf("del(.networks.%q)", name)
288
}
289
yq := strings.Join(networks, " | ")
290
return networkApplyYQ(yq)
291
}
292
293
func networkBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
294
return bashCompleteNetworkNames(cmd)
295
}
296
297