From 9603a4c7464f326a0263ec89d7ad2021d32a71db Mon Sep 17 00:00:00 2001
From: Malte Bauch <malte.bauch@stud.h-da.de>
Date: Fri, 8 Sep 2023 14:31:10 +0200
Subject: [PATCH] WIP: Use cobra.ExecuteContext to cancel requests to the
 controller

---
 cli/cmd/changeCommit.go                  |  5 +--
 cli/cmd/changeConfirm.go                 |  5 +--
 cli/cmd/changeGet.go                     |  5 +--
 cli/cmd/changeList.go                    | 10 +----
 cli/cmd/list.go                          |  8 +---
 cli/cmd/login.go                         |  5 +--
 cli/cmd/logout.go                        |  6 +--
 cli/cmd/networkElementCreate.go          |  5 +--
 cli/cmd/networkElementList.go            |  5 +--
 cli/cmd/networkElementPathDelete.go      |  5 +--
 cli/cmd/networkElementPathGet.go         |  5 +--
 cli/cmd/networkElementPathGetIntended.go |  5 +--
 cli/cmd/networkElementPathSet.go         |  5 +--
 cli/cmd/networkElementRemove.go          |  5 +--
 cli/cmd/networkElementShow.go            |  5 +--
 cli/cmd/networkElementSubscribe.go       |  5 +--
 cli/cmd/pluginList.go                    |  5 +--
 cli/cmd/pndCreate.go                     |  5 +--
 cli/cmd/pndGet.go                        |  5 +--
 cli/cmd/pndList.go                       |  5 +--
 cli/cmd/pndRemove.go                     |  5 +--
 cli/cmd/pndUse.go                        |  5 +--
 cli/cmd/prompt.go                        | 56 +++++++++++++++++++++---
 cli/cmd/root.go                          | 25 ++---------
 cli/cmd/userCreate.go                    |  5 +--
 cli/cmd/userDelete.go                    |  5 +--
 cli/cmd/userGet.go                       |  5 +--
 cli/cmd/userGetAll.go                    |  5 +--
 cli/cmd/userUpdate.go                    |  9 ++--
 cli/cmd/utils.go                         | 15 +++++++
 30 files changed, 99 insertions(+), 145 deletions(-)

