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, &params); 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(&params.nkeyConfig, "nkey", "", false, "generates an nkey account server configuration")
    56  	cmd.Flags().BoolVarP(&params.memResolverConfig, "mem-resolver", "", false, "generates a mem resolver server configuration")
    57  	cmd.Flags().BoolVarP(&params.natsResolverConfig, "nats-resolver", "", false, "generates a full nats resolver server configuration")
    58  	cmd.Flags().BoolVarP(&params.natsCacheResolverConfig, "nats-resolver-cache", "", false, "generates a cached nats resolver server configuration")
    59  	cmd.Flags().StringVarP(&params.outputFile, "config-file", "", "--", "output configuration file '--' is standard output (exclusive of --dir)")
    60  	cmd.Flags().StringVarP(&params.dirOut, "dir", "", "", "output configuration dir (only valid when --mem-resolver is specified)")
    61  	cmd.Flags().BoolVarP(&params.force, "force", "F", false, "overwrite output files if they exist")
    62  	cmd.Flags().StringVarP(&params.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  }