Repository: nats-io/nsc
Pull request #316 (2020-11-02 21:21:55)

Allow to overwrite an operator by same name and subject using --force
Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/addoperator.go b/cmd/addoperator.go
index 9acb3e6..ce0cba7 100644
--- a/cmd/addoperator.go
+++ b/cmd/addoperator.go
@@ -45,6 +45,7 @@ func createAddOperatorCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.name, "name", "n", "", "operator name")
 	cmd.Flags().StringVarP(&params.jwtPath, "url", "u", "", "import from a jwt server url, file, or well known operator")
 	cmd.Flags().BoolVarP(&params.sysAcc, "sys", "s", false, "generate system account with the operator")
+	cmd.Flags().BoolVarP(&params.force, "force", "", false, "on import, overwrite existing when already present")
 	params.TimeParams.BindFlags(cmd)
 
 	return cmd
@@ -62,6 +63,7 @@ type AddOperatorParams struct {
 	name     string
 	generate bool
 	sysAcc   bool
+	force    bool
 	keyPath  string
 }
 
@@ -216,9 +218,15 @@ func (p *AddOperatorParams) PostInteractive(ctx ActionCtx) error {
 func (p *AddOperatorParams) Validate(ctx ActionCtx) error {
 	var err error
 	if p.token != "" {
+		if p.sysAcc {
+			return fmt.Errorf("importing an operator is not compatible with system account generation")
+		}
 		// validated on load
 		return nil
 	}
+	if p.force {
+		return fmt.Errorf("force only works with -u")
+	}
 	if p.name == "" {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("operator name is required")
@@ -245,22 +253,29 @@ func (p *AddOperatorParams) Validate(ctx ActionCtx) error {
 		}
 	}
 
-	if p.sysAcc && p.signerKP == nil {
-		return fmt.Errorf("generating system account requires a key")
-	}
-
 	if err := p.Resolve(ctx); err != nil {
 		return err
 	}
 
+	if p.sysAcc && p.signerKP == nil {
+		return fmt.Errorf("generating system account requires a key")
+	}
+
 	return nil
 }
 
 func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
+	r := store.NewDetailedReport(false)
 	operator := &store.NamedKey{Name: p.name, KP: p.signerKP}
-	s, err := store.CreateStore(p.name, GetConfig().StoreRoot, operator)
+	s, err := GetConfig().LoadStore(p.name)
+	if s == nil {
+		s, err = store.CreateStore(p.name, GetConfig().StoreRoot, operator)
+	} else if !p.force {
+		err = fmt.Errorf("operator named %s exists already", p.name)
+		r.AddError("%v please inspect and use --force to overwrite", err)
+	}
 	if err != nil {
-		return nil, err
+		return r, err
 	}
 
 	var sAcc *keys
@@ -306,10 +321,27 @@ func (p *AddOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 		if err != nil {
 			return nil, err
 		}
-
+	} else {
+		ctx, err := s.GetContext()
+		if err != nil {
+			return nil, err
+		}
+		oc, err := ctx.Store.ReadOperatorClaim()
+		if err == nil {
+			ocNew, err := jwt.DecodeOperatorClaims(p.token)
+			if err != nil {
+				return nil, err
+			}
+			if oc.Subject != ocNew.Subject {
+				err := fmt.Errorf("existing and new operator represent different entities")
+				r.AddError("%v resolve conflict first", err)
+				return r, err
+			}
+		} else if err.(*store.ResourceErr).Err != store.ErrNotExist {
+			return nil, err
+		}
 	}
 
-	r := store.NewDetailedReport(false)
 	if p.generate && p.signerKP != nil {
 		pk, _ := p.signerKP.PublicKey()
 		r.AddOK("generated and stored operator key %q", pk)


Test patch:
diff --git a/cmd/addoperator_test.go b/cmd/addoperator_test.go
index b41a51c..19d1eb4 100644
--- a/cmd/addoperator_test.go
+++ b/cmd/addoperator_test.go
@@ -68,18 +68,29 @@ func TestImportOperator(t *testing.T) {
 	storeFile := filepath.Join(ts.Dir, "store", "O", ".nsc")
 	require.FileExists(t, storeFile)
 
-	d, err := Read(storeFile)
-	require.NoError(t, err)
-	var info store.Info
-	json.Unmarshal(d, &info)
-	require.True(t, info.Managed)
-	require.Equal(t, "O", info.Name)
+	check := func() {
+		d, err := Read(storeFile)
+		require.NoError(t, err)
+		var info store.Info
+		json.Unmarshal(d, &info)
+		require.True(t, info.Managed)
+		require.Equal(t, "O", info.Name)
+
+		target := filepath.Join(ts.Dir, "store", "O", "O.jwt")
+		require.FileExists(t, target)
+		d, err = Read(target)
+		require.NoError(t, err)
+		require.Equal(t, token, string(d))
+	}
+	check()
 
-	target := filepath.Join(ts.Dir, "store", "O", "O.jwt")
-	require.FileExists(t, target)
-	d, err = Read(target)
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf)
+	require.Error(t, err)
+
+	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf, "--force")
 	require.NoError(t, err)
-	require.Equal(t, token, string(d))
+	check()
+
 }
 
 func TestAddOperatorInteractive(t *testing.T) {