From 6ed7af6603ff87f8bec649ae58b07d04cd5fc83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9ssica=20Lins?= Date: Fri, 3 Jun 2022 10:20:37 +0200 Subject: [PATCH] Add rm context command (#30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rm context cmd Signed-off-by: Jéssica Lins * Update cmd description Signed-off-by: Jéssica Lins * Add TestRemoveContext Signed-off-by: Jéssica Lins * Update documentation Signed-off-by: Jéssica Lins * Update README.md Co-authored-by: Matej Gera <38492574+matej-g@users.noreply.github.com> Co-authored-by: Matej Gera <38492574+matej-g@users.noreply.github.com> --- README.md | 5 ++- pkg/cmd/context.go | 25 +++++++++++- pkg/config/config.go | 13 +++++++ pkg/config/config_test.go | 80 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3cb0ebc..5a14035 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,10 @@ Usage: obsctl context [command] Available Commands: - api Add/edit API configuration. + api Add/edit/remove API configuration. current View current context configuration. list View all context configuration. + rm Remove context configuration. switch Switch to another context. Flags: @@ -142,6 +143,8 @@ Global Flags: Use "obsctl context [command] --help" for more information about a command. ``` +You can also remove a context by using `obsctl context rm /`. In case an API configuration does not have a tenant associated with it, the API configuration can be removed using `obsctl context api rm `. + ### Metrics You can use `obsctl metrics` to get/set metrics-based resources. diff --git a/pkg/cmd/context.go b/pkg/cmd/context.go index 86ced33..39ff570 100644 --- a/pkg/cmd/context.go +++ b/pkg/cmd/context.go @@ -19,8 +19,8 @@ func NewContextCommand(ctx context.Context) *cobra.Command { apiCmd := &cobra.Command{ Use: "api", - Short: "Add/edit API configuration.", - Long: "Add/edit API configuration.", + Short: "Add/edit/remove API configuration.", + Long: "Add/edit/remove API configuration.", } var addURL, addName string @@ -136,10 +136,31 @@ func NewContextCommand(ctx context.Context) *cobra.Command { }, } + rmCmd := &cobra.Command{ + Use: "rm /", + Short: "Remove context configuration.", + Long: "Remove context configuration.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cntxt := strings.Split(args[0], "/") + if len(cntxt) != 2 { + return fmt.Errorf("invalid context name: use format /") + } + + conf, err := config.Read(logger) + if err != nil { + return err + } + + return conf.RemoveContext(logger, cntxt[0], cntxt[1]) + }, + } + cmd.AddCommand(apiCmd) cmd.AddCommand(switchCmd) cmd.AddCommand(currentCmd) cmd.AddCommand(listCmd) + cmd.AddCommand(rmCmd) apiCmd.AddCommand(apiAddCmd) apiCmd.AddCommand(apiRmCmd) diff --git a/pkg/config/config.go b/pkg/config/config.go index 5258eb4..3e00250 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -363,3 +363,16 @@ func (c *Config) SetCurrentContext(logger log.Logger, api string, tenant string) return c.Save(logger) } + +// RemoveContext removes the specified context /. If the API configuration has only one tenant, +// the API configuration is removed. +func (c *Config) RemoveContext(logger log.Logger, api string, tenant string) error { + // If there is only one tenant per API configuration, remove the whole API configuration. + if _, ok := c.APIs[api].Contexts[tenant]; ok { + if len(c.APIs[api].Contexts) == 1 { + return c.RemoveAPI(logger, api) + } + } + + return c.RemoveTenant(logger, tenant, api) +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e35d205..803dab3 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -920,3 +920,83 @@ func TestSetCurrentContext(t *testing.T) { }) }) } + +func TestRemoveContext(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "test-save") + testutil.Ok(t, err) + t.Cleanup(func() { testutil.Ok(t, os.RemoveAll(tmpDir)) }) + testutil.Ok(t, os.MkdirAll(filepath.Join(tmpDir, "obsctl", "test"), os.ModePerm)) + testutil.Ok(t, ioutil.WriteFile(filepath.Join(tmpDir, "obsctl", "test", "config.json"), []byte(""), os.ModePerm)) + testutil.Ok(t, os.Setenv("OBSCTL_CONFIG_PATH", filepath.Join(tmpDir, "obsctl", "test", "config.json"))) + + tlogger := level.NewFilter(log.NewJSONLogger(log.NewSyncWriter(os.Stderr)), level.AllowDebug()) + + t.Run("empty config", func(t *testing.T) { + cfg := Config{ + pathOverride: filepath.Join(tmpDir, "obsctl", "test", "config.json"), + } + + err := cfg.RemoveContext(tlogger, "stage", "first") + testutil.NotOk(t, err) + testutil.Equals(t, fmt.Errorf("api with name stage doesn't exist"), err) + }) + + t.Run("config with one API no tenant", func(t *testing.T) { + cfg := Config{ + pathOverride: filepath.Join(tmpDir, "obsctl", "test", "config.json"), + APIs: map[string]APIConfig{ + "stage": {URL: "https://stage.api:9090", Contexts: nil}, + }, + } + + err := cfg.RemoveContext(tlogger, "stage", "first") + + testutil.NotOk(t, err) + testutil.Equals(t, fmt.Errorf("tenant with name first doesn't exist in api stage"), err) + }) + + t.Run("config with one API and one tenant", func(t *testing.T) { + cfg := Config{ + pathOverride: filepath.Join(tmpDir, "obsctl", "test", "config.json"), + APIs: map[string]APIConfig{ + "stage": {URL: "https://stage.api:9090", Contexts: map[string]TenantConfig{ + "first": {Tenant: "first", OIDC: &OIDCConfig{Audience: "obs", ClientID: "first", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + }}, + }, + } + + testutil.Ok(t, cfg.RemoveContext(tlogger, "stage", "first")) + + testutil.Equals(t, cfg.APIs, map[string]APIConfig{}) + }) + + t.Run("config with multiple APIs and tenants", func(t *testing.T) { + cfg := Config{ + pathOverride: filepath.Join(tmpDir, "obsctl", "test", "config.json"), + APIs: map[string]APIConfig{ + "stage": {URL: "https://stage.api:9090", Contexts: map[string]TenantConfig{ + "first": {Tenant: "first", OIDC: &OIDCConfig{Audience: "obs", ClientID: "first", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + "second": {Tenant: "second", OIDC: &OIDCConfig{Audience: "obs", ClientID: "second", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + }}, + "prod": {URL: "https://prod.api:9090", Contexts: map[string]TenantConfig{ + "first": {Tenant: "first", OIDC: &OIDCConfig{Audience: "obs", ClientID: "first", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + "second": {Tenant: "second", OIDC: &OIDCConfig{Audience: "obs", ClientID: "second", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + }}, + }, + } + + testutil.Ok(t, cfg.RemoveContext(tlogger, "stage", "second")) + testutil.Ok(t, cfg.RemoveContext(tlogger, "prod", "first")) + + exp := map[string]APIConfig{ + "stage": {URL: "https://stage.api:9090", Contexts: map[string]TenantConfig{ + "first": {Tenant: "first", OIDC: &OIDCConfig{Audience: "obs", ClientID: "first", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + }}, + "prod": {URL: "https://prod.api:9090", Contexts: map[string]TenantConfig{ + "second": {Tenant: "second", OIDC: &OIDCConfig{Audience: "obs", ClientID: "second", ClientSecret: "secret", IssuerURL: "sso.obs.com"}}, + }}, + } + + testutil.Equals(t, cfg.APIs, exp) + }) +}