blob: 637acddb9ef8795dc5c8daf8d739ea1af565b640 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package openserverless
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"time"
"github.com/apache/openserverless-cli/auth"
"github.com/apache/openserverless-cli/config"
"github.com/apache/openserverless-cli/tools"
"github.com/mitchellh/go-homedir"
_ "embed"
)
func setupCmd(me string) (string, error) {
if os.Getenv("OPS_CMD") != "" {
return os.Getenv("OPS_CMD"), nil
}
// look in path
me, err := exec.LookPath(me)
if err != nil {
return "", err
}
trace("found", me)
// resolve links
fileInfo, err := os.Lstat(me)
if err != nil {
return "", err
}
if fileInfo.Mode()&os.ModeSymlink != 0 {
me, err = os.Readlink(me)
if err != nil {
return "", err
}
trace("resolving link to", me)
}
// get the absolute path
me, err = filepath.Abs(me)
if err != nil {
return "", err
}
//nolint:errcheck
os.Setenv("OPS_CMD", me)
trace("OPS_CMD:", me)
return me, nil
}
func setupBinPath() error {
// initialize tools (used by the shell to find myself)
bindir, err := EnsureBindir()
if err == nil {
os.Setenv("PATH", fmt.Sprintf("%s%c%s", bindir, os.PathListSeparator, os.Getenv("PATH")))
debugf("PATH=%s", os.Getenv("PATH"))
}
return err
}
func setOpsRootPluginEnv() {
if os.Getenv("OPS_ROOT_PLUGIN") == "" {
//nolint:errcheck
os.Setenv("OPS_ROOT_PLUGIN", os.Getenv("OPS_PWD"))
}
trace("set OPS_ROOT_PLUGIN", os.Getenv("OPS_ROOT_PLUGIN"))
}
func info() {
fmt.Println("OPS_VERSION:", os.Getenv("OPS_VERSION"))
fmt.Println("OPS_BRANCH:", os.Getenv("OPS_BRANCH"))
fmt.Println("OPS_CMD:", os.Getenv("OPS_CMD"))
fmt.Println("OPS_BIN:", os.Getenv("OPS_BIN"))
fmt.Println("OPS_TMP:", os.Getenv("OPS_TMP"))
fmt.Println("OPS_HOME:", os.Getenv("OPS_HOME"))
fmt.Println("OPS_ROOT:", os.Getenv("OPS_ROOT"))
fmt.Println("OPS_REPO:", os.Getenv("OPS_REPO"))
fmt.Println("OPS_PWD:", os.Getenv("OPS_PWD"))
fmt.Println("OPS_OLARIS:", os.Getenv("OPS_OLARIS"))
fmt.Println("OPS_ROOT_PLUGIN:", os.Getenv("OPS_ROOT_PLUGIN"))
//fmt.Println("OPS_TOOLS:", os.Getenv("OPS_TOOLS"))
//fmt.Println("OPS_COREUTILS:", os.Getenv("OPS_COREUTILS"))
}
// not available in taskfiles
var mainTools = []string{
"task", "version", "info", "help", "serve", "update", "retry", "login", "config", "plugin",
}
func executeMainToolsAndExit(cmd string, args []string, opsHome string) int {
if cmd == "" || cmd == "-" || cmd == "task" {
exitCode, err := Task(args[2:]...)
if err != nil {
log.Println(err)
}
return exitCode
}
switch cmd {
case "version":
fmt.Println(OpsVersion)
case "v":
fmt.Println(OpsVersion)
case "info":
info()
case "help":
tools.Help(mainTools)
case "serve":
opsRootDir := getRootDirOrExit()
if err := Serve(opsRootDir, args[1:]); err != nil {
log.Fatalf("error: %v", err)
}
case "update":
// ok no up, nor down, let's download it
dir, err := pullTasks(true, true)
if err != nil {
log.Fatalf("error: %v", err)
}
if err := setOpsOlarisHash(dir); err != nil {
log.Fatal("unable to set OPS_OLARIS...", err.Error())
}
case "retry":
if err := tools.ExpBackoffRetry(args[1:]); err != nil {
log.Fatalf("error: %s", err.Error())
}
case "login":
os.Args = args[1:]
loginResult, err := auth.LoginCmd()
if err != nil {
log.Fatalf("error: %s", err.Error())
}
if loginResult == nil {
os.Exit(1)
}
fmt.Println("Successfully logged in as " + loginResult.Login + ".")
if err := wskPropertySet(loginResult.ApiHost, loginResult.Auth); err != nil {
log.Fatalf("error: %s", err.Error())
}
fmt.Println("OpenServerless host and auth set successfully. You are now ready to use ops!")
case "config":
os.Args = args[1:]
opsRootPath := joinpath(getRootDirOrExit(), OPSROOT)
configPath := joinpath(opsHome, CONFIGFILE)
configMap, err := buildConfigMap(opsRootPath, configPath)
if err != nil {
log.Fatalf("error: %s", err.Error())
}
if err := config.ConfigTool(*configMap); err != nil {
log.Fatalf("error: %s", err.Error())
}
case "plugin":
os.Args = args[1:]
if err := pluginTool(); err != nil {
log.Fatalf("error: %s", err.Error())
}
default:
// check if it is an embedded to and invoke it
if tools.IsTool(cmd) {
code, err := tools.RunTool(cmd, args[2:])
if err != nil {
log.Print(err.Error())
}
return code
}
// no embeded tool found
warn("unknown tool", "-"+cmd)
}
return 0
}
func Main() {
var err error
args := os.Args
// disable log prefix
log.SetFlags(0)
// set default runtime.json
if os.Getenv("WSK_RUNTIMES_JSON") == "" {
os.Setenv("WSK_RUNTIMES_JSON", WSK_RUNTIMES_JSON)
trace("WSK_RUNTIMES_JSON len=", len(WSK_RUNTIMES_JSON))
}
// set runtime version as environment variable
if os.Getenv("OPS_VERSION") != "" {
OpsVersion = os.Getenv("OPS_VERSION")
} else {
OpsVersion = strings.TrimSpace(OpsVersion)
os.Setenv("OPS_VERSION", OpsVersion)
}
// setup OPS_CMD
me := args[0]
if strings.Contains("ops ops.exe ops ops.exe", filepath.Base(me)) {
_, err = setupCmd(me)
if err != nil {
log.Fatalf("cannot setup cmd: %s", err.Error())
}
}
// setup home
opsHome := os.Getenv("OPS_HOME")
if opsHome == "" {
opsHome, err = homedir.Expand("~/.ops")
}
if err != nil {
log.Fatalf("cannot setup home: %s", err.Error())
}
os.Setenv("OPS_HOME", opsHome)
// add ~/.ops/<os>-<arch>/bin to the path at the beginning
err = setupBinPath()
if err != nil {
log.Fatalf("cannot setup PATH: %s", err.Error())
}
// ensure there is ~/.ops/tmp
err = setupTmp()
if err != nil {
log.Fatalf("cannot setup OPS_TMP: %s", err.Error())
}
// setup the OPS_PWD variable
err = setOpsPwdEnv()
if err != nil {
log.Fatalf("cannot setup OPS_PWD: %s", err.Error())
}
// setup the envvar for the embedded tools
os.Setenv("OPS_TOOLS", strings.Join(append(mainTools, tools.ToolList...), " "))
// OPS_REPO && OPS_ROOT_PLUGIN
getOpsRepo()
setOpsRootPluginEnv()
// Check if olaris exists. If not, download tasks
olarisDir, err := getOpsRoot()
if err != nil {
olarisDir := joinpath(joinpath(opsHome, getOpsBranch()), "olaris")
if !isDir(olarisDir) {
log.Println("Welcome to ops! Setting up...")
olarisDir, err = pullTasks(true, true)
if err != nil {
log.Fatalf("cannot locate or download OPS_ROOT: %s", err.Error())
}
// just updated, do not repeat
if len(args) == 2 && args[1] == "-update" {
os.Exit(0)
}
} else {
// check if olaris was recently updated
checkUpdated(opsHome, 24*time.Hour)
}
}
if err = setOpsOlarisHash(olarisDir); err != nil {
os.Setenv("OPS_OLARIS", "<local>")
}
// set the enviroment variables from the config
opsRootDir := getRootDirOrExit()
debug("opsRootDir", opsRootDir)
err = setAllConfigEnvVars(opsRootDir, opsHome)
if err != nil {
log.Fatalf("cannot apply env vars from configs: %s", err.Error())
}
// preflight checks - we need at least ssh curl to proceed
if err := preflightChecks(); err != nil {
log.Fatalf("failed preflight check: %s", err.Error())
}
// in case args[1] is a wsk wrapper command invoke it and exit
if len(args) > 1 {
if cmd, ok := IsWskWrapperCommand(args[1]); ok {
trace("wsk wrapper command")
debug("extracted cmd", cmd)
rest := args[2:]
debug("extracted args", rest)
// if "invoke" is in the command, parse all a=b into -p a b
if (len(cmd) > 2 && cmd[2] == "invoke") || slices.Contains(rest, "invoke") {
rest = parseInvokeArgs(rest)
}
if err := tools.Wsk(cmd, rest...); err != nil {
log.Fatalf("error: %s", err.Error())
}
os.Exit(0)
}
}
// first argument with prefix "-" is considered an embedded tool
// using "-" or "--" or "-task" invokes the embedded task
if len(args) > 1 && len(args[1]) > 0 && args[1][0] == '-' {
cmd := args[1][1:]
trace("executing embedded tool", cmd, args)
// execute the embeded tool and exit
exitCode := executeMainToolsAndExit(cmd, args, opsHome)
os.Exit(exitCode)
}
if err := runOps(opsRootDir, args); err != nil {
log.Fatalf("task execution error: %s", err.Error())
}
}
// parse all a=b into -p a b
func parseInvokeArgs(rest []string) []string {
trace("parsing invoke args")
args := []string{}
for _, arg := range rest {
if strings.Contains(arg, "=") {
kv := strings.Split(arg, "=")
p := []string{"-p", kv[0], kv[1]}
args = append(args, p...)
} else {
args = append(args, arg)
}
}
debug("parsed invoke args", args)
return args
}
// getRootDirOrExit returns the olaris dir or exits (Fatal) if not found
func getRootDirOrExit() string {
dir, err := getOpsRoot()
if err != nil {
log.Fatalf("error: %s", err.Error())
}
return dir
}
func setAllConfigEnvVars(opsRootDir string, configDir string) error {
trace("setting all config env vars")
configMap, err := buildConfigMap(joinpath(opsRootDir, OPSROOT), joinpath(configDir, CONFIGFILE))
if err != nil {
return err
}
kv := configMap.Flatten()
for k, v := range kv {
if err := os.Setenv(k, v); err != nil {
return err
}
debug("env var set", k, v)
}
return nil
}
func wskPropertySet(apihost, auth string) error {
args := []string{"property", "set", "--apihost", apihost, "--auth", auth}
cmd := append([]string{"wsk"}, args...)
if err := tools.Wsk(cmd); err != nil {
return err
}
return nil
}
func runOps(baseDir string, args []string) error {
err := Ops(baseDir, args[1:])
if err == nil {
return nil
}
// If the task is not found,
// fallback to plugins
var taskNotFoundErr *TaskNotFoundErr
if errors.As(err, &taskNotFoundErr) {
trace("task not found, looking for plugin:", args[1])
plgDir, err := findTaskInPlugins(args[1])
if err != nil {
return taskNotFoundErr
}
debug("Found plugin", plgDir)
if err := Ops(plgDir, args[2:]); err != nil {
log.Fatalf("error: %s", err.Error())
}
return nil
}
return err
}
func setOpsPwdEnv() error {
if os.Getenv("OPS_PWD") == "" {
dir, err := os.Getwd()
if err != nil {
return err
}
//nolint:errcheck
os.Setenv("OPS_PWD", dir)
}
trace("set OPS_PWD", os.Getenv("OPS_PWD"))
return nil
}
func buildConfigMap(opsRootPath string, configPath string) (*config.ConfigMap, error) {
plgOpsRootMap, err := GetOpsRootPlugins()
if err != nil {
return nil, err
}
configMap, err := config.NewConfigMapBuilder().
WithOpsRoot(opsRootPath).
WithConfigJson(configPath).
WithPluginOpsRoots(plgOpsRootMap).
Build()
if err != nil {
return nil, err
}
return &configMap, nil
}
func IsWskWrapperCommand(name string) ([]string, bool) {
wskWrapperCommands := map[string][]string{
"action": {"wsk", "action"},
"activation": {"wsk", "activation"},
"invoke": {"wsk", "action", "invoke", "-r"},
"logs": {"wsk", "activation", "logs"},
"package": {"wsk", "package"},
"result": {"wsk", "activation", "result"},
"rule": {"wsk", "rule"},
"trigger": {"wsk", "trigger"},
"url": {"wsk", "action", "get", "--url"},
}
cmd, ok := wskWrapperCommands[name]
return cmd, ok
}