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 }