Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/dev/preview/previewctl/pkg/k8s/context/k3s/k3s.go
3622 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package k3s
6
7
import (
8
"bytes"
9
"context"
10
"fmt"
11
"os"
12
"os/exec"
13
"path/filepath"
14
"strings"
15
16
"github.com/cockroachdb/errors"
17
"github.com/sirupsen/logrus"
18
"golang.org/x/crypto/ssh"
19
"k8s.io/client-go/tools/clientcmd"
20
"k8s.io/client-go/tools/clientcmd/api"
21
22
"github.com/gitpod-io/gitpod/previewctl/pkg/k8s"
23
kctx "github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context"
24
pssh "github.com/gitpod-io/gitpod/previewctl/pkg/ssh"
25
)
26
27
var _ kctx.Loader = (*ConfigLoader)(nil)
28
29
const (
30
k3sConfigPath = "/etc/rancher/k3s/k3s.yaml"
31
catK3sConfigCmd = "sudo cat /etc/rancher/k3s/k3s.yaml"
32
)
33
34
var (
35
ErrK3SConfigNotFound = errors.New("k3s config file not found")
36
)
37
38
type ConfigLoader struct {
39
logger *logrus.Logger
40
41
sshClientFactory pssh.ClientFactory
42
client pssh.Client
43
44
configPath string
45
opts ConfigLoaderOpts
46
}
47
48
type ConfigLoaderOpts struct {
49
Logger *logrus.Logger
50
51
PreviewName string
52
PreviewNamespace string
53
SSHPrivateKeyPath string
54
SSHUser string
55
}
56
57
func New(ctx context.Context, opts ConfigLoaderOpts) (*ConfigLoader, error) {
58
key, err := os.ReadFile(opts.SSHPrivateKeyPath)
59
if err != nil {
60
return nil, err
61
}
62
63
signer, err := ssh.ParsePrivateKey(key)
64
if err != nil {
65
return nil, err
66
}
67
68
config := &ConfigLoader{
69
logger: opts.Logger,
70
sshClientFactory: &pssh.FactoryImplementation{
71
SSHConfig: &ssh.ClientConfig{
72
User: opts.SSHUser,
73
Auth: []ssh.AuthMethod{
74
ssh.PublicKeys(signer),
75
},
76
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
77
},
78
},
79
configPath: k3sConfigPath,
80
opts: opts,
81
}
82
83
return config, nil
84
}
85
86
func (k *ConfigLoader) installVMSSHKeys() error {
87
path := filepath.Join(os.Getenv("LEEWAY_WORKSPACE_ROOT"), "dev/preview/ssh-vm.sh")
88
cmd := exec.Command(path, "-c", "echo success", "-v", k.opts.PreviewName)
89
cmd.Env = os.Environ()
90
91
output, err := cmd.CombinedOutput()
92
if err != nil {
93
k.logger.WithError(err).WithField("output", string(output)).Error("failed to install VM SSH keys")
94
return errors.Wrap(err, string(output))
95
}
96
return nil
97
}
98
99
func (k *ConfigLoader) Load(ctx context.Context) (*api.Config, error) {
100
if k.client == nil {
101
err := k.installVMSSHKeys()
102
if err != nil {
103
k.logger.Error(err)
104
return nil, err
105
}
106
err = k.connectToHost(ctx, fmt.Sprintf("%s.preview.gitpod-dev.com", k.opts.PreviewName), "2222")
107
if err != nil {
108
k.logger.Error(err)
109
return nil, err
110
}
111
112
defer func(k *ConfigLoader) {
113
err := k.Close()
114
if err != nil {
115
k.logger.WithFields(logrus.Fields{"err": err}).Error("failed to close client")
116
return
117
}
118
}(k)
119
}
120
121
return k.getContext(ctx)
122
}
123
124
func (k *ConfigLoader) getContext(ctx context.Context) (*api.Config, error) {
125
stdout := new(bytes.Buffer)
126
stderr := new(bytes.Buffer)
127
128
err := k.client.Run(ctx, catK3sConfigCmd, stdout, stderr)
129
if err != nil {
130
if strings.Contains(stderr.String(), "No such file or directory") {
131
return nil, ErrK3SConfigNotFound
132
}
133
134
return nil, errors.Wrap(err, stderr.String())
135
}
136
137
c, err := clientcmd.NewClientConfigFromBytes(stdout.Bytes())
138
if err != nil {
139
return nil, err
140
}
141
142
rc, err := c.RawConfig()
143
if err != nil {
144
return nil, err
145
}
146
147
k3sConfig, err := k8s.RenameConfig(&rc, "default", k.opts.PreviewName)
148
if err != nil {
149
return nil, err
150
}
151
152
k3sConfig.Clusters[k.opts.PreviewName].Server = fmt.Sprintf("https://%s.preview.gitpod-dev.com:6443", k.opts.PreviewName)
153
154
return &rc, nil
155
}
156
157
func (k *ConfigLoader) connectToHost(ctx context.Context, host, port string) error {
158
client, err := k.sshClientFactory.Dial(ctx, host, port)
159
if err != nil {
160
return err
161
}
162
k.client = client
163
164
return nil
165
}
166
167
func (k *ConfigLoader) Close() error {
168
if k.client == nil {
169
return errors.New("attempting to close a nil client")
170
}
171
172
if err := k.client.Close(); err != nil {
173
return err
174
}
175
176
k.client = nil
177
return nil
178
}
179
180