github.com/scaleway/scaleway-cli@v1.11.1/pkg/cli/main.go (about)

     1  // Copyright (C) 2015 Scaleway. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE.md file.
     4  
     5  package cli
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/Sirupsen/logrus"
    17  	flag "github.com/docker/docker/pkg/mflag"
    18  
    19  	version "github.com/hashicorp/go-version"
    20  	"github.com/scaleway/scaleway-cli/pkg/api"
    21  	"github.com/scaleway/scaleway-cli/pkg/clilogger"
    22  	"github.com/scaleway/scaleway-cli/pkg/commands"
    23  	"github.com/scaleway/scaleway-cli/pkg/config"
    24  	"github.com/scaleway/scaleway-cli/pkg/scwversion"
    25  	"github.com/scaleway/scaleway-cli/pkg/utils"
    26  )
    27  
    28  // global options
    29  var (
    30  	flDebug     = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
    31  	flVerbose   = flag.Bool([]string{"V", "-verbose"}, false, "Enable verbose mode")
    32  	flVersion   = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
    33  	flQuiet     = flag.Bool([]string{"q", "-quiet"}, false, "Enable quiet mode")
    34  	flSensitive = flag.Bool([]string{"-sensitive"}, false, "Show sensitive data in outputs, i.e. API Token/Organization")
    35  	flRegion    = flag.String([]string{"-region"}, "par1", "Change the default region (e.g. ams1)")
    36  )
    37  
    38  // Start is the entrypoint
    39  func Start(rawArgs []string, streams *commands.Streams) (int, error) {
    40  	checkVersion()
    41  	if streams == nil {
    42  		streams = &commands.Streams{
    43  			Stdin:  os.Stdin,
    44  			Stdout: os.Stdout,
    45  			Stderr: os.Stderr,
    46  		}
    47  	}
    48  	flag.CommandLine.Parse(rawArgs)
    49  
    50  	config, cfgErr := config.GetConfig()
    51  	if cfgErr != nil && !os.IsNotExist(cfgErr) {
    52  		return 1, fmt.Errorf("unable to open .scwrc config file: %v", cfgErr)
    53  	}
    54  
    55  	if *flVersion {
    56  		fmt.Fprintf(streams.Stderr, "scw version %s, build %s\n", scwversion.VERSION, scwversion.GITCOMMIT)
    57  		return 0, nil
    58  	}
    59  
    60  	if *flSensitive {
    61  		os.Setenv("SCW_SENSITIVE", "1")
    62  	}
    63  
    64  	if *flDebug {
    65  		os.Setenv("DEBUG", "1")
    66  	}
    67  
    68  	if *flVerbose {
    69  		os.Setenv("SCW_VERBOSE_API", "1")
    70  	}
    71  
    72  	utils.Quiet(*flQuiet)
    73  	initLogging(os.Getenv("DEBUG") != "", *flVerbose, streams)
    74  
    75  	args := flag.Args()
    76  	if len(args) < 1 {
    77  		CmdHelp.Exec(CmdHelp, []string{})
    78  		return 1, nil
    79  	}
    80  	name := args[0]
    81  
    82  	args = args[1:]
    83  
    84  	// Apply default values
    85  	for _, cmd := range Commands {
    86  		cmd.streams = streams
    87  	}
    88  
    89  	for _, cmd := range Commands {
    90  		if cmd.Name() == name {
    91  			cmd.Flag.SetOutput(ioutil.Discard)
    92  			err := cmd.Flag.Parse(args)
    93  			if err != nil {
    94  				return 1, fmt.Errorf("usage: scw %s", cmd.UsageLine)
    95  			}
    96  			switch cmd.Name() {
    97  			case "login", "help", "version":
    98  				// commands that don't need API
    99  			case "_userdata":
   100  				// commands that may need API
   101  				api, _ := getScalewayAPI(*flRegion)
   102  				cmd.API = api
   103  			default:
   104  				// commands that do need API
   105  				if cfgErr != nil {
   106  					if name != "login" && config == nil {
   107  						logrus.Debugf("cfgErr: %v", cfgErr)
   108  						fmt.Fprintf(streams.Stderr, "You need to login first: 'scw login'\n")
   109  						return 1, nil
   110  					}
   111  				}
   112  				api, errGet := getScalewayAPI(*flRegion)
   113  				if errGet != nil {
   114  					return 1, fmt.Errorf("unable to initialize scw api: %v", errGet)
   115  				}
   116  				cmd.API = api
   117  			}
   118  			// clean cache between versions
   119  			if cmd.API != nil && config.Version != scwversion.VERSION {
   120  				cmd.API.ClearCache()
   121  				config.Save()
   122  			}
   123  			err = cmd.Exec(cmd, cmd.Flag.Args())
   124  			switch err {
   125  			case nil:
   126  			case ErrExitFailure:
   127  				return 1, nil
   128  			case ErrExitSuccess:
   129  				return 0, nil
   130  			default:
   131  				return 1, fmt.Errorf("cannot execute '%s': %v", cmd.Name(), err)
   132  			}
   133  			if cmd.API != nil {
   134  				cmd.API.Sync()
   135  			}
   136  			return 0, nil
   137  		}
   138  	}
   139  	return 1, fmt.Errorf("scw: unknown subcommand %s\nRun 'scw help' for usage", name)
   140  }
   141  
   142  // getScalewayAPI returns a ScalewayAPI using the user config file
   143  func getScalewayAPI(region string) (*api.ScalewayAPI, error) {
   144  	// We already get config globally, but whis way we can get explicit error when trying to create a ScalewayAPI object
   145  	config, err := config.GetConfig()
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return api.NewScalewayAPI(config.Organization, config.Token, scwversion.UserAgent(), region, clilogger.SetupLogger)
   150  }
   151  
   152  func initLogging(debug bool, verbose bool, streams *commands.Streams) {
   153  	logrus.SetOutput(streams.Stderr)
   154  	if debug {
   155  		logrus.SetLevel(logrus.DebugLevel)
   156  	} else if verbose {
   157  		logrus.SetLevel(logrus.InfoLevel)
   158  	} else {
   159  		logrus.SetLevel(logrus.WarnLevel)
   160  	}
   161  }
   162  
   163  func checkVersion() {
   164  	if os.Getenv("SCW_NOCHECKVERSION") == "1" {
   165  		return
   166  	}
   167  	homeDir, err := config.GetHomeDir()
   168  	if err != nil {
   169  		return
   170  	}
   171  	updateFiles := []string{"/var/run/.scw-update", "/tmp/.scw-update", filepath.Join(homeDir, ".scw-update")}
   172  	updateFile := ""
   173  
   174  	callAPI := false
   175  	for _, file := range updateFiles {
   176  		if stat, err := os.Stat(file); err == nil {
   177  			updateFile = file
   178  			callAPI = stat.ModTime().Before(time.Now().AddDate(0, 0, -1))
   179  			break
   180  		}
   181  	}
   182  	if updateFile == "" {
   183  		for _, file := range updateFiles {
   184  			if scwupdate, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600); err == nil {
   185  				scwupdate.Close()
   186  				updateFile = file
   187  				callAPI = true
   188  				break
   189  			}
   190  		}
   191  	}
   192  	if callAPI {
   193  		scwupdate, err := os.OpenFile(updateFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
   194  		if err != nil {
   195  			return
   196  		}
   197  		scwupdate.Close()
   198  		req := http.Client{
   199  			Timeout: 1 * time.Second,
   200  		}
   201  		resp, err := req.Get("https://fr-1.storage.online.net/scaleway/scaleway-cli/VERSION")
   202  		if resp != nil {
   203  			defer resp.Body.Close()
   204  		}
   205  		if err != nil {
   206  			return
   207  		}
   208  		body, err := ioutil.ReadAll(resp.Body)
   209  		if err != nil {
   210  			return
   211  		}
   212  		if scwversion.VERSION == "" {
   213  			return
   214  		}
   215  		ver := scwversion.VERSION
   216  		if ver[0] == 'v' {
   217  			ver = string([]byte(ver)[1:])
   218  		}
   219  		actual, err1 := version.NewVersion(ver)
   220  		update, err2 := version.NewVersion(strings.Trim(string(body), "\n"))
   221  		if err1 != nil || err2 != nil {
   222  			return
   223  		}
   224  		if actual.LessThan(update) {
   225  			logrus.Infof("A new version of scw is available (%v), beware that you are currently running %v", update, actual)
   226  		}
   227  	}
   228  }