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