github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/root.go (about)

     1  /*
     2   * Copyright 2018-2022 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package cmd
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	cli "github.com/nats-io/cliprompts/v2"
    27  	"github.com/nats-io/nats.go"
    28  	"github.com/nats-io/nkeys"
    29  	"github.com/nats-io/nsc/v2/cmd/store"
    30  	"github.com/spf13/cobra"
    31  )
    32  
    33  var (
    34  	ConfigDirFlag   string
    35  	KeysDirFlag     string
    36  	DataDirFlag     string
    37  	AllDirFlag      string
    38  	KeyPathFlag     string
    39  	InteractiveFlag bool
    40  )
    41  
    42  var NscCwdOnly bool
    43  var ErrNoOperator = errors.New("set an operator -- 'nsc env -o operatorName'")
    44  
    45  type InterceptorFn func(ctx ActionCtx, params interface{}) error
    46  
    47  func GetStoreForOperator(operator string) (*store.Store, error) {
    48  	config := GetConfig()
    49  	if config.StoreRoot == "" {
    50  		return nil, errors.New("no stores available")
    51  	}
    52  	if err := IsValidDir(config.StoreRoot); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	if operator != "" {
    57  		config.Operator = operator
    58  	}
    59  
    60  	if config.Operator == "" {
    61  		config.SetDefaults()
    62  		if config.Operator == "" {
    63  			return nil, ErrNoOperator
    64  		}
    65  	}
    66  
    67  	fp := filepath.Join(config.StoreRoot, config.Operator)
    68  	ngsStore, err := store.LoadStore(fp)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	if config.Account != "" {
    74  		ngsStore.DefaultAccount = config.Account
    75  	}
    76  	return ngsStore, nil
    77  }
    78  
    79  func GetStore() (*store.Store, error) {
    80  	return GetStoreForOperator("")
    81  }
    82  
    83  func ResolveKeyFlag() (nkeys.KeyPair, error) {
    84  	if KeyPathFlag != "" {
    85  		kp, err := store.ResolveKey(KeyPathFlag)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		return kp, nil
    90  	}
    91  	return nil, nil
    92  }
    93  
    94  func GetRootCmd() *cobra.Command {
    95  	return rootCmd
    96  }
    97  
    98  func precheckDataStore(cmd *cobra.Command) error {
    99  	if store, _ := GetStore(); store != nil {
   100  		if c, _ := store.ReadOperatorClaim(); c != nil && c.Version == 1 {
   101  			if c.Version > 2 {
   102  				return fmt.Errorf("the store %#q is at version %d. To upgrade nsc - type `%s update`",
   103  					store.GetName(), c.Version, os.Args[0])
   104  			} else if c.Version == 1 {
   105  				allowCmdWithJWTV1Store := cmd.Name() == "upgrade-jwt" || cmd.Name() == "env" || cmd.Name() == "help" || cmd.Name() == "update"
   106  				if !allowCmdWithJWTV1Store && cmd.Name() == "operator" {
   107  					for _, v := range addCmd.Commands() {
   108  						if v == cmd {
   109  							allowCmdWithJWTV1Store = true
   110  							break
   111  						}
   112  					}
   113  				}
   114  				if !allowCmdWithJWTV1Store {
   115  					//lint:ignore ST1005 this message is shown to the user
   116  					return fmt.Errorf(`This version of nsc only supports jwtV2. 
   117  If you are using a managed service, check your provider for 
   118  instructions on how to update your project. In most cases 
   119  all you need to do is:
   120  "%s add operator --force -u <url provided by your service>"
   121  
   122  If your service is well known, such as Synadia's NGS:
   123  "%s add operator --force -u synadia"
   124  
   125  If you are the operator, and you have your operator key, to 
   126  upgrade the v1 store %#q - type:
   127  "%s upgrade-jwt"
   128  
   129  Alternatively you can downgrade' %q to a compatible version using: 
   130  "%s update --version 0.5.0"
   131  `,
   132  						os.Args[0], os.Args[0], store.GetName(), os.Args[0], os.Args[0], os.Args[0])
   133  				}
   134  			}
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func precheckKeyStore(cmd *cobra.Command) error {
   141  	if cmd.Name() == "migrate" && cmd.Parent().Name() == "keys" {
   142  		return nil
   143  	}
   144  	// check if we need to perform any kind of migration
   145  	needsUpdate, err := store.KeysNeedMigration()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	if needsUpdate {
   150  		cmd.SilenceUsage = true
   151  		return fmt.Errorf("the keystore %#q needs migration - type `%s keys migrate` to update", AbbrevHomePaths(store.GetKeysDir()), os.Args[0])
   152  	}
   153  	return nil
   154  }
   155  
   156  var rootCmd = &cobra.Command{
   157  	Use:   "nsc",
   158  	Short: "nsc creates NATS operators, accounts, users, and manage their permissions.",
   159  	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
   160  		var err error
   161  		if AllDirFlag != "" {
   162  			ConfigDirFlag = AllDirFlag
   163  			DataDirFlag = AllDirFlag
   164  			KeysDirFlag = AllDirFlag
   165  		}
   166  
   167  		// if the flag is set we use it, if not check the old env
   168  		if ConfigDirFlag != "" {
   169  			if ConfigDirFlag, err = Expand(ConfigDirFlag); err != nil {
   170  				return err
   171  			}
   172  		} else if os.Getenv(NscHomeEnv) != "" {
   173  			if ConfigDirFlag, err = Expand(os.Getenv(NscHomeEnv)); err != nil {
   174  				return err
   175  			}
   176  		}
   177  
   178  		if DataDirFlag != "" {
   179  			if DataDirFlag, err = Expand(DataDirFlag); err != nil {
   180  				return err
   181  			}
   182  		}
   183  
   184  		if KeysDirFlag != "" {
   185  			if KeysDirFlag, err = Expand(KeysDirFlag); err != nil {
   186  				return err
   187  			}
   188  		} else if os.Getenv(store.NKeysPathEnv) != "" {
   189  			if KeysDirFlag, err = Expand(os.Getenv(store.NKeysPathEnv)); err != nil {
   190  				return err
   191  			}
   192  		}
   193  
   194  		if _, err = LoadOrInit(ConfigDirFlag, DataDirFlag, KeysDirFlag); err != nil {
   195  			return err
   196  		}
   197  		// check that the store is compatible
   198  		if err := precheckDataStore(cmd); err != nil {
   199  			return err
   200  		}
   201  		// intercept migrate keys
   202  		if err := precheckKeyStore(cmd); err != nil {
   203  			return err
   204  		}
   205  		return nil
   206  	},
   207  	Run: func(cmd *cobra.Command, args []string) {
   208  		// print command help by default
   209  		cmd.Help()
   210  	},
   211  }
   212  
   213  // Execute adds all child commands to the root command and sets flags appropriately.
   214  // This is called by main.main(). It only needs to happen once to the rootCmd.
   215  func Execute() {
   216  	err := ExecuteWithWriter(rootCmd.OutOrStderr())
   217  	if err != nil {
   218  		os.Exit(1)
   219  	}
   220  }
   221  
   222  // ExecuteWithWriter adds all child commands to the root command and sets flags appropriately.
   223  // This is called by main.main(). It only needs to happen once to the rootCmd.
   224  // But writer is decided by the caller function
   225  // returns error than os.Exit(1)
   226  func ExecuteWithWriter(out io.Writer) error {
   227  	cli.SetOutput(out)
   228  	if err := GetRootCmd().Execute(); err != nil {
   229  		return err
   230  	}
   231  	return nil
   232  }
   233  
   234  func SetEnvOptions() {
   235  	if _, ok := os.LookupEnv(NscNoGitIgnoreEnv); ok {
   236  		store.NscNotGitIgnore = true
   237  	}
   238  	if _, ok := os.LookupEnv(NscCwdOnlyEnv); ok {
   239  		NscCwdOnly = true
   240  	}
   241  	if f, ok := os.LookupEnv(NscRootCasNatsEnv); ok {
   242  		rootCAsFile = strings.TrimSpace(f)
   243  		rootCAsNats = nats.RootCAs(rootCAsFile)
   244  	}
   245  	key, okKey := os.LookupEnv(NscTlsKeyNatsEnv)
   246  	cert, okCert := os.LookupEnv(NscTlsCertNatsEnv)
   247  	if okKey || okCert {
   248  		tlsKeyNats = nats.ClientCert(cert, key)
   249  	}
   250  }
   251  
   252  func init() {
   253  	SetEnvOptions()
   254  	root := GetRootCmd()
   255  	root.Flags().BoolP("version", "v", false, "version for nsc")
   256  	HoistRootFlags(root)
   257  }
   258  
   259  // HoistRootFlags adds persistent flags that would be added by the cobra framework
   260  // but are not because the unit tests are testing the command directly
   261  func HoistRootFlags(cmd *cobra.Command) *cobra.Command {
   262  	cmd.PersistentFlags().StringVarP(&KeyPathFlag, "private-key", "K", "", "Key used to sign. Can be specified as role (where applicable),\npublic key (private portion is retrieved)\nor file path to a private key or private key ")
   263  	cmd.PersistentFlags().BoolVarP(&InteractiveFlag, "interactive", "i", false, "ask questions for various settings")
   264  
   265  	cmd.PersistentFlags().StringVarP(&ConfigDirFlag, "config-dir", "", "", "nsc config directory")
   266  	cmd.PersistentFlags().StringVarP(&DataDirFlag, "data-dir", "", "", "nsc data store directory")
   267  	cmd.PersistentFlags().StringVarP(&KeysDirFlag, "keystore-dir", "", "", "nsc keystore directory")
   268  	cmd.PersistentFlags().StringVarP(&AllDirFlag, "all-dirs", "H", "", "sets --config-dir, --data-dir, and --keystore-dir to the same value")
   269  	return cmd
   270  }