github.com/kbehouse/nsc@v0.0.6/cmd/root.go (about)

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