From 5b5554ec45837a22ba016d75084e060ba44fa149 Mon Sep 17 00:00:00 2001 From: Adesh Deshmukh Date: Wed, 17 Jun 2026 09:04:06 +0530 Subject: [PATCH 1/3] fix(stop): add --name flag to stop named contexts and instances The stop command previously only supported stopping the current context, with no way to target a specific context or instance by name. This adds a --name flag that resolves the target via two-step lookup: 1. Try the name as a context name directly (handles login --name) 2. Fall back to scanning contexts by Instance field (handles start --name, where context name is the server URL) Fixes #438 Signed-off-by: Adesh Deshmukh --- cmd/stop.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/cmd/stop.go b/cmd/stop.go index eaba91fd..6363a99a 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -11,11 +11,17 @@ import ( ) func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { + var name string var stopCmd = &cobra.Command{ Use: "stop", Short: "stop microcks instance", Long: "stop microcks instance", + Example: `# Stop the instance from the current context +microcks stop + +# Stop by context name or instance name +microcks stop --name myinstance`, Run: func(cmd *cobra.Command, args []string) { configFile := globalClientOpts.ConfigPath @@ -27,8 +33,27 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { return } - ctx, err := localConfig.ResolveContext("") - errors.CheckError(err) + var ctx *config.Context + if name != "" { + ctx, err = localConfig.ResolveContext(name) + if err != nil { + var ctxRef *config.ContextRef + for _, c := range localConfig.Contexts { + if c.Instance == name { + ctxRef = &c + break + } + } + if ctxRef == nil { + log.Fatalf("No context found for '%s'", name) + } + ctx, err = localConfig.ResolveContext(ctxRef.Name) + errors.CheckError(err) + } + } else { + ctx, err = localConfig.ResolveContext("") + errors.CheckError(err) + } instance := ctx.Instance if instance.Name == "" { @@ -73,5 +98,7 @@ func NewStopCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { }, } + stopCmd.Flags().StringVar(&name, "name", "", "Name of the context or instance to stop (uses current context if empty)") + return stopCmd } From 7b4ece9f9f38dacfa07eb65163b76dcd4ad4d870 Mon Sep 17 00:00:00 2001 From: Adesh Deshmukh Date: Sun, 21 Jun 2026 17:33:25 +0530 Subject: [PATCH 2/3] fix(stop): use indexed loop for pointer-safe context lookup Signed-off-by: Adesh Deshmukh --- cmd/stop.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/stop.go b/cmd/stop.go index 6363a99a..ccea24d4 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -38,9 +38,9 @@ microcks stop --name myinstance`, ctx, err = localConfig.ResolveContext(name) if err != nil { var ctxRef *config.ContextRef - for _, c := range localConfig.Contexts { - if c.Instance == name { - ctxRef = &c + for i := range localConfig.Contexts { + if localConfig.Contexts[i].Instance == name { + ctxRef = &localConfig.Contexts[i] break } } From 7c960ac4c6488da2b19a76e6512dcd5f3f39d0d4 Mon Sep 17 00:00:00 2001 From: Adesh Deshmukh Date: Thu, 16 Jul 2026 20:44:48 +0530 Subject: [PATCH 3/3] fix(stop): extract resolveStopTarget and add tests Extract context resolution logic into resolveStopTarget() function with explicit precedence comment (context name checked first). Add 5 targeted tests: resolve by context name, instance name, not-found, collision (context wins), and empty name fallback. Signed-off-by: Adesh Deshmukh --- cmd/stop.go | 35 +++++++------- cmd/stop_test.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 cmd/stop_test.go diff --git a/cmd/stop.go b/cmd/stop.go index ccea24d4..89dc62cd 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -35,25 +35,11 @@ microcks stop --name myinstance`, var ctx *config.Context if name != "" { - ctx, err = localConfig.ResolveContext(name) - if err != nil { - var ctxRef *config.ContextRef - for i := range localConfig.Contexts { - if localConfig.Contexts[i].Instance == name { - ctxRef = &localConfig.Contexts[i] - break - } - } - if ctxRef == nil { - log.Fatalf("No context found for '%s'", name) - } - ctx, err = localConfig.ResolveContext(ctxRef.Name) - errors.CheckError(err) - } + ctx, err = resolveStopTarget(name, localConfig) } else { ctx, err = localConfig.ResolveContext("") - errors.CheckError(err) } + errors.CheckError(err) instance := ctx.Instance if instance.Name == "" { @@ -102,3 +88,20 @@ microcks stop --name myinstance`, return stopCmd } + +// resolveStopTarget resolves a stop target by name. +// Context name is checked first because it is the explicit user-assigned +// identifier. Instance name is a secondary label (from start --name) — +// used as fallback when no context with the given name exists. +func resolveStopTarget(name string, cfg *config.LocalConfig) (*config.Context, error) { + ctx, err := cfg.ResolveContext(name) + if err == nil { + return ctx, nil + } + for i := range cfg.Contexts { + if cfg.Contexts[i].Instance == name { + return cfg.ResolveContext(cfg.Contexts[i].Name) + } + } + return nil, fmt.Errorf("no context found for '%s'", name) +} diff --git a/cmd/stop_test.go b/cmd/stop_test.go new file mode 100644 index 00000000..9d0ab3e6 --- /dev/null +++ b/cmd/stop_test.go @@ -0,0 +1,120 @@ +/* + * Copyright The Microcks Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "testing" + + "github.com/microcks/microcks-cli/pkg/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolveStopTargetByContextName(t *testing.T) { + cfg := &config.LocalConfig{ + Contexts: []config.ContextRef{ + {Name: "dev", Server: "http://dev-server:8080", User: "u1"}, + }, + Servers: []config.Server{ + {Server: "http://dev-server:8080"}, + }, + Users: []config.User{ + {Name: "u1"}, + }, + } + + ctx, err := resolveStopTarget("dev", cfg) + require.NoError(t, err) + assert.Equal(t, "dev", ctx.Name) +} + +func TestResolveStopTargetByInstanceName(t *testing.T) { + cfg := &config.LocalConfig{ + Contexts: []config.ContextRef{ + {Name: "http://server", Server: "http://instance-server:8080", User: "u1", Instance: "myinst"}, + }, + Servers: []config.Server{ + {Server: "http://instance-server:8080"}, + }, + Users: []config.User{ + {Name: "u1"}, + }, + } + + ctx, err := resolveStopTarget("myinst", cfg) + require.NoError(t, err) + assert.Equal(t, "http://server", ctx.Name) +} + +func TestResolveStopTargetNotFound(t *testing.T) { + cfg := &config.LocalConfig{ + Contexts: []config.ContextRef{ + {Name: "dev", Server: "http://dev-server:8080", User: "u1"}, + }, + Servers: []config.Server{ + {Server: "http://dev-server:8080"}, + }, + Users: []config.User{ + {Name: "u1"}, + }, + } + + _, err := resolveStopTarget("nonexistent", cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "nonexistent") +} + +func TestResolveStopTargetCollisionContextWins(t *testing.T) { + cfg := &config.LocalConfig{ + Contexts: []config.ContextRef{ + {Name: "shared", Server: "http://server-a:8080", User: "u1", Instance: "other"}, + {Name: "different", Server: "http://server-b:8080", User: "u2", Instance: "shared"}, + }, + Servers: []config.Server{ + {Server: "http://server-a:8080"}, + {Server: "http://server-b:8080"}, + }, + Users: []config.User{ + {Name: "u1"}, + {Name: "u2"}, + }, + } + + ctx, err := resolveStopTarget("shared", cfg) + require.NoError(t, err) + assert.Equal(t, "shared", ctx.Name) + assert.Equal(t, "http://server-a:8080", ctx.Server.Server) +} + +func TestResolveStopTargetEmptyName(t *testing.T) { + cfg := &config.LocalConfig{ + CurrentContext: "dev", + Contexts: []config.ContextRef{ + {Name: "dev", Server: "http://dev-server:8080", User: "u1"}, + }, + Servers: []config.Server{ + {Server: "http://dev-server:8080"}, + }, + Users: []config.User{ + {Name: "u1"}, + }, + } + + ctx, err := resolveStopTarget("", cfg) + require.NoError(t, err) + assert.Equal(t, "dev", ctx.Name) +}