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 }