github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/defaults.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  	"encoding/json"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/nats-io/jwt/v2"
    27  	"github.com/nats-io/nats.go"
    28  	"github.com/nats-io/nsc/v2/cmd/store"
    29  	"github.com/nats-io/nsc/v2/home"
    30  )
    31  
    32  // NscHomeEnv the folder for the config file
    33  const NscHomeEnv = "NSC_HOME"
    34  const NscCwdOnlyEnv = "NSC_CWD_ONLY"
    35  const NscNoGitIgnoreEnv = "NSC_NO_GIT_IGNORE"
    36  const NscRootCasNatsEnv = "NATS_CA"
    37  const NscTlsKeyNatsEnv = "NATS_KEY"
    38  const NscTlsCertNatsEnv = "NATS_CERT"
    39  
    40  type ToolConfig struct {
    41  	ContextConfig
    42  	GithubUpdates string `json:"github_updates"` // git hub repo
    43  	LastUpdate    int64  `json:"last_update"`
    44  }
    45  
    46  var config ToolConfig
    47  var rootCAsNats nats.Option // Will be skipped, when nil and passed to a connection
    48  var tlsKeyNats nats.Option  // Will be skipped, when nil and passed to a connection
    49  var tlsCertNats nats.Option // Will be skipped, when nil and passed to a connection
    50  var rootCAsFile string
    51  
    52  func GetToolName() string {
    53  	return "nsc"
    54  }
    55  
    56  // GetConfig returns the global config
    57  func GetConfig() *ToolConfig {
    58  	return &config
    59  }
    60  
    61  func GetCwdCtx() *ContextConfig {
    62  	var ctx ContextConfig
    63  	cwd, err := os.Getwd()
    64  	if err != nil {
    65  		return nil
    66  	}
    67  	dir := cwd
    68  	info, ok, err := isOperatorDir(dir)
    69  	if err != nil {
    70  		return nil
    71  	}
    72  	if ok {
    73  		ctx.StoreRoot = filepath.Dir(dir)
    74  		ctx.Operator = info.Name
    75  		return &ctx
    76  	}
    77  
    78  	// search down
    79  	dirEntries, err := os.ReadDir(dir)
    80  	if err != nil {
    81  		return nil
    82  	}
    83  	for _, v := range dirEntries {
    84  		if !v.IsDir() {
    85  			continue
    86  		}
    87  		_, ok, err := isOperatorDir(filepath.Join(dir, v.Name()))
    88  		if err != nil {
    89  			return nil
    90  		}
    91  		if ok {
    92  			ctx.StoreRoot = dir
    93  			return &ctx
    94  		}
    95  	}
    96  
    97  	// search up
    98  	dir = cwd
    99  	for {
   100  		info, ok, err := isOperatorDir(dir)
   101  		if err != nil {
   102  			return nil
   103  		}
   104  		if ok {
   105  			ctx.StoreRoot = filepath.Dir(dir)
   106  			ctx.Operator = info.Name
   107  			sep := string(os.PathSeparator)
   108  			name := fmt.Sprintf("%s%s%s%s%s", sep, info.Name, sep, store.Accounts, sep)
   109  			idx := strings.Index(cwd, name)
   110  			if idx != -1 {
   111  				prefix := cwd[:idx+len(name)]
   112  				sub, err := filepath.Rel(prefix, cwd)
   113  				if err == nil && len(sub) > 0 {
   114  					names := strings.Split(sub, sep)
   115  
   116  					if len(names) > 0 {
   117  						ctx.Account = names[0]
   118  					}
   119  				}
   120  			}
   121  			return &ctx
   122  		}
   123  		pdir := filepath.Dir(dir)
   124  		if pdir == dir {
   125  			// not found
   126  			return nil
   127  		}
   128  		dir = pdir
   129  	}
   130  }
   131  
   132  func isOperatorDir(dir string) (store.Info, bool, error) {
   133  	var v store.Info
   134  	fi, err := os.Stat(dir)
   135  	if os.IsNotExist(err) {
   136  		return v, false, nil
   137  	}
   138  	if err != nil {
   139  		return v, false, err
   140  	}
   141  	if !fi.IsDir() {
   142  		return v, false, nil
   143  	}
   144  	tf := filepath.Join(dir, ".nsc")
   145  	fi, err = os.Stat(tf)
   146  	if os.IsNotExist(err) {
   147  		return v, false, nil
   148  	}
   149  	if err != nil {
   150  		return v, false, nil
   151  	}
   152  	if !fi.IsDir() {
   153  		d, err := os.ReadFile(tf)
   154  		if err != nil {
   155  			return v, false, err
   156  		}
   157  		err = json.Unmarshal(d, &v)
   158  		if err != nil {
   159  			return v, false, err
   160  		}
   161  		if v.Name != "" && v.Kind == jwt.OperatorClaim {
   162  			return v, true, nil
   163  		}
   164  	}
   165  	return v, false, nil
   166  }
   167  
   168  func GetConfigDir() string {
   169  	if ConfigDirFlag == "" {
   170  		// this is running under a test...
   171  		return os.Getenv(NscHomeEnv)
   172  	}
   173  	return ConfigDirFlag
   174  }
   175  
   176  func LoadOrInit(configDir string, dataDir string, keystoreDir string) (*ToolConfig, error) {
   177  	const github = "nats-io/nsc"
   178  
   179  	if configDir == "" {
   180  		configDir = home.NscConfigHome()
   181  	}
   182  	ConfigDirFlag = configDir
   183  	if err := MaybeMakeDir(ConfigDirFlag); err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	dataDirFlagSet := dataDir != ""
   188  	if dataDir == "" {
   189  		dataDir = home.NscDataHome(home.StoresSubDirName)
   190  	}
   191  	// dir created if files written
   192  	DataDirFlag = dataDir
   193  
   194  	if keystoreDir == "" {
   195  		keystoreDir = home.NscDataHome(home.KeysSubDirName)
   196  	}
   197  	// dir created if keys added
   198  	KeysDirFlag = keystoreDir
   199  	store.KeyStorePath = KeysDirFlag
   200  
   201  	if err := config.load(); err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	if !NscCwdOnly && (ToolConfig{}) == config {
   206  		// is the struct modified from the file - so this is a first run here
   207  		config.GithubUpdates = github
   208  
   209  		config.LastUpdate = time.Now().UTC().Unix() // this is not "true" but avoids the initial check
   210  		config.StoreRoot = dataDir
   211  		if err := MaybeMakeDir(config.StoreRoot); err != nil {
   212  			return nil, fmt.Errorf("error creating store root: %v", err)
   213  		}
   214  
   215  		// load any default entries if there are any
   216  		config.SetDefaults()
   217  
   218  		if err := config.Save(); err != nil {
   219  			return nil, err
   220  		}
   221  	} else {
   222  		ctx := GetCwdCtx()
   223  		if ctx != nil {
   224  			config.StoreRoot = ctx.StoreRoot
   225  			if ctx.Operator != "" {
   226  				config.Operator = ctx.Operator
   227  				if ctx.Account != "" {
   228  					config.Account = ctx.Account
   229  				}
   230  			}
   231  			config.SetDefaults()
   232  		}
   233  	}
   234  	if dataDirFlagSet {
   235  		config.StoreRoot = dataDir
   236  	}
   237  
   238  	// trigger updating defaults
   239  	config.SetDefaults()
   240  
   241  	return &config, nil
   242  }
   243  
   244  func SetVersion(version string) {
   245  	// sem version gets very angry if there's a v in the release
   246  	if strings.HasPrefix(version, "v") || strings.HasPrefix(version, "V") {
   247  		version = version[1:]
   248  	}
   249  	GetRootCmd().Version = version
   250  }
   251  
   252  func (d *ToolConfig) SetVersion(version string) {
   253  	SetVersion(version)
   254  }
   255  
   256  func (d *ToolConfig) load() error {
   257  	if NscCwdOnly {
   258  		// don't read it
   259  		return nil
   260  	}
   261  	err := ReadJson(d.configFile(), &config)
   262  	if err != nil && os.IsNotExist(err) {
   263  		return nil
   264  	}
   265  	return err
   266  }
   267  
   268  func (d *ToolConfig) Save() error {
   269  	d.SetDefaults()
   270  	if !NscCwdOnly {
   271  		return WriteJson(d.configFile(), d)
   272  	}
   273  	return nil
   274  }
   275  
   276  func (d *ToolConfig) configFile() string {
   277  	return filepath.Join(GetConfigDir(), "nsc.json")
   278  }