/
switch.go
156 lines (139 loc) · 6.92 KB
/
switch.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
package context_switch
import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis-portal/api/golang/constructors"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/context_id_arg"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/defaults"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/engine_manager"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/portal_manager"
"github.com/kurtosis-tech/kurtosis/contexts-config-store/store"
"github.com/kurtosis-tech/stacktrace"
"github.com/sirupsen/logrus"
)
const (
contextIdentifierArgKey = "context"
contextIdentifierArgIsGreedy = false
)
var ContextSwitchCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.ContextSwitchCmdStr,
ShortDescription: "Switches to a different Kurtosis context",
LongDescription: fmt.Sprintf("Switches to a different Kurtosis context. The context needs to be added "+
"first using the `%s` command. When switching to a remote context, the connection will be established with "+
"the remote Kurtosis server. Kurtosis Portal needs to be running for this. If the remote server can't be "+
"reached, the context will remain unchanged.", command_str_consts.ContextAddCmdStr),
Flags: []*flags.FlagConfig{},
Args: []*args.ArgConfig{
context_id_arg.NewContextIdentifierArg(store.GetContextsConfigStore(), contextIdentifierArgKey, contextIdentifierArgIsGreedy),
},
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}
func run(ctx context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error {
contextIdentifier, err := args.GetNonGreedyArg(contextIdentifierArgKey)
if err != nil {
return stacktrace.Propagate(err, "Expected a value for context identifier arg '%v' but none was found; this is a bug in the Kurtosis CLI!", contextIdentifierArgKey)
}
return SwitchContext(ctx, contextIdentifier)
}
func SwitchContext(
ctx context.Context,
contextIdentifier string,
) error {
isContextSwitchSuccessful := false
logrus.Info("Switching context...")
contextsConfigStore := store.GetContextsConfigStore()
contextPriorToSwitch, err := contextsConfigStore.GetCurrentContext()
if err != nil {
return stacktrace.NewError("An error occurred retrieving current context prior to switching to the new one '%s'", contextIdentifier)
}
if !store.IsRemote(contextPriorToSwitch) {
engineManager, err := engine_manager.NewEngineManager(ctx)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager.")
}
if err := engineManager.StopEngineIdempotently(ctx); err != nil {
return stacktrace.Propagate(err, "An error occurred stopping the local engine. The local engine"+
"needs to be stopped before the context can be switched. The engine status can be obtained running"+
"kurtosis %s %s and it can be stopped manually by running kurtosis %s %s.",
command_str_consts.EngineCmdStr, command_str_consts.EngineStatusCmdStr,
command_str_consts.EngineCmdStr, command_str_consts.EngineStartCmdStr)
}
}
contextsMatchingIdentifiers, err := context_id_arg.GetContextUuidForContextIdentifier(contextsConfigStore, []string{contextIdentifier})
if err != nil {
return stacktrace.Propagate(err, "Error searching for context matching context identifier: '%s'", contextIdentifier)
}
contextUuidToSwitchTo, found := contextsMatchingIdentifiers[contextIdentifier]
if !found {
return stacktrace.NewError("No context matching identifier '%s' could be found", contextIdentifier)
}
if contextUuidToSwitchTo.GetValue() == contextPriorToSwitch.GetUuid().GetValue() {
logrus.Infof("Already on context '%s'", contextPriorToSwitch.GetName())
return nil
}
if err = contextsConfigStore.SwitchContext(contextUuidToSwitchTo); err != nil {
return stacktrace.Propagate(err, "An error occurred switching to context '%s' with UUID '%s'", contextIdentifier, contextUuidToSwitchTo.GetValue())
}
defer func() {
if isContextSwitchSuccessful {
return
}
if err = contextsConfigStore.SwitchContext(contextPriorToSwitch.GetUuid()); err != nil {
logrus.Errorf("An unexpected error occurred switching to context '%s' with UUID "+
"'%s'. Kurtosis tried to roll back to previous context '%s' with UUID '%s' but the roll back "+
"failed. It is likely that the current context is still set to '%s' but it is not fully functional. "+
"Try manually switching back to '%s' to get back to a working state. Error was:\n%v",
contextIdentifier, contextUuidToSwitchTo.GetValue(), contextPriorToSwitch.GetName(),
contextPriorToSwitch.GetUuid(), contextIdentifier, contextPriorToSwitch.GetName(), err.Error())
}
}()
currentContext, err := contextsConfigStore.GetCurrentContext()
if err != nil {
return stacktrace.Propagate(err, "Error retrieving context info for context '%s' after switching to it", contextIdentifier)
}
portalManager := portal_manager.NewPortalManager()
if store.IsRemote(currentContext) {
if err := portalManager.DownloadAndStart(ctx); err != nil {
return stacktrace.Propagate(err, "An error occurred starting the portal")
}
portalDaemonClient := portalManager.GetClient()
if portalDaemonClient != nil {
switchContextArg := constructors.NewSwitchContextArgs()
if _, err = portalDaemonClient.SwitchContext(ctx, switchContextArg); err != nil {
return stacktrace.Propagate(err, "Error switching Kurtosis portal context")
}
}
} else {
if err := portalManager.StopExisting(ctx); err != nil {
return stacktrace.Propagate(err, "An error occurred stopping Kurtosis Portal")
}
}
logrus.Infof("Context switched to '%s', Kurtosis engine will now be restarted", contextIdentifier)
// Instantiate the engine manager after storing the new context so the manager can read it.
engineManager, err := engine_manager.NewEngineManager(ctx)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager for the new context.")
}
_, engineClientCloseFunc, startEngineErr := engineManager.StartEngineIdempotentlyWithDefaultVersion(ctx, logrus.InfoLevel, defaults.DefaultEngineEnclavePoolSize)
if startEngineErr != nil {
logrus.Warnf("The context was successfully switched to '%s' but Kurtosis failed to start an engine in "+
"this new context. An engine should be started manually with 'kurtosis %s %s'. The error was:\n%v",
contextIdentifier, command_str_consts.EngineCmdStr, command_str_consts.EngineStartCmdStr, startEngineErr)
} else {
defer func() {
if err = engineClientCloseFunc(); err != nil {
logrus.Warnf("Error closing connection to the engine running in context '%s':\n'%v'",
contextIdentifier, err)
}
}()
logrus.Info("Successfully switched context")
}
isContextSwitchSuccessful = true
return nil
}