github.com/nuvolaris/nuv@v0.0.0-20240511174247-a74e3a52bfd8/main.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  //	http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  package main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"slices"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/mitchellh/go-homedir"
    31  	"github.com/nuvolaris/nuv/auth"
    32  	"github.com/nuvolaris/nuv/config"
    33  	"github.com/nuvolaris/nuv/tools"
    34  
    35  	_ "embed"
    36  )
    37  
    38  func setupCmd(me string) (string, error) {
    39  	if os.Getenv("NUV_CMD") != "" {
    40  		return os.Getenv("NUV_CMD"), nil
    41  	}
    42  
    43  	// look in path
    44  	me, err := exec.LookPath(me)
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  	trace("found", me)
    49  
    50  	// resolve links
    51  	fileInfo, err := os.Lstat(me)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	if fileInfo.Mode()&os.ModeSymlink != 0 {
    56  		me, err = os.Readlink(me)
    57  		if err != nil {
    58  			return "", err
    59  		}
    60  		trace("resolving link to", me)
    61  	}
    62  
    63  	// get the absolute path
    64  	me, err = filepath.Abs(me)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  	trace("ME:", me)
    69  	//nolint:errcheck
    70  	os.Setenv("NUV_CMD", me)
    71  	return me, nil
    72  }
    73  
    74  func setupBinPath(cmd string) {
    75  	// initialize tools (used by the shell to find myself)
    76  	if os.Getenv("NUV_BIN") == "" {
    77  		os.Setenv("NUV_BIN", filepath.Dir(cmd))
    78  	}
    79  	os.Setenv("PATH", fmt.Sprintf("%s%c%s", os.Getenv("NUV_BIN"), os.PathListSeparator, os.Getenv("PATH")))
    80  	debugf("PATH=%s", os.Getenv("PATH"))
    81  
    82  	//subpath := fmt.Sprintf("\"%s\"%c\"%s\"", os.Getenv("NUV_BIN"), os.PathListSeparator, joinpath(os.Getenv("NUV_BIN"), runtime.GOOS+"-"+runtime.GOARCH))
    83  	//os.Setenv("PATH", fmt.Sprintf("%s%c%s", subpath, os.PathListSeparator, os.Getenv("PATH")))
    84  }
    85  
    86  func info() {
    87  	fmt.Println("NUV_VERSION:", NuvVersion)
    88  	fmt.Println("NUV_BRANCH:", getNuvBranch())
    89  	fmt.Println("NUV_CMD:", tools.GetNuvCmd())
    90  	fmt.Println("NUV_REPO:", getNuvRepo())
    91  	fmt.Println("NUV_BIN:", os.Getenv("NUV_BIN"))
    92  	fmt.Println("NUV_TMP:", os.Getenv("NUV_TMP"))
    93  	root, _ := getNuvRoot()
    94  	fmt.Println("NUV_ROOT:", root)
    95  	fmt.Println("NUV_PWD:", os.Getenv("NUV_PWD"))
    96  	fmt.Println("NUV_OLARIS:", os.Getenv("NUV_OLARIS"))
    97  }
    98  
    99  //go:embed runtimes.json
   100  var WSK_RUNTIMES_JSON string
   101  
   102  func main() {
   103  	// set runtime version as environment variable
   104  	if os.Getenv("WSK_RUNTIMES_JSON") == "" {
   105  		os.Setenv("WSK_RUNTIMES_JSON", WSK_RUNTIMES_JSON)
   106  		trace(WSK_RUNTIMES_JSON)
   107  	}
   108  
   109  	// set version
   110  	if os.Getenv("NUV_VERSION") != "" {
   111  		NuvVersion = os.Getenv("NUV_VERSION")
   112  	}
   113  
   114  	// disable log prefix
   115  	log.SetFlags(0)
   116  
   117  	var err error
   118  	me := os.Args[0]
   119  	if filepath.Base(me) == "nuv" || filepath.Base(me) == "nuv.exe" {
   120  		tools.NuvCmd, err = setupCmd(me)
   121  		if err != nil {
   122  			log.Fatalf("cannot setup cmd: %s", err.Error())
   123  		}
   124  		setupBinPath(tools.NuvCmd)
   125  	}
   126  
   127  	nuvHome, err := homedir.Expand("~/.nuv")
   128  	if err != nil {
   129  		log.Fatalf("error: %s", err.Error())
   130  	}
   131  
   132  	// Check if olaris exists. If not, run `-update` to auto setup
   133  	olarisDir := joinpath(joinpath(nuvHome, getNuvBranch()), "olaris")
   134  	if !isDir(olarisDir) {
   135  		if !(len(os.Args) == 2 && os.Args[1] == "-update") {
   136  			log.Println("Welcome to nuv! Setting up...")
   137  			dir, err := pullTasks(true, true)
   138  			if err != nil {
   139  				log.Fatalf("error: %v", err)
   140  			}
   141  			if err := setNuvOlarisHash(dir); err != nil {
   142  				warn("unable to set NUV_OLARIS...", err.Error())
   143  			}
   144  		}
   145  	}
   146  
   147  	setupTmp()
   148  	setNuvPwdEnv()
   149  	setNuvRootPluginEnv()
   150  	if err := setNuvOlarisHash(olarisDir); err != nil {
   151  		warn("unable to set NUV_OLARIS...", err.Error())
   152  	}
   153  
   154  	nuvRootDir := getRootDirOrExit()
   155  	debug("nuvRootDir", nuvRootDir)
   156  	err = setAllConfigEnvVars(nuvRootDir, nuvHome)
   157  	if err != nil {
   158  		log.Fatalf("cannot apply env vars from configs: %s", err.Error())
   159  	}
   160  
   161  	// first argument with prefix "-" is an embedded tool
   162  	// using "-" or "--" or "-task" invokes embedded task
   163  	trace("OS args:", os.Args)
   164  	args := os.Args
   165  
   166  	if len(args) > 1 && len(args[1]) > 0 && args[1][0] == '-' {
   167  		cmd := args[1][1:]
   168  		if cmd == "" || cmd == "-" || cmd == "task" {
   169  			exitCode, err := Task(args[2:]...)
   170  			if err != nil {
   171  				log.Println(err)
   172  			}
   173  			os.Exit(exitCode)
   174  		}
   175  
   176  		switch cmd {
   177  		case "version":
   178  			fmt.Println(NuvVersion)
   179  		case "v":
   180  			fmt.Println(NuvVersion)
   181  
   182  		case "info":
   183  			info()
   184  
   185  		case "help":
   186  			tools.Help()
   187  
   188  		case "serve":
   189  			nuvRootDir := getRootDirOrExit()
   190  			if err := Serve(nuvRootDir, args[1:]); err != nil {
   191  				log.Fatalf("error: %v", err)
   192  			}
   193  
   194  		case "update":
   195  			// ok no up, nor down, let's download it
   196  			dir, err := pullTasks(true, true)
   197  			if err != nil {
   198  				log.Fatalf("error: %v", err)
   199  			}
   200  			if err := setNuvOlarisHash(dir); err != nil {
   201  				log.Fatal("unable to set NUV_OLARIS...", err.Error())
   202  			}
   203  
   204  		case "retry":
   205  			if err := tools.ExpBackoffRetry(args[1:]); err != nil {
   206  				log.Fatalf("error: %s", err.Error())
   207  			}
   208  
   209  		case "login":
   210  			os.Args = args[1:]
   211  			loginResult, err := auth.LoginCmd()
   212  			if err != nil {
   213  				log.Fatalf("error: %s", err.Error())
   214  			}
   215  
   216  			if loginResult == nil {
   217  				os.Exit(1)
   218  			}
   219  
   220  			fmt.Println("Successfully logged in as " + loginResult.Login + ".")
   221  			if err := wskPropertySet(loginResult.ApiHost, loginResult.Auth); err != nil {
   222  				log.Fatalf("error: %s", err.Error())
   223  			}
   224  			fmt.Println("Nuvolaris host and auth set successfully. You are now ready to use nuv -wsk!")
   225  
   226  		case "config":
   227  			os.Args = args[1:]
   228  			nuvRootPath := joinpath(getRootDirOrExit(), NUVROOT)
   229  			configPath := joinpath(nuvHome, CONFIGFILE)
   230  			configMap, err := buildConfigMap(nuvRootPath, configPath)
   231  			if err != nil {
   232  				log.Fatalf("error: %s", err.Error())
   233  			}
   234  
   235  			if err := config.ConfigTool(*configMap); err != nil {
   236  				log.Fatalf("error: %s", err.Error())
   237  			}
   238  
   239  		case "plugin":
   240  			os.Args = args[1:]
   241  			if err := pluginTool(); err != nil {
   242  				log.Fatalf("error: %s", err.Error())
   243  			}
   244  
   245  		default:
   246  			// check if it is an embedded to and invoke it
   247  			if tools.IsTool(cmd) {
   248  				code, err := tools.RunTool(cmd, args[2:])
   249  				if err != nil {
   250  					log.Print(err.Error())
   251  				}
   252  				os.Exit(code)
   253  			}
   254  			// no embeded tool found
   255  			warn("unknown tool", "-"+cmd)
   256  		}
   257  		os.Exit(0)
   258  	}
   259  
   260  	// check if olaris was recently updated
   261  	checkUpdated(nuvHome, 24*time.Hour)
   262  
   263  	// in case args[1] is a wsk wrapper command
   264  	// invoke it and exit
   265  	if len(args) > 1 {
   266  		if cmd, ok := IsWskWrapperCommand(args[1]); ok {
   267  			trace("wsk wrapper command")
   268  			debug("extracted cmd", cmd)
   269  			rest := args[2:]
   270  			debug("extracted args", rest)
   271  
   272  			// if "invoke" is in the command, parse all a=b into -p a b
   273  			if (len(cmd) > 2 && cmd[2] == "invoke") || slices.Contains(rest, "invoke") {
   274  				rest = parseInvokeArgs(rest)
   275  			}
   276  
   277  			if err := tools.Wsk(cmd, rest...); err != nil {
   278  				log.Fatalf("error: %s", err.Error())
   279  			}
   280  			return //(skip runNuv)
   281  		}
   282  	}
   283  
   284  	// preflight checks
   285  	if err := preflightChecks(); err != nil {
   286  		log.Fatalf("[PREFLIGHT CHECK] error: %s", err.Error())
   287  	}
   288  	// ***************
   289  
   290  	if err := runNuv(nuvRootDir, args); err != nil {
   291  		log.Fatalf("error: %s", err.Error())
   292  	}
   293  }
   294  
   295  // parse all a=b into -p a b
   296  func parseInvokeArgs(rest []string) []string {
   297  	trace("parsing invoke args")
   298  	args := []string{}
   299  
   300  	for _, arg := range rest {
   301  		if strings.Contains(arg, "=") {
   302  			kv := strings.Split(arg, "=")
   303  			p := []string{"-p", kv[0], kv[1]}
   304  			args = append(args, p...)
   305  		} else {
   306  			args = append(args, arg)
   307  		}
   308  	}
   309  
   310  	debug("parsed invoke args", args)
   311  	return args
   312  }
   313  
   314  // getRootDirOrExit returns the olaris dir or exits (Fatal) if not found
   315  func getRootDirOrExit() string {
   316  	dir, err := getNuvRoot()
   317  	if err != nil {
   318  		log.Fatalf("error: %s", err.Error())
   319  	}
   320  	return dir
   321  }
   322  
   323  func setAllConfigEnvVars(nuvRootDir string, configDir string) error {
   324  	trace("setting all config env vars")
   325  
   326  	configMap, err := buildConfigMap(joinpath(nuvRootDir, NUVROOT), joinpath(configDir, CONFIGFILE))
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	kv := configMap.Flatten()
   332  	for k, v := range kv {
   333  		if err := os.Setenv(k, v); err != nil {
   334  			return err
   335  		}
   336  		debug("env var set", k, v)
   337  	}
   338  
   339  	return nil
   340  }
   341  
   342  func wskPropertySet(apihost, auth string) error {
   343  	args := []string{"property", "set", "--apihost", apihost, "--auth", auth}
   344  	cmd := append([]string{"wsk"}, args...)
   345  	if err := tools.Wsk(cmd); err != nil {
   346  		return err
   347  	}
   348  	return nil
   349  }
   350  
   351  func runNuv(baseDir string, args []string) error {
   352  	err := Nuv(baseDir, args[1:])
   353  	if err == nil {
   354  		return nil
   355  	}
   356  
   357  	// If the task is not found,
   358  	// fallback to plugins
   359  	var taskNotFoundErr *TaskNotFoundErr
   360  	if errors.As(err, &taskNotFoundErr) {
   361  		trace("task not found, looking for plugin:", args[1])
   362  		plgDir, err := findTaskInPlugins(args[1])
   363  		if err != nil {
   364  			return taskNotFoundErr
   365  		}
   366  
   367  		debug("Found plugin", plgDir)
   368  		if err := Nuv(plgDir, args[2:]); err != nil {
   369  			log.Fatalf("error: %s", err.Error())
   370  		}
   371  		return nil
   372  	}
   373  
   374  	return err
   375  }
   376  
   377  func setNuvRootPluginEnv() {
   378  	if os.Getenv("NUV_ROOT_PLUGIN") == "" {
   379  		//nolint:errcheck
   380  		os.Setenv("NUV_ROOT_PLUGIN", os.Getenv("NUV_PWD"))
   381  	}
   382  	trace("set NUV_ROOT_PLUGIN", os.Getenv("NUV_ROOT_PLUGIN"))
   383  }
   384  
   385  func setNuvPwdEnv() {
   386  	if os.Getenv("NUV_PWD") == "" {
   387  		dir, _ := os.Getwd()
   388  		//nolint:errcheck
   389  		os.Setenv("NUV_PWD", dir)
   390  	}
   391  	trace("set NUV_PWD", os.Getenv("NUV_PWD"))
   392  }
   393  
   394  func buildConfigMap(nuvRootPath string, configPath string) (*config.ConfigMap, error) {
   395  	plgNuvRootMap, err := GetNuvRootPlugins()
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  
   400  	configMap, err := config.NewConfigMapBuilder().
   401  		WithNuvRoot(nuvRootPath).
   402  		WithConfigJson(configPath).
   403  		WithPluginNuvRoots(plgNuvRootMap).
   404  		Build()
   405  
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  
   410  	return &configMap, nil
   411  }
   412  
   413  func IsWskWrapperCommand(name string) ([]string, bool) {
   414  	wskWrapperCommands := map[string][]string{
   415  		"action":     {"wsk", "action"},
   416  		"activation": {"wsk", "activation"},
   417  		"invoke":     {"wsk", "action", "invoke", "-r"},
   418  		"logs":       {"wsk", "activation", "logs"},
   419  		"package":    {"wsk", "package"},
   420  		"result":     {"wsk", "activation", "result"},
   421  		"rule":       {"wsk", "rule"},
   422  		"trigger":    {"wsk", "trigger"},
   423  		"url":        {"wsk", "action", "get", "--url"},
   424  	}
   425  
   426  	cmd, ok := wskWrapperCommands[name]
   427  	return cmd, ok
   428  }