/
root.go
234 lines (184 loc) · 5.61 KB
/
root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package talos
import (
"context"
"fmt"
"io"
"slices"
"strings"
criconstants "github.com/containerd/containerd/pkg/cri/constants"
"github.com/siderolabs/gen/maps"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/global"
_ "github.com/siderolabs/talos/pkg/grpc/codec" // register codec
"github.com/siderolabs/talos/pkg/machinery/api/common"
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
"github.com/siderolabs/talos/pkg/machinery/client"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/formatters"
)
var kubernetesFlag bool
// GlobalArgs is the common arguments for the root command.
var GlobalArgs global.Args
const pathAutoCompleteLimit = 500
// WithClientNoNodes wraps common code to initialize Talos client and provide cancellable context.
//
// WithClientNoNodes doesn't set any node information on the request context.
func WithClientNoNodes(action func(context.Context, *client.Client) error, dialOptions ...grpc.DialOption) error {
return GlobalArgs.WithClientNoNodes(action, dialOptions...)
}
// WithClient builds upon WithClientNoNodes to provide set of nodes on request context based on config & flags.
func WithClient(action func(context.Context, *client.Client) error, dialOptions ...grpc.DialOption) error {
return GlobalArgs.WithClient(action, dialOptions...)
}
// WithClientMaintenance wraps common code to initialize Talos client in maintenance (insecure mode).
func WithClientMaintenance(enforceFingerprints []string, action func(context.Context, *client.Client) error) error {
return GlobalArgs.WithClientMaintenance(enforceFingerprints, action)
}
// Commands is a list of commands published by the package.
var Commands []*cobra.Command
func addCommand(cmd *cobra.Command) {
Commands = append(Commands, cmd)
}
// completeResource represents tab complete options for `ls` and `ls *` commands.
func completePathFromNode(inputPath string) []string {
pathToSearch := inputPath
// If the pathToSearch is empty, use root '/'
if pathToSearch == "" {
pathToSearch = "/"
}
var paths map[string]struct{}
// search up one level to find possible completions
if pathToSearch != "/" && !strings.HasSuffix(pathToSearch, "/") {
index := strings.LastIndex(pathToSearch, "/")
// we need a trailing slash to search for items in a directory
pathToSearch = pathToSearch[:index] + "/"
}
paths = getPathFromNode(pathToSearch, inputPath)
return maps.Keys(paths)
}
//nolint:gocyclo
func getPathFromNode(path, filter string) map[string]struct{} {
paths := make(map[string]struct{})
//nolint:errcheck
GlobalArgs.WithClient(
func(ctx context.Context, c *client.Client) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
stream, err := c.LS(
ctx, &machineapi.ListRequest{
Root: path,
},
)
if err != nil {
return err
}
for {
resp, err := stream.Recv()
if err != nil {
if err == io.EOF || client.StatusCode(err) == codes.Canceled {
return nil
}
return fmt.Errorf("error streaming results: %s", err)
}
if resp.Metadata != nil && resp.Metadata.Error != "" {
continue
}
if resp.Error != "" {
continue
}
// skip reference to the same directory
if resp.RelativeName == "." {
continue
}
// limit the results to a reasonable amount
if len(paths) > pathAutoCompleteLimit {
return nil
}
// directories have a trailing slash
if resp.IsDir {
fullPath := path + resp.RelativeName + "/"
if relativeTo(fullPath, filter) {
paths[fullPath] = struct{}{}
}
} else {
fullPath := path + resp.RelativeName
if relativeTo(fullPath, filter) {
paths[fullPath] = struct{}{}
}
}
}
},
)
return paths
}
func getServiceFromNode() []string {
var svcIDs []string
//nolint:errcheck
GlobalArgs.WithClient(
func(ctx context.Context, c *client.Client) error {
var remotePeer peer.Peer
resp, err := c.ServiceList(ctx, grpc.Peer(&remotePeer))
if err != nil {
return err
}
for _, msg := range resp.Messages {
for _, s := range msg.Services {
svc := formatters.ServiceInfoWrapper{ServiceInfo: s}
svcIDs = append(svcIDs, svc.Id)
}
}
return nil
},
)
return svcIDs
}
func getContainersFromNode(kubernetes bool) []string {
var containerIDs []string
//nolint:errcheck
GlobalArgs.WithClient(
func(ctx context.Context, c *client.Client) error {
var (
namespace string
driver common.ContainerDriver
)
if kubernetes {
namespace = criconstants.K8sContainerdNamespace
driver = common.ContainerDriver_CRI
} else {
namespace = constants.SystemContainerdNamespace
driver = common.ContainerDriver_CONTAINERD
}
resp, err := c.Containers(ctx, namespace, driver)
if err != nil {
return err
}
for _, msg := range resp.Messages {
for _, p := range msg.Containers {
if p.Pid == 0 {
continue
}
if kubernetes && p.Id == p.PodId {
continue
}
containerIDs = append(containerIDs, p.Id)
}
}
return nil
},
)
return containerIDs
}
func mergeSuggestions(s ...[]string) []string {
merged := slices.Concat(s...)
slices.Sort(merged)
return slices.Compact(merged)
}
func relativeTo(fullPath string, filter string) bool {
return strings.HasPrefix(fullPath, filter)
}