diff --git a/cli/cmd/changeCommit.go b/cli/cmd/changeCommit.go
index 744a90eeb..46d3acd19 100644
--- a/cli/cmd/changeCommit.go
+++ b/cli/cmd/changeCommit.go
@@ -52,10 +52,7 @@ Change UUID must be specified as positional argument.`,
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.Commit(ctx, cuid)
+		resp, err := pndAdapter.Commit(cmd.Context(), cuid)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/changeConfirm.go b/cli/cmd/changeConfirm.go
index a67b83926..51ad31594 100644
--- a/cli/cmd/changeConfirm.go
+++ b/cli/cmd/changeConfirm.go
@@ -52,10 +52,7 @@ Change UUID must be specified as positional argument`,
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.Confirm(ctx, cuid)
+		resp, err := pndAdapter.Confirm(cmd.Context(), cuid)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/changeGet.go b/cli/cmd/changeGet.go
index debb73d8a..470646c77 100644
--- a/cli/cmd/changeGet.go
+++ b/cli/cmd/changeGet.go
@@ -46,10 +46,7 @@ var getCmd = &cobra.Command{
     changes UUID has to be specified via a positional argument.`,
 
 	Run: func(cmd *cobra.Command, args []string) {
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		changes, err := pndAdapter.GetChange(ctx, args[0])
+		changes, err := pndAdapter.GetChange(cmd.Context(), args[0])
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/changeList.go b/cli/cmd/changeList.go
index bbe583461..bf86def5d 100644
--- a/cli/cmd/changeList.go
+++ b/cli/cmd/changeList.go
@@ -45,19 +45,13 @@ var changeListCmd = &cobra.Command{
 
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Process change list request")
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		committed, err := pndAdapter.CommittedChanges(ctx)
+		committed, err := pndAdapter.CommittedChanges(cmd.Context())
 		if err != nil {
 			spinner.Fail(err)
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		pending, err := pndAdapter.PendingChanges(ctx)
+		pending, err := pndAdapter.PendingChanges(cmd.Context())
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/list.go b/cli/cmd/list.go
index 6427712a7..885dc6741 100644
--- a/cli/cmd/list.go
+++ b/cli/cmd/list.go
@@ -47,18 +47,14 @@ var listCmd = &cobra.Command{
 
 	Run: func(cmd *cobra.Command, args []string) {
 		addr := viper.GetString("controllerApiEndpoint")
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
 
-		resp, err := api.GetIds(ctx, addr)
+		resp, err := api.GetIds(cmd.Context(), addr)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
 		}
 		for i, pnd := range resp {
-			// create a authorizedContext for further requests
-			ctx, ctxCancelFn = createContextWithAuthorization()
-			mneResp, err := api.GetFlattenedNetworkElements(ctx, addr, pnd.GetId())
+			mneResp, err := api.GetFlattenedNetworkElements(cmd.Context(), addr, pnd.GetId())
 			if err != nil {
 				pterm.Error.Println(err)
 				return
diff --git a/cli/cmd/login.go b/cli/cmd/login.go
index 60a685bcd..7439ca243 100644
--- a/cli/cmd/login.go
+++ b/cli/cmd/login.go
@@ -56,10 +56,7 @@ var loginCmd = &cobra.Command{
 			pterm.Info.Println("New controller address: ", viper.GetString("controllerAPIEndpoint"))
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.Login(ctx, viper.GetString("controllerAPIEndpoint"), nbUserName, nbUserPwd)
+		resp, err := api.Login(cmd.Context(), viper.GetString("controllerAPIEndpoint"), nbUserName, nbUserPwd)
 		if err != nil {
 			spinner.Fail("Login failed: ", err)
 			return
diff --git a/cli/cmd/logout.go b/cli/cmd/logout.go
index f042097b4..662e89c97 100644
--- a/cli/cmd/logout.go
+++ b/cli/cmd/logout.go
@@ -48,11 +48,7 @@ var logoutCmd = &cobra.Command{
 
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Logout attempt for user: ", nbUserName)
-
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.Logout(ctx, viper.GetString("controllerAPIEndpoint"), nbUserName)
+		resp, err := api.Logout(cmd.Context(), viper.GetString("controllerAPIEndpoint"), nbUserName)
 		if err != nil {
 			spinner.Fail("Logout failed: ", err)
 			return
diff --git a/cli/cmd/networkElementCreate.go b/cli/cmd/networkElementCreate.go
index ffffd5e25..25aac3d4d 100644
--- a/cli/cmd/networkElementCreate.go
+++ b/cli/cmd/networkElementCreate.go
@@ -71,10 +71,7 @@ if they diverge from the default credentials (user:'admin' and pw:'arista').`,
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.AddNetworkElement(ctx, mneName, opt, pluginUUID)
+		resp, err := pndAdapter.AddNetworkElement(cmd.Context(), mneName, opt, pluginUUID)
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/networkElementList.go b/cli/cmd/networkElementList.go
index 1ec9fdb7b..941b86a91 100644
--- a/cli/cmd/networkElementList.go
+++ b/cli/cmd/networkElementList.go
@@ -47,10 +47,7 @@ var networkElementListCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Fetching data from controller")
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.GetFlattenedNetworkElements(ctx)
+		resp, err := pndAdapter.GetFlattenedNetworkElements(cmd.Context())
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/networkElementPathDelete.go b/cli/cmd/networkElementPathDelete.go
index ee81e4425..3a0e56497 100644
--- a/cli/cmd/networkElementPathDelete.go
+++ b/cli/cmd/networkElementPathDelete.go
@@ -62,11 +62,8 @@ The network element UUID and request path must be specified as a positional argu
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		resp, err := pndAdapter.ChangeMNE(
-			ctx,
+			cmd.Context(),
 			mneid,
 			mnepb.ApiOperation_API_OPERATION_DELETE,
 			path,
diff --git a/cli/cmd/networkElementPathGet.go b/cli/cmd/networkElementPathGet.go
index 56c99dab9..476116686 100644
--- a/cli/cmd/networkElementPathGet.go
+++ b/cli/cmd/networkElementPathGet.go
@@ -54,11 +54,8 @@ The network element UUID and request path must be specified as a positional argu
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		res, err := pndAdapter.RequestPath(
-			ctx,
+			cmd.Context(),
 			mneid,
 			args[1],
 		)
diff --git a/cli/cmd/networkElementPathGetIntended.go b/cli/cmd/networkElementPathGetIntended.go
index b8ceba56c..ca422869f 100644
--- a/cli/cmd/networkElementPathGetIntended.go
+++ b/cli/cmd/networkElementPathGetIntended.go
@@ -56,11 +56,8 @@ The network element UUID and request path must be specified as a positional argu
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		res, err := pndAdapter.RequestIntendedPath(
-			ctx,
+			cmd.Context(),
 			mneid,
 			args[1],
 		)
diff --git a/cli/cmd/networkElementPathSet.go b/cli/cmd/networkElementPathSet.go
index 087b95e6a..937fcf1e7 100644
--- a/cli/cmd/networkElementPathSet.go
+++ b/cli/cmd/networkElementPathSet.go
@@ -119,11 +119,8 @@ To enable replacing behaviour (destructive!), set the --replace flag."`,
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		resp, err := pndAdapter.ChangeMNE(
-			ctx,
+			cmd.Context(),
 			mneid,
 			operation,
 			path,
diff --git a/cli/cmd/networkElementRemove.go b/cli/cmd/networkElementRemove.go
index 1e80546a8..ec1a91293 100644
--- a/cli/cmd/networkElementRemove.go
+++ b/cli/cmd/networkElementRemove.go
@@ -55,10 +55,7 @@ The network element UUID must be specified as a positional argument.`,
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		_, err = pndAdapter.RemoveNetworkElement(ctx, mneid)
+		_, err = pndAdapter.RemoveNetworkElement(cmd.Context(), mneid)
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/networkElementShow.go b/cli/cmd/networkElementShow.go
index 721381486..683f2350f 100644
--- a/cli/cmd/networkElementShow.go
+++ b/cli/cmd/networkElementShow.go
@@ -47,10 +47,7 @@ The network element information returned is the information as currently stored
 The actual network element is not queried directly.`,
 
 	Run: func(cmd *cobra.Command, args []string) {
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.GetNetworkElement(ctx, args[0])
+		resp, err := pndAdapter.GetNetworkElement(cmd.Context(), args[0])
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/networkElementSubscribe.go b/cli/cmd/networkElementSubscribe.go
index 554e1e12a..1180af843 100644
--- a/cli/cmd/networkElementSubscribe.go
+++ b/cli/cmd/networkElementSubscribe.go
@@ -57,11 +57,8 @@ The device UUID and requested paths must be specified as a positional arguments.
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		subClient, err := pndAdapter.SubscribeMNEPath(
-			ctx,
+			cmd.Context(),
 			did,
 			&mnepb.SubscriptionList{
 				Subscription: []*mnepb.Subscription{
diff --git a/cli/cmd/pluginList.go b/cli/cmd/pluginList.go
index 4b6e18935..cb64ca5f0 100644
--- a/cli/cmd/pluginList.go
+++ b/cli/cmd/pluginList.go
@@ -47,10 +47,7 @@ var pluginListCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Fetching list of available plugins from the controller.")
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := pndAdapter.GetAvailablePlugins(ctx)
+		resp, err := pndAdapter.GetAvailablePlugins(cmd.Context())
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/pndCreate.go b/cli/cmd/pndCreate.go
index 4e2ed1808..fdfb3cb94 100644
--- a/cli/cmd/pndCreate.go
+++ b/cli/cmd/pndCreate.go
@@ -52,10 +52,7 @@ A description must be passed as positional argument.`,
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Creating new PND")
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.AddPnd(ctx, viper.GetString("controllerApiEndpoint"), pndName, pndDescription)
+		resp, err := api.AddPnd(cmd.Context(), viper.GetString("controllerApiEndpoint"), pndName, pndDescription)
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/pndGet.go b/cli/cmd/pndGet.go
index 5fecaed10..b9f82172f 100644
--- a/cli/cmd/pndGet.go
+++ b/cli/cmd/pndGet.go
@@ -47,10 +47,7 @@ var pndGetCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Fetching requested PNDs from controller.")
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.GetPnd(ctx, viper.GetString("controllerApiEndpoint"), args[0])
+		resp, err := api.GetPnd(cmd.Context(), viper.GetString("controllerApiEndpoint"), args[0])
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/pndList.go b/cli/cmd/pndList.go
index 51adf9946..9e216b82f 100644
--- a/cli/cmd/pndList.go
+++ b/cli/cmd/pndList.go
@@ -48,10 +48,7 @@ var pndListCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		spinner, _ := pterm.DefaultSpinner.Start("Fetching PND list from controller")
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.GetPnds(ctx, pndAdapter.Endpoint())
+		resp, err := api.GetPnds(cmd.Context(), pndAdapter.Endpoint())
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/pndRemove.go b/cli/cmd/pndRemove.go
index 4ed103281..24b9081b7 100644
--- a/cli/cmd/pndRemove.go
+++ b/cli/cmd/pndRemove.go
@@ -53,10 +53,7 @@ var pndRemoveCmd = &cobra.Command{
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		_, err = pndAdapter.RemovePnd(ctx, pid)
+		_, err = pndAdapter.RemovePnd(cmd.Context(), pid)
 		if err != nil {
 			spinner.Fail(err)
 			return
diff --git a/cli/cmd/pndUse.go b/cli/cmd/pndUse.go
index 03835c081..750eac377 100644
--- a/cli/cmd/pndUse.go
+++ b/cli/cmd/pndUse.go
@@ -55,10 +55,7 @@ var pndUseCmd = &cobra.Command{
 			return
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		_, err = api.GetPnd(ctx, viper.GetString("controllerAPIEndpoint"), newPND)
+		_, err = api.GetPnd(cmd.Context(), viper.GetString("controllerAPIEndpoint"), newPND)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/prompt.go b/cli/cmd/prompt.go
index 4fe3141ff..fcbfb97cc 100644
--- a/cli/cmd/prompt.go
+++ b/cli/cmd/prompt.go
@@ -32,24 +32,31 @@ POSSIBILITY OF SUCH DAMAGE.
 package cmd
 
 import (
+	"context"
 	"os"
 	"os/exec"
 	"strings"
+	"syscall"
 
 	"code.fbi.h-da.de/danet/gosdn/cli/completer"
 	"code.fbi.h-da.de/danet/gosdn/controller/api"
 	"github.com/c-bata/go-prompt"
 	"github.com/google/uuid"
 	"github.com/openconfig/goyang/pkg/yang"
+	"github.com/pkg/term/termios"
 	"github.com/pterm/pterm"
 	"github.com/pterm/pterm/putils"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/viper"
+	"golang.org/x/sys/unix"
 )
 
 var c *PromptCompleter
+var fd int
+var originalTermios *unix.Termios
+var mutContext *ContextMutable
 
 // suggestionTracker is used to keep track of the last used command in
 // combination with the resulting suggestions.
@@ -67,6 +74,10 @@ type PromptCompleter struct {
 	history  []string
 }
 
+type ContextMutable struct {
+	context.Context
+}
+
 // NewPromptCompleter returns a new promptCompleter.
 func NewPromptCompleter() *PromptCompleter {
 	return &PromptCompleter{
@@ -105,6 +116,19 @@ func (pc *PromptCompleter) Run() {
 		prompt.OptionSelectedDescriptionTextColor(prompt.DarkGray),
 	)
 
+	var err error
+
+	fd, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
+	if err != nil {
+		panic(err)
+	}
+
+	// get the original settings
+	originalTermios, err = termios.Tcgetattr(uintptr(fd))
+	if err != nil {
+		panic(err)
+	}
+
 	p.Run()
 }
 
@@ -112,8 +136,18 @@ func executeFunc(s string) {
 	if s := strings.TrimSpace(s); s == "" {
 		return
 	}
+	// restore the original settings to allow ctrl-c to generate signal
+	if err := termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*unix.Termios)(originalTermios)); err != nil {
+		panic(err)
+	}
+
+	ctx, ctxCancelFn := createContextWithAuthorization()
+
+	mutContext.Context = ctx
+
+	startContextListener(mutContext, ctxCancelFn)
 	rootCmd.SetArgs(strings.Fields(s))
-	err := rootCmd.Execute()
+	err := rootCmd.ExecuteContext(mutContext)
 
 	if err != nil {
 		pterm.Error.Println("Could not execute:", err)
@@ -312,7 +346,8 @@ func completionBasedOnCmd(c *PromptCompleter, cmd *cobra.Command, inputSplit []s
 func getNetworkElements() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching Network Elements from controller.")
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	resp, err := pndAdapter.GetFlattenedNetworkElements(ctx)
 	if err != nil {
 		spinner.Fail(err)
@@ -330,7 +365,8 @@ func getNetworkElements() ([]prompt.Suggest, error) {
 func getAvailablePlugins() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching available plugins from controller.")
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	resp, err := pndAdapter.GetAvailablePlugins(ctx)
 	if err != nil {
 		spinner.Fail(err)
@@ -351,7 +387,8 @@ func getAvailablePlugins() ([]prompt.Suggest, error) {
 func getSchemaTreeForNetworkElementID(id uuid.UUID) (map[string]*yang.Entry, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching schema tree for Device with ID: ", id)
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	networkElement, err := pndAdapter.GetFlattenedNetworkElement(ctx, id.String())
 	if err != nil {
 		spinner.Fail(err)
@@ -362,6 +399,7 @@ func getSchemaTreeForNetworkElementID(id uuid.UUID) (map[string]*yang.Entry, err
 	pluginUUID := uuid.MustParse(pluginID)
 	// create a authorizedContext for further requests
 	ctx, ctxCancelFn = createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	schemaTree, err := pndAdapter.GetPluginSchemaTree(ctx, pluginUUID)
 	if err != nil {
 		spinner.Fail(err)
@@ -377,7 +415,8 @@ func getSchemaTreeForNetworkElementID(id uuid.UUID) (map[string]*yang.Entry, err
 func getPnds() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching PNDs from controller.")
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	resp, err := api.GetIds(ctx, viper.GetString("controllerAPIEndpoint"))
 	if err != nil {
 		spinner.Fail(err)
@@ -399,7 +438,8 @@ func getPnds() ([]prompt.Suggest, error) {
 func getPendingChanges() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching committed changes.")
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	resp, err := pndAdapter.PendingChanges(ctx)
 	if err != nil {
 		spinner.Fail(err)
@@ -421,7 +461,8 @@ func getPendingChanges() ([]prompt.Suggest, error) {
 func getCommittedChanges() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching pending changes.")
 	// create a authorizedContext for further requests
-	ctx, ctxCancelFn = createContextWithAuthorization()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
 	resp, err := pndAdapter.CommittedChanges(ctx)
 	if err != nil {
 		spinner.Fail(err)
@@ -461,6 +502,7 @@ var promptCmd = &cobra.Command{
     provides the user with autocompletion and more...`,
 
 	Run: func(cmd *cobra.Command, args []string) {
+		mutContext = &ContextMutable{Context: nil}
 		c = NewPromptCompleter()
 		c.Run()
 	},
diff --git a/cli/cmd/root.go b/cli/cmd/root.go
index bb88e3c96..15aec3bcc 100644
--- a/cli/cmd/root.go
+++ b/cli/cmd/root.go
@@ -31,11 +31,9 @@ POSSIBILITY OF SUCH DAMAGE.
 package cmd
 
 import (
-	"context"
 	"errors"
 	"fmt"
 	"os"
-	"os/signal"
 
 	"code.fbi.h-da.de/danet/gosdn/cli/adapter"
 
@@ -55,9 +53,6 @@ var nbUserName string
 var nbUserPwd string
 var userToken string
 
-var ctx context.Context
-var ctxCancelFn context.CancelFunc
-
 var pndAdapter *adapter.PndAdapter
 
 // rootCmd represents the base command when called without any subcommands.
@@ -76,7 +71,9 @@ The login command must be called for authorization.
 // Execute adds all child commands to the root command and sets flags appropriately.
 // This is called by main.main(). It only needs to happen once to the rootCmd.
 func Execute() {
-	err := rootCmd.Execute()
+	ctx, ctxCancelFn := createContextWithAuthorization()
+	startContextListener(ctx, ctxCancelFn)
+	err := rootCmd.ExecuteContext(ctx)
 	if err != nil {
 		log.Error("Could not execute root command: ", err)
 	}
@@ -84,8 +81,7 @@ func Execute() {
 }
 
 func init() {
-	ctx, ctxCancelFn = context.WithCancel(context.Background())
-	cobra.OnInitialize(initConfig, startContextListener)
+	cobra.OnInitialize(initConfig)
 
 	// add CLI global parameters
 	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (./.gosdnc.toml)")
@@ -95,19 +91,6 @@ func init() {
 	rootCmd.Flags().StringVar(&grpcPort, "grpc-port", "55055", "port for gRPC NBI")
 }
 
-func startContextListener() {
-	// listen for CTRL-C as interrupt
-	ch := make(chan os.Signal, 1)
-	signal.Notify(ch, os.Interrupt)
-	go func() {
-		select {
-		case <-ch:
-			ctxCancelFn()
-		case <-ctx.Done():
-		}
-	}()
-}
-
 // initConfig reads in config file and ENV variables if set.
 func initConfig() {
 	configFileName := ".gosdnc.toml"
diff --git a/cli/cmd/userCreate.go b/cli/cmd/userCreate.go
index fab47e9d2..6f969e2c0 100644
--- a/cli/cmd/userCreate.go
+++ b/cli/cmd/userCreate.go
@@ -63,10 +63,7 @@ var userCreateCmd = &cobra.Command{
 			},
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.CreateUsers(ctx, viper.GetString("controllerAPIEndpoint"), users)
+		resp, err := api.CreateUsers(cmd.Context(), viper.GetString("controllerAPIEndpoint"), users)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/userDelete.go b/cli/cmd/userDelete.go
index 4fd0bc525..a12a03c50 100644
--- a/cli/cmd/userDelete.go
+++ b/cli/cmd/userDelete.go
@@ -51,10 +51,7 @@ var userDeleteCmd = &cobra.Command{
 		// only one user for now, add more later if needed
 		users := []string{nbUserName}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.DeleteUsers(ctx, viper.GetString("controllerAPIEndpoint"), users)
+		resp, err := api.DeleteUsers(cmd.Context(), viper.GetString("controllerAPIEndpoint"), users)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/userGet.go b/cli/cmd/userGet.go
index 0aadd3767..db387fe89 100644
--- a/cli/cmd/userGet.go
+++ b/cli/cmd/userGet.go
@@ -47,11 +47,8 @@ var userGetCmd = &cobra.Command{
 	Long:  `Requests one user using the provided name to search for it in the stored users.`,
 
 	Run: func(cmd *cobra.Command, args []string) {
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
 		resp, err := api.GetUser(
-			ctx,
+			cmd.Context(),
 			viper.GetString("controllerAPIEndpoint"),
 			nbUserName,
 			uuid.Nil,
diff --git a/cli/cmd/userGetAll.go b/cli/cmd/userGetAll.go
index a2611d340..d262c821e 100644
--- a/cli/cmd/userGetAll.go
+++ b/cli/cmd/userGetAll.go
@@ -46,10 +46,7 @@ var userGetAllCmd = &cobra.Command{
 	Long:  `Requests all the available users.`,
 
 	Run: func(cmd *cobra.Command, args []string) {
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.GetAllUsers(ctx, viper.GetString("controllerAPIEndpoint"))
+		resp, err := api.GetAllUsers(cmd.Context(), viper.GetString("controllerAPIEndpoint"))
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/userUpdate.go b/cli/cmd/userUpdate.go
index 2130f9867..70f122231 100644
--- a/cli/cmd/userUpdate.go
+++ b/cli/cmd/userUpdate.go
@@ -51,10 +51,10 @@ var userUpdateCmd = &cobra.Command{
 
 	Run: func(cmd *cobra.Command, args []string) {
 		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
+		//ctx, ctxCancelFn = createContextWithAuthorization()
 
 		existingUser, err := api.GetUser(
-			ctx,
+			cmd.Context(),
 			viper.GetString("controllerAPIEndpoint"),
 			nbUserName,
 			uuid.Nil,
@@ -79,10 +79,7 @@ var userUpdateCmd = &cobra.Command{
 			},
 		}
 
-		// create a authorizedContext for further requests
-		ctx, ctxCancelFn = createContextWithAuthorization()
-
-		resp, err := api.UpdateUsers(ctx, viper.GetString("controllerAPIEndpoint"), users)
+		resp, err := api.UpdateUsers(cmd.Context(), viper.GetString("controllerAPIEndpoint"), users)
 		if err != nil {
 			pterm.Error.Println(err)
 			return
diff --git a/cli/cmd/utils.go b/cli/cmd/utils.go
index 10c1b95b2..2a849b02a 100644
--- a/cli/cmd/utils.go
+++ b/cli/cmd/utils.go
@@ -36,6 +36,8 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"os"
+	"os/signal"
 	"strconv"
 	"time"
 
@@ -122,3 +124,16 @@ func convertStringToUintTypedValue(s string) (*gpb.TypedValue, error) {
 		},
 	}, nil
 }
+
+func startContextListener(ctx context.Context, ctxCancelFn context.CancelFunc) {
+	// listen for CTRL-C as interrupt
+	ch := make(chan os.Signal, 1)
+	signal.Notify(ch, os.Interrupt)
+	go func() {
+		select {
+		case <-ch:
+			ctxCancelFn()
+		case <-ctx.Done():
+		}
+	}()
+}
-- 
GitLab