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 }