github.com/kbehouse/nsc@v0.0.6/cmd/defaults.go (about)

     1  /*
     2   * Copyright 2018-2020 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  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/kbehouse/nsc/cmd/store"
    29  	"github.com/mitchellh/go-homedir"
    30  	"github.com/nats-io/jwt/v2"
    31  	"github.com/nats-io/nats.go"
    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 toolName = strings.ReplaceAll(filepath.Base(os.Args[0]), ".exe", "")
    49  var toolName = "nsc"
    50  var config ToolConfig
    51  var toolHome string
    52  var homeEnv string
    53  var rootCAsNats nats.Option // Will be skipped, when nil and passed to a connection
    54  var tlsKeyNats nats.Option  // Will be skipped, when nil and passed to a connection
    55  var tlsCertNats nats.Option // Will be skipped, when nil and passed to a connection
    56  
    57  var rootCAsFile string
    58  
    59  func SetToolName(name string) {
    60  	toolName = name
    61  }
    62  
    63  func GetToolName() string {
    64  	return toolName
    65  }
    66  
    67  // GetConfig returns the global config
    68  func GetConfig() *ToolConfig {
    69  	return &config
    70  }
    71  
    72  func GetCwdCtx() *ContextConfig {
    73  	var ctx ContextConfig
    74  	cwd, err := os.Getwd()
    75  	if err != nil {
    76  		return nil
    77  	}
    78  	dir := cwd
    79  	info, ok, err := isOperatorDir(dir)
    80  	if err != nil {
    81  		return nil
    82  	}
    83  	if ok {
    84  		ctx.StoreRoot = filepath.Dir(dir)
    85  		ctx.Operator = info.Name
    86  		return &ctx
    87  	}
    88  
    89  	// search down
    90  	infos, err := ioutil.ReadDir(dir)
    91  	if err != nil {
    92  		return nil
    93  	}
    94  	for _, v := range infos {
    95  		if !v.IsDir() {
    96  			continue
    97  		}
    98  		_, ok, err := isOperatorDir(filepath.Join(dir, v.Name()))
    99  		if err != nil {
   100  			return nil
   101  		}
   102  		if ok {
   103  			ctx.StoreRoot = dir
   104  			return &ctx
   105  		}
   106  	}
   107  
   108  	// search up
   109  	dir = cwd
   110  	for {
   111  		info, ok, err := isOperatorDir(dir)
   112  		if err != nil {
   113  			return nil
   114  		}
   115  		if ok {
   116  			ctx.StoreRoot = filepath.Dir(dir)
   117  			ctx.Operator = info.Name
   118  			sep := string(os.PathSeparator)
   119  			name := fmt.Sprintf("%s%s%s%s%s", sep, info.Name, sep, store.Accounts, sep)
   120  			idx := strings.Index(cwd, name)
   121  			if idx != -1 {
   122  				prefix := cwd[:idx+len(name)]
   123  				sub, err := filepath.Rel(prefix, cwd)
   124  				if err == nil && len(sub) > 0 {
   125  					names := strings.Split(sub, sep)
   126  
   127  					if len(names) > 0 {
   128  						ctx.Account = names[0]
   129  					}
   130  				}
   131  			}
   132  			return &ctx
   133  		}
   134  		pdir := filepath.Dir(dir)
   135  		if pdir == dir {
   136  			// not found
   137  			return nil
   138  		}
   139  		dir = pdir
   140  	}
   141  }
   142  
   143  func isOperatorDir(dir string) (store.Info, bool, error) {
   144  	var v store.Info
   145  	fi, err := os.Stat(dir)
   146  	if os.IsNotExist(err) {
   147  		return v, false, nil
   148  	}
   149  	if err != nil {
   150  		return v, false, err
   151  	}
   152  	if !fi.IsDir() {
   153  		return v, false, nil
   154  	}
   155  	tf := filepath.Join(dir, ".nsc")
   156  	fi, err = os.Stat(tf)
   157  	if os.IsNotExist(err) {
   158  		return v, false, nil
   159  	}
   160  	if err != nil {
   161  		return v, false, nil
   162  	}
   163  	if !fi.IsDir() {
   164  		d, err := ioutil.ReadFile(tf)
   165  		if err != nil {
   166  			return v, false, err
   167  		}
   168  		err = json.Unmarshal(d, &v)
   169  		if err != nil {
   170  			return v, false, err
   171  		}
   172  		if v.Name != "" && v.Kind == jwt.OperatorClaim {
   173  			return v, true, nil
   174  		}
   175  	}
   176  	return v, false, nil
   177  }
   178  
   179  func LoadOrInit(github string, toolHomeEnvName string) (*ToolConfig, error) {
   180  	var err error
   181  	if toolHomeEnvName == "" {
   182  		return nil, errors.New("toolHomeEnv is required")
   183  	}
   184  	homeEnv = toolHomeEnvName
   185  
   186  	toolHome, err = initToolHome(toolHomeEnvName)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	if err := config.load(); err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// is the struct modified from the file
   196  	if (ToolConfig{}) == config {
   197  		config.GithubUpdates = github
   198  
   199  		config.LastUpdate = time.Now().UTC().Unix() // this is not "true" but avoids the initial check
   200  		// ~/.ngs_cli/nats
   201  		config.StoreRoot = filepath.Join(toolHome, "nats")
   202  		if err := MaybeMakeDir(config.StoreRoot); err != nil {
   203  			return nil, fmt.Errorf("error creating store root: %v", err)
   204  		}
   205  
   206  		// load any default entries if there are any
   207  		config.SetDefaults()
   208  
   209  		if err := config.Save(); err != nil {
   210  			return nil, err
   211  		}
   212  	} else {
   213  		ctx := GetCwdCtx()
   214  		if ctx != nil {
   215  			config.StoreRoot = ctx.StoreRoot
   216  			if ctx.Operator != "" {
   217  				config.Operator = ctx.Operator
   218  				if ctx.Account != "" {
   219  					config.Account = ctx.Account
   220  				}
   221  			}
   222  			config.SetDefaults()
   223  		}
   224  	}
   225  
   226  	// trigger updating defaults
   227  	config.SetDefaults()
   228  
   229  	return &config, nil
   230  }
   231  
   232  func (d *ToolConfig) SetVersion(version string) {
   233  	// sem version gets very angry if there's a v in the release
   234  	if strings.HasPrefix(version, "v") || strings.HasPrefix(version, "V") {
   235  		version = version[1:]
   236  	}
   237  	GetRootCmd().Version = version
   238  }
   239  
   240  func (d *ToolConfig) load() error {
   241  	if NscCwdOnly {
   242  		// don't read it
   243  		return nil
   244  	}
   245  	err := ReadJson(d.configFile(), &config)
   246  	if err != nil && os.IsNotExist(err) {
   247  		return nil
   248  	}
   249  	return err
   250  }
   251  
   252  func (d *ToolConfig) Save() error {
   253  	d.SetDefaults()
   254  	if !NscCwdOnly {
   255  		return WriteJson(d.configFile(), d)
   256  	}
   257  	return nil
   258  }
   259  
   260  // GetToolHome returns the . folder used fro this CLIs config and optionally the projects
   261  func initToolHome(envVarName string) (string, error) {
   262  	toolHome = os.Getenv(envVarName)
   263  
   264  	if toolHome == "" {
   265  		dir, err := homedir.Dir()
   266  		if err != nil {
   267  			return "", fmt.Errorf("error getting homedir: %v", err.Error())
   268  		}
   269  		toolHome = filepath.Join(dir, fmt.Sprintf(".%s", GetToolName()))
   270  	}
   271  
   272  	if err := MaybeMakeDir(toolHome); err != nil {
   273  		return "", fmt.Errorf("error creating tool home %#q: %v", toolHome, err)
   274  	}
   275  
   276  	return toolHome, nil
   277  
   278  }
   279  
   280  func (d *ToolConfig) configFile() string {
   281  	configFileName := fmt.Sprintf("%s.json", GetToolName())
   282  	return filepath.Join(toolHome, configFileName)
   283  }