diff --git a/.gitignore b/.gitignore index f744bddefd..bea63b5237 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # builds /scaleway-cli /scw -scwversion/version.go +pkg/scwversion/version.go dist/ # junk @@ -9,4 +9,5 @@ dist/ *# .#* profile.cov +profile.out .vendor diff --git a/Makefile b/Makefile index 9659b7065e..c8c90dba1f 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ FPM_ARGS ?= \ NAME = scw -SRC = . -PACKAGES = api commands utils +SRC = cmd/scw +PACKAGES = pkg/api pkg/commands pkg/utils pkg/cli REV = $(shell git rev-parse HEAD || echo "nogit") TAG = $(shell git describe --tags --always || echo "nogit") BUILDER = scaleway-cli-builder @@ -43,7 +43,7 @@ COVER_LIST = $(foreach int, $(PACKAGES), $(int)_cover) all: build -build: scwversion/version.go $(BUILD_LIST) +build: pkg/scwversion/version.go $(BUILD_LIST) clean: $(CLEAN_LIST) install: $(INSTALL_LIST) test: $(TEST_LIST) @@ -59,8 +59,8 @@ cover: touch $@ -scwversion/version.go: .git - @sed 's/\(.*GITCOMMIT.* = \).*/\1"$(REV)"/;s/\(.*VERSION.* = \).*/\1"$(TAG)"/' scwversion/version.tpl > $@.tmp +pkg/scwversion/version.go: .git + @sed 's/\(.*GITCOMMIT.* = \).*/\1"$(REV)"/;s/\(.*VERSION.* = \).*/\1"$(TAG)"/' pkg/scwversion/version.tpl > $@.tmp @if [ "$$(diff $@.tmp $@ 2>&1)" != "" ]; then mv $@.tmp $@; fi @rm -f $@.tmp @@ -72,7 +72,7 @@ $(CLEAN_LIST): %_clean: $(GOCLEAN) ./$* $(INSTALL_LIST): %_install: $(GOINSTALL) ./$* -$(IREF_LIST): %_iref: scwversion/version.go +$(IREF_LIST): %_iref: pkg/scwversion/version.go $(GOTEST) -i ./$* $(TEST_LIST): %_test: $(GOTEST) ./$* @@ -83,7 +83,7 @@ $(FMT_LIST): %_fmt: $(GOFMT) ./$* -cross: scwversion/version.go +cross: pkg/scwversion/version.go docker build -t $(BUILDER) . @docker rm scaleway-cli-builer 2>/dev/null || true mkdir -p dist diff --git a/README.md b/README.md index 0b5fbc9255..0269a5316a 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ chmod +x /usr/local/bin/scw To install Scaleway CLI master git, run the following command: ```bash -go get github.com/scaleway/scaleway-cli +go get github.com/scaleway/scaleway-cli/... ``` ### Requirements @@ -106,7 +106,7 @@ $ docker run -it --rm --volume=$HOME/.scwrc:/.scwrc scaleway/cli ps 2. Ensure you have `$GOPATH` and `$PATH` well configured, something like: * `export GOPATH=$HOME/go` * `export PATH=$PATH:$GOPATH/bin` -3. Install the project: `go get github.com/scaleway/scaleway-cli` +3. Install the project: `go get github.com/scaleway/scaleway-cli/...` 4. Run: `scaleway-cli` ## Usage @@ -1050,6 +1050,8 @@ $ scw inspect myserver | jq '.[0].public_ip.address' #### Fixes +* Global refactor to improve Golang library usage, allow chaining of commands and ease the writing of unit tests ([#80](https://github.com/scaleway/scaleway-cli/issues/80)) +* `scw search TERM` was not restricting results based on `TERM` * Bumped dependencies * Hiding more sensitive data ([#77](https://github.com/scaleway/scaleway-cli/issues/77)) * Fixed "Run in Docker" usage ([#90](https://github.com/scaleway/scaleway-cli/issues/90)) @@ -1230,7 +1232,7 @@ Feel free to contribute :smiley::beers: 2. Ensure you have `$GOPATH` and `$PATH` well configured, something like: * `export GOPATH=$HOME/go` * `export PATH=$PATH:$GOPATH/bin` -3. Fetch the project: `go get -d github.com/scaleway/scaleway-cli` +3. Fetch the project: `go get -d github.com/scaleway/scaleway-cli/...` 4. Go to scaleway-cli directory: `cd $GOPATH/src/github.com/scaleway/scaleway-cli` 5. Hack: `emacs` 6. Build: `make` diff --git a/main.go b/cmd/scw/main.go similarity index 96% rename from main.go rename to cmd/scw/main.go index 82f205f9e0..85c8b566b2 100644 --- a/main.go +++ b/cmd/scw/main.go @@ -14,10 +14,10 @@ import ( log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" flag "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/mflag" - "github.com/scaleway/scaleway-cli/api" - cmds "github.com/scaleway/scaleway-cli/commands" - "github.com/scaleway/scaleway-cli/scwversion" - "github.com/scaleway/scaleway-cli/utils" + "github.com/scaleway/scaleway-cli/pkg/api" + cmds "github.com/scaleway/scaleway-cli/pkg/cli" + "github.com/scaleway/scaleway-cli/pkg/scwversion" + "github.com/scaleway/scaleway-cli/pkg/utils" ) // CommandListOpts holds a list of parameters diff --git a/commands/events.go b/commands/events.go deleted file mode 100644 index c2c6dba598..0000000000 --- a/commands/events.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" - - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdEvents = &types.Command{ - Exec: runEvents, - UsageLine: "events [OPTIONS]", - Description: "Get real time events from the API", - Help: "Get real time events from the API.", -} - -func init() { - cmdEvents.Flag.BoolVar(&eventsHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var eventsHelp bool // -h, --help flag - -func runEvents(cmd *types.Command, args []string) { - if eventsHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - - events, err := cmd.API.GetTasks() - if err != nil { - log.Fatalf("unable to fetch tasks from the Scaleway API: %v", err) - } - - for _, event := range *events { - startedAt, err := time.Parse("2006-01-02T15:04:05.000000+00:00", event.StartDate) - if err != nil { - log.Fatalf("unable to parse started date from the Scaleway API: %v", err) - } - - terminatedAt := "" - if event.TerminationDate != "" { - terminatedAtTime, err := time.Parse("2006-01-02T15:04:05.000000+00:00", event.TerminationDate) - if err != nil { - log.Fatalf("unable to parse terminated date from the Scaleway API: %v", err) - } - terminatedAt = units.HumanDuration(time.Now().UTC().Sub(terminatedAtTime)) - } - - fmt.Printf("%s %s: %s (%s %d) %s\n", startedAt, event.HrefFrom, event.Description, event.Status, event.Progress, terminatedAt) - } -} diff --git a/commands/history.go b/commands/history.go deleted file mode 100644 index d6ec4bd7f1..0000000000 --- a/commands/history.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "text/tabwriter" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" - - types "github.com/scaleway/scaleway-cli/commands/types" - utils "github.com/scaleway/scaleway-cli/utils" -) - -var cmdHistory = &types.Command{ - Exec: runHistory, - UsageLine: "history [OPTIONS] IMAGE", - Description: "Show the history of an image", - Help: "Show the history of an image.", -} - -func init() { - cmdHistory.Flag.BoolVar(&historyNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") - cmdHistory.Flag.BoolVar(&historyQuiet, []string{"q", "-quiet"}, false, "Only show numeric IDs") - cmdHistory.Flag.BoolVar(&historyHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var historyNoTrunc bool // --no-trunc flag -var historyQuiet bool // -q, --quiet flag -var historyHelp bool // -h, --help flag - -func runHistory(cmd *types.Command, args []string) { - if historyHelp { - cmd.PrintUsage() - } - if len(args) != 1 { - cmd.PrintShortUsage() - } - - imageID := cmd.API.GetImageID(args[0], true) - image, err := cmd.API.GetImage(imageID) - if err != nil { - log.Fatalf("Cannot get image %s: %v", imageID, err) - } - - if imagesQ { - fmt.Println(imageID) - return - } - - w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) - defer w.Flush() - fmt.Fprintf(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\n") - - identifier := utils.TruncIf(image.Identifier, 8, !historyNoTrunc) - - creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", image.CreationDate) - if err != nil { - log.Fatalf("Unable to parse creation date from the Scaleway API: %v", err) - } - creationDateStr := units.HumanDuration(time.Now().UTC().Sub(creationDate)) - - volumeName := utils.TruncIf(image.RootVolume.Name, 25, !historyNoTrunc) - size := units.HumanSize(float64(image.RootVolume.Size)) - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", identifier, creationDateStr, volumeName, size) -} diff --git a/commands/info.go b/commands/info.go deleted file mode 100644 index 0af769f35f..0000000000 --- a/commands/info.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "runtime" - - "github.com/scaleway/scaleway-cli/vendor/github.com/kardianos/osext" - - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdInfo = &types.Command{ - Exec: runInfo, - UsageLine: "info [OPTIONS]", - Description: "Display system-wide information", - Help: "Display system-wide information.", -} - -func init() { - cmdInfo.Flag.BoolVar(&infoHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var infoHelp bool // -h, --help flag - -func runInfo(cmd *types.Command, args []string) { - if infoHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - - // FIXME: fmt.Printf("Servers: %s\n", "quantity") - // FIXME: fmt.Printf("Images: %s\n", "quantity") - fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "") - - fmt.Printf("Organization: %s\n", cmd.API.Organization) - // FIXME: add partially-masked token - fmt.Printf("API Endpoint: %s\n", os.Getenv("scaleway_api_endpoint")) - configPath, _ := utils.GetConfigFilePath() - fmt.Printf("RC file: %s\n", configPath) - fmt.Printf("User: %s\n", os.Getenv("USER")) - fmt.Printf("CPUs: %d\n", runtime.NumCPU()) - hostname, _ := os.Hostname() - fmt.Printf("Hostname: %s\n", hostname) - cliPath, _ := osext.Executable() - fmt.Printf("CLI Path: %s\n", cliPath) - - fmt.Printf("Cache: %s\n", cmd.API.Cache.Path) - fmt.Printf(" Servers: %d\n", cmd.API.Cache.GetNbServers()) - fmt.Printf(" Images: %d\n", cmd.API.Cache.GetNbImages()) - fmt.Printf(" Snapshots: %d\n", cmd.API.Cache.GetNbSnapshots()) - fmt.Printf(" Volumes: %d\n", cmd.API.Cache.GetNbVolumes()) - fmt.Printf(" Bootscripts: %d\n", cmd.API.Cache.GetNbBootscripts()) -} diff --git a/commands/inspect.go b/commands/inspect.go deleted file mode 100644 index 971af1e98f..0000000000 --- a/commands/inspect.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "encoding/json" - "fmt" - "os" - "text/template" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - "github.com/scaleway/scaleway-cli/vendor/github.com/skratchdot/open-golang/open" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdInspect = &types.Command{ - Exec: runInspect, - UsageLine: "inspect [OPTIONS] IDENTIFIER [IDENTIFIER...]", - Description: "Return low-level information on a server, image, snapshot, volume or bootscript", - Help: "Return low-level information on a server, image, snapshot, volume or bootscript.", - Examples: ` - $ scw inspect my-server - $ scw inspect server:my-server - $ scw inspect --browser my-server - $ scw inspect a-public-image - $ scw inspect image:a-public-image - $ scw inspect my-snapshot - $ scw inspect snapshot:my-snapshot - $ scw inspect my-volume - $ scw inspect volume:my-volume - $ scw inspect my-image - $ scw inspect image:my-image - $ scw inspect my-server | jq '.[0].public_ip.address' - $ scw inspect $(scw inspect my-image | jq '.[0].root_volume.id') - $ scw inspect -f "{{ .PublicAddress.IP }}" my-server - $ scw --sensitive inspect my-server -`, -} - -func init() { - cmdInspect.Flag.BoolVar(&inspectHelp, []string{"h", "-help"}, false, "Print usage") - cmdInspect.Flag.StringVar(&inspectFormat, []string{"f", "-format"}, "", "Format the output using the given go template") - cmdInspect.Flag.BoolVar(&inspectBrowser, []string{"b", "-browser"}, false, "Inspect object in browser") -} - -// Flags -var inspectFormat string // -f, --format flag -var inspectBrowser bool // -b, --browser flag -var inspectHelp bool // -h, --help flag - -func runInspect(cmd *types.Command, args []string) { - if inspectHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - nbInspected := 0 - ci := make(chan api.ScalewayResolvedIdentifier) - cj := make(chan api.InspectIdentifierResult) - go api.ResolveIdentifiers(cmd.API, args, ci) - go api.InspectIdentifiers(cmd.API, ci, cj) - - if inspectBrowser { - // --browser will open links in the browser - for { - data, isOpen := <-cj - if !isOpen { - break - } - - switch data.Type { - case api.IdentifierServer: - err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/servers/%s", data.Object.(*api.ScalewayServer).Identifier)) - if err != nil { - log.Fatalf("Cannot open browser: %v", err) - } - nbInspected++ - case api.IdentifierImage: - err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/images/%s", data.Object.(*api.ScalewayImage).Identifier)) - if err != nil { - log.Fatalf("Cannot open browser: %v", err) - } - nbInspected++ - case api.IdentifierVolume: - err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/volumes/%s", data.Object.(*api.ScalewayVolume).Identifier)) - if err != nil { - log.Fatalf("Cannot open browser: %v", err) - } - nbInspected++ - case api.IdentifierSnapshot: - log.Errorf("Cannot use '--browser' option for snapshots") - case api.IdentifierBootscript: - log.Errorf("Cannot use '--browser' option for bootscripts") - } - } - - } else { - // without --browser option, inspect will print object info to the terminal - res := "[" - for { - data, isOpen := <-cj - if !isOpen { - break - } - if inspectFormat == "" { - dataB, err := json.MarshalIndent(data.Object, "", " ") - if err == nil { - if nbInspected != 0 { - res += ",\n" - } - res += string(dataB) - nbInspected++ - } - } else { - tmpl, err := template.New("").Funcs(api.FuncMap).Parse(inspectFormat) - if err != nil { - log.Fatalf("Format parsing error: %v", err) - } - - err = tmpl.Execute(os.Stdout, data.Object) - if err != nil { - log.Fatalf("Format execution error: %v", err) - } - fmt.Fprint(os.Stdout, "\n") - nbInspected++ - } - } - res += "]" - - if inspectFormat == "" { - if os.Getenv("SCW_SENSITIVE") != "1" { - res = cmd.API.HideAPICredentials(res) - } - fmt.Println(res) - } - } - - if len(args) != nbInspected { - os.Exit(1) - } -} diff --git a/commands/kill.go b/commands/kill.go deleted file mode 100644 index 7ee6b9c8a0..0000000000 --- a/commands/kill.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "os" - "os/exec" - "strings" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdKill = &types.Command{ - Exec: runKill, - UsageLine: "kill [OPTIONS] SERVER", - Description: "Kill a running server", - Help: "Kill a running server.", -} - -func init() { - cmdKill.Flag.BoolVar(&killHelp, []string{"h", "-help"}, false, "Print usage") - cmdKill.Flag.StringVar(&killGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") - // FIXME: add --signal option -} - -// Flags -var killHelp bool // -h, --help flag -var killGateway string // -g, --gateway flag - -func runKill(cmd *types.Command, args []string) { - if killHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - serverID := cmd.API.GetServerID(args[0]) - command := "halt" - server, err := cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Failed to get server information for %s: %v", serverID, err) - } - - // Resolve gateway - if killGateway == "" { - killGateway = os.Getenv("SCW_GATEWAY") - } - var gateway string - if killGateway == serverID || killGateway == args[0] { - gateway = "" - } else { - gateway, err = api.ResolveGateway(cmd.API, killGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", killGateway, err) - } - } - - execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, nil, []string{command}, gateway)) - - log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) - - spawn := exec.Command("ssh", execCmd...) - spawn.Stdout = os.Stdout - spawn.Stdin = os.Stdin - spawn.Stderr = os.Stderr - err = spawn.Run() - if err != nil { - log.Fatal(err) - } -} diff --git a/commands/login.go b/commands/login.go deleted file mode 100644 index b51c3e902a..0000000000 --- a/commands/login.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - "strings" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - "github.com/scaleway/scaleway-cli/vendor/golang.org/x/crypto/ssh/terminal" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdLogin = &types.Command{ - Exec: runLogin, - UsageLine: "login [OPTIONS]", - Description: "Log in to Scaleway API", - Help: `Generates a configuration file in '/home/$USER/.scwrc' -containing credentials used to interact with the Scaleway API. This -configuration file is automatically used by the 'scw' commands. - -You can get your credentials on https://cloud.scaleway.com/#/credentials -`, -} - -func promptUser(prompt string, output *string, echo bool) { - fmt.Fprintf(os.Stdout, prompt) - os.Stdout.Sync() - - if !echo { - b, err := terminal.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - log.Fatalf("Unable to prompt for password: %s", err) - } - *output = string(b) - fmt.Fprintf(os.Stdout, "\n") - } else { - reader := bufio.NewReader(os.Stdin) - *output, _ = reader.ReadString('\n') - } -} - -func init() { - cmdLogin.Flag.StringVar(&organization, []string{"o", "-organization"}, "", "Organization") - cmdLogin.Flag.StringVar(&token, []string{"t", "-token"}, "", "Token") - cmdLogin.Flag.BoolVar(&loginHelp, []string{"h", "-help"}, false, "Print usage") -} - -// FLags -var organization string // -o flag -var token string // -t flag -var loginHelp bool // -h, --help flag - -func runLogin(cmd *types.Command, args []string) { - if loginHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - - if len(organization) == 0 { - fmt.Println("You can get your credentials on https://cloud.scaleway.com/#/credentials") - promptUser("Organization (access key): ", &organization, true) - } - if len(token) == 0 { - promptUser("Token: ", &token, false) - } - - cfg := &api.Config{ - APIEndPoint: "https://account.scaleway.com/", - Organization: strings.Trim(organization, "\n"), - Token: strings.Trim(token, "\n"), - } - - api, err := api.NewScalewayAPI(cfg.APIEndPoint, cfg.Organization, cfg.Token) - if err != nil { - log.Fatalf("Unable to create ScalewayAPI: %s", err) - } - err = api.CheckCredentials() - if err != nil { - log.Fatalf("Unable to contact ScalewayAPI: %s", err) - } - - scwrcPath, err := utils.GetConfigFilePath() - if err != nil { - log.Fatalf("Unable to get scwrc config file path: %s", err) - } - scwrc, err := os.OpenFile(scwrcPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) - if err != nil { - log.Fatalf("Unable to create scwrc config file: %s", err) - } - defer scwrc.Close() - encoder := json.NewEncoder(scwrc) - cfg.APIEndPoint = "https://api.scaleway.com/" - err = encoder.Encode(cfg) - if err != nil { - log.Fatalf("Unable to encode scw config file: %s", err) - } -} diff --git a/commands/logout.go b/commands/logout.go deleted file mode 100644 index 9428546e81..0000000000 --- a/commands/logout.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdLogout = &types.Command{ - Exec: runLogout, - UsageLine: "logout [OPTIONS]", - Description: "Log out from the Scaleway API", - Help: "Log out from the Scaleway API.", -} - -func init() { - cmdLogout.Flag.BoolVar(&logoutHelp, []string{"h", "-help"}, false, "Print usage") -} - -// FLags -var logoutHelp bool // -h, --help flag - -func runLogout(cmd *types.Command, args []string) { - if logoutHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - - scwrcPath, err := utils.GetConfigFilePath() - if err != nil { - log.Fatalf("Unable to get scwrc config file path: %v", err) - } - - if _, err = os.Stat(scwrcPath); err == nil { - err = os.Remove(scwrcPath) - if err != nil { - log.Fatalf("Unable to remove scwrc config file: %v", err) - } - } -} diff --git a/commands/logs.go b/commands/logs.go deleted file mode 100644 index 780ee8f997..0000000000 --- a/commands/logs.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdLogs = &types.Command{ - Exec: runLogs, - UsageLine: "logs [OPTIONS] SERVER", - Description: "Fetch the logs of a server", - Help: "Fetch the logs of a server.", -} - -func init() { - cmdLogs.Flag.BoolVar(&logsHelp, []string{"h", "-help"}, false, "Print usage") - cmdLogs.Flag.StringVar(&logsGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") -} - -// FLags -var logsHelp bool // -h, --help flag -var logsGateway string // -g, --gateway flag - -func runLogs(cmd *types.Command, args []string) { - if logsHelp { - cmd.PrintUsage() - } - if len(args) != 1 { - cmd.PrintShortUsage() - } - - serverID := cmd.API.GetServerID(args[0]) - server, err := cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Failed to get server information for %s: %v", serverID, err) - } - - // FIXME: switch to serial history when API is ready - - // Resolve gateway - if logsGateway == "" { - logsGateway = os.Getenv("SCW_GATEWAY") - } - var gateway string - if logsGateway == serverID || logsGateway == args[0] { - gateway = "" - } else { - gateway, err = api.ResolveGateway(cmd.API, logsGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", logsGateway, err) - } - } - - command := []string{"dmesg"} - err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, command, true, gateway) - if err != nil { - log.Fatalf("Command execution failed: %v", err) - } -} diff --git a/commands/port.go b/commands/port.go deleted file mode 100644 index 78a31d9011..0000000000 --- a/commands/port.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdPort = &types.Command{ - Exec: runPort, - UsageLine: "port [OPTIONS] SERVER [PRIVATE_PORT[/PROTO]]", - Description: "Lookup the public-facing port that is NAT-ed to PRIVATE_PORT", - Help: "List port mappings for the SERVER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT", -} - -func init() { - cmdPort.Flag.BoolVar(&portHelp, []string{"h", "-help"}, false, "Print usage") - cmdPort.Flag.StringVar(&portGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") -} - -// FLags -var portHelp bool // -h, --help flag -var portGateway string // -g, --gateway flag - -func runPort(cmd *types.Command, args []string) { - if portHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - serverID := cmd.API.GetServerID(args[0]) - server, err := cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Failed to get server information for %s: %v", serverID, err) - } - - // Resolve gateway - if portGateway == "" { - portGateway = os.Getenv("SCW_GATEWAY") - } - var gateway string - if portGateway == serverID || portGateway == args[0] { - gateway = "" - } else { - gateway, err = api.ResolveGateway(cmd.API, portGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", portGateway, err) - } - } - - command := []string{"netstat -lutn 2>/dev/null | grep LISTEN"} - err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, command, true, gateway) - if err != nil { - log.Fatalf("Command execution failed: %v", err) - } -} diff --git a/commands/ps.go b/commands/ps.go deleted file mode 100644 index 7eecdbd11b..0000000000 --- a/commands/ps.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "text/tabwriter" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" - - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdPs = &types.Command{ - Exec: runPs, - UsageLine: "ps [OPTIONS]", - Description: "List servers", - Help: "List servers. By default, only running servers are displayed.", -} - -func init() { - cmdPs.Flag.BoolVar(&psA, []string{"a", "-all"}, false, "Show all servers. Only running servers are shown by default") - cmdPs.Flag.BoolVar(&psL, []string{"l", "-latest"}, false, "Show only the latest created server, include non-running ones") - cmdPs.Flag.IntVar(&psN, []string{"n"}, 0, "Show n last created servers, include non-running ones") - cmdPs.Flag.BoolVar(&psNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") - cmdPs.Flag.BoolVar(&psQ, []string{"q", "-quiet"}, false, "Only display numeric IDs") - cmdPs.Flag.BoolVar(&psHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var psA bool // -a flag -var psL bool // -l flag -var psQ bool // -q flag -var psNoTrunc bool // -no-trunc flag -var psN int // -n flag -var psHelp bool // -h, --help flag - -func runPs(cmd *types.Command, args []string) { - if psHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - - limit := psN - if psL { - limit = 1 - } - servers, err := cmd.API.GetServers(psA || psN > 0 || psL, limit) - if err != nil { - log.Fatalf("Unable to fetch servers from the Scaleway API: %v", err) - } - - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - defer w.Flush() - if !psQ { - fmt.Fprintf(w, "SERVER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAME\n") - } - for _, server := range *servers { - if psQ { - fmt.Fprintf(w, "%s\n", server.Identifier) - } else { - shortID := utils.TruncIf(server.Identifier, 8, !psNoTrunc) - shortImage := utils.TruncIf(utils.Wordify(server.Image.Name), 25, !psNoTrunc) - shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !psNoTrunc) - creationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.CreationDate) - shortCreationDate := units.HumanDuration(time.Now().UTC().Sub(creationTime)) - port := server.PublicAddress.IP - fmt.Fprintf(w, "%s\t%s\t\t%s\t%s\t%s\t%s\n", shortID, shortImage, shortCreationDate, server.State, port, shortName) - } - } -} diff --git a/commands/rename.go b/commands/rename.go deleted file mode 100644 index b1f8e787a6..0000000000 --- a/commands/rename.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdRename = &types.Command{ - Exec: runRename, - UsageLine: "rename [OPTIONS] SERVER NEW_NAME", - Description: "Rename a server", - Help: "Rename a server.", -} - -func init() { - cmdRename.Flag.BoolVar(&renameHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var renameHelp bool // -h, --help flag - -func runRename(cmd *types.Command, args []string) { - if renameHelp { - cmd.PrintUsage() - } - if len(args) != 2 { - cmd.PrintShortUsage() - } - - serverID := cmd.API.GetServerID(args[0]) - - var server api.ScalewayServerPatchDefinition - server.Name = &args[1] - - err := cmd.API.PatchServer(serverID, server) - if err != nil { - log.Fatalf("Cannot rename server: %v", err) - } else { - cmd.API.Cache.InsertServer(serverID, *server.Name) - } -} diff --git a/commands/restart.go b/commands/restart.go deleted file mode 100644 index a13b17ef55..0000000000 --- a/commands/restart.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdRestart = &types.Command{ - Exec: runRestart, - UsageLine: "restart [OPTIONS] SERVER [SERVER...]", - Description: "Restart a running server", - Help: "Restart a running server.", -} - -func init() { - cmdRestart.Flag.BoolVar(&restartHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var restartHelp bool // -h, --help flag - -func runRestart(cmd *types.Command, args []string) { - if restartHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - hasError := false - for _, needle := range args { - server := cmd.API.GetServerID(needle) - err := cmd.API.PostServerAction(server, "reboot") - if err != nil { - if err.Error() != "server is being stopped or rebooted" { - log.Errorf("failed to restart server %s: %s", server, err) - hasError = true - } - } else { - fmt.Println(needle) - } - if hasError { - os.Exit(1) - } - } -} diff --git a/commands/rmi.go b/commands/rmi.go deleted file mode 100644 index f804a721f9..0000000000 --- a/commands/rmi.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdRmi = &types.Command{ - Exec: runRmi, - UsageLine: "rmi [OPTIONS] IMAGE [IMAGE...]", - Description: "Remove one or more images", - Help: "Remove one or more images.", - Examples: ` - $ scw rmi myimage - $ scw rmi $(scw images -q) -`, -} - -func init() { - cmdRmi.Flag.BoolVar(&rmiHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var rmiHelp bool // -h, --help flag - -func runRmi(cmd *types.Command, args []string) { - if rmiHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - if len(args) == 0 { - log.Fatalf("usage: scw %s", cmd.UsageLine) - } - hasError := false - for _, needle := range args { - image := cmd.API.GetImageID(needle, true) - err := cmd.API.DeleteImage(image) - if err != nil { - log.Errorf("failed to delete image %s: %s", image, err) - hasError = true - } else { - fmt.Println(needle) - } - } - if hasError { - os.Exit(1) - } -} diff --git a/commands/run.go b/commands/run.go deleted file mode 100644 index 3306929036..0000000000 --- a/commands/run.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - api "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdRun = &types.Command{ - Exec: runRun, - UsageLine: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", - Description: "Run a command in a new server", - Help: "Run a command in a new server.", - Examples: ` - $ scw run ubuntu-trusty - $ scw run --gateway=myotherserver ubuntu-trusty - $ scw run ubuntu-trusty bash - $ scw run --name=mydocker docker docker run moul/nyancat:armhf - $ scw run --bootscript=3.2.34 --env="boot=live rescue_image=http://j.mp/scaleway-ubuntu-trusty-tarball" 50GB bash - $ scw run --attach alpine - $ scw run --detach alpine -`, -} - -func init() { - cmdRun.Flag.StringVar(&runCreateName, []string{"-name"}, "", "Assign a name") - cmdRun.Flag.StringVar(&runCreateBootscript, []string{"-bootscript"}, "", "Assign a bootscript") - cmdRun.Flag.StringVar(&runCreateEnv, []string{"e", "-env"}, "", "Provide metadata tags passed to initrd (i.e., boot=resue INITRD_DEBUG=1)") - cmdRun.Flag.StringVar(&runCreateVolume, []string{"v", "-volume"}, "", "Attach additional volume (i.e., 50G)") - cmdRun.Flag.BoolVar(&runHelpFlag, []string{"h", "-help"}, false, "Print usage") - cmdRun.Flag.BoolVar(&runAttachFlag, []string{"a", "-attach"}, false, "Attach to serial console") - cmdRun.Flag.BoolVar(&runDetachFlag, []string{"d", "-detach"}, false, "Run server in background and print server ID") - cmdRun.Flag.StringVar(&runGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") - // FIXME: handle start --timeout -} - -// Flags -var runCreateName string // --name flag -var runCreateBootscript string // --bootscript flag -var runCreateEnv string // -e, --env flag -var runCreateVolume string // -v, --volume flag -var runHelpFlag bool // -h, --help flag -var runAttachFlag bool // -a, --attach flag -var runDetachFlag bool // -d, --detach flag -var runGateway string // -g, --gateway flag - -func runRun(cmd *types.Command, args []string) { - if runHelpFlag { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - if runAttachFlag && len(args) > 1 { - log.Fatalf("Conflicting options: -a and COMMAND") - } - if runAttachFlag && runDetachFlag { - log.Fatalf("Conflicting options: -a and -d") - } - if runDetachFlag && len(args) > 1 { - log.Fatalf("Conflicting options: -d and COMMAND") - } - - if runGateway == "" { - runGateway = os.Getenv("SCW_GATEWAY") - } - - // create IMAGE - log.Info("Server creation ...") - dynamicIPRequired := runGateway == "" - serverID, err := api.CreateServer(cmd.API, args[0], runCreateName, runCreateBootscript, runCreateEnv, runCreateVolume, dynamicIPRequired) - if err != nil { - log.Fatalf("Failed to create server: %v", err) - } - log.Infof("Server created: %s", serverID) - - // start SERVER - log.Info("Server start requested ...") - err = api.StartServer(cmd.API, serverID, false) - if err != nil { - log.Fatalf("Failed to start server %s: %v", serverID, err) - } - log.Info("Server is starting, this may take up to a minute ...") - - if runDetachFlag { - fmt.Println(serverID) - return - } - - if runAttachFlag { - // Attach to server serial - log.Info("Attaching to server console ...") - err = utils.AttachToSerial(serverID, cmd.API.Token, true) - if err != nil { - log.Fatalf("Cannot attach to server serial: %v", err) - } - } else { - // Resolve gateway - gateway, err := api.ResolveGateway(cmd.API, runGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", runGateway, err) - } - - // waiting for server to be ready - log.Debug("Waiting for server to be ready") - // We wait for 30 seconds, which is the minimal amount of time needed by a server to boot - server, err := api.WaitForServerReady(cmd.API, serverID, gateway) - if err != nil { - log.Fatalf("Cannot get access to server %s: %v", serverID, err) - } - log.Debugf("SSH server is available: %s:22", server.PublicAddress.IP) - log.Info("Server is ready !") - - // exec -w SERVER COMMAND ARGS... - if len(args) < 2 { - log.Info("Connecting to server ...") - err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, []string{}, false, gateway) - } else { - log.Infof("Executing command: %s ...", args[1:]) - err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args[1:], false, gateway) - } - if err != nil { - log.Infof("Command execution failed: %v", err) - os.Exit(1) - } - log.Info("Command successfuly executed") - } -} diff --git a/commands/search.go b/commands/search.go deleted file mode 100644 index 6716c7c839..0000000000 --- a/commands/search.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "text/tabwriter" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdSearch = &types.Command{ - Exec: runSearch, - UsageLine: "search [OPTIONS] TERM", - Description: "Search the Scaleway Hub for images", - Help: "Search the Scaleway Hub for images.", -} - -func init() { - cmdSearch.Flag.BoolVar(&searchNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") - cmdSearch.Flag.BoolVar(&searchHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var searchNoTrunc bool // --no-trunc flag -var searchHelp bool // -h, --help flag - -func runSearch(cmd *types.Command, args []string) { - if searchHelp { - cmd.PrintUsage() - } - if len(args) != 1 { - cmd.PrintShortUsage() - } - - w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) - defer w.Flush() - fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") - - var entries = []api.ScalewayImageInterface{} - - images, err := cmd.API.GetImages() - if err != nil { - log.Fatalf("unable to fetch images from the Scaleway API: %v", err) - } - for _, val := range *images { - entries = append(entries, api.ScalewayImageInterface{ - Type: "image", - Name: val.Name, - Public: val.Public, - }) - } - - snapshots, err := cmd.API.GetSnapshots() - if err != nil { - log.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) - } - for _, val := range *snapshots { - entries = append(entries, api.ScalewayImageInterface{ - Type: "snapshot", - Name: val.Name, - Public: false, - }) - } - - for _, image := range entries { - // name field - name := utils.TruncIf(utils.Wordify(image.Name), 45, !searchNoTrunc) - - // description field - var description string - switch image.Type { - case "image": - if image.Public { - description = "public image" - } else { - description = "user image" - } - - case "snapshot": - description = "user snapshot" - } - description = utils.TruncIf(utils.Wordify(description), 45, !searchNoTrunc) - - // official field - var official string - if image.Public { - official = "[OK]" - } else { - official = "" - } - - fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\n", name, description, 0, official, "") - } -} diff --git a/commands/start.go b/commands/start.go deleted file mode 100644 index bc780188b5..0000000000 --- a/commands/start.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdStart = &types.Command{ - Exec: runStart, - UsageLine: "start [OPTIONS] SERVER [SERVER...]", - Description: "Start a stopped server", - Help: "Start a stopped server.", -} - -func init() { - cmdStart.Flag.BoolVar(&startW, []string{"w", "-wait"}, false, "Synchronous start. Wait for SSH to be ready") - cmdStart.Flag.Float64Var(&startTimeout, []string{"T", "-timeout"}, 0, "Set timeout values to seconds") - cmdStart.Flag.BoolVar(&startHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var startW bool // -w flag -var startTimeout float64 // -T flag -var startHelp bool // -h, --help flag - -func runStart(cmd *types.Command, args []string) { - if startHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - hasError := false - errChan := make(chan error) - successChan := make(chan bool) - remainingItems := len(args) - - for i := range args { - needle := args[i] - go api.StartServerOnce(cmd.API, needle, startW, successChan, errChan) - } - - if startTimeout > 0 { - go func() { - time.Sleep(time.Duration(startTimeout*1000) * time.Millisecond) - log.Fatalf("Operation timed out") - }() - } - - for { - select { - case _ = <-successChan: - remainingItems-- - case err := <-errChan: - log.Errorf(fmt.Sprintf("%s", err)) - remainingItems-- - hasError = true - } - - if remainingItems == 0 { - break - } - } - if hasError { - os.Exit(1) - } -} diff --git a/commands/stop.go b/commands/stop.go deleted file mode 100644 index 4546ce656f..0000000000 --- a/commands/stop.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdStop = &types.Command{ - Exec: runStop, - UsageLine: "stop [OPTIONS] SERVER [SERVER...]", - Description: "Stop a running server", - Help: "Stop a running server.", - Examples: ` - $ scw stop my-running-server my-second-running-server - $ scw stop -t my-running-server my-second-running-server - $ scw stop $(scw ps -q) - $ scw stop $(scw ps | grep mysql | awk '{print $1}') - $ scw stop server && stop wait server - $ scw stop -w server -`, -} - -func init() { - cmdStop.Flag.BoolVar(&stopT, []string{"t", "-terminate"}, false, "Stop and trash a server with its volumes") - cmdStop.Flag.BoolVar(&stopHelp, []string{"h", "-help"}, false, "Print usage") - cmdStop.Flag.BoolVar(&stopW, []string{"w", "-wait"}, false, "Synchronous stop. Wait for SSH to be ready") -} - -// Flags -var stopT bool // -t flag -var stopHelp bool // -h, --help flag -var stopW bool // -w, --wait flat - -// FIXME: parallelize stop when stopping multiple servers -func runStop(cmd *types.Command, args []string) { - if stopHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - hasError := false - for _, needle := range args { - serverID := cmd.API.GetServerID(needle) - action := "poweroff" - if stopT { - action = "terminate" - } - err := cmd.API.PostServerAction(serverID, action) - if err != nil { - if err.Error() != "server should be running" && err.Error() != "server is being stopped or rebooted" { - log.Warningf("failed to stop server %s: %s", serverID, err) - hasError = true - } - } else { - if stopW { - // We wait for 10 seconds which is the minimal amount of time needed for a server to stop - time.Sleep(10 * time.Second) - _, err = api.WaitForServerStopped(cmd.API, serverID) - if err != nil { - log.Errorf("failed to wait for server %s: %v", serverID, err) - hasError = true - } - } - if stopT { - cmd.API.Cache.RemoveServer(serverID) - } - fmt.Println(needle) - } - } - - if hasError { - os.Exit(1) - } -} diff --git a/commands/top.go b/commands/top.go deleted file mode 100644 index 0b28cd4c87..0000000000 --- a/commands/top.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "fmt" - "os" - "os/exec" - "strings" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" -) - -var cmdTop = &types.Command{ - Exec: runTop, - UsageLine: "top [OPTIONS] SERVER", // FIXME: add ps options - Description: "Lookup the running processes of a server", - Help: "Lookup the running processes of a server.", -} - -func init() { - cmdTop.Flag.BoolVar(&topHelp, []string{"h", "-help"}, false, "Print usage") - cmdTop.Flag.StringVar(&topGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") -} - -// Flags -var topHelp bool // -h, --help flag -var topGateway string // -g, --gateway flag - -func runTop(cmd *types.Command, args []string) { - if topHelp { - cmd.PrintUsage() - } - if len(args) != 1 { - cmd.PrintShortUsage() - } - - serverID := cmd.API.GetServerID(args[0]) - command := "ps" - server, err := cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Failed to get server information for %s: %v", serverID, err) - } - - // Resolve gateway - if topGateway == "" { - topGateway = os.Getenv("SCW_GATEWAY") - } - var gateway string - if topGateway == serverID || topGateway == args[0] { - gateway = "" - } else { - gateway, err = api.ResolveGateway(cmd.API, topGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", topGateway, err) - } - } - - execCmd := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, nil, []string{command}, gateway) - log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) - out, err := exec.Command("ssh", execCmd...).CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - log.Fatal(err) - } -} diff --git a/commands/wait.go b/commands/wait.go deleted file mode 100644 index 5c60e4b106..0000000000 --- a/commands/wait.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2015 Scaleway. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE.md file. - -package commands - -import ( - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" -) - -var cmdWait = &types.Command{ - Exec: runWait, - UsageLine: "wait [OPTIONS] SERVER [SERVER...]", - Description: "Block until a server stops", - Help: "Block until a server stops.", -} - -func init() { - cmdWait.Flag.BoolVar(&waitHelp, []string{"h", "-help"}, false, "Print usage") -} - -// Flags -var waitHelp bool // -h, --help flag - -func runWait(cmd *types.Command, args []string) { - if waitHelp { - cmd.PrintUsage() - } - if len(args) < 1 { - cmd.PrintShortUsage() - } - - hasError := false - for _, needle := range args { - serverIdentifier := cmd.API.GetServerID(needle) - - _, err := api.WaitForServerStopped(cmd.API, serverIdentifier) - if err != nil { - log.Errorf("failed to wait for server %s: %v", serverIdentifier, err) - hasError = true - } - } - - if hasError { - os.Exit(1) - } -} diff --git a/api/README.md b/pkg/api/README.md similarity index 100% rename from api/README.md rename to pkg/api/README.md diff --git a/api/api.go b/pkg/api/api.go similarity index 100% rename from api/api.go rename to pkg/api/api.go diff --git a/api/cache.go b/pkg/api/cache.go similarity index 100% rename from api/cache.go rename to pkg/api/cache.go diff --git a/api/helpers.go b/pkg/api/helpers.go similarity index 99% rename from api/helpers.go rename to pkg/api/helpers.go index 35f4084f79..55ed9e5875 100644 --- a/api/helpers.go +++ b/pkg/api/helpers.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "github.com/scaleway/scaleway-cli/utils" + "github.com/scaleway/scaleway-cli/pkg/utils" log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/namesgenerator" "github.com/scaleway/scaleway-cli/vendor/github.com/dustin/go-humanize" diff --git a/api/types.go b/pkg/api/types.go similarity index 100% rename from api/types.go rename to pkg/api/types.go diff --git a/commands/attach.go b/pkg/cli/attach.go similarity index 64% rename from commands/attach.go rename to pkg/cli/attach.go index 9253e20d2f..fc47dff5f2 100644 --- a/commands/attach.go +++ b/pkg/cli/attach.go @@ -2,17 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdAttach = &types.Command{ - Exec: runAttach, +var cmdAttach = &Command{ + Exec: cmdExecAttach, UsageLine: "attach [OPTIONS] SERVER", Description: "Attach to a server serial console", Help: "Attach to a running server serial console.", @@ -32,18 +30,21 @@ func init() { var attachHelp bool // -h, --help flag var attachNoStdin bool // --no-stdin flag -func runAttach(cmd *types.Command, args []string) { +func cmdExecAttach(cmd *Command, rawArgs []string) { if attachHelp { cmd.PrintUsage() } - if len(args) != 1 { + if len(rawArgs) != 1 { cmd.PrintShortUsage() } - serverID := cmd.API.GetServerID(args[0]) - - err := utils.AttachToSerial(serverID, cmd.API.Token, !attachNoStdin) + args := commands.AttachArgs{ + NoStdin: attachNoStdin, + Server: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunAttach(ctx, args) if err != nil { - log.Fatalf("%v", err) + logrus.Fatalf("Cannot execute 'attach': %v", err) } } diff --git a/commands/types/command.go b/pkg/cli/command.go similarity index 82% rename from commands/types/command.go rename to pkg/cli/command.go index 364313e58e..147075dd67 100644 --- a/commands/types/command.go +++ b/pkg/cli/command.go @@ -2,23 +2,23 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -// Golang structs for scw commands -package types +package cli +// Command is a Scaleway command import ( "bytes" "fmt" - "log" "os" "strings" "text/template" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" flag "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/mflag" - "github.com/scaleway/scaleway-cli/api" + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/commands" ) -// Command is a Scaleway command type Command struct { // Exec executes the command Exec func(cmd *Command, args []string) @@ -45,6 +45,18 @@ type Command struct { API *api.ScalewayAPI } +// GetContext returns a standard context, with real stdin, stdout, stderr, a configured API and raw arguments +func (c *Command) GetContext(rawArgs []string) commands.CommandContext { + return commands.CommandContext{ + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: os.Environ(), + RawArgs: rawArgs, + API: c.API, + } +} + // Name returns the command's name func (c *Command) Name() string { name := c.UsageLine @@ -59,7 +71,7 @@ func (c *Command) Name() string { func (c *Command) PrintUsage() { helpMessage, err := commandHelpMessage(c) if err != nil { - log.Fatalf("%v", err) + logrus.Fatalf("%v", err) } fmt.Fprintf(os.Stderr, "%s\n", helpMessage) os.Exit(1) diff --git a/commands/commands.go b/pkg/cli/commands.go similarity index 85% rename from commands/commands.go rename to pkg/cli/commands.go index 0951c0db9a..502321e41f 100644 --- a/commands/commands.go +++ b/pkg/cli/commands.go @@ -5,12 +5,10 @@ // Docker-style commands to manage BareMetal servers // Package commands contains CLI commands -package commands - -import types "github.com/scaleway/scaleway-cli/commands/types" +package cli // Commands is the list of enabled CLI commands -var Commands = []*types.Command{ +var Commands = []*Command{ CmdHelp, cmdAttach, diff --git a/commands/commit.go b/pkg/cli/commit.go similarity index 52% rename from commands/commit.go rename to pkg/cli/commit.go index 68c833410c..e946feaba2 100644 --- a/commands/commit.go +++ b/pkg/cli/commit.go @@ -2,18 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "fmt" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdCommit = &types.Command{ - Exec: runCommit, +var cmdCommit = &Command{ + Exec: cmdExecCommit, UsageLine: "commit [OPTIONS] SERVER [NAME]", Description: "Create a new snapshot from a server's volume", Help: "Create a new snapshot from a server's volume.", @@ -32,29 +29,26 @@ func init() { var commitVolume int // -v, --volume flag var commitHelp bool // -h, --help flag -func runCommit(cmd *types.Command, args []string) { +func cmdExecCommit(cmd *Command, rawArgs []string) { if commitHelp { cmd.PrintUsage() } - if len(args) < 1 { + if len(rawArgs) < 1 { cmd.PrintShortUsage() } - serverID := cmd.API.GetServerID(args[0]) - server, err := cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Cannot fetch server: %v", err) + args := commands.CommitArgs{ + Volume: commitVolume, + Server: rawArgs[0], + Name: "", } - var volume = server.Volumes[fmt.Sprintf("%d", commitVolume)] - var name string - if len(args) > 1 { - name = args[1] - } else { - name = volume.Name + "-snapshot" + if len(rawArgs) > 1 { + args.Name = rawArgs[1] } - snapshot, err := cmd.API.PostSnapshot(volume.Identifier, name) + + ctx := cmd.GetContext(rawArgs) + err := commands.RunCommit(ctx, args) if err != nil { - log.Fatalf("Cannot create snapshot: %v", err) + logrus.Fatalf("Cannot execute 'commit': %v", err) } - fmt.Println(snapshot) } diff --git a/pkg/cli/cp.go b/pkg/cli/cp.go new file mode 100644 index 0000000000..f51db23c12 --- /dev/null +++ b/pkg/cli/cp.go @@ -0,0 +1,62 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdCp = &Command{ + Exec: runCp, + UsageLine: "cp [OPTIONS] SERVER:PATH|HOSTPATH|- SERVER:PATH|HOSTPATH|-", + Description: "Copy files/folders from a PATH on the server to a HOSTDIR on the host", + Help: "Copy files/folders from a PATH on the server to a HOSTDIR on the host\nrunning the command. Use '-' to write the data as a tar file to STDOUT.", + Examples: ` + $ scw cp path/to/my/local/file myserver:path + $ scw cp --gateway=myotherserver path/to/my/local/file myserver:path + $ scw cp myserver:path/to/file path/to/my/local/dir + $ scw cp myserver:path/to/file myserver2:path/to/dir + $ scw cp myserver:path/to/file - > myserver-pathtofile-backup.tar + $ scw cp myserver:path/to/file - | tar -tvf - + $ scw cp path/to/my/local/dir myserver:path + $ scw cp myserver:path/to/dir path/to/my/local/dir + $ scw cp myserver:path/to/dir myserver2:path/to/dir + $ scw cp myserver:path/to/dir - > myserver-pathtodir-backup.tar + $ scw cp myserver:path/to/dir - | tar -tvf - + $ cat archive.tar | scw cp - myserver:/path + $ tar -cvf - . | scw cp - myserver:path +`, +} + +func init() { + cmdCp.Flag.BoolVar(&cpHelp, []string{"h", "-help"}, false, "Print usage") + cmdCp.Flag.StringVar(&cpGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") +} + +// Flags +var cpHelp bool // -h, --help flag +var cpGateway string // -g, --gateway flag + +func runCp(cmd *Command, rawArgs []string) { + if cpHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 2 { + cmd.PrintShortUsage() + } + + args := commands.CpArgs{ + Gateway: cpGateway, + Source: rawArgs[0], + Destination: rawArgs[1], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunCp(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'cp': %v", err) + } + +} diff --git a/commands/create.go b/pkg/cli/create.go similarity index 72% rename from commands/create.go rename to pkg/cli/create.go index 561ca28b84..4806637efe 100644 --- a/commands/create.go +++ b/pkg/cli/create.go @@ -2,18 +2,16 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "fmt" + "strings" - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdCreate = &types.Command{ +var cmdCreate = &Command{ Exec: runCreate, UsageLine: "create [OPTIONS] IMAGE", Description: "Create a new server but do not start it", @@ -42,19 +40,24 @@ var createEnv string // -e, --env flag var createVolume string // -v, --volume flag var createHelp bool // -h, --help flag -func runCreate(cmd *types.Command, args []string) { +func runCreate(cmd *Command, rawArgs []string) { if createHelp { cmd.PrintUsage() } - if len(args) != 1 { + if len(rawArgs) != 1 { cmd.PrintShortUsage() } - serverID, err := api.CreateServer(cmd.API, args[0], createName, createBootscript, createEnv, createVolume, true) - + args := commands.CreateArgs{ + Name: createName, + Bootscript: createBootscript, + Tags: strings.Split(createEnv, " "), + Volumes: strings.Split(createVolume, " "), + Image: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunCreate(ctx, args) if err != nil { - log.Fatalf("Failed to create server: %v", err) + logrus.Fatalf("Cannot execute 'create': %v", err) } - - fmt.Println(serverID) } diff --git a/pkg/cli/events.go b/pkg/cli/events.go new file mode 100644 index 0000000000..79b54a6959 --- /dev/null +++ b/pkg/cli/events.go @@ -0,0 +1,40 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdEvents = &Command{ + Exec: cmdExecEvents, + UsageLine: "events [OPTIONS]", + Description: "Get real time events from the API", + Help: "Get real time events from the API.", +} + +func init() { + cmdEvents.Flag.BoolVar(&eventsHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var eventsHelp bool // -h, --help flag + +func cmdExecEvents(cmd *Command, rawArgs []string) { + if eventsHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.EventsArgs{} + ctx := cmd.GetContext(rawArgs) + err := commands.RunEvents(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'events': %v", err) + } +} diff --git a/commands/exec.go b/pkg/cli/exec.go similarity index 50% rename from commands/exec.go rename to pkg/cli/exec.go index 2852c5ff36..76eeaae79c 100644 --- a/commands/exec.go +++ b/pkg/cli/exec.go @@ -2,21 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "os" - "time" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdExec = &types.Command{ - Exec: runExec, +var cmdExec = &Command{ + Exec: cmdExecExec, UsageLine: "exec [OPTIONS] SERVER [COMMAND] [ARGS...]", Description: "Run a command on a running server", Help: "Run a command on a running server.", @@ -47,57 +41,24 @@ var execTimeout float64 // -T flag var execHelp bool // -h, --help flag var execGateway string // -g, --gateway flag -func runExec(cmd *types.Command, args []string) { +func cmdExecExec(cmd *Command, rawArgs []string) { if execHelp { cmd.PrintUsage() } - if len(args) < 1 { + if len(rawArgs) < 1 { cmd.PrintShortUsage() } - serverID := cmd.API.GetServerID(args[0]) - - // Resolve gateway - if execGateway == "" { - execGateway = os.Getenv("SCW_GATEWAY") + args := commands.ExecArgs{ + Timeout: execTimeout, + Wait: execW, + Gateway: execGateway, + Server: rawArgs[0], + Command: rawArgs[1:], } - var gateway string - var err error - if execGateway == serverID || execGateway == args[0] { - gateway = "" - } else { - gateway, err = api.ResolveGateway(cmd.API, execGateway) - if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", execGateway, err) - } - } - - var server *api.ScalewayServer - if execW { - // --wait - server, err = api.WaitForServerReady(cmd.API, serverID, gateway) - if err != nil { - log.Fatalf("Failed to wait for server to be ready, %v", err) - } - } else { - // no --wait - server, err = cmd.API.GetServer(serverID) - if err != nil { - log.Fatalf("Failed to get server information for %s: %v", serverID, err) - } - } - - if execTimeout > 0 { - go func() { - time.Sleep(time.Duration(execTimeout*1000) * time.Millisecond) - log.Fatalf("Operation timed out") - }() - } - - err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args[1:], !execW, gateway) + ctx := cmd.GetContext(rawArgs) + err := commands.RunExec(ctx, args) if err != nil { - log.Fatalf("%v", err) - os.Exit(1) + logrus.Fatalf("Cannot exec 'exec': %v", err) } - log.Debugf("Command successfuly executed") } diff --git a/commands/help.go b/pkg/cli/help.go similarity index 88% rename from commands/help.go rename to pkg/cli/help.go index 0ba0e42aa2..a1c1e18f3f 100644 --- a/commands/help.go +++ b/pkg/cli/help.go @@ -2,18 +2,17 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "log" "os" "text/template" - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) // CmdHelp is the 'scw help' command -var CmdHelp = &types.Command{ +var CmdHelp = &Command{ Exec: nil, UsageLine: "help [COMMAND]", Description: "help of the scw command line", @@ -54,7 +53,7 @@ Commands: Run 'scw COMMAND --help' for more information on a command. ` -func runHelp(cmd *types.Command, args []string) { +func runHelp(cmd *Command, args []string) { if waitHelp { cmd.PrintUsage() } @@ -69,7 +68,7 @@ func runHelp(cmd *types.Command, args []string) { command.PrintUsage() } } - log.Fatalf("Unknown help topic `%s`. Run 'scw help'.", name) + logrus.Fatalf("Unknown help topic `%s`. Run 'scw help'.", name) } else { t := template.New("top") template.Must(t.Parse(helpTemplate)) diff --git a/pkg/cli/history.go b/pkg/cli/history.go new file mode 100644 index 0000000000..3f0e808720 --- /dev/null +++ b/pkg/cli/history.go @@ -0,0 +1,48 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdHistory = &Command{ + Exec: runHistory, + UsageLine: "history [OPTIONS] IMAGE", + Description: "Show the history of an image", + Help: "Show the history of an image.", +} + +func init() { + cmdHistory.Flag.BoolVar(&historyNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") + cmdHistory.Flag.BoolVar(&historyQuiet, []string{"q", "-quiet"}, false, "Only show numeric IDs") + cmdHistory.Flag.BoolVar(&historyHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var historyNoTrunc bool // --no-trunc flag +var historyQuiet bool // -q, --quiet flag +var historyHelp bool // -h, --help flag + +func runHistory(cmd *Command, rawArgs []string) { + if historyHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 1 { + cmd.PrintShortUsage() + } + + args := commands.HistoryArgs{ + Quiet: historyQuiet, + NoTrunc: historyNoTrunc, + Image: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunHistory(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'history': %v", err) + } +} diff --git a/pkg/cli/images.go b/pkg/cli/images.go new file mode 100644 index 0000000000..f26b1bf3b6 --- /dev/null +++ b/pkg/cli/images.go @@ -0,0 +1,50 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdImages = &Command{ + Exec: runImages, + UsageLine: "images [OPTIONS]", + Description: "List images", + Help: "List images.", +} + +func init() { + cmdImages.Flag.BoolVar(&imagesA, []string{"a", "-all"}, false, "Show all iamges") + cmdImages.Flag.BoolVar(&imagesNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") + cmdImages.Flag.BoolVar(&imagesQ, []string{"q", "-quiet"}, false, "Only show numeric IDs") + cmdImages.Flag.BoolVar(&imagesHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var imagesA bool // -a flag +var imagesQ bool // -q flag +var imagesNoTrunc bool // -no-trunc flag +var imagesHelp bool // -h, --help flag + +func runImages(cmd *Command, rawArgs []string) { + if imagesHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.ImagesArgs{ + All: imagesA, + Quiet: imagesQ, + NoTrunc: imagesNoTrunc, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunImages(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'images': %v", err) + } +} diff --git a/pkg/cli/info.go b/pkg/cli/info.go new file mode 100644 index 0000000000..74b9e207ee --- /dev/null +++ b/pkg/cli/info.go @@ -0,0 +1,40 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdInfo = &Command{ + Exec: runInfo, + UsageLine: "info [OPTIONS]", + Description: "Display system-wide information", + Help: "Display system-wide information.", +} + +func init() { + cmdInfo.Flag.BoolVar(&infoHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var infoHelp bool // -h, --help flag + +func runInfo(cmd *Command, rawArgs []string) { + if infoHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.InfoArgs{} + ctx := cmd.GetContext(rawArgs) + err := commands.RunInfo(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'info': %v", err) + } +} diff --git a/pkg/cli/inspect.go b/pkg/cli/inspect.go new file mode 100644 index 0000000000..0c5cba302e --- /dev/null +++ b/pkg/cli/inspect.go @@ -0,0 +1,66 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdInspect = &Command{ + Exec: runInspect, + UsageLine: "inspect [OPTIONS] IDENTIFIER [IDENTIFIER...]", + Description: "Return low-level information on a server, image, snapshot, volume or bootscript", + Help: "Return low-level information on a server, image, snapshot, volume or bootscript.", + Examples: ` + $ scw inspect my-server + $ scw inspect server:my-server + $ scw inspect --browser my-server + $ scw inspect a-public-image + $ scw inspect image:a-public-image + $ scw inspect my-snapshot + $ scw inspect snapshot:my-snapshot + $ scw inspect my-volume + $ scw inspect volume:my-volume + $ scw inspect my-image + $ scw inspect image:my-image + $ scw inspect my-server | jq '.[0].public_ip.address' + $ scw inspect $(scw inspect my-image | jq '.[0].root_volume.id') + $ scw inspect -f "{{ .PublicAddress.IP }}" my-server + $ scw --sensitive inspect my-server +`, +} + +func init() { + cmdInspect.Flag.BoolVar(&inspectHelp, []string{"h", "-help"}, false, "Print usage") + cmdInspect.Flag.StringVar(&inspectFormat, []string{"f", "-format"}, "", "Format the output using the given go template") + cmdInspect.Flag.BoolVar(&inspectBrowser, []string{"b", "-browser"}, false, "Inspect object in browser") +} + +// Flags +var inspectFormat string // -f, --format flag +var inspectBrowser bool // -b, --browser flag +var inspectHelp bool // -h, --help flag + +func runInspect(cmd *Command, rawArgs []string) { + if inspectHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.InspectArgs{ + Format: inspectFormat, + Browser: inspectBrowser, + Identifiers: rawArgs, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunInspect(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'inspect': %v", err) + } +} diff --git a/pkg/cli/kill.go b/pkg/cli/kill.go new file mode 100644 index 0000000000..a5d181e465 --- /dev/null +++ b/pkg/cli/kill.go @@ -0,0 +1,47 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdKill = &Command{ + Exec: runKill, + UsageLine: "kill [OPTIONS] SERVER", + Description: "Kill a running server", + Help: "Kill a running server.", +} + +func init() { + cmdKill.Flag.BoolVar(&killHelp, []string{"h", "-help"}, false, "Print usage") + cmdKill.Flag.StringVar(&killGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") + // FIXME: add --signal option +} + +// Flags +var killHelp bool // -h, --help flag +var killGateway string // -g, --gateway flag + +func runKill(cmd *Command, rawArgs []string) { + if killHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.KillArgs{ + Gateway: killGateway, + Server: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunKill(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'kill': %v", err) + } +} diff --git a/pkg/cli/login.go b/pkg/cli/login.go new file mode 100644 index 0000000000..8cbe1bd6e2 --- /dev/null +++ b/pkg/cli/login.go @@ -0,0 +1,52 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdLogin = &Command{ + Exec: cmdExecLogin, + UsageLine: "login [OPTIONS]", + Description: "Log in to Scaleway API", + Help: `Generates a configuration file in '/home/$USER/.scwrc' +containing credentials used to interact with the Scaleway API. This +configuration file is automatically used by the 'scw' commands. + +You can get your credentials on https://cloud.scaleway.com/#/credentials +`, +} + +func init() { + cmdLogin.Flag.StringVar(&organization, []string{"o", "-organization"}, "", "Organization") + cmdLogin.Flag.StringVar(&token, []string{"t", "-token"}, "", "Token") + cmdLogin.Flag.BoolVar(&loginHelp, []string{"h", "-help"}, false, "Print usage") +} + +// FLags +var organization string // -o flag +var token string // -t flag +var loginHelp bool // -h, --help flag + +func cmdExecLogin(cmd *Command, rawArgs []string) { + if loginHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.LoginArgs{ + Organization: organization, + Token: token, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunLogin(ctx, args) + if err != nil { + log.Fatalf("Cannot execute 'login': %v", err) + } +} diff --git a/pkg/cli/logout.go b/pkg/cli/logout.go new file mode 100644 index 0000000000..e8d0160a7a --- /dev/null +++ b/pkg/cli/logout.go @@ -0,0 +1,40 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdLogout = &Command{ + Exec: runLogout, + UsageLine: "logout [OPTIONS]", + Description: "Log out from the Scaleway API", + Help: "Log out from the Scaleway API.", +} + +func init() { + cmdLogout.Flag.BoolVar(&logoutHelp, []string{"h", "-help"}, false, "Print usage") +} + +// FLags +var logoutHelp bool // -h, --help flag + +func runLogout(cmd *Command, rawArgs []string) { + if logoutHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.LogoutArgs{} + ctx := cmd.GetContext(rawArgs) + err := commands.RunLogout(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'logout': %v", err) + } +} diff --git a/pkg/cli/logs.go b/pkg/cli/logs.go new file mode 100644 index 0000000000..a89091c10b --- /dev/null +++ b/pkg/cli/logs.go @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdLogs = &Command{ + Exec: runLogs, + UsageLine: "logs [OPTIONS] SERVER", + Description: "Fetch the logs of a server", + Help: "Fetch the logs of a server.", +} + +func init() { + cmdLogs.Flag.BoolVar(&logsHelp, []string{"h", "-help"}, false, "Print usage") + cmdLogs.Flag.StringVar(&logsGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") +} + +// FLags +var logsHelp bool // -h, --help flag +var logsGateway string // -g, --gateway flag + +func runLogs(cmd *Command, rawArgs []string) { + if logsHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 1 { + cmd.PrintShortUsage() + } + + args := commands.LogsArgs{ + Gateway: logsGateway, + Server: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunLogs(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'logs': %v", err) + } +} diff --git a/pkg/cli/port.go b/pkg/cli/port.go new file mode 100644 index 0000000000..e7bf460330 --- /dev/null +++ b/pkg/cli/port.go @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdPort = &Command{ + Exec: runPort, + UsageLine: "port [OPTIONS] SERVER [PRIVATE_PORT[/PROTO]]", + Description: "Lookup the public-facing port that is NAT-ed to PRIVATE_PORT", + Help: "List port mappings for the SERVER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT", +} + +func init() { + cmdPort.Flag.BoolVar(&portHelp, []string{"h", "-help"}, false, "Print usage") + cmdPort.Flag.StringVar(&portGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") +} + +// FLags +var portHelp bool // -h, --help flag +var portGateway string // -g, --gateway flag + +func runPort(cmd *Command, rawArgs []string) { + if portHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.PortArgs{ + Gateway: portGateway, + Server: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunPort(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'port': %v", err) + } +} diff --git a/pkg/cli/ps.go b/pkg/cli/ps.go new file mode 100644 index 0000000000..91066164fe --- /dev/null +++ b/pkg/cli/ps.go @@ -0,0 +1,56 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdPs = &Command{ + Exec: cmdExecPs, + UsageLine: "ps [OPTIONS]", + Description: "List servers", + Help: "List servers. By default, only running servers are displayed.", +} + +func init() { + cmdPs.Flag.BoolVar(&psA, []string{"a", "-all"}, false, "Show all servers. Only running servers are shown by default") + cmdPs.Flag.BoolVar(&psL, []string{"l", "-latest"}, false, "Show only the latest created server, include non-running ones") + cmdPs.Flag.IntVar(&psN, []string{"n"}, 0, "Show n last created servers, include non-running ones") + cmdPs.Flag.BoolVar(&psNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") + cmdPs.Flag.BoolVar(&psQ, []string{"q", "-quiet"}, false, "Only display numeric IDs") + cmdPs.Flag.BoolVar(&psHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var psA bool // -a flag +var psL bool // -l flag +var psQ bool // -q flag +var psNoTrunc bool // -no-trunc flag +var psN int // -n flag +var psHelp bool // -h, --help flag + +func cmdExecPs(cmd *Command, rawArgs []string) { + if psHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 0 { + cmd.PrintShortUsage() + } + + args := commands.PsArgs{ + All: psA, + Latest: psL, + Quiet: psQ, + NoTrunc: psNoTrunc, + NLast: psN, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunPs(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'ps': %v", err) + } +} diff --git a/pkg/cli/rename.go b/pkg/cli/rename.go new file mode 100644 index 0000000000..8aa6a6978e --- /dev/null +++ b/pkg/cli/rename.go @@ -0,0 +1,44 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdRename = &Command{ + Exec: runRename, + UsageLine: "rename [OPTIONS] SERVER NEW_NAME", + Description: "Rename a server", + Help: "Rename a server.", +} + +func init() { + cmdRename.Flag.BoolVar(&renameHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var renameHelp bool // -h, --help flag + +func runRename(cmd *Command, rawArgs []string) { + if renameHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 2 { + cmd.PrintShortUsage() + } + + args := commands.RenameArgs{ + Server: rawArgs[0], + NewName: rawArgs[1], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunRename(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'rename': %v", err) + } +} diff --git a/pkg/cli/restart.go b/pkg/cli/restart.go new file mode 100644 index 0000000000..ba045b6fee --- /dev/null +++ b/pkg/cli/restart.go @@ -0,0 +1,43 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdRestart = &Command{ + Exec: runRestart, + UsageLine: "restart [OPTIONS] SERVER [SERVER...]", + Description: "Restart a running server", + Help: "Restart a running server.", +} + +func init() { + cmdRestart.Flag.BoolVar(&restartHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var restartHelp bool // -h, --help flag + +func runRestart(cmd *Command, rawArgs []string) { + if restartHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.RestartArgs{ + Servers: rawArgs, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunRestart(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'restart': %v", err) + } + +} diff --git a/commands/rm.go b/pkg/cli/rm.go similarity index 55% rename from commands/rm.go rename to pkg/cli/rm.go index f2c59f986f..0a420614ce 100644 --- a/commands/rm.go +++ b/pkg/cli/rm.go @@ -2,18 +2,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "fmt" - "os" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdRm = &types.Command{ +var cmdRm = &Command{ Exec: runRm, UsageLine: "rm [OPTIONS] SERVER [SERVER...]", Description: "Remove one or more servers", @@ -32,26 +28,21 @@ func init() { // Flags var rmHelp bool // -h, --help flag -func runRm(cmd *types.Command, args []string) { +func runRm(cmd *Command, rawArgs []string) { if rmHelp { cmd.PrintUsage() } - if len(args) < 1 { + if len(rawArgs) < 1 { cmd.PrintShortUsage() } - hasError := false - for _, needle := range args { - server := cmd.API.GetServerID(needle) - err := cmd.API.DeleteServer(server) - if err != nil { - log.Errorf("failed to delete server %s: %s", server, err) - hasError = true - } else { - fmt.Println(needle) - } + args := commands.RmArgs{ + Servers: rawArgs, } - if hasError { - os.Exit(1) + ctx := cmd.GetContext(rawArgs) + err := commands.RunRm(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'rm': %v", err) } + } diff --git a/pkg/cli/rmi.go b/pkg/cli/rmi.go new file mode 100644 index 0000000000..2c7375ec92 --- /dev/null +++ b/pkg/cli/rmi.go @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +var cmdRmi = &Command{ + Exec: runRmi, + UsageLine: "rmi [OPTIONS] IMAGE [IMAGE...]", + Description: "Remove one or more images", + Help: "Remove one or more images.", + Examples: ` + $ scw rmi myimage + $ scw rmi $(scw images -q) +`, +} + +func init() { + cmdRmi.Flag.BoolVar(&rmiHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var rmiHelp bool // -h, --help flag + +func runRmi(cmd *Command, rawArgs []string) { + if rmiHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.RmiArgs{ + Images: rawArgs, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunRmi(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'rmi': %v", err) + } +} diff --git a/pkg/cli/run.go b/pkg/cli/run.go new file mode 100644 index 0000000000..2e1f382d05 --- /dev/null +++ b/pkg/cli/run.go @@ -0,0 +1,89 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "strings" + + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdRun = &Command{ + Exec: runRun, + UsageLine: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", + Description: "Run a command in a new server", + Help: "Run a command in a new server.", + Examples: ` + $ scw run ubuntu-trusty + $ scw run --gateway=myotherserver ubuntu-trusty + $ scw run ubuntu-trusty bash + $ scw run --name=mydocker docker docker run moul/nyancat:armhf + $ scw run --bootscript=3.2.34 --env="boot=live rescue_image=http://j.mp/scaleway-ubuntu-trusty-tarball" 50GB bash + $ scw run --attach alpine + $ scw run --detach alpine +`, +} + +func init() { + cmdRun.Flag.StringVar(&runCreateName, []string{"-name"}, "", "Assign a name") + cmdRun.Flag.StringVar(&runCreateBootscript, []string{"-bootscript"}, "", "Assign a bootscript") + cmdRun.Flag.StringVar(&runCreateEnv, []string{"e", "-env"}, "", "Provide metadata tags passed to initrd (i.e., boot=resue INITRD_DEBUG=1)") + cmdRun.Flag.StringVar(&runCreateVolume, []string{"v", "-volume"}, "", "Attach additional volume (i.e., 50G)") + cmdRun.Flag.BoolVar(&runHelpFlag, []string{"h", "-help"}, false, "Print usage") + cmdRun.Flag.BoolVar(&runAttachFlag, []string{"a", "-attach"}, false, "Attach to serial console") + cmdRun.Flag.BoolVar(&runDetachFlag, []string{"d", "-detach"}, false, "Run server in background and print server ID") + cmdRun.Flag.StringVar(&runGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") + // FIXME: handle start --timeout +} + +// Flags +var runCreateName string // --name flag +var runCreateBootscript string // --bootscript flag +var runCreateEnv string // -e, --env flag +var runCreateVolume string // -v, --volume flag +var runHelpFlag bool // -h, --help flag +var runAttachFlag bool // -a, --attach flag +var runDetachFlag bool // -d, --detach flag +var runGateway string // -g, --gateway flag + +func runRun(cmd *Command, rawArgs []string) { + if runHelpFlag { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + if runAttachFlag && len(rawArgs) > 1 { + log.Fatalf("Conflicting options: -a and COMMAND") + } + if runAttachFlag && runDetachFlag { + log.Fatalf("Conflicting options: -a and -d") + } + if runDetachFlag && len(rawArgs) > 1 { + log.Fatalf("Conflicting options: -d and COMMAND") + } + + args := commands.RunArgs{ + Attach: runAttachFlag, + Bootscript: runCreateBootscript, + Command: rawArgs[1:], + Detach: runDetachFlag, + Gateway: runGateway, + Image: rawArgs[0], + Name: runCreateName, + Tags: strings.Split(runCreateEnv, " "), + Volumes: strings.Split(runCreateVolume, " "), + // FIXME: DynamicIPRequired + // FIXME: Timeout + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunRun(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'run': %v", err) + } +} diff --git a/pkg/cli/search.go b/pkg/cli/search.go new file mode 100644 index 0000000000..af832965c6 --- /dev/null +++ b/pkg/cli/search.go @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdSearch = &Command{ + Exec: runSearch, + UsageLine: "search [OPTIONS] TERM", + Description: "Search the Scaleway Hub for images", + Help: "Search the Scaleway Hub for images.", +} + +func init() { + cmdSearch.Flag.BoolVar(&searchNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") + cmdSearch.Flag.BoolVar(&searchHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var searchNoTrunc bool // --no-trunc flag +var searchHelp bool // -h, --help flag + +func runSearch(cmd *Command, rawArgs []string) { + if searchHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 1 { + cmd.PrintShortUsage() + } + + args := commands.SearchArgs{ + Term: rawArgs[0], + NoTrunc: searchNoTrunc, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunSearch(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'search': %v", err) + } +} diff --git a/pkg/cli/start.go b/pkg/cli/start.go new file mode 100644 index 0000000000..ddb536ddf6 --- /dev/null +++ b/pkg/cli/start.go @@ -0,0 +1,49 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdStart = &Command{ + Exec: runStart, + UsageLine: "start [OPTIONS] SERVER [SERVER...]", + Description: "Start a stopped server", + Help: "Start a stopped server.", +} + +func init() { + cmdStart.Flag.BoolVar(&startW, []string{"w", "-wait"}, false, "Synchronous start. Wait for SSH to be ready") + cmdStart.Flag.Float64Var(&startTimeout, []string{"T", "-timeout"}, 0, "Set timeout values to seconds") + cmdStart.Flag.BoolVar(&startHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var startW bool // -w flag +var startTimeout float64 // -T flag +var startHelp bool // -h, --help flag + +func runStart(cmd *Command, rawArgs []string) { + if startHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.StartArgs{ + Servers: rawArgs, + Timeout: startTimeout, + Wait: startW, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunStart(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'start': %v", err) + } +} diff --git a/pkg/cli/stop.go b/pkg/cli/stop.go new file mode 100644 index 0000000000..226cdc8cd5 --- /dev/null +++ b/pkg/cli/stop.go @@ -0,0 +1,57 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdStop = &Command{ + Exec: runStop, + UsageLine: "stop [OPTIONS] SERVER [SERVER...]", + Description: "Stop a running server", + Help: "Stop a running server.", + Examples: ` + $ scw stop my-running-server my-second-running-server + $ scw stop -t my-running-server my-second-running-server + $ scw stop $(scw ps -q) + $ scw stop $(scw ps | grep mysql | awk '{print $1}') + $ scw stop server && stop wait server + $ scw stop -w server +`, +} + +func init() { + cmdStop.Flag.BoolVar(&stopT, []string{"t", "-terminate"}, false, "Stop and trash a server with its volumes") + cmdStop.Flag.BoolVar(&stopHelp, []string{"h", "-help"}, false, "Print usage") + cmdStop.Flag.BoolVar(&stopW, []string{"w", "-wait"}, false, "Synchronous stop. Wait for SSH to be ready") +} + +// Flags +var stopT bool // -t flag +var stopHelp bool // -h, --help flag +var stopW bool // -w, --wait flat + +func runStop(cmd *Command, rawArgs []string) { + if stopHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.StopArgs{ + Terminate: stopT, + Wait: stopW, + Servers: rawArgs, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunStop(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'stop': %v", err) + } +} diff --git a/commands/tag.go b/pkg/cli/tag.go similarity index 50% rename from commands/tag.go rename to pkg/cli/tag.go index f54287b148..3c655694c9 100644 --- a/commands/tag.go +++ b/pkg/cli/tag.go @@ -2,17 +2,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "fmt" - - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdTag = &types.Command{ +var cmdTag = &Command{ Exec: runTag, UsageLine: "tag [OPTIONS] SNAPSHOT NAME", Description: "Tag a snapshot into an image", @@ -26,23 +23,21 @@ func init() { // Flags var tagHelp bool // -h, --help flag -func runTag(cmd *types.Command, args []string) { +func runTag(cmd *Command, rawArgs []string) { if tagHelp { cmd.PrintUsage() } - if len(args) != 2 { + if len(rawArgs) != 2 { cmd.PrintShortUsage() } - snapshotID := cmd.API.GetSnapshotID(args[0]) - snapshot, err := cmd.API.GetSnapshot(snapshotID) - if err != nil { - log.Fatalf("Cannot fetch snapshot: %v", err) + args := commands.TagArgs{ + Snapshot: rawArgs[0], + Name: rawArgs[1], } - - image, err := cmd.API.PostImage(snapshot.Identifier, args[1]) + ctx := cmd.GetContext(rawArgs) + err := commands.RunTag(ctx, args) if err != nil { - log.Fatalf("Cannot create image: %v", err) + logrus.Fatalf("Cannot execute 'tag': %v", err) } - fmt.Println(image) } diff --git a/pkg/cli/top.go b/pkg/cli/top.go new file mode 100644 index 0000000000..992e3c4114 --- /dev/null +++ b/pkg/cli/top.go @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdTop = &Command{ + Exec: runTop, + UsageLine: "top [OPTIONS] SERVER", // FIXME: add ps options + Description: "Lookup the running processes of a server", + Help: "Lookup the running processes of a server.", +} + +func init() { + cmdTop.Flag.BoolVar(&topHelp, []string{"h", "-help"}, false, "Print usage") + cmdTop.Flag.StringVar(&topGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") +} + +// Flags +var topHelp bool // -h, --help flag +var topGateway string // -g, --gateway flag + +func runTop(cmd *Command, rawArgs []string) { + if topHelp { + cmd.PrintUsage() + } + if len(rawArgs) != 1 { + cmd.PrintShortUsage() + } + + args := commands.TopArgs{ + Gateway: topGateway, + Server: rawArgs[0], + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunTop(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'top': %v", err) + } +} diff --git a/commands/version.go b/pkg/cli/version.go similarity index 50% rename from commands/version.go rename to pkg/cli/version.go index 7449ce02b3..a521710915 100644 --- a/commands/version.go +++ b/pkg/cli/version.go @@ -2,18 +2,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( - "fmt" - "runtime" - - "github.com/scaleway/scaleway-cli/scwversion" - - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/pkg/commands" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdVersion = &types.Command{ +var cmdVersion = &Command{ Exec: runVersion, UsageLine: "version [OPTIONS]", Description: "Show the version information", @@ -27,17 +23,18 @@ func init() { // Flags var versionHelp bool // -h, --help flag -func runVersion(cmd *types.Command, args []string) { +func runVersion(cmd *Command, rawArgs []string) { if versionHelp { cmd.PrintUsage() } - if len(args) != 0 { + if len(rawArgs) != 0 { cmd.PrintShortUsage() } - fmt.Printf("Client version: %s\n", scwversion.VERSION) - fmt.Printf("Go version (client): %s\n", runtime.Version()) - fmt.Printf("Git commit (client): %s\n", scwversion.GITCOMMIT) - fmt.Printf("OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) - // FIXME: API version information + args := commands.VersionArgs{} + ctx := cmd.GetContext(rawArgs) + err := commands.RunVersion(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'version': %v", err) + } } diff --git a/pkg/cli/wait.go b/pkg/cli/wait.go new file mode 100644 index 0000000000..7086bf909d --- /dev/null +++ b/pkg/cli/wait.go @@ -0,0 +1,43 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package cli + +import ( + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) + +var cmdWait = &Command{ + Exec: runWait, + UsageLine: "wait [OPTIONS] SERVER [SERVER...]", + Description: "Block until a server stops", + Help: "Block until a server stops.", +} + +func init() { + cmdWait.Flag.BoolVar(&waitHelp, []string{"h", "-help"}, false, "Print usage") +} + +// Flags +var waitHelp bool // -h, --help flag + +func runWait(cmd *Command, rawArgs []string) { + if waitHelp { + cmd.PrintUsage() + } + if len(rawArgs) < 1 { + cmd.PrintShortUsage() + } + + args := commands.WaitArgs{ + Servers: rawArgs, + } + ctx := cmd.GetContext(rawArgs) + err := commands.RunWait(ctx, args) + if err != nil { + logrus.Fatalf("Cannot execute 'wait': %v", err) + } +} diff --git a/commands/x_completion.go b/pkg/cli/x_completion.go similarity index 91% rename from commands/x_completion.go rename to pkg/cli/x_completion.go index 3b7519ea8f..7133e26112 100644 --- a/commands/x_completion.go +++ b/pkg/cli/x_completion.go @@ -2,19 +2,18 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( "fmt" - "log" "sort" "strings" - types "github.com/scaleway/scaleway-cli/commands/types" - utils "github.com/scaleway/scaleway-cli/utils" + utils "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdCompletion = &types.Command{ +var cmdCompletion = &Command{ Exec: runCompletion, UsageLine: "_completion [OPTIONS] CATEGORY", Description: "Completion helper", @@ -52,7 +51,7 @@ func wordifyName(name string, kind string) string { return ret } -func runCompletion(cmd *types.Command, args []string) { +func runCompletion(cmd *Command, args []string) { if completionHelp { cmd.PrintUsage() } @@ -106,7 +105,7 @@ func runCompletion(cmd *types.Command, args []string) { elements = append(elements, wordifyName(name, "bootscript")) } default: - log.Fatalf("Unhandled category of completion: %s", category) + logrus.Fatalf("Unhandled category of completion: %s", category) } sort.Strings(elements) diff --git a/commands/x_flushcache.go b/pkg/cli/x_flushcache.go similarity index 75% rename from commands/x_flushcache.go rename to pkg/cli/x_flushcache.go index 531212efee..87a280377b 100644 --- a/commands/x_flushcache.go +++ b/pkg/cli/x_flushcache.go @@ -2,16 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( "fmt" - "log" - types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" ) -var cmdFlushCache = &types.Command{ +var cmdFlushCache = &Command{ Exec: runFlushCache, UsageLine: "_flush-cache [OPTIONS]", Description: "", @@ -26,7 +25,7 @@ func init() { // Flags var flushCacheHelp bool // -h, --help flag -func runFlushCache(cmd *types.Command, args []string) { +func runFlushCache(cmd *Command, args []string) { if flushCacheHelp { cmd.PrintUsage() } @@ -36,7 +35,7 @@ func runFlushCache(cmd *types.Command, args []string) { err := cmd.API.Cache.Flush() if err != nil { - log.Fatal("Failed to flush the cache") + logrus.Fatal("Failed to flush the cache") } fmt.Println("Cache flushed") diff --git a/commands/x_patch.go b/pkg/cli/x_patch.go similarity index 93% rename from commands/x_patch.go rename to pkg/cli/x_patch.go index fffd4506f1..670c315438 100644 --- a/commands/x_patch.go +++ b/pkg/cli/x_patch.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE.md file. -package commands +package cli import ( "fmt" @@ -10,11 +10,10 @@ import ( log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" - api "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" + api "github.com/scaleway/scaleway-cli/pkg/api" ) -var cmdPatch = &types.Command{ +var cmdPatch = &Command{ Exec: runPatch, UsageLine: "_patch [OPTIONS] IDENTIFIER FIELD=VALUE", Description: "", @@ -33,7 +32,7 @@ func init() { // Flags var patchHelp bool // -h, --help flag -func runPatch(cmd *types.Command, args []string) { +func runPatch(cmd *Command, args []string) { if patchHelp { cmd.PrintUsage() } diff --git a/pkg/commands/attach.go b/pkg/commands/attach.go new file mode 100644 index 0000000000..b3804bebea --- /dev/null +++ b/pkg/commands/attach.go @@ -0,0 +1,20 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import "github.com/scaleway/scaleway-cli/pkg/utils" + +// AttachArgs are flags for the `RunAttach` function +type AttachArgs struct { + NoStdin bool + Server string +} + +// RunAttach is the handler for 'scw attach' +func RunAttach(ctx CommandContext, args AttachArgs) error { + serverID := ctx.API.GetServerID(args.Server) + + return utils.AttachToSerial(serverID, ctx.API.Token, !args.NoStdin) +} diff --git a/pkg/commands/command.go b/pkg/commands/command.go new file mode 100644 index 0000000000..18b92d2b7c --- /dev/null +++ b/pkg/commands/command.go @@ -0,0 +1,29 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +// Golang structs for scw commands +package commands + +import ( + "io" + "os" + + "github.com/scaleway/scaleway-cli/pkg/api" +) + +// CommandContext is passed to all commands and contains streams, environment, api and arguments +type CommandContext struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + Env []string + RawArgs []string + API *api.ScalewayAPI +} + +// Getenv returns the equivalent of os.Getenv for the CommandContext.Env +func (c *CommandContext) Getenv(key string) string { + // FIXME: parse c.Env instead + return os.Getenv(key) +} diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go new file mode 100644 index 0000000000..6db8ff1720 --- /dev/null +++ b/pkg/commands/commit.go @@ -0,0 +1,38 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" +) + +// CommitArgs are flags for the `RunCommit` function +type CommitArgs struct { + Volume int + Server string + Name string +} + +// RunCommit is the handler for 'scw commit' +func RunCommit(ctx CommandContext, args CommitArgs) error { + serverID := ctx.API.GetServerID(args.Server) + server, err := ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("Cannot fetch server: %v", err) + } + var volume = server.Volumes[fmt.Sprintf("%d", args.Volume)] + var name string + if args.Name != "" { + name = args.Name + } else { + name = volume.Name + "-snapshot" + } + snapshot, err := ctx.API.PostSnapshot(volume.Identifier, name) + if err != nil { + return fmt.Errorf("Cannot create snapshot: %v", err) + } + fmt.Println(snapshot) + return nil +} diff --git a/commands/cp.go b/pkg/commands/cp.go similarity index 50% rename from commands/cp.go rename to pkg/commands/cp.go index 9657461706..4af6194829 100644 --- a/commands/cp.go +++ b/pkg/commands/cp.go @@ -12,94 +12,85 @@ import ( "path/filepath" "strings" - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/archive" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" ) -var cmdCp = &types.Command{ - Exec: runCp, - UsageLine: "cp [OPTIONS] SERVER:PATH|HOSTPATH|- SERVER:PATH|HOSTPATH|-", - Description: "Copy files/folders from a PATH on the server to a HOSTDIR on the host", - Help: "Copy files/folders from a PATH on the server to a HOSTDIR on the host\nrunning the command. Use '-' to write the data as a tar file to STDOUT.", - Examples: ` - $ scw cp path/to/my/local/file myserver:path - $ scw cp --gateway=myotherserver path/to/my/local/file myserver:path - $ scw cp myserver:path/to/file path/to/my/local/dir - $ scw cp myserver:path/to/file myserver2:path/to/dir - $ scw cp myserver:path/to/file - > myserver-pathtofile-backup.tar - $ scw cp myserver:path/to/file - | tar -tvf - - $ scw cp path/to/my/local/dir myserver:path - $ scw cp myserver:path/to/dir path/to/my/local/dir - $ scw cp myserver:path/to/dir myserver2:path/to/dir - $ scw cp myserver:path/to/dir - > myserver-pathtodir-backup.tar - $ scw cp myserver:path/to/dir - | tar -tvf - - $ cat archive.tar | scw cp - myserver:/path - $ tar -cvf - . | scw cp - myserver:path -`, +type CpArgs struct { + Gateway string + Source string + Destination string } -func init() { - cmdCp.Flag.BoolVar(&cpHelp, []string{"h", "-help"}, false, "Print usage") - cmdCp.Flag.StringVar(&cpGateway, []string{"g", "-gateway"}, "", "Use a SSH gateway") -} +// RunCp is the handler for 'scw cp' +func RunCp(ctx CommandContext, args CpArgs) error { + if strings.Count(args.Source, ":") > 1 || strings.Count(args.Destination, ":") > 1 { + return fmt.Errorf("bad usage, see 'scw help cp'.") + } -// Flags -var cpHelp bool // -h, --help flag -var cpGateway string // -g, --gateway flag + sourceStream, err := TarFromSource(ctx, args.Source, args.Gateway) + if err != nil { + return fmt.Errorf("cannot tar from source '%s': %v", args.Source, err) + } + + err = UntarToDest(ctx, sourceStream, args.Destination, args.Gateway) + if err != nil { + return fmt.Errorf("cannot untar to destination '%s': %v", args.Destination, err) + } + return nil +} // TarFromSource creates a stream buffer with the tarballed content of the user source -func TarFromSource(apiClient *api.ScalewayAPI, source string) (*io.ReadCloser, error) { +func TarFromSource(ctx CommandContext, source string, gateway string) (*io.ReadCloser, error) { var tarOutputStream io.ReadCloser // source is a server address + path (scp-like uri) if strings.Index(source, ":") > -1 { - log.Debugf("Creating a tarball remotely and streaming it using SSH") + logrus.Debugf("Creating a tarball remotely and streaming it using SSH") serverParts := strings.Split(source, ":") if len(serverParts) != 2 { return nil, fmt.Errorf("invalid source uri, see 'scw cp -h' for usage") } - serverID := apiClient.GetServerID(serverParts[0]) + serverID := ctx.API.GetServerID(serverParts[0]) - server, err := apiClient.GetServer(serverID) + server, err := ctx.API.GetServer(serverID) if err != nil { return nil, err } dir, base := utils.PathToTARPathparts(serverParts[1]) - log.Debugf("Equivalent to 'scp root@%s:%s/%s ...'", server.PublicAddress.IP, dir, base) + logrus.Debugf("Equivalent to 'scp root@%s:%s/%s ...'", server.PublicAddress.IP, dir, base) // remoteCommand is executed on the remote server // it streams a tarball raw content remoteCommand := []string{"tar"} remoteCommand = append(remoteCommand, "-C", dir) - if os.Getenv("DEBUG") == "1" { + if ctx.Getenv("DEBUG") == "1" { remoteCommand = append(remoteCommand, "-v") } remoteCommand = append(remoteCommand, "-cf", "-") remoteCommand = append(remoteCommand, base) // Resolve gateway - if cpGateway == "" { - cpGateway = os.Getenv("SCW_GATEWAY") + if gateway == "" { + gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string - if cpGateway == serverID || cpGateway == serverParts[0] { + if gateway == serverID || gateway == serverParts[0] { gateway = "" } else { - gateway, err = api.ResolveGateway(apiClient, cpGateway) + gateway, err = api.ResolveGateway(ctx.API, gateway) if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", cpGateway, err) + return nil, fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err) } } // execCmd contains the ssh connection + the remoteCommand execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, false, nil, remoteCommand, gateway)) - log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) + logrus.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) spawnSrc := exec.Command("ssh", execCmd...) tarOutputStream, err = spawnSrc.StdoutPipe() @@ -112,7 +103,7 @@ func TarFromSource(apiClient *api.ScalewayAPI, source string) (*io.ReadCloser, e return nil, err } defer tarErrorStream.Close() - io.Copy(os.Stderr, tarErrorStream) + io.Copy(ctx.Stderr, tarErrorStream) err = spawnSrc.Start() if err != nil { @@ -125,13 +116,14 @@ func TarFromSource(apiClient *api.ScalewayAPI, source string) (*io.ReadCloser, e // source is stdin if source == "-" { - log.Debugf("Streaming tarball from stdin") + logrus.Debugf("Streaming tarball from stdin") + // FIXME: should be ctx.Stdin tarOutputStream = os.Stdin return &tarOutputStream, nil } // source is a path on localhost - log.Debugf("Taring local path %s", source) + logrus.Debugf("Taring local path %s", source) path, err := filepath.Abs(source) if err != nil { return nil, err @@ -140,7 +132,7 @@ func TarFromSource(apiClient *api.ScalewayAPI, source string) (*io.ReadCloser, e if err != nil { return nil, err } - log.Debugf("Real local path is %s", path) + logrus.Debugf("Real local path is %s", path) dir, base := utils.PathToTARPathparts(path) @@ -155,18 +147,18 @@ func TarFromSource(apiClient *api.ScalewayAPI, source string) (*io.ReadCloser, e } // UntarToDest writes to user destination the streamed tarball in input -func UntarToDest(apiClient *api.ScalewayAPI, sourceStream *io.ReadCloser, destination string) error { +func UntarToDest(ctx CommandContext, sourceStream *io.ReadCloser, destination string, gateway string) error { // destination is a server address + path (scp-like uri) if strings.Index(destination, ":") > -1 { - log.Debugf("Streaming using ssh and untaring remotely") + logrus.Debugf("Streaming using ssh and untaring remotely") serverParts := strings.Split(destination, ":") if len(serverParts) != 2 { return fmt.Errorf("invalid destination uri, see 'scw cp -h' for usage") } - serverID := apiClient.GetServerID(serverParts[0]) + serverID := ctx.API.GetServerID(serverParts[0]) - server, err := apiClient.GetServer(serverID) + server, err := ctx.API.GetServer(serverID) if err != nil { return err } @@ -175,28 +167,28 @@ func UntarToDest(apiClient *api.ScalewayAPI, sourceStream *io.ReadCloser, destin // it streams a tarball raw content remoteCommand := []string{"tar"} remoteCommand = append(remoteCommand, "-C", serverParts[1]) - if os.Getenv("DEBUG") == "1" { + if ctx.Getenv("DEBUG") == "1" { remoteCommand = append(remoteCommand, "-v") } remoteCommand = append(remoteCommand, "-xf", "-") // Resolve gateway - if cpGateway == "" { - cpGateway = os.Getenv("SCW_GATEWAY") + if gateway == "" { + gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string - if cpGateway == serverID || cpGateway == serverParts[0] { + if gateway == serverID || gateway == serverParts[0] { gateway = "" } else { - gateway, err = api.ResolveGateway(apiClient, cpGateway) + gateway, err = api.ResolveGateway(ctx.API, gateway) if err != nil { - log.Fatalf("Cannot resolve Gateway '%s': %v", cpGateway, err) + return fmt.Errorf("cannot resolve Gateway '%s': %v", gateway, err) } } // execCmd contains the ssh connection + the remoteCommand execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, false, nil, remoteCommand, gateway)) - log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) + logrus.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) spawnDst := exec.Command("ssh", execCmd...) untarInputStream, err := spawnDst.StdinPipe() @@ -205,8 +197,8 @@ func UntarToDest(apiClient *api.ScalewayAPI, sourceStream *io.ReadCloser, destin } defer untarInputStream.Close() - // spawnDst.Stderr = os.Stderr - // spawnDst.Stdout = os.Stdout + // spawnDst.Stderr = ctx.Stderr + // spawnDst.Stdout = ctx.Stdout err = spawnDst.Start() if err != nil { @@ -219,36 +211,13 @@ func UntarToDest(apiClient *api.ScalewayAPI, sourceStream *io.ReadCloser, destin // destination is stdout if destination == "-" { // stdout - log.Debugf("Writing sourceStream(%v) to os.Stdout(%v)", sourceStream, os.Stdout) - _, err := io.Copy(os.Stdout, *sourceStream) + logrus.Debugf("Writing sourceStream(%v) to ctx.Stdout(%v)", sourceStream, ctx.Stdout) + _, err := io.Copy(ctx.Stdout, *sourceStream) return err } // destination is a path on localhost - log.Debugf("Untaring to local path: %s", destination) + logrus.Debugf("Untaring to local path: %s", destination) err := archive.Untar(*sourceStream, destination, &archive.TarOptions{NoLchown: true}) return err } - -func runCp(cmd *types.Command, args []string) { - if cpHelp { - cmd.PrintUsage() - } - if len(args) != 2 { - cmd.PrintShortUsage() - } - - if strings.Count(args[0], ":") > 1 || strings.Count(args[1], ":") > 1 { - log.Fatalf("usage: scw %s", cmd.UsageLine) - } - - sourceStream, err := TarFromSource(cmd.API, args[0]) - if err != nil { - log.Fatalf("Cannot tar from source '%s': %v", args[0], err) - } - - err = UntarToDest(cmd.API, sourceStream, args[1]) - if err != nil { - log.Fatalf("Cannot untar to destination '%s': %v", args[1], err) - } -} diff --git a/pkg/commands/create.go b/pkg/commands/create.go new file mode 100644 index 0000000000..ee0d3ba50f --- /dev/null +++ b/pkg/commands/create.go @@ -0,0 +1,34 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-cli/pkg/api" +) + +type CreateArgs struct { + Name string + Bootscript string + Tags []string + Volumes []string + Image string +} + +// RunCreate is the handler for 'scw create' +func RunCreate(ctx CommandContext, args CreateArgs) error { + env := strings.Join(args.Tags, " ") + volume := strings.Join(args.Volumes, " ") + serverID, err := api.CreateServer(ctx.API, args.Image, args.Name, args.Bootscript, env, volume, true) + if err != nil { + return err + } + + fmt.Fprintln(ctx.Stdout, serverID) + + return nil +} diff --git a/pkg/commands/events.go b/pkg/commands/events.go new file mode 100644 index 0000000000..63f9998f62 --- /dev/null +++ b/pkg/commands/events.go @@ -0,0 +1,41 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "time" + + "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" +) + +type EventsArgs struct{} + +// RunEvents is the handler for 'scw events' +func RunEvents(ctx CommandContext, args EventsArgs) error { + events, err := ctx.API.GetTasks() + if err != nil { + return fmt.Errorf("unable to fetch tasks from the Scaleway API: %v", err) + } + + for _, event := range *events { + startedAt, err := time.Parse("2006-01-02T15:04:05.000000+00:00", event.StartDate) + if err != nil { + return fmt.Errorf("unable to parse started date from the Scaleway API: %v", err) + } + + terminatedAt := "" + if event.TerminationDate != "" { + terminatedAtTime, err := time.Parse("2006-01-02T15:04:05.000000+00:00", event.TerminationDate) + if err != nil { + return fmt.Errorf("unable to parse terminated date from the Scaleway API: %v", err) + } + terminatedAt = units.HumanDuration(time.Now().UTC().Sub(terminatedAtTime)) + } + + fmt.Printf("%s %s: %s (%s %d) %s\n", startedAt, event.HrefFrom, event.Description, event.Status, event.Progress, terminatedAt) + } + return nil +} diff --git a/pkg/commands/exec.go b/pkg/commands/exec.go new file mode 100644 index 0000000000..e4753a88cd --- /dev/null +++ b/pkg/commands/exec.go @@ -0,0 +1,83 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "time" + + log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// ExecArgs are flags for the `RunExec` function +type ExecArgs struct { + Timeout float64 + Wait bool + Gateway string + Server string + Command []string +} + +// RunExec is the handler for 'scw exec' +func RunExec(ctx CommandContext, args ExecArgs) error { + serverID := ctx.API.GetServerID(args.Server) + + // Resolve gateway + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + var gateway string + var err error + if args.Gateway == serverID || args.Gateway == args.Server { + log.Debugf("The server and the gateway are the same host, using direct access to the server") + gateway = "" + } else { + gateway, err = api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("Cannot resolve Gateway '%s': %v", args.Gateway, err) + } + if gateway != "" { + log.Debugf("The server will be accessed using the gateway '%s' as a SSH relay", gateway) + } + } + + var server *api.ScalewayServer + if args.Wait { + // --wait + log.Debugf("Waiting for server to be ready") + server, err = api.WaitForServerReady(ctx.API, serverID, gateway) + if err != nil { + return fmt.Errorf("Failed to wait for server to be ready, %v", err) + } + } else { + // no --wait + log.Debugf("scw won't wait for the server to be ready, if it is not, the command will fail") + server, err = ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("Failed to get server information for %s: %v", serverID, err) + } + } + + // --timeout + if args.Timeout > 0 { + log.Debugf("Setting up a global timeout of %d seconds", args.Timeout) + // FIXME: avoid use of log.Fatalf here + go func() { + time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond) + log.Fatalf("Operation timed out") + }() + } + + err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.Command, !args.Wait, gateway) + if err != nil { + return fmt.Errorf("Failed to run the command: %v", err) + } + + log.Debugf("Command successfuly executed") + return nil +} diff --git a/pkg/commands/help.go b/pkg/commands/help.go new file mode 100644 index 0000000000..36fd33d90e --- /dev/null +++ b/pkg/commands/help.go @@ -0,0 +1,7 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +// nothing here diff --git a/pkg/commands/history.go b/pkg/commands/history.go new file mode 100644 index 0000000000..34f97295d4 --- /dev/null +++ b/pkg/commands/history.go @@ -0,0 +1,53 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "text/tabwriter" + "time" + + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" +) + +// HistoryArgs are flags for the `RunHistory` function +type HistoryArgs struct { + NoTrunc bool + Quiet bool + Image string +} + +// RunHistory is the handler for 'scw history' +func RunHistory(ctx CommandContext, args HistoryArgs) error { + imageID := ctx.API.GetImageID(args.Image, true) + image, err := ctx.API.GetImage(imageID) + if err != nil { + return fmt.Errorf("cannot get image %s: %v", imageID, err) + } + + if args.Quiet { + fmt.Fprintln(ctx.Stdout, imageID) + return nil + } + + w := tabwriter.NewWriter(ctx.Stdout, 10, 1, 3, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\n") + + identifier := utils.TruncIf(image.Identifier, 8, !args.NoTrunc) + + creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", image.CreationDate) + if err != nil { + return fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) + } + creationDateStr := units.HumanDuration(time.Now().UTC().Sub(creationDate)) + + volumeName := utils.TruncIf(image.RootVolume.Name, 25, !args.NoTrunc) + size := units.HumanSize(float64(image.RootVolume.Size)) + + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", identifier, creationDateStr, volumeName, size) + return nil +} diff --git a/commands/images.go b/pkg/commands/images.go similarity index 62% rename from commands/images.go rename to pkg/commands/images.go index fe5374a7d1..2be42e7858 100644 --- a/commands/images.go +++ b/pkg/commands/images.go @@ -6,63 +6,43 @@ package commands import ( "fmt" - "os" "sort" "sync" "text/tabwriter" "time" - log "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" - - "github.com/scaleway/scaleway-cli/api" - types "github.com/scaleway/scaleway-cli/commands/types" - "github.com/scaleway/scaleway-cli/utils" ) -var cmdImages = &types.Command{ - Exec: runImages, - UsageLine: "images [OPTIONS]", - Description: "List images", - Help: "List images.", -} - -func init() { - cmdImages.Flag.BoolVar(&imagesA, []string{"a", "-all"}, false, "Show all iamges") - cmdImages.Flag.BoolVar(&imagesNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output") - cmdImages.Flag.BoolVar(&imagesQ, []string{"q", "-quiet"}, false, "Only show numeric IDs") - cmdImages.Flag.BoolVar(&imagesHelp, []string{"h", "-help"}, false, "Print usage") +// ImagesArgs are flags for the `RunImages` function +type ImagesArgs struct { + All bool + NoTrunc bool + Quiet bool } -// Flags -var imagesA bool // -a flag -var imagesQ bool // -q flag -var imagesNoTrunc bool // -no-trunc flag -var imagesHelp bool // -h, --help flag - -func runImages(cmd *types.Command, args []string) { - if imagesHelp { - cmd.PrintUsage() - } - if len(args) != 0 { - cmd.PrintShortUsage() - } - +// RunImages is the handler for 'scw images' +func RunImages(ctx CommandContext, args ImagesArgs) error { wg := sync.WaitGroup{} chEntries := make(chan api.ScalewayImageInterface) var entries = []api.ScalewayImageInterface{} + // FIXME: remove log.Fatalf in routines + wg.Add(1) go func() { defer wg.Done() - images, err := cmd.API.GetImages() + images, err := ctx.API.GetImages() if err != nil { - log.Fatalf("unable to fetch images from the Scaleway API: %v", err) + logrus.Fatalf("unable to fetch images from the Scaleway API: %v", err) } for _, val := range *images { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { - log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "image", @@ -76,18 +56,18 @@ func runImages(cmd *types.Command, args []string) { } }() - if imagesA { + if args.All { wg.Add(1) go func() { defer wg.Done() - snapshots, err := cmd.API.GetSnapshots() + snapshots, err := ctx.API.GetSnapshots() if err != nil { - log.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) + logrus.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) } for _, val := range *snapshots { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { - log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "snapshot", @@ -104,9 +84,9 @@ func runImages(cmd *types.Command, args []string) { wg.Add(1) go func() { defer wg.Done() - bootscripts, err := cmd.API.GetBootscripts() + bootscripts, err := ctx.API.GetBootscripts() if err != nil { - log.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) + logrus.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) } for _, val := range *bootscripts { chEntries <- api.ScalewayImageInterface{ @@ -122,14 +102,14 @@ func runImages(cmd *types.Command, args []string) { wg.Add(1) go func() { defer wg.Done() - volumes, err := cmd.API.GetVolumes() + volumes, err := ctx.API.GetVolumes() if err != nil { - log.Fatalf("unable to fetch volumes from the Scaleway API: %v", err) + logrus.Fatalf("unable to fetch volumes from the Scaleway API: %v", err) } for _, val := range *volumes { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { - log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "volume", @@ -164,23 +144,23 @@ func runImages(cmd *types.Command, args []string) { } } - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() - if !imagesQ { + if !args.Quiet { fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\n") } sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { - if imagesQ { - fmt.Fprintf(w, "%s\n", image.Identifier) + if args.Quiet { + fmt.Fprintf(ctx.Stdout, "%s\n", image.Identifier) } else { tag := image.Tag - shortID := utils.TruncIf(image.Identifier, 8, !imagesNoTrunc) + shortID := utils.TruncIf(image.Identifier, 8, !args.NoTrunc) name := utils.Wordify(image.Name) if !image.Public && image.Type == "image" { name = "user/" + name } - shortName := utils.TruncIf(name, 25, !imagesNoTrunc) + shortName := utils.TruncIf(name, 25, !args.NoTrunc) var creationDate, virtualSize string if image.CreationDate.IsZero() { creationDate = "n/a" @@ -195,4 +175,5 @@ func runImages(cmd *types.Command, args []string) { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize) } } + return nil } diff --git a/pkg/commands/info.go b/pkg/commands/info.go new file mode 100644 index 0000000000..8dc3139cf8 --- /dev/null +++ b/pkg/commands/info.go @@ -0,0 +1,45 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "os" + "runtime" + + "github.com/scaleway/scaleway-cli/vendor/github.com/kardianos/osext" + + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// InfoArgs are flags for the `RunInfo` function +type InfoArgs struct{} + +// RunInfo is the handler for 'scw info' +func RunInfo(ctx CommandContext, args InfoArgs) error { + // FIXME: fmt.Fprintf(ctx.Stdout, "Servers: %s\n", "quantity") + // FIXME: fmt.Fprintf(ctx.Stdout, "Images: %s\n", "quantity") + fmt.Fprintf(ctx.Stdout, "Debug mode (client): %v\n", ctx.Getenv("DEBUG") != "") + + fmt.Fprintf(ctx.Stdout, "Organization: %s\n", ctx.API.Organization) + // FIXME: add partially-masked token + fmt.Fprintf(ctx.Stdout, "API Endpoint: %s\n", ctx.Getenv("scaleway_api_endpoint")) + configPath, _ := utils.GetConfigFilePath() + fmt.Fprintf(ctx.Stdout, "RC file: %s\n", configPath) + fmt.Fprintf(ctx.Stdout, "User: %s\n", ctx.Getenv("USER")) + fmt.Fprintf(ctx.Stdout, "CPUs: %d\n", runtime.NumCPU()) + hostname, _ := os.Hostname() + fmt.Fprintf(ctx.Stdout, "Hostname: %s\n", hostname) + cliPath, _ := osext.Executable() + fmt.Fprintf(ctx.Stdout, "CLI Path: %s\n", cliPath) + + fmt.Fprintf(ctx.Stdout, "Cache: %s\n", ctx.API.Cache.Path) + fmt.Fprintf(ctx.Stdout, " Servers: %d\n", ctx.API.Cache.GetNbServers()) + fmt.Fprintf(ctx.Stdout, " Images: %d\n", ctx.API.Cache.GetNbImages()) + fmt.Fprintf(ctx.Stdout, " Snapshots: %d\n", ctx.API.Cache.GetNbSnapshots()) + fmt.Fprintf(ctx.Stdout, " Volumes: %d\n", ctx.API.Cache.GetNbVolumes()) + fmt.Fprintf(ctx.Stdout, " Bootscripts: %d\n", ctx.API.Cache.GetNbBootscripts()) + return nil +} diff --git a/pkg/commands/inspect.go b/pkg/commands/inspect.go new file mode 100644 index 0000000000..61a45a1eac --- /dev/null +++ b/pkg/commands/inspect.go @@ -0,0 +1,111 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "encoding/json" + "fmt" + "text/template" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/vendor/github.com/skratchdot/open-golang/open" +) + +// InspectArgs are flags for the `RunInspect` function +type InspectArgs struct { + Format string + Browser bool + Identifiers []string +} + +// RunInspect is the handler for 'scw inspect' +func RunInspect(ctx CommandContext, args InspectArgs) error { + nbInspected := 0 + ci := make(chan api.ScalewayResolvedIdentifier) + cj := make(chan api.InspectIdentifierResult) + go api.ResolveIdentifiers(ctx.API, args.Identifiers, ci) + go api.InspectIdentifiers(ctx.API, ci, cj) + + if args.Browser { + // --browser will open links in the browser + for { + data, isOpen := <-cj + if !isOpen { + break + } + + switch data.Type { + case api.IdentifierServer: + err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/servers/%s", data.Object.(*api.ScalewayServer).Identifier)) + if err != nil { + return fmt.Errorf("cannot open browser: %v", err) + } + nbInspected++ + case api.IdentifierImage: + err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/images/%s", data.Object.(*api.ScalewayImage).Identifier)) + if err != nil { + return fmt.Errorf("cannot open browser: %v", err) + } + nbInspected++ + case api.IdentifierVolume: + err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/volumes/%s", data.Object.(*api.ScalewayVolume).Identifier)) + if err != nil { + return fmt.Errorf("cannot open browser: %v", err) + } + nbInspected++ + case api.IdentifierSnapshot: + logrus.Errorf("Cannot use '--browser' option for snapshots") + case api.IdentifierBootscript: + logrus.Errorf("Cannot use '--browser' option for bootscripts") + } + } + + } else { + // without --browser option, inspect will print object info to the terminal + res := "[" + for { + data, isOpen := <-cj + if !isOpen { + break + } + if args.Format == "" { + dataB, err := json.MarshalIndent(data.Object, "", " ") + if err == nil { + if nbInspected != 0 { + res += ",\n" + } + res += string(dataB) + nbInspected++ + } + } else { + tmpl, err := template.New("").Funcs(api.FuncMap).Parse(args.Format) + if err != nil { + return fmt.Errorf("format parsing error: %v", err) + } + + err = tmpl.Execute(ctx.Stdout, data.Object) + if err != nil { + return fmt.Errorf("format execution error: %v", err) + } + fmt.Fprint(ctx.Stdout, "\n") + nbInspected++ + } + } + res += "]" + + if args.Format == "" { + if ctx.Getenv("SCW_SENSITIVE") != "1" { + res = ctx.API.HideAPICredentials(res) + } + fmt.Fprintln(ctx.Stdout, res) + } + } + + if len(args.Identifiers) != nbInspected { + return fmt.Errorf("at least 1 item failed to be inspected") + } + return nil +} diff --git a/pkg/commands/kill.go b/pkg/commands/kill.go new file mode 100644 index 0000000000..b8b90c3983 --- /dev/null +++ b/pkg/commands/kill.go @@ -0,0 +1,56 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// KillArgs are flags for the `RunKill` function +type KillArgs struct { + Gateway string + Server string +} + +// RunKill is the handler for 'scw kill' +func RunKill(ctx CommandContext, args KillArgs) error { + serverID := ctx.API.GetServerID(args.Server) + command := "halt" + server, err := ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("failed to get server information for %s: %v", serverID, err) + } + + // Resolve gateway + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + var gateway string + if args.Gateway == serverID || args.Gateway == args.Server { + gateway = "" + } else { + gateway, err = api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) + } + } + + execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, nil, []string{command}, gateway)) + + logrus.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) + + spawn := exec.Command("ssh", execCmd...) + spawn.Stdout = ctx.Stdout + spawn.Stdin = ctx.Stdin + spawn.Stderr = ctx.Stderr + + return spawn.Run() +} diff --git a/pkg/commands/login.go b/pkg/commands/login.go new file mode 100644 index 0000000000..3a981e6c55 --- /dev/null +++ b/pkg/commands/login.go @@ -0,0 +1,85 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/vendor/golang.org/x/crypto/ssh/terminal" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +type LoginArgs struct { + Organization string + Token string +} + +// RunLogin is the handler for 'scw login' +func RunLogin(ctx CommandContext, args LoginArgs) error { + if args.Organization == "" { + fmt.Println("You can get your credentials on https://cloud.scaleway.com/#/credentials") + promptUser("Organization (access key): ", &args.Organization, true) + } + if args.Token == "" { + promptUser("Token: ", &args.Token, false) + } + + cfg := &api.Config{ + APIEndPoint: "https://account.scaleway.com/", + Organization: strings.Trim(args.Organization, "\n"), + Token: strings.Trim(args.Token, "\n"), + } + + api, err := api.NewScalewayAPI(cfg.APIEndPoint, cfg.Organization, cfg.Token) + if err != nil { + return fmt.Errorf("Unable to create ScalewayAPI: %s", err) + } + err = api.CheckCredentials() + if err != nil { + return fmt.Errorf("Unable to contact ScalewayAPI: %s", err) + } + + scwrcPath, err := utils.GetConfigFilePath() + if err != nil { + return fmt.Errorf("Unable to get scwrc config file path: %s", err) + } + scwrc, err := os.OpenFile(scwrcPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) + if err != nil { + return fmt.Errorf("Unable to create scwrc config file: %s", err) + } + defer scwrc.Close() + encoder := json.NewEncoder(scwrc) + cfg.APIEndPoint = "https://api.scaleway.com/" + err = encoder.Encode(cfg) + if err != nil { + return fmt.Errorf("Unable to encode scw config file: %s", err) + } + return nil +} + +func promptUser(prompt string, output *string, echo bool) { + // FIXME: should use stdin/stdout from command context + fmt.Fprintf(os.Stdout, prompt) + os.Stdout.Sync() + + if !echo { + b, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + logrus.Fatalf("Unable to prompt for password: %s", err) + } + *output = string(b) + fmt.Fprintf(os.Stdout, "\n") + } else { + reader := bufio.NewReader(os.Stdin) + *output, _ = reader.ReadString('\n') + } +} diff --git a/pkg/commands/logout.go b/pkg/commands/logout.go new file mode 100644 index 0000000000..da4e3ab159 --- /dev/null +++ b/pkg/commands/logout.go @@ -0,0 +1,32 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "os" + + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// LogoutArgs are flags for the `RunLogout` function +type LogoutArgs struct{} + +// RunLogout is the handler for 'scw logout' +func RunLogout(ctx CommandContext, args LogoutArgs) error { + // FIXME: ask if we need to remove the local ssh key on the account + scwrcPath, err := utils.GetConfigFilePath() + if err != nil { + return fmt.Errorf("unable to get scwrc config file path: %v", err) + } + + if _, err = os.Stat(scwrcPath); err == nil { + err = os.Remove(scwrcPath) + if err != nil { + return fmt.Errorf("unable to remove scwrc config file: %v", err) + } + } + return nil +} diff --git a/pkg/commands/logs.go b/pkg/commands/logs.go new file mode 100644 index 0000000000..3cbdcfe5e2 --- /dev/null +++ b/pkg/commands/logs.go @@ -0,0 +1,50 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// LogsArgs are flags for the `RunLogs` function +type LogsArgs struct { + Gateway string + Server string +} + +// RunLogs is the handler for 'scw logs' +func RunLogs(ctx CommandContext, args LogsArgs) error { + serverID := ctx.API.GetServerID(args.Server) + server, err := ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("failed to get server information for %s: %v", serverID, err) + } + + // FIXME: switch to serial history when API is ready + + // Resolve gateway + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + var gateway string + if args.Gateway == serverID || args.Gateway == args.Server { + gateway = "" + } else { + gateway, err = api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) + } + } + + command := []string{"dmesg"} + err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, command, true, gateway) + if err != nil { + return fmt.Errorf("command execution failed: %v", err) + } + return nil +} diff --git a/pkg/commands/port.go b/pkg/commands/port.go new file mode 100644 index 0000000000..3d4092f8e7 --- /dev/null +++ b/pkg/commands/port.go @@ -0,0 +1,49 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// PortArgs are flags for the `RunPort` function +type PortArgs struct { + Gateway string + Server string +} + +// RunPort is the handler for 'scw port' +func RunPort(ctx CommandContext, args PortArgs) error { + serverID := ctx.API.GetServerID(args.Server) + server, err := ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("failed to get server information for %s: %v", serverID, err) + } + + // Resolve gateway + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + var gateway string + if args.Gateway == serverID || args.Gateway == args.Server { + gateway = "" + } else { + gateway, err = api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) + } + } + + command := []string{"netstat -lutn 2>/dev/null | grep LISTEN"} + err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, command, true, gateway) + if err != nil { + return fmt.Errorf("command execution failed: %v", err) + } + + return nil +} diff --git a/pkg/commands/ps.go b/pkg/commands/ps.go new file mode 100644 index 0000000000..e316d30387 --- /dev/null +++ b/pkg/commands/ps.go @@ -0,0 +1,57 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "text/tabwriter" + "time" + + "github.com/scaleway/scaleway-cli/vendor/github.com/docker/docker/pkg/units" + + "github.com/scaleway/scaleway-cli/pkg/utils" +) + +// PsArgs are flags for the `RunPs` function +type PsArgs struct { + All bool + Latest bool + NLast int + NoTrunc bool + Quiet bool +} + +// RunPs is the handler for 'scw ps' +func RunPs(ctx CommandContext, args PsArgs) error { + limit := args.NLast + if args.Latest { + limit = 1 + } + all := args.All || args.NLast > 0 || args.Latest + servers, err := ctx.API.GetServers(all, limit) + if err != nil { + return fmt.Errorf("Unable to fetch servers from the Scaleway API: %v", err) + } + + w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) + defer w.Flush() + if !args.Quiet { + fmt.Fprintf(w, "SERVER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAME\n") + } + for _, server := range *servers { + if args.Quiet { + fmt.Fprintf(w, "%s\n", server.Identifier) + } else { + shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) + shortImage := utils.TruncIf(utils.Wordify(server.Image.Name), 25, !args.NoTrunc) + shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) + creationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.CreationDate) + shortCreationDate := units.HumanDuration(time.Now().UTC().Sub(creationTime)) + port := server.PublicAddress.IP + fmt.Fprintf(w, "%s\t%s\t\t%s\t%s\t%s\t%s\n", shortID, shortImage, shortCreationDate, server.State, port, shortName) + } + } + return nil +} diff --git a/pkg/commands/rename.go b/pkg/commands/rename.go new file mode 100644 index 0000000000..dcd54df2ae --- /dev/null +++ b/pkg/commands/rename.go @@ -0,0 +1,33 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/pkg/api" +) + +// RenameArgs are flags for the `RunRename` function +type RenameArgs struct { + Server string + NewName string +} + +// RunRename is the handler for 'scw rename' +func RunRename(ctx CommandContext, args RenameArgs) error { + serverID := ctx.API.GetServerID(args.Server) + + var server api.ScalewayServerPatchDefinition + server.Name = &args.NewName + + err := ctx.API.PatchServer(serverID, server) + if err != nil { + return fmt.Errorf("cannot rename server: %v", err) + } else { + ctx.API.Cache.InsertServer(serverID, *server.Name) + } + return nil +} diff --git a/pkg/commands/restart.go b/pkg/commands/restart.go new file mode 100644 index 0000000000..c3b4b18cd3 --- /dev/null +++ b/pkg/commands/restart.go @@ -0,0 +1,37 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// RestartArgs are flags for the `RunRestart` function +type RestartArgs struct { + Servers []string +} + +// RunRestart is the handler for 'scw restart' +func RunRestart(ctx CommandContext, args RestartArgs) error { + hasError := false + for _, needle := range args.Servers { + server := ctx.API.GetServerID(needle) + err := ctx.API.PostServerAction(server, "reboot") + if err != nil { + if err.Error() != "server is being stopped or rebooted" { + logrus.Errorf("failed to restart server %s: %s", server, err) + hasError = true + } + } else { + fmt.Fprintln(ctx.Stdout, needle) + } + if hasError { + return fmt.Errorf("at least 1 server failed to restart") + } + } + return nil +} diff --git a/pkg/commands/rm.go b/pkg/commands/rm.go new file mode 100644 index 0000000000..a62a52f7af --- /dev/null +++ b/pkg/commands/rm.go @@ -0,0 +1,35 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// RmArgs are flags for the `RunRm` function +type RmArgs struct { + Servers []string +} + +// RunRm is the handler for 'scw rm' +func RunRm(ctx CommandContext, args RmArgs) error { + hasError := false + for _, needle := range args.Servers { + server := ctx.API.GetServerID(needle) + err := ctx.API.DeleteServer(server) + if err != nil { + logrus.Errorf("failed to delete server %s: %s", server, err) + hasError = true + } else { + fmt.Fprintln(ctx.Stdout, needle) + } + } + if hasError { + return fmt.Errorf("at least 1 server failed to be removed") + } + return nil +} diff --git a/pkg/commands/rmi.go b/pkg/commands/rmi.go new file mode 100644 index 0000000000..0bd41db683 --- /dev/null +++ b/pkg/commands/rmi.go @@ -0,0 +1,35 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// RmiArgs are flags for the `RunRmi` function +type RmiArgs struct { + Images []string +} + +// RunRmi is the handler for 'scw rmi' +func RunRmi(ctx CommandContext, args RmiArgs) error { + hasError := false + for _, needle := range args.Images { + image := ctx.API.GetImageID(needle, true) + err := ctx.API.DeleteImage(image) + if err != nil { + logrus.Errorf("failed to delete image %s: %s", image, err) + hasError = true + } else { + fmt.Fprintln(ctx.Stdout, needle) + } + } + if hasError { + return fmt.Errorf("at least 1 image failed to be removed") + } + return nil +} diff --git a/pkg/commands/run.go b/pkg/commands/run.go new file mode 100644 index 0000000000..86629a6161 --- /dev/null +++ b/pkg/commands/run.go @@ -0,0 +1,100 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "strings" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// RunArgs are flags for the `RunRun` function +type RunArgs struct { + Attach bool + Bootscript string + Command []string + Detach bool + Gateway string + Image string + Name string + Tags []string + Volumes []string + // DynamicIPRequired + // Timeout +} + +// RunRun is the handler for 'scw run' +func RunRun(ctx CommandContext, args RunArgs) error { + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + + env := strings.Join(args.Tags, " ") + volume := strings.Join(args.Volumes, " ") + + // create IMAGE + logrus.Info("Server creation ...") + dynamicIPRequired := args.Gateway == "" + serverID, err := api.CreateServer(ctx.API, args.Image, args.Name, args.Bootscript, env, volume, dynamicIPRequired) + if err != nil { + return fmt.Errorf("failed to create server: %v", err) + } + logrus.Infof("Server created: %s", serverID) + + // start SERVER + logrus.Info("Server start requested ...") + err = api.StartServer(ctx.API, serverID, false) + if err != nil { + return fmt.Errorf("failed to start server %s: %v", serverID, err) + } + logrus.Info("Server is starting, this may take up to a minute ...") + + if args.Detach { + fmt.Fprintln(ctx.Stdout, serverID) + return nil + } + + if args.Attach { + // Attach to server serial + logrus.Info("Attaching to server console ...") + err = utils.AttachToSerial(serverID, ctx.API.Token, true) + if err != nil { + return fmt.Errorf("cannot attach to server serial: %v", err) + } + } else { + // Resolve gateway + gateway, err := api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) + } + + // waiting for server to be ready + logrus.Debug("Waiting for server to be ready") + // We wait for 30 seconds, which is the minimal amount of time needed by a server to boot + server, err := api.WaitForServerReady(ctx.API, serverID, gateway) + if err != nil { + return fmt.Errorf("cannot get access to server %s: %v", serverID, err) + } + logrus.Debugf("SSH server is available: %s:22", server.PublicAddress.IP) + logrus.Info("Server is ready !") + + // exec -w SERVER COMMAND ARGS... + if len(args.Command) < 1 { + logrus.Info("Connecting to server ...") + err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, []string{}, false, gateway) + } else { + logrus.Infof("Executing command: %s ...", args.Command) + err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.Command, false, gateway) + } + if err != nil { + return fmt.Errorf("command execution failed: %v", err) + } + logrus.Info("Command successfuly executed") + } + return nil +} diff --git a/pkg/commands/search.go b/pkg/commands/search.go new file mode 100644 index 0000000000..60c65bd946 --- /dev/null +++ b/pkg/commands/search.go @@ -0,0 +1,92 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "strings" + "text/tabwriter" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/renstrom/fuzzysearch/fuzzy" +) + +// SearchArgs are flags for the `RunSearch` function +type SearchArgs struct { + Term string + NoTrunc bool +} + +// RunSearch is the handler for 'scw search' +func RunSearch(ctx CommandContext, args SearchArgs) error { + // FIXME: parallelize API calls + + term := strings.ToLower(args.Term) + w := tabwriter.NewWriter(ctx.Stdout, 10, 1, 3, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") + + var entries = []api.ScalewayImageInterface{} + + images, err := ctx.API.GetImages() + if err != nil { + return fmt.Errorf("unable to fetch images from the Scaleway API: %v", err) + } + for _, val := range *images { + if fuzzy.Match(term, strings.ToLower(val.Name)) { + entries = append(entries, api.ScalewayImageInterface{ + Type: "image", + Name: val.Name, + Public: val.Public, + }) + } + } + + snapshots, err := ctx.API.GetSnapshots() + if err != nil { + return fmt.Errorf("unable to fetch snapshots from the Scaleway API: %v", err) + } + for _, val := range *snapshots { + if fuzzy.Match(term, strings.ToLower(val.Name)) { + entries = append(entries, api.ScalewayImageInterface{ + Type: "snapshot", + Name: val.Name, + Public: false, + }) + } + } + + for _, image := range entries { + // name field + name := utils.TruncIf(utils.Wordify(image.Name), 45, !args.NoTrunc) + + // description field + var description string + switch image.Type { + case "image": + if image.Public { + description = "public image" + } else { + description = "user image" + } + + case "snapshot": + description = "user snapshot" + } + description = utils.TruncIf(utils.Wordify(description), 45, !args.NoTrunc) + + // official field + var official string + if image.Public { + official = "[OK]" + } else { + official = "" + } + + fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\n", name, description, 0, official, "") + } + return nil +} diff --git a/pkg/commands/start.go b/pkg/commands/start.go new file mode 100644 index 0000000000..0f37b91c25 --- /dev/null +++ b/pkg/commands/start.go @@ -0,0 +1,59 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "time" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// StartArgs are flags for the `RunStart` function +type StartArgs struct { + Servers []string + Wait bool + Timeout float64 +} + +// RunStart is the handler for 'scw start' +func RunStart(ctx CommandContext, args StartArgs) error { + hasError := false + errChan := make(chan error) + successChan := make(chan bool) + remainingItems := len(args.Servers) + + for _, needle := range args.Servers { + go api.StartServerOnce(ctx.API, needle, args.Wait, successChan, errChan) + } + + if args.Timeout > 0 { + go func() { + time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond) + // FIXME: avoid use of fatalf + logrus.Fatalf("Operation timed out") + }() + } + + for { + select { + case _ = <-successChan: + remainingItems-- + case err := <-errChan: + logrus.Errorf(fmt.Sprintf("%s", err)) + remainingItems-- + hasError = true + } + + if remainingItems == 0 { + break + } + } + if hasError { + return fmt.Errorf("at least 1 server failed to start") + } + return nil +} diff --git a/pkg/commands/stop.go b/pkg/commands/stop.go new file mode 100644 index 0000000000..1369432c4d --- /dev/null +++ b/pkg/commands/stop.go @@ -0,0 +1,59 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "time" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// StopArgs are flags for the `RunStop` function +type StopArgs struct { + Terminate bool + Wait bool + Servers []string +} + +// RunStop is the handler for 'scw stop' +func RunStop(ctx CommandContext, args StopArgs) error { + // FIXME: parallelize stop when stopping multiple servers + hasError := false + for _, needle := range args.Servers { + serverID := ctx.API.GetServerID(needle) + action := "poweroff" + if args.Terminate { + action = "terminate" + } + err := ctx.API.PostServerAction(serverID, action) + if err != nil { + if err.Error() != "server should be running" && err.Error() != "server is being stopped or rebooted" { + logrus.Warningf("failed to stop server %s: %s", serverID, err) + hasError = true + } + } else { + if args.Wait { + // We wait for 10 seconds which is the minimal amount of time needed for a server to stop + time.Sleep(10 * time.Second) + _, err = api.WaitForServerStopped(ctx.API, serverID) + if err != nil { + logrus.Errorf("failed to wait for server %s: %v", serverID, err) + hasError = true + } + } + if args.Terminate { + ctx.API.Cache.RemoveServer(serverID) + } + fmt.Fprintln(ctx.Stdout, needle) + } + } + + if hasError { + return fmt.Errorf("at least 1 server failed to be stopped") + } + return nil +} diff --git a/pkg/commands/tag.go b/pkg/commands/tag.go new file mode 100644 index 0000000000..d381a7533a --- /dev/null +++ b/pkg/commands/tag.go @@ -0,0 +1,29 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import "fmt" + +// TagArgs are flags for the `RunTag` function +type TagArgs struct { + Snapshot string + Name string +} + +// RunTag is the handler for 'scw tag' +func RunTag(ctx CommandContext, args TagArgs) error { + snapshotID := ctx.API.GetSnapshotID(args.Snapshot) + snapshot, err := ctx.API.GetSnapshot(snapshotID) + if err != nil { + return fmt.Errorf("cannot fetch snapshot: %v", err) + } + + image, err := ctx.API.PostImage(snapshot.Identifier, args.Name) + if err != nil { + return fmt.Errorf("cannot create image: %v", err) + } + fmt.Fprintln(ctx.Stdout, image) + return nil +} diff --git a/pkg/commands/top.go b/pkg/commands/top.go new file mode 100644 index 0000000000..7c9c9bd699 --- /dev/null +++ b/pkg/commands/top.go @@ -0,0 +1,51 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// TopArgs are flags for the `RunTop` function +type TopArgs struct { + Server string + Gateway string +} + +// RunTop is the handler for 'scw top' +func RunTop(ctx CommandContext, args TopArgs) error { + serverID := ctx.API.GetServerID(args.Server) + command := "ps" + server, err := ctx.API.GetServer(serverID) + if err != nil { + return fmt.Errorf("failed to get server information for %s: %v", serverID, err) + } + + // Resolve gateway + if args.Gateway == "" { + args.Gateway = ctx.Getenv("SCW_GATEWAY") + } + var gateway string + if args.Gateway == serverID || args.Gateway == args.Server { + gateway = "" + } else { + gateway, err = api.ResolveGateway(ctx.API, args.Gateway) + if err != nil { + return fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) + } + } + + execCmd := utils.NewSSHExecCmd(server.PublicAddress.IP, server.PrivateIP, true, nil, []string{command}, gateway) + logrus.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) + out, err := exec.Command("ssh", execCmd...).CombinedOutput() + fmt.Printf("%s", out) + return err +} diff --git a/pkg/commands/version.go b/pkg/commands/version.go new file mode 100644 index 0000000000..7395a83a6f --- /dev/null +++ b/pkg/commands/version.go @@ -0,0 +1,26 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + "runtime" + + "github.com/scaleway/scaleway-cli/pkg/scwversion" +) + +// VersionArgs are flags for the `RunVersion` function +type VersionArgs struct{} + +// RunVersion is the handler for 'scw version' +func RunVersion(ctx CommandContext, args VersionArgs) error { + fmt.Fprintf(ctx.Stdout, "Client version: %s\n", scwversion.VERSION) + fmt.Fprintf(ctx.Stdout, "Go version (client): %s\n", runtime.Version()) + fmt.Fprintf(ctx.Stdout, "Git commit (client): %s\n", scwversion.GITCOMMIT) + fmt.Fprintf(ctx.Stdout, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) + // FIXME: API version information + + return nil +} diff --git a/pkg/commands/wait.go b/pkg/commands/wait.go new file mode 100644 index 0000000000..e178aecf65 --- /dev/null +++ b/pkg/commands/wait.go @@ -0,0 +1,36 @@ +// Copyright (C) 2015 Scaleway. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE.md file. + +package commands + +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus" +) + +// WaitArgs are flags for the `RunWait` function +type WaitArgs struct { + Servers []string +} + +// RunWait is the handler for 'scw wait' +func RunWait(ctx CommandContext, args WaitArgs) error { + hasError := false + for _, needle := range args.Servers { + serverIdentifier := ctx.API.GetServerID(needle) + + _, err := api.WaitForServerStopped(ctx.API, serverIdentifier) + if err != nil { + logrus.Errorf("failed to wait for server %s: %v", serverIdentifier, err) + hasError = true + } + } + + if hasError { + return fmt.Errorf("at least 1 server failed to be stopped") + } + return nil +} diff --git a/scwversion/placeholder.go b/pkg/scwversion/placeholder.go similarity index 100% rename from scwversion/placeholder.go rename to pkg/scwversion/placeholder.go diff --git a/scwversion/version.tpl b/pkg/scwversion/version.tpl similarity index 100% rename from scwversion/version.tpl rename to pkg/scwversion/version.tpl diff --git a/utils/quiet.go b/pkg/utils/quiet.go similarity index 100% rename from utils/quiet.go rename to pkg/utils/quiet.go diff --git a/utils/utils.go b/pkg/utils/utils.go similarity index 100% rename from utils/utils.go rename to pkg/utils/utils.go diff --git a/utils/utils_test.go b/pkg/utils/utils_test.go similarity index 100% rename from utils/utils_test.go rename to pkg/utils/utils_test.go