-
Notifications
You must be signed in to change notification settings - Fork 4
/
start.go
305 lines (259 loc) · 8.66 KB
/
start.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
package start
import (
"context"
"fmt"
"os"
"os/exec"
"github.com/loft-sh/api/v3/pkg/product"
"github.com/loft-sh/loftctl/v3/cmd/loftctl/flags"
"github.com/loft-sh/loftctl/v3/pkg/client"
"github.com/loft-sh/loftctl/v3/pkg/clihelper"
"github.com/loft-sh/log"
"github.com/loft-sh/log/survey"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/util/term"
)
// Options holds the cmd flags
type Options struct {
*flags.GlobalFlags
// Will be filled later
KubeClient kubernetes.Interface
Log log.Logger
RestConfig *rest.Config
Context string
Values string
LocalPort string
Version string
DockerImage string
Namespace string
Password string
Host string
Email string
ChartRepo string
Product string
ChartName string
ChartPath string
DockerArgs []string
Reset bool
NoPortForwarding bool
NoTunnel bool
NoLogin bool
NoWait bool
Upgrade bool
ReuseValues bool
Docker bool
}
func NewLoftStarter(options Options) *LoftStarter {
return &LoftStarter{
Options: options,
}
}
type LoftStarter struct {
Options
}
// Start executes the functionality "loft start"
func (l *LoftStarter) Start(ctx context.Context) error {
// start in Docker?
if l.Docker {
return l.startDocker(ctx, "loft")
}
// only set local port by default in kubernetes installation
if l.LocalPort == "" {
l.LocalPort = "9898"
}
err := l.prepare()
if err != nil {
return err
}
l.Log.WriteString(logrus.InfoLevel, "\n")
// Uninstall already existing Loft instance
if l.Reset {
err = clihelper.UninstallLoft(ctx, l.KubeClient, l.RestConfig, l.Context, l.Namespace, l.Log)
if err != nil {
return err
}
}
// Is already installed?
isInstalled, err := clihelper.IsLoftAlreadyInstalled(ctx, l.KubeClient, l.Namespace)
if err != nil {
return err
}
// Use default password if none is set
if l.Password == "" {
defaultPassword, err := clihelper.GetLoftDefaultPassword(ctx, l.KubeClient, l.Namespace)
if err != nil {
return err
}
l.Password = defaultPassword
}
// Upgrade Loft if already installed
if isInstalled {
return l.handleAlreadyExistingInstallation(ctx)
}
// Install Loft
l.Log.Info(product.Replace("Welcome to Loft!"))
l.Log.Info(product.Replace("This installer will help you configure and deploy Loft."))
// make sure we are ready for installing
err = l.prepareInstall(ctx)
if err != nil {
return err
}
err = l.upgradeLoft()
if err != nil {
return err
}
return l.success(ctx)
}
func (l *LoftStarter) prepareInstall(ctx context.Context) error {
// delete admin user & secret
return clihelper.UninstallLoft(ctx, l.KubeClient, l.RestConfig, l.Context, l.Namespace, log.Discard)
}
func (l *LoftStarter) prepare() error {
loader, err := client.NewClientFromPath(l.Config)
if err != nil {
return err
}
loftConfig := loader.Config()
// first load the kube config
kubeClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{})
// load the raw config
kubeConfig, err := kubeClientConfig.RawConfig()
if err != nil {
return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err)
}
// we switch the context to the install config
contextToLoad := kubeConfig.CurrentContext
if l.Context != "" {
contextToLoad = l.Context
} else if loftConfig.LastInstallContext != "" && loftConfig.LastInstallContext != contextToLoad {
contextToLoad, err = l.Log.Question(&survey.QuestionOptions{
Question: product.Replace("Seems like you try to use 'loft start' with a different kubernetes context than before. Please choose which kubernetes context you want to use"),
DefaultValue: contextToLoad,
Options: []string{contextToLoad, loftConfig.LastInstallContext},
})
if err != nil {
return err
}
}
l.Context = contextToLoad
loftConfig.LastInstallContext = contextToLoad
_ = loader.Save()
// kube client config
kubeClientConfig = clientcmd.NewNonInteractiveClientConfig(kubeConfig, contextToLoad, &clientcmd.ConfigOverrides{}, clientcmd.NewDefaultClientConfigLoadingRules())
// test for helm and kubectl
_, err = exec.LookPath("helm")
if err != nil {
return fmt.Errorf("seems like helm is not installed. Helm is required for the installation of loft. Please visit https://helm.sh/docs/intro/install/ for install instructions")
}
output, err := exec.Command("helm", "version").CombinedOutput()
if err != nil {
return fmt.Errorf("seems like there are issues with your helm client: \n\n%s", output)
}
_, err = exec.LookPath("kubectl")
if err != nil {
return fmt.Errorf("seems like kubectl is not installed. Kubectl is required for the installation of loft. Please visit https://kubernetes.io/docs/tasks/tools/install-kubectl/ for install instructions")
}
output, err = exec.Command("kubectl", "version", "--context", contextToLoad).CombinedOutput()
if err != nil {
return fmt.Errorf("seems like kubectl cannot connect to your Kubernetes cluster: \n\n%s", output)
}
l.RestConfig, err = kubeClientConfig.ClientConfig()
if err != nil {
return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err)
}
l.KubeClient, err = kubernetes.NewForConfig(l.RestConfig)
if err != nil {
return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err)
}
// Check if cluster has RBAC correctly configured
_, err = l.KubeClient.RbacV1().ClusterRoles().Get(context.Background(), "cluster-admin", metav1.GetOptions{})
if err != nil {
return fmt.Errorf("error retrieving cluster role 'cluster-admin': %w. Please make sure RBAC is correctly configured in your cluster", err)
}
return nil
}
func (l *LoftStarter) handleAlreadyExistingInstallation(ctx context.Context) error {
enableIngress := false
// Only ask if ingress should be enabled if --upgrade flag is not provided
if !l.Upgrade && term.IsTerminal(os.Stdin) {
l.Log.Info(product.Replace("Existing Loft instance found."))
// Check if Loft is installed in a local cluster
isLocal := clihelper.IsLoftInstalledLocally(ctx, l.KubeClient, l.Namespace)
// Skip question if --host flag is provided
if l.Host != "" {
enableIngress = true
}
if enableIngress {
if isLocal {
// Confirm with user if this is a local cluster
const (
YesOption = "Yes"
NoOption = "No, my cluster is running not locally (GKE, EKS, Bare Metal, etc.)"
)
answer, err := l.Log.Question(&survey.QuestionOptions{
Question: "Seems like your cluster is running locally (docker desktop, minikube, kind etc.). Is that correct?",
DefaultValue: YesOption,
Options: []string{
YesOption,
NoOption,
},
})
if err != nil {
return err
}
isLocal = answer == YesOption
}
if isLocal {
// Confirm with user if ingress should be installed in local cluster
var (
YesOption = product.Replace("Yes, enable the ingress for Loft anyway")
NoOption = "No"
)
answer, err := l.Log.Question(&survey.QuestionOptions{
Question: product.Replace("Enabling ingress is usually only useful for remote clusters. Do you still want to deploy the ingress for Loft to your local cluster?"),
DefaultValue: NoOption,
Options: []string{
NoOption,
YesOption,
},
})
if err != nil {
return err
}
enableIngress = answer == YesOption
}
}
// Check if we need to enable ingress
if enableIngress {
// Ask for hostname if --host flag is not provided
if l.Host == "" {
host, err := clihelper.EnterHostNameQuestion(l.Log)
if err != nil {
return err
}
l.Host = host
} else {
l.Log.Info(product.Replace("Will enable Loft ingress with hostname: ") + l.Host)
}
if term.IsTerminal(os.Stdin) {
err := clihelper.EnsureIngressController(ctx, l.KubeClient, l.Context, l.Log)
if err != nil {
return errors.Wrap(err, "install ingress controller")
}
}
}
}
// Only upgrade if --upgrade flag is present or user decided to enable ingress
if l.Upgrade || enableIngress {
err := l.upgradeLoft()
if err != nil {
return err
}
}
return l.success(ctx)
}