github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/generateserverconfig.go (about) 1 /* 2 * Copyright 2018-2023 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 "os" 22 "path/filepath" 23 24 "github.com/nats-io/nkeys" 25 "github.com/nats-io/nsc/v2/cmd/store" 26 "github.com/spf13/cobra" 27 ) 28 29 func createServerConfigCmd() *cobra.Command { 30 var params GenerateServerConfigParams 31 cmd := &cobra.Command{ 32 Use: "config", 33 Short: "Generate an account config file for an operator", 34 Args: MaxArgs(0), 35 SilenceUsage: true, 36 Example: `nsc generate config --mem-resolver 37 nsc generate config --mem-resolver --config-file <outfile> 38 nsc generate config --mem-resolver --config-file <outfile> --force 39 nsc generate config --nats-resolver 40 nsc generate config --nats-resolver-cache 41 `, 42 RunE: func(cmd *cobra.Command, args []string) error { 43 if err := RunAction(cmd, args, ¶ms); err != nil { 44 return err 45 } 46 if params.outputFile != "" && params.outputFile != "--" { 47 cmd.Printf("Success!! - generated %#q\n", AbbrevHomePaths(params.outputFile)) 48 } 49 if params.dirOut != "" { 50 cmd.Printf("Success!! - generated %#q\n", AbbrevHomePaths(filepath.Join(params.dirOut, "resolver.conf"))) 51 } 52 return nil 53 }, 54 } 55 cmd.Flags().BoolVarP(¶ms.nkeyConfig, "nkey", "", false, "generates an nkey account server configuration") 56 cmd.Flags().BoolVarP(¶ms.memResolverConfig, "mem-resolver", "", false, "generates a mem resolver server configuration") 57 cmd.Flags().BoolVarP(¶ms.natsResolverConfig, "nats-resolver", "", false, "generates a full nats resolver server configuration") 58 cmd.Flags().BoolVarP(¶ms.natsCacheResolverConfig, "nats-resolver-cache", "", false, "generates a cached nats resolver server configuration") 59 cmd.Flags().StringVarP(¶ms.outputFile, "config-file", "", "--", "output configuration file '--' is standard output (exclusive of --dir)") 60 cmd.Flags().StringVarP(¶ms.dirOut, "dir", "", "", "output configuration dir (only valid when --mem-resolver is specified)") 61 cmd.Flags().BoolVarP(¶ms.force, "force", "F", false, "overwrite output files if they exist") 62 cmd.Flags().StringVarP(¶ms.sysAccount, "sys-account", "", "", "system account name") 63 cmd.Flags().MarkHidden("nkey") 64 cmd.Flags().MarkHidden("dir") 65 return cmd 66 } 67 68 func init() { 69 generateCmd.AddCommand(createServerConfigCmd()) 70 } 71 72 type GenerateServerConfigParams struct { 73 sysAccount string 74 dirOut string 75 outputFile string 76 force bool 77 nkeyConfig bool 78 memResolverConfig bool 79 natsResolverConfig bool 80 natsCacheResolverConfig bool 81 generator ServerConfigGenerator 82 } 83 84 func (p *GenerateServerConfigParams) SetDefaults(ctx ActionCtx) error { 85 if ctx.NothingToDo("nkey", "mem-resolver", "dir", "nats-resolver", "nats-resolver-cache") { 86 ctx.CurrentCmd().SilenceUsage = false 87 return fmt.Errorf("specify a config type option") 88 } 89 90 if p.dirOut != "" && p.nkeyConfig { 91 ctx.CurrentCmd().SilenceUsage = false 92 return fmt.Errorf("--dir is not valid with nkey configuration") 93 } 94 if (p.natsResolverConfig || p.natsCacheResolverConfig) && p.dirOut != "" { 95 ctx.CurrentCmd().SilenceUsage = false 96 return fmt.Errorf("--dir is not valid with nats-resolver configuration") 97 } 98 99 if p.dirOut != "" && p.outputFile != "--" { 100 ctx.CurrentCmd().SilenceUsage = false 101 return fmt.Errorf("--dir is exclusive of --config-file") 102 } 103 104 cfgCnt := 0 105 for _, c := range []bool{p.memResolverConfig, p.natsResolverConfig, p.natsCacheResolverConfig, p.nkeyConfig} { 106 if c { 107 cfgCnt++ 108 } 109 } 110 if cfgCnt > 1 { 111 return fmt.Errorf("can only generate one config at a time") 112 } 113 114 if p.sysAccount == "" { 115 if o, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); o != nil { 116 p.sysAccount = o.SystemAccount 117 } 118 } 119 120 if p.nkeyConfig { 121 p.generator = NewNKeyConfigBuilder() 122 } else if p.memResolverConfig { 123 p.generator = NewMemResolverConfigBuilder() 124 } else if p.natsResolverConfig { 125 p.generator = NewNatsResolverConfigBuilder(false) 126 } else if p.natsCacheResolverConfig { 127 p.generator = NewNatsResolverConfigBuilder(true) 128 } 129 return nil 130 } 131 132 func (p *GenerateServerConfigParams) PreInteractive(ctx ActionCtx) error { 133 return nil 134 } 135 136 func (p *GenerateServerConfigParams) Load(ctx ActionCtx) error { 137 return nil 138 } 139 140 func (p *GenerateServerConfigParams) PostInteractive(ctx ActionCtx) error { 141 return nil 142 } 143 144 func (p *GenerateServerConfigParams) checkFile(fp string) (string, error) { 145 if fp == "--" { 146 return fp, nil 147 } 148 afp, err := Expand(fp) 149 if err != nil { 150 return "", err 151 } 152 _, err = os.Stat(afp) 153 if err == nil { 154 // file exists, if force - delete it 155 if p.force { 156 if err := os.Remove(afp); err != nil { 157 return "", err 158 } 159 return afp, nil 160 } 161 return "", fmt.Errorf("%#q already exists", afp) 162 } 163 return afp, nil 164 } 165 166 func (p *GenerateServerConfigParams) checkDir(fp string) (string, error) { 167 if fp == "--" { 168 return fp, nil 169 } 170 afp, err := Expand(fp) 171 if err != nil { 172 return "", err 173 } 174 175 fi, err := os.Stat(afp) 176 if err == nil { 177 if !p.force { 178 return "", fmt.Errorf("%#q already exists", fp) 179 } 180 // file exists, if force - delete it 181 if !fi.IsDir() { 182 return "", fmt.Errorf("%#q already exists and is not a directory", fp) 183 } 184 } 185 return afp, nil 186 } 187 188 func (p *GenerateServerConfigParams) Validate(ctx ActionCtx) error { 189 var err error 190 if p.outputFile != "" { 191 p.outputFile, err = p.checkFile(p.outputFile) 192 if err != nil { 193 return err 194 } 195 } 196 if p.dirOut != "" { 197 p.dirOut, err = p.checkDir(p.dirOut) 198 if err != nil { 199 return err 200 } 201 if err := p.generator.SetOutputDir(p.dirOut); err != nil { 202 return err 203 } 204 } 205 206 if p.sysAccount != "" { 207 accSubj := "" 208 if nkeys.IsValidPublicAccountKey(p.sysAccount) { 209 accSubj = p.sysAccount 210 } else { 211 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAccount) 212 if err != nil { 213 return fmt.Errorf("error reading account %q: %v", p.sysAccount, err) 214 } 215 accSubj = ac.Subject 216 } 217 if err := p.generator.SetSystemAccount(accSubj); err != nil { 218 return err 219 } 220 } 221 222 if ctx.StoreCtx().Operator.Name == "" { 223 return errors.New("set an operator first - 'nsc env --operator <name>'") 224 } 225 return nil 226 } 227 228 func (p *GenerateServerConfigParams) Run(ctx ActionCtx) (store.Status, error) { 229 s := ctx.StoreCtx().Store 230 231 op, err := s.Read(store.JwtName(s.GetName())) 232 if err != nil { 233 return nil, err 234 } 235 p.generator.Add(op) 236 237 names, err := GetConfig().ListAccounts() 238 if err != nil { 239 return nil, err 240 } 241 if len(names) == 0 { 242 return nil, fmt.Errorf("operator %q has no accounts", GetConfig().Operator) 243 } 244 245 for _, n := range names { 246 d, err := s.Read(store.Accounts, n, store.JwtName(n)) 247 if err != nil { 248 return nil, err 249 } 250 p.generator.Add(d) 251 252 if p.nkeyConfig { 253 users, err := s.ListEntries(store.Accounts, n, store.Users) 254 if err != nil { 255 return nil, err 256 } 257 for _, u := range users { 258 d, err := s.Read(store.Accounts, n, store.Users, store.JwtName(u)) 259 if err != nil { 260 return nil, err 261 } 262 p.generator.Add(d) 263 } 264 } 265 } 266 267 d, err := p.generator.Generate() 268 if err != nil { 269 return nil, err 270 } 271 if err := Write(p.outputFile, d); err != nil { 272 return nil, err 273 } 274 if !IsStdOut(p.outputFile) { 275 return store.OKStatus("wrote server configuration to %#q", AbbrevHomePaths(p.outputFile)), nil 276 } 277 return nil, err 278 } 279 280 type ServerConfigGenerator interface { 281 Add(rawClaim []byte) error 282 Generate() ([]byte, error) 283 SetOutputDir(fp string) error 284 SetSystemAccount(pubkey string) error 285 }