-
Notifications
You must be signed in to change notification settings - Fork 510
/
k8s_config.go
342 lines (299 loc) · 10.6 KB
/
k8s_config.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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
package client
import (
"context"
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Important for various cloud provider auth
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
"github.com/datawire/dlib/dlog"
"github.com/telepresenceio/telepresence/rpc/v2/connector"
"github.com/telepresenceio/telepresence/v2/pkg/errcat"
"github.com/telepresenceio/telepresence/v2/pkg/iputil"
"github.com/telepresenceio/telepresence/v2/pkg/maps"
)
// The DnsConfig is part of the KubeconfigExtension struct.
type DnsConfig struct {
// LocalIP is the address of the local DNS server. This entry is only
// used on Linux system that are not configured to use systemd-resolved and
// can be overridden by using the option --dns on the command line and defaults
// to the first line of /etc/resolv.conf
LocalIP iputil.IPKey `json:"local-ip,omitempty"`
// RemoteIP is the address of the cluster's DNS service. It will default
// to the IP of the kube-dns.kube-system or the dns-default.openshift-dns service.
RemoteIP iputil.IPKey `json:"remote-ip,omitempty"`
// ExcludeSuffixes are suffixes for which the DNS resolver will always return
// NXDOMAIN (or fallback in case of the overriding resolver).
ExcludeSuffixes []string `json:"exclude-suffixes,omitempty"`
// IncludeSuffixes are suffixes for which the DNS resolver will always attempt to do
// a lookup. Includes have higher priority than excludes.
IncludeSuffixes []string `json:"include-suffixes,omitempty"`
// The maximum time to wait for a cluster side host lookup.
LookupTimeout v1.Duration `json:"lookup-timeout,omitempty"`
}
// The ManagerConfig is part of the KubeconfigExtension struct. It configures discovery of the traffic manager.
type ManagerConfig struct {
// Namespace is the name of the namespace where the traffic manager is to be found
Namespace string `json:"namespace,omitempty"`
}
// KubeconfigExtension is an extension read from the selected kubeconfig Cluster.
type KubeconfigExtension struct {
DNS *DnsConfig `json:"dns,omitempty"`
AlsoProxy []*iputil.Subnet `json:"also-proxy,omitempty"`
NeverProxy []*iputil.Subnet `json:"never-proxy,omitempty"`
Manager *ManagerConfig `json:"manager,omitempty"`
}
type Kubeconfig struct {
KubeconfigExtension
Namespace string // default cluster namespace.
Context string
Server string
FlagMap map[string]string
ConfigFlags *genericclioptions.ConfigFlags
RestConfig *rest.Config
}
const (
configExtension = "telepresence.io"
defaultManagerNamespace = "ambassador"
)
func ConfigFlags(flagMap map[string]string) (*genericclioptions.ConfigFlags, error) {
configFlags := genericclioptions.NewConfigFlags(false)
flags := pflag.NewFlagSet("", 0)
configFlags.AddFlags(flags)
for k, v := range flagMap {
f := flags.Lookup(k)
if f == nil {
continue
}
var err error
if sv, ok := f.Value.(pflag.SliceValue); ok {
var vs []string
if vs, err = csv.NewReader(strings.NewReader(v)).Read(); err == nil {
err = sv.Replace(vs)
}
} else {
err = flags.Set(k, v)
}
if err != nil {
return nil, errcat.User.Newf("error processing kubectl flag --%s=%s: %w", k, v, err)
}
}
return configFlags, nil
}
// CurrentContext returns the name of the current Kubernetes context, and the context itself.
func CurrentContext(flagMap map[string]string) (string, *api.Context, error) {
configFlags, err := ConfigFlags(flagMap)
if err != nil {
return "", nil, err
}
config, err := configFlags.ToRawKubeConfigLoader().RawConfig()
if err != nil {
return "", nil, err
}
if len(config.Contexts) == 0 {
return "", nil, errcat.Config.New("kubeconfig has no context definition")
}
cc := flagMap["context"]
if cc == "" {
cc = config.CurrentContext
}
return cc, config.Contexts[cc], nil
}
func NewKubeconfig(c context.Context, flagMap map[string]string, managerNamespaceOverride string) (*Kubeconfig, error) {
configFlags, err := ConfigFlags(flagMap)
if err != nil {
return nil, err
}
return newKubeconfig(c, flagMap, managerNamespaceOverride, configFlags)
}
func DaemonKubeconfig(c context.Context, cr *connector.ConnectRequest) (*Kubeconfig, error) {
if cr.IsPodDaemon {
return NewInClusterConfig(c, cr.KubeFlags)
}
flagMap := cr.KubeFlags
// Namespace option will be passed only when explicitly needed. The k8Cluster is namespace agnostic with
// respect to this option.
delete(flagMap, "namespace")
// The GOOGLE_APPLICATION_CREDENTIALS and KUBECONFIG entries are copies of the environment variables
// sent to us from the CLI to give this long-running daemon a chance to update them. Here we set/unset
// our them in our environment accordingly and remove them from the flagMap
transferEnvFlag := func(key string) error {
if env, ok := flagMap[key]; ok {
delete(flagMap, key)
if err := os.Setenv(key, env); err != nil {
return err
}
dlog.Debugf(c, "Using %s %s", key, env)
return nil
}
// If user unsets the env, we need to do that too
return os.Unsetenv(key)
}
if err := transferEnvFlag("GOOGLE_APPLICATION_CREDENTIALS"); err != nil {
return nil, err
}
// Using the --kubeconfig flag to send the info isn't sufficient because that flag doesn't allow for multiple
// path entries like the KUBECONFIG does.
if err := transferEnvFlag("KUBECONFIG"); err != nil {
return nil, err
}
configFlags, err := ConfigFlags(flagMap)
if err != nil {
return nil, err
}
return newKubeconfig(c, flagMap, cr.ManagerNamespace, configFlags)
}
func newKubeconfig(c context.Context, flagMap map[string]string, managerNamespaceOverride string, configFlags *genericclioptions.ConfigFlags) (*Kubeconfig, error) {
configLoader := configFlags.ToRawKubeConfigLoader()
config, err := configLoader.RawConfig()
if err != nil {
return nil, err
}
if len(config.Contexts) == 0 {
return nil, errcat.Config.New("kubeconfig has no context definition")
}
ctxName := flagMap["context"]
if ctxName == "" {
ctxName = config.CurrentContext
}
ctx, ok := config.Contexts[ctxName]
if !ok {
return nil, errcat.Config.Newf("context %q does not exist in the kubeconfig", ctxName)
}
cluster, ok := config.Clusters[ctx.Cluster]
if !ok {
return nil, errcat.Config.Newf("the cluster %q declared in context %q does exists in the kubeconfig", ctx.Cluster, ctxName)
}
restConfig, err := configLoader.ClientConfig()
if err != nil {
return nil, err
}
namespace := ctx.Namespace
if namespace == "" {
namespace = "default"
}
k := &Kubeconfig{
Context: ctxName,
Server: cluster.Server,
Namespace: namespace,
FlagMap: flagMap,
ConfigFlags: configFlags,
RestConfig: restConfig,
}
if ext, ok := cluster.Extensions[configExtension].(*runtime.Unknown); ok {
if err = json.Unmarshal(ext.Raw, &k.KubeconfigExtension); err != nil {
return nil, errcat.Config.Newf("unable to parse extension %s in kubeconfig: %w", configExtension, err)
}
}
if k.KubeconfigExtension.Manager == nil {
k.KubeconfigExtension.Manager = &ManagerConfig{}
}
k.KubeconfigExtension.Manager.Namespace = managerNamespaceOverride
if k.KubeconfigExtension.Manager.Namespace == "" {
k.KubeconfigExtension.Manager.Namespace = GetEnv(c).ManagerNamespace
}
if k.KubeconfigExtension.Manager.Namespace == "" {
k.KubeconfigExtension.Manager.Namespace = GetConfig(c).Cluster.DefaultManagerNamespace
}
if k.KubeconfigExtension.Manager.Namespace == "" {
k.KubeconfigExtension.Manager.Namespace = defaultManagerNamespace
}
dlog.Infof(c, "Will look for traffic manager in namespace %s", k.KubeconfigExtension.Manager.Namespace)
return k, nil
}
// This represents an inClusterConfig.
func NewInClusterConfig(c context.Context, flagMap map[string]string) (*Kubeconfig, error) {
// Namespace option will be passed only when explicitly needed. The k8Cluster is namespace agnostic with
// respect to this option.
delete(flagMap, "namespace")
configFlags := genericclioptions.NewConfigFlags(false)
flags := pflag.NewFlagSet("", 0)
configFlags.AddFlags(flags)
for k, v := range flagMap {
if err := flags.Set(k, v); err != nil {
return nil, errcat.User.Newf("error processing kubectl flag --%s=%s: %w", k, v, err)
}
}
configLoader := configFlags.ToRawKubeConfigLoader()
restConfig, err := configLoader.ClientConfig()
if err != nil {
return nil, err
}
namespace, ok, err := configLoader.Namespace()
if err != nil || !ok {
namespace = "default"
}
managerNamespace := GetEnv(c).ManagerNamespace
if managerNamespace == "" {
managerNamespace = GetConfig(c).Cluster.DefaultManagerNamespace
}
return &Kubeconfig{
Namespace: namespace,
Server: restConfig.Host,
FlagMap: flagMap,
ConfigFlags: configFlags,
RestConfig: restConfig,
// it may be empty, but we should avoid nil deref
KubeconfigExtension: KubeconfigExtension{
Manager: &ManagerConfig{
Namespace: managerNamespace,
},
},
}, nil
}
// ContextServiceAndFlagsEqual determines if this instance is equal to the given instance with respect to context,
// server, and flag arguments.
func (kf *Kubeconfig) ContextServiceAndFlagsEqual(okf *Kubeconfig) bool {
return kf != nil && okf != nil &&
kf.Context == okf.Context &&
kf.Server == okf.Server &&
maps.Equal(kf.FlagMap, okf.FlagMap)
}
func (kf *Kubeconfig) GetContext() string {
return kf.Context
}
func (kf *Kubeconfig) GetManagerNamespace() string {
return kf.KubeconfigExtension.Manager.Namespace
}
func (kf *Kubeconfig) GetRestConfig() *rest.Config {
return kf.RestConfig
}
func (kf *Kubeconfig) AddRemoteKubeConfigExtension(ctx context.Context, cfgYaml []byte) error {
dlog.Debugf(ctx, "Applying remote dns and routing: %s", cfgYaml)
remote := struct {
DNS *DNS `yaml:"dns,omitempty"`
Routing *Routing `yaml:"routing,omitempty"`
}{}
if err := yaml.Unmarshal(cfgYaml, &remote); err != nil {
return fmt.Errorf("unable to parse remote kubeconfig: %w", err)
}
if kf.DNS == nil {
kf.DNS = &DnsConfig{}
}
if dns := remote.DNS; dns != nil {
if kf.DNS.LocalIP == "" {
kf.DNS.LocalIP = iputil.IPKey(dns.LocalIP)
}
if kf.DNS.RemoteIP == "" {
kf.DNS.RemoteIP = iputil.IPKey(dns.RemoteIP)
}
kf.DNS.ExcludeSuffixes = append(kf.DNS.ExcludeSuffixes, dns.ExcludeSuffixes...)
kf.DNS.IncludeSuffixes = append(kf.DNS.IncludeSuffixes, dns.IncludeSuffixes...)
if kf.DNS.LookupTimeout.Duration == 0 {
kf.DNS.LookupTimeout.Duration = dns.LookupTimeout
}
}
if routing := remote.Routing; routing != nil {
kf.AlsoProxy = append(kf.AlsoProxy, routing.AlsoProxy...)
kf.NeverProxy = append(kf.NeverProxy, routing.NeverProxy...)
}
return nil
}