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