From b128ae69ac04be62bd85dfcaf3d8d9c37b8e7e76 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Fri, 12 Jun 2015 17:41:25 +0200 Subject: [PATCH 1/7] Split code across packages --- api.go => api/api.go | 4 +- cache.go => api/cache.go | 31 +-- api_helpers.go => api/helpers.go | 71 +++--- api/types.go | 13 ++ cli.go | 251 +--------------------- attach.go => commands/attach.go | 8 +- commands/commands.go | 38 ++++ commit.go => commands/commit.go | 8 +- cp.go => commands/cp.go | 22 +- create.go => commands/create.go | 11 +- events.go => commands/events.go | 8 +- exec.go => commands/exec.go | 16 +- help.go => commands/help.go | 16 +- history.go => commands/history.go | 13 +- images.go => commands/images.go | 30 +-- info.go => commands/info.go | 13 +- inspect.go => commands/inspect.go | 17 +- kill.go => commands/kill.go | 11 +- login.go => commands/login.go | 17 +- logout.go => commands/logout.go | 11 +- logs.go => commands/logs.go | 15 +- port.go => commands/port.go | 15 +- ps.go => commands/ps.go | 15 +- rename.go => commands/rename.go | 15 +- restart.go => commands/restart.go | 8 +- rm.go => commands/rm.go | 8 +- rmi.go => commands/rmi.go | 8 +- run.go => commands/run.go | 20 +- search.go => commands/search.go | 20 +- start.go => commands/start.go | 11 +- stop.go => commands/stop.go | 8 +- tag.go => commands/tag.go | 8 +- top.go => commands/top.go | 11 +- commands/types/command.go | 115 ++++++++++ version.go => commands/version.go | 8 +- wait.go => commands/wait.go | 8 +- completion.go => commands/x_completion.go | 21 +- patch.go => commands/x_patch.go | 15 +- log.go | 16 -- main.go | 86 ++++++++ utils.go => utils/utils.go | 43 +++- utils_test.go => utils/utils_test.go | 2 +- 42 files changed, 600 insertions(+), 485 deletions(-) rename api.go => api/api.go (99%) rename cache.go => api/cache.go (95%) rename api_helpers.go => api/helpers.go (89%) create mode 100644 api/types.go rename attach.go => commands/attach.go (89%) create mode 100644 commands/commands.go rename commit.go => commands/commit.go (88%) rename cp.go => commands/cp.go (88%) rename create.go => commands/create.go (82%) rename events.go => commands/events.go (89%) rename exec.go => commands/exec.go (81%) rename help.go => commands/help.go (79%) rename history.go => commands/history.go (81%) rename images.go => commands/images.go (85%) rename info.go => commands/info.go (82%) rename inspect.go => commands/inspect.go (81%) rename kill.go => commands/kill.go (76%) rename login.go => commands/login.go (86%) rename logout.go => commands/logout.go (73%) rename logs.go => commands/logs.go (70%) rename port.go => commands/port.go (73%) rename ps.go => commands/ps.go (82%) rename rename.go => commands/rename.go (68%) rename restart.go => commands/restart.go (84%) rename rm.go => commands/rm.go (85%) rename rmi.go => commands/rmi.go (85%) rename run.go => commands/run.go (76%) rename search.go => commands/search.go (75%) rename start.go => commands/start.go (82%) rename stop.go => commands/stop.go (89%) rename tag.go => commands/tag.go (83%) rename top.go => commands/top.go (75%) create mode 100644 commands/types/command.go rename version.go => commands/version.go (82%) rename wait.go => commands/wait.go (85%) rename completion.go => commands/x_completion.go (67%) rename patch.go => commands/x_patch.go (78%) delete mode 100644 log.go create mode 100644 main.go rename utils.go => utils/utils.go (68%) rename utils_test.go => utils/utils_test.go (99%) diff --git a/api.go b/api/api.go similarity index 99% rename from api.go rename to api/api.go index f337030cbf..c75ed60bec 100644 --- a/api.go +++ b/api/api.go @@ -1,4 +1,4 @@ -package main +package api import ( "bytes" @@ -452,7 +452,7 @@ type ScalewayImageDefinition struct { Arch string `json:"arch"` } -var funcMap = template.FuncMap{ +var FuncMap = template.FuncMap{ "json": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) diff --git a/cache.go b/api/cache.go similarity index 95% rename from cache.go rename to api/cache.go index 7981c25bd0..134e39f8d9 100644 --- a/cache.go +++ b/api/cache.go @@ -1,4 +1,4 @@ -package main +package api import ( "encoding/json" @@ -9,6 +9,8 @@ import ( "strings" "sync" + "github.com/scaleway/scaleway-cli/utils" + "code.google.com/p/go-uuid/uuid" ) @@ -160,7 +162,7 @@ func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) []string { return exactMatches } - return RemoveDuplicates(res) + return utils.RemoveDuplicates(res) } // LookUpSnapshots attempts to return identifiers matching a pattern @@ -190,7 +192,7 @@ func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) []string return exactMatches } - return RemoveDuplicates(res) + return utils.RemoveDuplicates(res) } // LookUpVolumes attempts to return identifiers matching a pattern @@ -219,7 +221,7 @@ func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) []string { return exactMatches } - return RemoveDuplicates(res) + return utils.RemoveDuplicates(res) } // LookUpBootscripts attempts to return identifiers matching a pattern @@ -248,7 +250,7 @@ func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) []stri return exactMatches } - return RemoveDuplicates(res) + return utils.RemoveDuplicates(res) } // LookUpServers attempts to return identifiers matching a pattern @@ -277,7 +279,7 @@ func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) []string { return exactMatches } - return RemoveDuplicates(res) + return utils.RemoveDuplicates(res) } // LookUpIdentifiers attempts to return identifiers matching a pattern @@ -322,23 +324,6 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) []ScalewayIdentifier { return results } -// RemoveDuplicates transforms an array into a unique array -func RemoveDuplicates(elements []string) []string { - encountered := map[string]bool{} - - // Create a map of all unique elements. - for v := range elements { - encountered[elements[v]] = true - } - - // Place all keys from the map into a slice. - result := []string{} - for key := range encountered { - result = append(result, key) - } - return result -} - // InsertServer registers a server in the cache func (c *ScalewayCache) InsertServer(identifier, name string) { c.Lock.Lock() diff --git a/api_helpers.go b/api/helpers.go similarity index 89% rename from api_helpers.go rename to api/helpers.go index 41685e7d52..57e05a5277 100644 --- a/api_helpers.go +++ b/api/helpers.go @@ -1,4 +1,4 @@ -package main +package api import ( "fmt" @@ -10,8 +10,29 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/namesgenerator" "github.com/dustin/go-humanize" + "github.com/scaleway/scaleway-cli/utils" ) +// ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern +type ScalewayResolvedIdentifier struct { + // Identifiers holds matching identifiers + Identifiers []ScalewayIdentifier + + // Needle is the criteria used to lookup identifiers + Needle string +} + +// ScalewayImageInterface is an interface to multiple Scaleway items +type ScalewayImageInterface struct { + CreationDate time.Time + Identifier string + Name string + Tag string + VirtualSize float64 + Public bool + Type string +} + // CreateVolumeFromHumanSize creates a volume on the API with a human readable size func CreateVolumeFromHumanSize(api *ScalewayAPI, size string) (*string, error) { bytes, err := humanize.ParseBytes(size) @@ -32,15 +53,6 @@ func CreateVolumeFromHumanSize(api *ScalewayAPI, size string) (*string, error) { return &volumeID, nil } -// ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern -type ScalewayResolvedIdentifier struct { - // Identifiers holds matching identifiers - Identifiers []ScalewayIdentifier - - // Needle is the criteria used to lookup identifiers - Needle string -} - // fillIdentifierCache fills the cache by fetching fro the API func fillIdentifierCache(api *ScalewayAPI) { log.Debugf("Filling the cache") @@ -69,9 +81,9 @@ func fillIdentifierCache(api *ScalewayAPI) { wg.Wait() } -// getIdentifier returns a an identifier if the resolved needles only match one element, else, it exists the program -func getIdentifier(api *ScalewayAPI, needle string) *ScalewayIdentifier { - idents := resolveIdentifier(api, needle) +// GetIdentifier returns a an identifier if the resolved needles only match one element, else, it exists the program +func GetIdentifier(api *ScalewayAPI, needle string) *ScalewayIdentifier { + idents := ResolveIdentifier(api, needle) if len(idents) == 1 { return &idents[0] @@ -88,8 +100,8 @@ func getIdentifier(api *ScalewayAPI, needle string) *ScalewayIdentifier { return nil } -// resolveIdentifier resolves needle provided by the user -func resolveIdentifier(api *ScalewayAPI, needle string) []ScalewayIdentifier { +// ResolveIdentifier resolves needle provided by the user +func ResolveIdentifier(api *ScalewayAPI, needle string) []ScalewayIdentifier { idents := api.Cache.LookUpIdentifiers(needle) if len(idents) > 0 { return idents @@ -101,8 +113,8 @@ func resolveIdentifier(api *ScalewayAPI, needle string) []ScalewayIdentifier { return idents } -// resolveIdentifiers resolves needles provided by the user -func resolveIdentifiers(api *ScalewayAPI, needles []string, out chan ScalewayResolvedIdentifier) { +// ResolveIdentifiers resolves needles provided by the user +func ResolveIdentifiers(api *ScalewayAPI, needles []string, out chan ScalewayResolvedIdentifier) { // first attempt, only lookup from the cache var unresolved []string for _, needle := range needles { @@ -131,8 +143,8 @@ func resolveIdentifiers(api *ScalewayAPI, needles []string, out chan ScalewayRes close(out) } -// inspectIdentifiers inspects identifiers concurrently -func inspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj chan interface{}) { +// InspectIdentifiers inspects identifiers concurrently +func InspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj chan interface{}) { var wg sync.WaitGroup for { idents, ok := <-ci @@ -187,7 +199,7 @@ func inspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj close(cj) } -func createServer(api *ScalewayAPI, imageName string, name string, bootscript string, env string, additionalVolumes string) (string, error) { +func CreateServer(api *ScalewayAPI, imageName string, name string, bootscript string, env string, additionalVolumes string) (string, error) { if name == "" { name = strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1) } @@ -268,7 +280,7 @@ func WaitForServerReady(api *ScalewayAPI, serverID string) (*ScalewayServer, err dest := fmt.Sprintf("%s:22", server.PublicAddress.IP) - err = WaitForTCPPortOpen(dest) + err = utils.WaitForTCPPortOpen(dest) if err != nil { return nil, err } @@ -276,17 +288,6 @@ func WaitForServerReady(api *ScalewayAPI, serverID string) (*ScalewayServer, err return server, nil } -// ScalewayImageInterface is an interface to multiple Scaleway items -type ScalewayImageInterface struct { - CreationDate time.Time - Identifier string - Name string - Tag string - VirtualSize float64 - Public bool - Type string -} - // ByCreationDate sorts images by CreationDate field type ByCreationDate []ScalewayImageInterface @@ -294,7 +295,7 @@ func (a ByCreationDate) Len() int { return len(a) } func (a ByCreationDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByCreationDate) Less(i, j int) bool { return a[j].CreationDate.Before(a[i].CreationDate) } -func startServer(api *ScalewayAPI, needle string, wait bool) error { +func StartServer(api *ScalewayAPI, needle string, wait bool) error { server := api.GetServerID(needle) err := api.PostServerAction(server, "poweron") @@ -313,8 +314,8 @@ func startServer(api *ScalewayAPI, needle string, wait bool) error { return nil } -func startServerOnce(api *ScalewayAPI, needle string, wait bool, successChan chan bool, errChan chan error) { - err := startServer(api, needle, wait) +func StartServerOnce(api *ScalewayAPI, needle string, wait bool, successChan chan bool, errChan chan error) { + err := StartServer(api, needle, wait) if err != nil { errChan <- err diff --git a/api/types.go b/api/types.go new file mode 100644 index 0000000000..a45f91ae18 --- /dev/null +++ b/api/types.go @@ -0,0 +1,13 @@ +package api + +// Config is a Scaleway CLI configuration file +type Config struct { + // APIEndpoint is the endpoint to the Scaleway API + APIEndPoint string `json:"api_endpoint"` + + // Organization is the identifier of the Scaleway orgnization + Organization string `json:"organization"` + + // Token is the authentication token for the Scaleway organization + Token string `json:"token"` +} diff --git a/cli.go b/cli.go index ac622e71a5..262d15c7ea 100644 --- a/cli.go +++ b/cli.go @@ -2,23 +2,18 @@ package main import ( - "bytes" "encoding/json" - "errors" "fmt" "io/ioutil" "os" - "path/filepath" - "strings" - "text/template" log "github.com/Sirupsen/logrus" flag "github.com/docker/docker/pkg/mflag" - "github.com/scaleway/scaleway-cli/scwversion" -) -var ( - config *Config + "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" ) // CommandListOpts holds a list of parameters @@ -45,249 +40,23 @@ func (opts *CommandListOpts) Set(value string) error { return nil } -// Command is a Scaleway command -type Command struct { - // Exec executes the command - Exec func(cmd *Command, args []string) - - // Usage is the one-line usage message. - UsageLine string - - // Description is the description of the command - Description string - - // Help is the full description of the command - Help string - - // Examples are some examples of the command - Examples string - - // Flag is a set of flags specific to this command. - Flag flag.FlagSet - - // Hidden is a flat to hide command from global help commands listing - Hidden bool - - // API is the interface used to communicate with Scaleway's API - API *ScalewayAPI -} - -// Name returns the command's name -func (c *Command) Name() string { - name := c.UsageLine - i := strings.Index(name, " ") - if i >= 0 { - name = name[:i] - } - return name -} - -var fullHelpTemplate = ` -Usage: scw {{.UsageLine}} - -{{.Help}} - -{{.Options}} -{{.ExamplesHelp}} -` - -func commandHelpMessage(cmd *Command) (string, error) { - t := template.New("full") - template.Must(t.Parse(fullHelpTemplate)) - var output bytes.Buffer - err := t.Execute(&output, cmd) - if err != nil { - return "", err - } - return strings.Trim(output.String(), "\n"), nil -} - func commandUsage(name string) { } -// PrintUsage prints a full command usage and exits -func (c *Command) PrintUsage() { - helpMessage, err := commandHelpMessage(c) - if err != nil { - log.Fatalf("%v", err) - } - fmt.Fprintf(os.Stderr, "%s\n", helpMessage) - os.Exit(1) -} - -// PrintShortUsage prints a short command usage and exits -func (c *Command) PrintShortUsage() { - fmt.Fprintf(os.Stderr, "usage: scw %s. See 'scw %s --help'.\n", c.UsageLine, c.Name()) - os.Exit(1) -} - -// Options returns a string describing options of the command -func (c *Command) Options() string { - var options string - visitor := func(flag *flag.Flag) { - name := strings.Join(flag.Names, ", -") - var optionUsage string - if flag.DefValue == "" { - optionUsage = fmt.Sprintf("%s=\"\"", name) - } else { - optionUsage = fmt.Sprintf("%s=%s", name, flag.DefValue) - } - options += fmt.Sprintf(" -%-20s %s\n", optionUsage, flag.Usage) - } - c.Flag.VisitAll(visitor) - if len(options) == 0 { - return "" - } - return fmt.Sprintf("Options:\n\n%s", options) -} - -// ExamplesHelp returns a string describing examples of the command -func (c *Command) ExamplesHelp() string { - if c.Examples == "" { - return "" - } - return fmt.Sprintf("Examples:\n\n%s", strings.Trim(c.Examples, "\n")) -} - -var commands = []*Command{ - cmdAttach, - cmdCommit, - cmdCompletion, - cmdCp, - cmdCreate, - cmdEvents, - cmdExec, - cmdHelp, - cmdHistory, - cmdImages, - cmdInfo, - cmdInspect, - cmdKill, - cmdLogin, - cmdLogout, - cmdLogs, - cmdPatch, - cmdPort, - cmdPs, - cmdRename, - cmdRestart, - cmdRm, - cmdRmi, - cmdRun, - cmdSearch, - cmdStart, - cmdStop, - cmdTag, - cmdTop, - cmdVersion, - cmdWait, -} - var ( flAPIEndPoint *string flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") flVersion = flag.Bool([]string{"v", "--version"}, false, "Print version information and quit") ) -func main() { - var cfgErr error - config, cfgErr = getConfig() - if cfgErr != nil && !os.IsNotExist(cfgErr) { - log.Fatalf("Unable to open .scwrc config file: %v", cfgErr) - } - - if config != nil { - flAPIEndPoint = flag.String([]string{"-api-endpoint"}, config.APIEndPoint, "Set the API endpoint") - } - flag.Parse() - - if *flVersion { - showVersion() - return - } - - if flAPIEndPoint != nil { - os.Setenv("scaleway_api_endpoint", *flAPIEndPoint) - } - - if *flDebug { - os.Setenv("DEBUG", "1") - } - - initLogging(os.Getenv("DEBUG") != "") - - args := flag.Args() - if len(args) < 1 { - usage() - } - name := args[0] - - args = args[1:] - - for _, cmd := range commands { - if cmd.Name() == name { - cmd.Flag.SetOutput(ioutil.Discard) - err := cmd.Flag.Parse(args) - if err != nil { - log.Fatalf("usage: scw %s", cmd.UsageLine) - } - if cmd.Name() != "login" && cmd.Name() != "help" { - if cfgErr != nil { - if name != "login" && config == nil { - fmt.Fprintf(os.Stderr, "You need to login first: 'scw login'\n") - os.Exit(1) - } - } - api, err := getScalewayAPI() - if err != nil { - log.Fatalf("unable to initialize scw api: %s", err) - } - cmd.API = api - } - cmd.Exec(cmd, cmd.Flag.Args()) - if cmd.API != nil { - cmd.API.Sync() - } - os.Exit(0) - } - } - - log.Fatalf("scw: unknown subcommand %s\nRun 'scw help' for usage.", name) -} - func usage() { - cmdHelp.Exec(cmdHelp, []string{}) + cmds.CmdHelp.Exec(cmds.CmdHelp, []string{}) os.Exit(1) } -// Config is a Scaleway CLI configuration file -type Config struct { - // APIEndpoint is the endpoint to the Scaleway API - APIEndPoint string `json:"api_endpoint"` - - // Organization is the identifier of the Scaleway orgnization - Organization string `json:"organization"` - - // Token is the authentication token for the Scaleway organization - Token string `json:"token"` -} - -// GetConfigFilePath returns the path to the Scaleway CLI config file -func GetConfigFilePath() (string, error) { - homeDir := os.Getenv("HOME") // *nix - if homeDir == "" { // Windows - homeDir = os.Getenv("USERPROFILE") - } - if homeDir == "" { - return "", errors.New("user home directory not found") - } - - return filepath.Join(homeDir, ".scwrc"), nil -} - // getConfig returns the Scaleway CLI config file for the current user -func getConfig() (*Config, error) { - scwrcPath, err := GetConfigFilePath() +func getConfig() (*api.Config, error) { + scwrcPath, err := utils.GetConfigFilePath() if err != nil { return nil, err } @@ -305,7 +74,7 @@ func getConfig() (*Config, error) { if err != nil { return nil, err } - var config Config + var config api.Config err = json.Unmarshal(file, &config) if err != nil { return nil, err @@ -317,13 +86,13 @@ func getConfig() (*Config, error) { } // getScalewayAPI returns a ScalewayAPI using the user config file -func getScalewayAPI() (*ScalewayAPI, error) { +func getScalewayAPI() (*api.ScalewayAPI, error) { // We already get config globally, but whis way we can get explicit error when trying to create a ScalewayAPI object config, err := getConfig() if err != nil { return nil, err } - return NewScalewayAPI(os.Getenv("scaleway_api_endpoint"), config.Organization, config.Token) + return api.NewScalewayAPI(os.Getenv("scaleway_api_endpoint"), config.Organization, config.Token) } func showVersion() { diff --git a/attach.go b/commands/attach.go similarity index 89% rename from attach.go rename to commands/attach.go index b824ef7eeb..474f17504d 100644 --- a/attach.go +++ b/commands/attach.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,11 @@ import ( "os/exec" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdAttach = &Command{ +var cmdAttach = &types.Command{ Exec: runAttach, UsageLine: "attach [OPTIONS] SERVER", Description: "Attach to a server serial console", @@ -29,7 +31,7 @@ var attachHelp bool // -h, --help flag const termjsBin string = "termjs-cli" -func runAttach(cmd *Command, args []string) { +func runAttach(cmd *types.Command, args []string) { if attachHelp { cmd.PrintUsage() } diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 0000000000..c442708c69 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,38 @@ +package commands + +import types "github.com/scaleway/scaleway-cli/commands/types" + +var Commands = []*types.Command{ + CmdHelp, + + cmdAttach, + cmdCommit, + cmdCompletion, + cmdCp, + cmdCreate, + cmdEvents, + cmdExec, + cmdHistory, + cmdImages, + cmdInfo, + cmdInspect, + cmdKill, + cmdLogin, + cmdLogout, + cmdLogs, + cmdPatch, + cmdPort, + cmdPs, + cmdRename, + cmdRestart, + cmdRm, + cmdRmi, + cmdRun, + cmdSearch, + cmdStart, + cmdStop, + cmdTag, + cmdTop, + cmdVersion, + cmdWait, +} diff --git a/commit.go b/commands/commit.go similarity index 88% rename from commit.go rename to commands/commit.go index e990726287..bdf992f60d 100644 --- a/commit.go +++ b/commands/commit.go @@ -1,12 +1,14 @@ -package main +package commands import ( "fmt" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdCommit = &Command{ +var cmdCommit = &types.Command{ Exec: runCommit, UsageLine: "commit [OPTIONS] SERVER [NAME]", Description: "Create a new snapshot from a server's volume", @@ -26,7 +28,7 @@ func init() { var commitVolume int // -v, --volume flag var commitHelp bool // -h, --help flag -func runCommit(cmd *Command, args []string) { +func runCommit(cmd *types.Command, args []string) { if commitHelp { cmd.PrintUsage() } diff --git a/cp.go b/commands/cp.go similarity index 88% rename from cp.go rename to commands/cp.go index 90ff592551..6c98909880 100644 --- a/cp.go +++ b/commands/cp.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -10,9 +10,13 @@ import ( log "github.com/Sirupsen/logrus" "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 = &Command{ +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", @@ -40,7 +44,7 @@ func init() { // Flags var cpHelp bool // -h, --help flag -func TarFromSource(api *ScalewayAPI, source string) (*io.ReadCloser, error) { +func TarFromSource(api *api.ScalewayAPI, source string) (*io.ReadCloser, error) { var tarOutputStream io.ReadCloser // source is a server address + path (scp-like uri) @@ -58,7 +62,7 @@ func TarFromSource(api *ScalewayAPI, source string) (*io.ReadCloser, error) { return nil, err } - dir, base := PathToTARPathparts(serverParts[1]) + dir, base := utils.PathToTARPathparts(serverParts[1]) log.Debugf("Equivalent to 'scp root@%s:%s/%s ...'", server.PublicAddress.IP, dir, base) // remoteCommand is executed on the remote server @@ -72,7 +76,7 @@ func TarFromSource(api *ScalewayAPI, source string) (*io.ReadCloser, error) { remoteCommand = append(remoteCommand, base) // execCmd contains the ssh connection + the remoteCommand - execCmd := append(NewSSHExecCmd(server.PublicAddress.IP, false, remoteCommand)) + execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, false, remoteCommand)) log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) spawnSrc := exec.Command("ssh", execCmd...) @@ -116,7 +120,7 @@ func TarFromSource(api *ScalewayAPI, source string) (*io.ReadCloser, error) { } log.Debugf("Real local path is %s", path) - dir, base := PathToTARPathparts(path) + dir, base := utils.PathToTARPathparts(path) tarOutputStream, err = archive.TarWithOptions(dir, &archive.TarOptions{ Compression: archive.Uncompressed, @@ -128,7 +132,7 @@ func TarFromSource(api *ScalewayAPI, source string) (*io.ReadCloser, error) { return &tarOutputStream, nil } -func UntarToDest(api *ScalewayAPI, sourceStream *io.ReadCloser, destination string) error { +func UntarToDest(api *api.ScalewayAPI, sourceStream *io.ReadCloser, destination string) error { // destination is a server address + path (scp-like uri) if strings.Index(destination, ":") > -1 { log.Debugf("Streaming using ssh and untaring remotely") @@ -154,7 +158,7 @@ func UntarToDest(api *ScalewayAPI, sourceStream *io.ReadCloser, destination stri remoteCommand = append(remoteCommand, "-xf", "-") // execCmd contains the ssh connection + the remoteCommand - execCmd := append(NewSSHExecCmd(server.PublicAddress.IP, false, remoteCommand)) + execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, false, remoteCommand)) log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) spawnDst := exec.Command("ssh", execCmd...) @@ -189,7 +193,7 @@ func UntarToDest(api *ScalewayAPI, sourceStream *io.ReadCloser, destination stri return err } -func runCp(cmd *Command, args []string) { +func runCp(cmd *types.Command, args []string) { if cpHelp { cmd.PrintUsage() } diff --git a/create.go b/commands/create.go similarity index 82% rename from create.go rename to commands/create.go index 73bac0b6c6..b8378a3148 100644 --- a/create.go +++ b/commands/create.go @@ -1,12 +1,15 @@ -package main +package commands import ( "fmt" log "github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/api" + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdCreate = &Command{ +var cmdCreate = &types.Command{ Exec: runCreate, UsageLine: "create [OPTIONS] IMAGE", Description: "Create a new server but do not start it", @@ -35,7 +38,7 @@ var createEnv string // -e, --env flag var createVolume string // -v, --volume flag var createHelp bool // -h, --help flag -func runCreate(cmd *Command, args []string) { +func runCreate(cmd *types.Command, args []string) { if createHelp { cmd.PrintUsage() } @@ -43,7 +46,7 @@ func runCreate(cmd *Command, args []string) { cmd.PrintShortUsage() } - serverID, err := createServer(cmd.API, args[0], createName, createBootscript, createEnv, createVolume) + serverID, err := api.CreateServer(cmd.API, args[0], createName, createBootscript, createEnv, createVolume) if err != nil { log.Fatalf("Failed to create server: %v", err) diff --git a/events.go b/commands/events.go similarity index 89% rename from events.go rename to commands/events.go index 51a85714fe..869ed90c65 100644 --- a/events.go +++ b/commands/events.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,11 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/units" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdEvents = &Command{ +var cmdEvents = &types.Command{ Exec: runEvents, UsageLine: "events [OPTIONS]", Description: "Get real time events from the API", @@ -22,7 +24,7 @@ func init() { // Flags var eventsHelp bool // -h, --help flag -func runEvents(cmd *Command, args []string) { +func runEvents(cmd *types.Command, args []string) { if eventsHelp { cmd.PrintUsage() } diff --git a/exec.go b/commands/exec.go similarity index 81% rename from exec.go rename to commands/exec.go index 7e038e4bb0..2c3cff1f52 100644 --- a/exec.go +++ b/commands/exec.go @@ -1,13 +1,17 @@ -package main +package commands import ( "os" "time" log "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 cmdExec = &Command{ +var cmdExec = &types.Command{ Exec: runExec, UsageLine: "exec [OPTIONS] SERVER COMMAND [ARGS...]", Description: "Run a command on a running server", @@ -35,7 +39,7 @@ var execW bool // -w, --wait flag var execTimeout float64 // -T flag var execHelp bool // -h, --help flag -func runExec(cmd *Command, args []string) { +func runExec(cmd *types.Command, args []string) { if execHelp { cmd.PrintUsage() } @@ -45,11 +49,11 @@ func runExec(cmd *Command, args []string) { serverID := cmd.API.GetServerID(args[0]) - var server *ScalewayServer + var server *api.ScalewayServer var err error if execW { // --wait - server, err = WaitForServerReady(cmd.API, serverID) + server, err = api.WaitForServerReady(cmd.API, serverID) if err != nil { log.Fatalf("Failed to wait for server to be ready, %v", err) } @@ -68,7 +72,7 @@ func runExec(cmd *Command, args []string) { }() } - err = sshExec(server.PublicAddress.IP, args[1:], !execW) + err = utils.SshExec(server.PublicAddress.IP, args[1:], !execW) if err != nil { log.Fatalf("%v", err) os.Exit(1) diff --git a/help.go b/commands/help.go similarity index 79% rename from help.go rename to commands/help.go index a6b32f852d..7be6f316fa 100644 --- a/help.go +++ b/commands/help.go @@ -1,12 +1,14 @@ -package main +package commands import ( "log" "os" "text/template" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdHelp = &Command{ +var CmdHelp = &types.Command{ Exec: nil, UsageLine: "help [COMMAND]", Description: "help of the scw command line", @@ -21,8 +23,8 @@ the command. func init() { // break dependency loop - cmdHelp.Exec = runHelp - cmdHelp.Flag.BoolVar(&helpHelp, []string{"h", "-help"}, false, "Print usage") + CmdHelp.Exec = runHelp + CmdHelp.Flag.BoolVar(&helpHelp, []string{"h", "-help"}, false, "Print usage") } // Flags @@ -44,7 +46,7 @@ Commands: Run 'scw COMMAND --help' for more information on a command. ` -func runHelp(cmd *Command, args []string) { +func runHelp(cmd *types.Command, args []string) { if waitHelp { cmd.PrintUsage() } @@ -54,7 +56,7 @@ func runHelp(cmd *Command, args []string) { if len(args) == 1 { name := args[0] - for _, command := range commands { + for _, command := range Commands { if command.Name() == name { command.PrintUsage() } @@ -63,7 +65,7 @@ func runHelp(cmd *Command, args []string) { } else { t := template.New("top") template.Must(t.Parse(helpTemplate)) - if err := t.Execute(os.Stdout, commands); err != nil { + if err := t.Execute(os.Stdout, Commands); err != nil { panic(err) } } diff --git a/history.go b/commands/history.go similarity index 81% rename from history.go rename to commands/history.go index e3879f3c5a..0b5ff7943c 100644 --- a/history.go +++ b/commands/history.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -8,9 +8,12 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/units" + + types "github.com/scaleway/scaleway-cli/commands/types" + utils "github.com/scaleway/scaleway-cli/utils" ) -var cmdHistory = &Command{ +var cmdHistory = &types.Command{ Exec: runHistory, UsageLine: "history [OPTIONS] IMAGE", Description: "Show the history of an image", @@ -28,7 +31,7 @@ var historyNoTrunc bool // --no-trunc flag var historyQuiet bool // -q, --quiet flag var historyHelp bool // -h, --help flag -func runHistory(cmd *Command, args []string) { +func runHistory(cmd *types.Command, args []string) { if historyHelp { cmd.PrintUsage() } @@ -51,7 +54,7 @@ func runHistory(cmd *Command, args []string) { defer w.Flush() fmt.Fprintf(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\n") - identifier := truncIf(image.Identifier, 8, !historyNoTrunc) + identifier := utils.TruncIf(image.Identifier, 8, !historyNoTrunc) creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", image.CreationDate) if err != nil { @@ -59,7 +62,7 @@ func runHistory(cmd *Command, args []string) { } creationDateStr := units.HumanDuration(time.Now().UTC().Sub(creationDate)) - volumeName := truncIf(image.RootVolume.Name, 25, !historyNoTrunc) + 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/images.go b/commands/images.go similarity index 85% rename from images.go rename to commands/images.go index 7d01eb3c90..f72e19e9d5 100644 --- a/images.go +++ b/commands/images.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -10,9 +10,13 @@ import ( log "github.com/Sirupsen/logrus" "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 = &Command{ +var cmdImages = &types.Command{ Exec: runImages, UsageLine: "images [OPTIONS]", Description: "List images", @@ -32,7 +36,7 @@ var imagesQ bool // -q flag var imagesNoTrunc bool // -no-trunc flag var imagesHelp bool // -h, --help flag -func runImages(cmd *Command, args []string) { +func runImages(cmd *types.Command, args []string) { if imagesHelp { cmd.PrintUsage() } @@ -41,8 +45,8 @@ func runImages(cmd *Command, args []string) { } wg := sync.WaitGroup{} - chEntries := make(chan ScalewayImageInterface) - var entries = []ScalewayImageInterface{} + chEntries := make(chan api.ScalewayImageInterface) + var entries = []api.ScalewayImageInterface{} wg.Add(1) go func() { @@ -56,7 +60,7 @@ func runImages(cmd *Command, args []string) { if err != nil { log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } - chEntries <- ScalewayImageInterface{ + chEntries <- api.ScalewayImageInterface{ Type: "image", CreationDate: creationDate, Identifier: val.Identifier, @@ -81,7 +85,7 @@ func runImages(cmd *Command, args []string) { if err != nil { log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } - chEntries <- ScalewayImageInterface{ + chEntries <- api.ScalewayImageInterface{ Type: "snapshot", CreationDate: creationDate, Identifier: val.Identifier, @@ -101,7 +105,7 @@ func runImages(cmd *Command, args []string) { log.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) } for _, val := range *bootscripts { - chEntries <- ScalewayImageInterface{ + chEntries <- api.ScalewayImageInterface{ Type: "bootscript", Identifier: val.Identifier, Name: val.Title, @@ -123,7 +127,7 @@ func runImages(cmd *Command, args []string) { if err != nil { log.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } - chEntries <- ScalewayImageInterface{ + chEntries <- api.ScalewayImageInterface{ Type: "volume", CreationDate: creationDate, Identifier: val.Identifier, @@ -161,18 +165,18 @@ func runImages(cmd *Command, args []string) { if !imagesQ { fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\n") } - sort.Sort(ByCreationDate(entries)) + sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { if imagesQ { fmt.Fprintf(w, "%s\n", image.Identifier) } else { tag := image.Tag - shortID := truncIf(image.Identifier, 8, !imagesNoTrunc) - name := wordify(image.Name) + shortID := utils.TruncIf(image.Identifier, 8, !imagesNoTrunc) + name := utils.Wordify(image.Name) if !image.Public { name = "user/" + name } - shortName := truncIf(name, 25, !imagesNoTrunc) + shortName := utils.TruncIf(name, 25, !imagesNoTrunc) var creationDate, virtualSize string if image.CreationDate.IsZero() { creationDate = "n/a" diff --git a/info.go b/commands/info.go similarity index 82% rename from info.go rename to commands/info.go index 299147c2c8..2ca85d0bc0 100644 --- a/info.go +++ b/commands/info.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,12 @@ import ( "runtime" "github.com/kardianos/osext" + + types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/utils" ) -var cmdInfo = &Command{ +var cmdInfo = &types.Command{ Exec: runInfo, UsageLine: "info [OPTIONS]", Description: "Display system-wide information", @@ -22,7 +25,7 @@ func init() { // Flags var infoHelp bool // -h, --help flag -func runInfo(cmd *Command, args []string) { +func runInfo(cmd *types.Command, args []string) { if infoHelp { cmd.PrintUsage() } @@ -34,10 +37,10 @@ func runInfo(cmd *Command, args []string) { // FIXME: fmt.Printf("Images: %s\n", "quantity") fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "") - fmt.Printf("Organization: %s\n", config.Organization) + 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, _ := GetConfigFilePath() + 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()) diff --git a/inspect.go b/commands/inspect.go similarity index 81% rename from inspect.go rename to commands/inspect.go index e16263af76..7a0c21185b 100644 --- a/inspect.go +++ b/commands/inspect.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/json" @@ -7,9 +7,12 @@ import ( "text/template" log "github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/api" + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdInspect = &Command{ +var cmdInspect = &types.Command{ Exec: runInspect, UsageLine: "inspect [OPTIONS] IDENTIFIER [IDENTIFIER...]", Description: "Return low-level information on a server, image, snapshot, volume or bootscript", @@ -36,7 +39,7 @@ func init() { var inspectFormat string // -f, --format flat var inspectHelp bool // -h, --help flag -func runInspect(cmd *Command, args []string) { +func runInspect(cmd *types.Command, args []string) { if inspectHelp { cmd.PrintUsage() } @@ -46,10 +49,10 @@ func runInspect(cmd *Command, args []string) { res := "[" nbInspected := 0 - ci := make(chan ScalewayResolvedIdentifier) + ci := make(chan api.ScalewayResolvedIdentifier) cj := make(chan interface{}) - go resolveIdentifiers(cmd.API, args, ci) - go inspectIdentifiers(cmd.API, ci, cj) + go api.ResolveIdentifiers(cmd.API, args, ci) + go api.InspectIdentifiers(cmd.API, ci, cj) for { data, open := <-cj if !open { @@ -65,7 +68,7 @@ func runInspect(cmd *Command, args []string) { nbInspected++ } } else { - tmpl, err := template.New("").Funcs(funcMap).Parse(inspectFormat) + tmpl, err := template.New("").Funcs(api.FuncMap).Parse(inspectFormat) if err != nil { log.Fatalf("Format parsing error: %v", err) } diff --git a/kill.go b/commands/kill.go similarity index 76% rename from kill.go rename to commands/kill.go index 7f080ae3dc..3c7d65f5c7 100644 --- a/kill.go +++ b/commands/kill.go @@ -1,4 +1,4 @@ -package main +package commands import ( "os" @@ -6,9 +6,12 @@ import ( "strings" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/utils" ) -var cmdKill = &Command{ +var cmdKill = &types.Command{ Exec: runKill, UsageLine: "kill [OPTIONS] SERVER", Description: "Kill a running server", @@ -23,7 +26,7 @@ func init() { // Flags var killHelp bool // -h, --help flag -func runKill(cmd *Command, args []string) { +func runKill(cmd *types.Command, args []string) { if killHelp { cmd.PrintUsage() } @@ -38,7 +41,7 @@ func runKill(cmd *Command, args []string) { log.Fatalf("Failed to get server information for %s: %v", serverID, err) } - execCmd := append(NewSSHExecCmd(server.PublicAddress.IP, true, []string{command})) + execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, true, []string{command})) log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) diff --git a/login.go b/commands/login.go similarity index 86% rename from login.go rename to commands/login.go index 165b5be5a8..3bdf368aed 100644 --- a/login.go +++ b/commands/login.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bufio" @@ -7,12 +7,15 @@ import ( "os" "strings" + log "github.com/Sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" - log "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 cmdLogin = &Command{ +var cmdLogin = &types.Command{ Exec: runLogin, UsageLine: "login [OPTIONS]", Description: "Log in to Scaleway API", @@ -52,7 +55,7 @@ var organization string // -o flag var token string // -t flag var loginHelp bool // -h, --help flag -func runLogin(cmd *Command, args []string) { +func runLogin(cmd *types.Command, args []string) { if loginHelp { cmd.PrintUsage() } @@ -68,13 +71,13 @@ func runLogin(cmd *Command, args []string) { promptUser("Token: ", &token, false) } - cfg := &Config{ + cfg := &api.Config{ APIEndPoint: "https://account.scaleway.com/", Organization: strings.Trim(organization, "\n"), Token: strings.Trim(token, "\n"), } - api, err := NewScalewayAPI(cfg.APIEndPoint, cfg.Organization, cfg.Token) + api, err := api.NewScalewayAPI(cfg.APIEndPoint, cfg.Organization, cfg.Token) if err != nil { log.Fatalf("Unable to create ScalewayAPI: %s", err) } @@ -83,7 +86,7 @@ func runLogin(cmd *Command, args []string) { log.Fatalf("Unable to contact ScalewayAPI: %s", err) } - scwrcPath, err := GetConfigFilePath() + scwrcPath, err := utils.GetConfigFilePath() if err != nil { log.Fatalf("Unable to get scwrc config file path: %s", err) } diff --git a/logout.go b/commands/logout.go similarity index 73% rename from logout.go rename to commands/logout.go index 00ee7d9ba3..898a7c1c2e 100644 --- a/logout.go +++ b/commands/logout.go @@ -1,12 +1,15 @@ -package main +package commands import ( "os" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/utils" ) -var cmdLogout = &Command{ +var cmdLogout = &types.Command{ Exec: runLogout, UsageLine: "logout [OPTIONS]", Description: "Log out from the Scaleway API", @@ -20,7 +23,7 @@ func init() { // FLags var logoutHelp bool // -h, --help flag -func runLogout(cmd *Command, args []string) { +func runLogout(cmd *types.Command, args []string) { if logoutHelp { cmd.PrintUsage() } @@ -28,7 +31,7 @@ func runLogout(cmd *Command, args []string) { cmd.PrintShortUsage() } - scwrcPath, err := GetConfigFilePath() + scwrcPath, err := utils.GetConfigFilePath() if err != nil { log.Fatalf("Unable to get scwrc config file path: %v", err) } diff --git a/logs.go b/commands/logs.go similarity index 70% rename from logs.go rename to commands/logs.go index da274f6c63..b3c3fbd2fa 100644 --- a/logs.go +++ b/commands/logs.go @@ -1,8 +1,13 @@ -package main +package commands -import log "github.com/Sirupsen/logrus" +import ( + log "github.com/Sirupsen/logrus" -var cmdLogs = &Command{ + 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", @@ -16,7 +21,7 @@ func init() { // FLags var logsHelp bool // -h, --help flag -func runLogs(cmd *Command, args []string) { +func runLogs(cmd *types.Command, args []string) { if logsHelp { cmd.PrintUsage() } @@ -33,7 +38,7 @@ func runLogs(cmd *Command, args []string) { // FIXME: switch to serial history when API is ready command := []string{"dmesg"} - err = sshExec(server.PublicAddress.IP, command, true) + err = utils.SshExec(server.PublicAddress.IP, command, true) if err != nil { log.Fatalf("Command execution failed: %v", err) } diff --git a/port.go b/commands/port.go similarity index 73% rename from port.go rename to commands/port.go index 0bb89a0cd7..d5e7620789 100644 --- a/port.go +++ b/commands/port.go @@ -1,8 +1,13 @@ -package main +package commands -import log "github.com/Sirupsen/logrus" +import ( + log "github.com/Sirupsen/logrus" -var cmdPort = &Command{ + 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", @@ -16,7 +21,7 @@ func init() { // FLags var portHelp bool // -h, --help flag -func runPort(cmd *Command, args []string) { +func runPort(cmd *types.Command, args []string) { if portHelp { cmd.PrintUsage() } @@ -31,7 +36,7 @@ func runPort(cmd *Command, args []string) { } command := []string{"netstat -lutn 2>/dev/null | grep LISTEN"} - err = sshExec(server.PublicAddress.IP, command, true) + err = utils.SshExec(server.PublicAddress.IP, command, true) if err != nil { log.Fatalf("Command execution failed: %v", err) } diff --git a/ps.go b/commands/ps.go similarity index 82% rename from ps.go rename to commands/ps.go index 7246532b7f..ee7d496370 100644 --- a/ps.go +++ b/commands/ps.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -8,9 +8,12 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/units" + + types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/utils" ) -var cmdPs = &Command{ +var cmdPs = &types.Command{ Exec: runPs, UsageLine: "ps [OPTIONS]", Description: "List servers", @@ -34,7 +37,7 @@ var psNoTrunc bool // -no-trunc flag var psN int // -n flag var psHelp bool // -h, --help flag -func runPs(cmd *Command, args []string) { +func runPs(cmd *types.Command, args []string) { if psHelp { cmd.PrintUsage() } @@ -60,9 +63,9 @@ func runPs(cmd *Command, args []string) { if psQ { fmt.Fprintf(w, "%s\n", server.Identifier) } else { - shortID := truncIf(server.Identifier, 8, !psNoTrunc) - shortImage := truncIf(wordify(server.Image.Name), 25, !psNoTrunc) - shortName := truncIf(wordify(server.Name), 25, !psNoTrunc) + 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 diff --git a/rename.go b/commands/rename.go similarity index 68% rename from rename.go rename to commands/rename.go index 5d3ad7a415..d9213860bf 100644 --- a/rename.go +++ b/commands/rename.go @@ -1,8 +1,13 @@ -package main +package commands -import log "github.com/Sirupsen/logrus" +import ( + log "github.com/Sirupsen/logrus" -var cmdRename = &Command{ + "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", @@ -16,7 +21,7 @@ func init() { // Flags var renameHelp bool // -h, --help flag -func runRename(cmd *Command, args []string) { +func runRename(cmd *types.Command, args []string) { if renameHelp { cmd.PrintUsage() } @@ -26,7 +31,7 @@ func runRename(cmd *Command, args []string) { serverID := cmd.API.GetServerID(args[0]) - var server ScalewayServerPatchDefinition + var server api.ScalewayServerPatchDefinition server.Name = &args[1] err := cmd.API.PatchServer(serverID, server) diff --git a/restart.go b/commands/restart.go similarity index 84% rename from restart.go rename to commands/restart.go index 66a0ee2570..c96de395ff 100644 --- a/restart.go +++ b/commands/restart.go @@ -1,13 +1,15 @@ -package main +package commands import ( "fmt" "os" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdRestart = &Command{ +var cmdRestart = &types.Command{ Exec: runRestart, UsageLine: "restart [OPTIONS] SERVER [SERVER...]", Description: "Restart a running server", @@ -21,7 +23,7 @@ func init() { // Flags var restartHelp bool // -h, --help flag -func runRestart(cmd *Command, args []string) { +func runRestart(cmd *types.Command, args []string) { if restartHelp { cmd.PrintUsage() } diff --git a/rm.go b/commands/rm.go similarity index 85% rename from rm.go rename to commands/rm.go index be32d726e5..3b8de666c5 100644 --- a/rm.go +++ b/commands/rm.go @@ -1,13 +1,15 @@ -package main +package commands import ( "fmt" "os" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdRm = &Command{ +var cmdRm = &types.Command{ Exec: runRm, UsageLine: "rm [OPTIONS] SERVER [SERVER...]", Description: "Remove one or more servers", @@ -26,7 +28,7 @@ func init() { // Flags var rmHelp bool // -h, --help flag -func runRm(cmd *Command, args []string) { +func runRm(cmd *types.Command, args []string) { if rmHelp { cmd.PrintUsage() } diff --git a/rmi.go b/commands/rmi.go similarity index 85% rename from rmi.go rename to commands/rmi.go index 1273a74e80..4c9a3a31ed 100644 --- a/rmi.go +++ b/commands/rmi.go @@ -1,13 +1,15 @@ -package main +package commands import ( "fmt" "os" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdRmi = &Command{ +var cmdRmi = &types.Command{ Exec: runRmi, UsageLine: "rmi [OPTIONS] IMAGE [IMAGE...]", Description: "Remove one or more images", @@ -25,7 +27,7 @@ func init() { // Flags var rmiHelp bool // -h, --help flag -func runRmi(cmd *Command, args []string) { +func runRmi(cmd *types.Command, args []string) { if rmiHelp { cmd.PrintUsage() } diff --git a/run.go b/commands/run.go similarity index 76% rename from run.go rename to commands/run.go index a8aac2ada0..df2acb617c 100644 --- a/run.go +++ b/commands/run.go @@ -1,12 +1,16 @@ -package main +package commands import ( "os" log "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 = &Command{ +var cmdRun = &types.Command{ Exec: runRun, UsageLine: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", Description: "Run a command in a new server", @@ -34,7 +38,7 @@ var runCreateEnv string // -e, --env flag var runCreateVolume string // -v, --volume flag var runHelpFlag bool // -h, --help flag -func runRun(cmd *Command, args []string) { +func runRun(cmd *types.Command, args []string) { if runHelpFlag { cmd.PrintUsage() } @@ -46,7 +50,7 @@ func runRun(cmd *Command, args []string) { // create IMAGE log.Debugf("Creating a new server") - serverID, err := createServer(cmd.API, args[0], runCreateName, runCreateBootscript, runCreateEnv, runCreateVolume) + serverID, err := api.CreateServer(cmd.API, args[0], runCreateName, runCreateBootscript, runCreateEnv, runCreateVolume) if err != nil { log.Fatalf("Failed to create server: %v", err) } @@ -54,7 +58,7 @@ func runRun(cmd *Command, args []string) { // start SERVER log.Debugf("Starting server") - err = startServer(cmd.API, serverID, false) + err = api.StartServer(cmd.API, serverID, false) if err != nil { log.Fatalf("Failed to start server %s: %v", serverID, err) } @@ -62,7 +66,7 @@ func runRun(cmd *Command, args []string) { // waiting for server to be ready log.Debugf("Waiting for server to be ready") - server, err := WaitForServerReady(cmd.API, serverID) + server, err := api.WaitForServerReady(cmd.API, serverID) if err != nil { log.Fatalf("Cannot get access to server %s: %v", serverID, err) } @@ -71,9 +75,9 @@ func runRun(cmd *Command, args []string) { // exec -w SERVER COMMAND ARGS... log.Debugf("Executing command") if len(args) < 2 { - err = sshExec(server.PublicAddress.IP, []string{"if [ -x /bin/bash ]; then /bin/bash; else /bin/sh; fi"}, false) + err = utils.SshExec(server.PublicAddress.IP, []string{"if [ -x /bin/bash ]; then /bin/bash; else /bin/sh; fi"}, false) } else { - err = sshExec(server.PublicAddress.IP, args[1:], false) + err = utils.SshExec(server.PublicAddress.IP, args[1:], false) } if err != nil { log.Debugf("Command execution failed: %v", err) diff --git a/search.go b/commands/search.go similarity index 75% rename from search.go rename to commands/search.go index 4f7ce25587..61ef5a426b 100644 --- a/search.go +++ b/commands/search.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,13 @@ import ( "text/tabwriter" log "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 = &Command{ +var cmdSearch = &types.Command{ Exec: runSearch, UsageLine: "search [OPTIONS] TERM", Description: "Search the Scaleway Hub for images", @@ -24,7 +28,7 @@ func init() { var searchNoTrunc bool // --no-trunc flag var searchHelp bool // -h, --help flag -func runSearch(cmd *Command, args []string) { +func runSearch(cmd *types.Command, args []string) { if searchHelp { cmd.PrintUsage() } @@ -36,14 +40,14 @@ func runSearch(cmd *Command, args []string) { defer w.Flush() fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") - var entries = []ScalewayImageInterface{} + 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, ScalewayImageInterface{ + entries = append(entries, api.ScalewayImageInterface{ Type: "image", Name: val.Name, Public: val.Public, @@ -55,7 +59,7 @@ func runSearch(cmd *Command, args []string) { log.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) } for _, val := range *snapshots { - entries = append(entries, ScalewayImageInterface{ + entries = append(entries, api.ScalewayImageInterface{ Type: "snapshot", Name: val.Name, Public: false, @@ -64,7 +68,7 @@ func runSearch(cmd *Command, args []string) { for _, image := range entries { // name field - name := truncIf(wordify(image.Name), 45, !searchNoTrunc) + name := utils.TruncIf(utils.Wordify(image.Name), 45, !searchNoTrunc) // description field var description string @@ -79,7 +83,7 @@ func runSearch(cmd *Command, args []string) { case "snapshot": description = "user snapshot" } - description = truncIf(wordify(description), 45, !searchNoTrunc) + description = utils.TruncIf(utils.Wordify(description), 45, !searchNoTrunc) // official field var official string diff --git a/start.go b/commands/start.go similarity index 82% rename from start.go rename to commands/start.go index c928a21576..ba864dd04d 100644 --- a/start.go +++ b/commands/start.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,12 @@ import ( "time" log "github.com/Sirupsen/logrus" + + "github.com/scaleway/scaleway-cli/api" + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdStart = &Command{ +var cmdStart = &types.Command{ Exec: runStart, UsageLine: "start [OPTIONS] SERVER [SERVER...]", Description: "Start a stopped server", @@ -26,7 +29,7 @@ var startW bool // -w flag var startTimeout float64 // -T flag var startHelp bool // -h, --help flag -func runStart(cmd *Command, args []string) { +func runStart(cmd *types.Command, args []string) { if startHelp { cmd.PrintUsage() } @@ -41,7 +44,7 @@ func runStart(cmd *Command, args []string) { for i := range args { needle := args[i] - go startServerOnce(cmd.API, needle, startW, successChan, errChan) + go api.StartServerOnce(cmd.API, needle, startW, successChan, errChan) } if startTimeout > 0 { diff --git a/stop.go b/commands/stop.go similarity index 89% rename from stop.go rename to commands/stop.go index 4ec6e9965b..d0dc9667f2 100644 --- a/stop.go +++ b/commands/stop.go @@ -1,13 +1,15 @@ -package main +package commands import ( "fmt" "os" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdStop = &Command{ +var cmdStop = &types.Command{ Exec: runStop, UsageLine: "stop [OPTIONS] SERVER [SERVER...]", Description: "Stop a running server", @@ -29,7 +31,7 @@ func init() { var stopT bool // -t flag var stopHelp bool // -h, --help flag -func runStop(cmd *Command, args []string) { +func runStop(cmd *types.Command, args []string) { if stopHelp { cmd.PrintUsage() } diff --git a/tag.go b/commands/tag.go similarity index 83% rename from tag.go rename to commands/tag.go index 0c84641bc0..0ef8837245 100644 --- a/tag.go +++ b/commands/tag.go @@ -1,12 +1,14 @@ -package main +package commands import ( "fmt" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdTag = &Command{ +var cmdTag = &types.Command{ Exec: runTag, UsageLine: "tag [OPTIONS] SNAPSHOT NAME", Description: "Tag a snapshot into an image", @@ -20,7 +22,7 @@ func init() { // Flags var tagHelp bool // -h, --help flag -func runTag(cmd *Command, args []string) { +func runTag(cmd *types.Command, args []string) { if tagHelp { cmd.PrintUsage() } diff --git a/top.go b/commands/top.go similarity index 75% rename from top.go rename to commands/top.go index 54651fed42..af0e013d91 100644 --- a/top.go +++ b/commands/top.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -6,9 +6,12 @@ import ( "strings" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" + "github.com/scaleway/scaleway-cli/utils" ) -var cmdTop = &Command{ +var cmdTop = &types.Command{ Exec: runTop, UsageLine: "top [OPTIONS] SERVER", // FIXME: add ps options Description: "Lookup the running processes of a server", @@ -22,7 +25,7 @@ func init() { // Flags var topHelp bool // -h, --help flag -func runTop(cmd *Command, args []string) { +func runTop(cmd *types.Command, args []string) { if topHelp { cmd.PrintUsage() } @@ -37,7 +40,7 @@ func runTop(cmd *Command, args []string) { log.Fatalf("Failed to get server information for %s: %v", serverID, err) } - execCmd := append(NewSSHExecCmd(server.PublicAddress.IP, true, []string{command})) + execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, true, []string{command})) log.Debugf("Executing: ssh %s", strings.Join(execCmd, " ")) out, err := exec.Command("ssh", execCmd...).CombinedOutput() diff --git a/commands/types/command.go b/commands/types/command.go new file mode 100644 index 0000000000..7860aa37aa --- /dev/null +++ b/commands/types/command.go @@ -0,0 +1,115 @@ +package types + +import ( + "bytes" + "fmt" + "log" + "os" + "strings" + "text/template" + + flag "github.com/docker/docker/pkg/mflag" + + "github.com/scaleway/scaleway-cli/api" +) + +// Command is a Scaleway command +type Command struct { + // Exec executes the command + Exec func(cmd *Command, args []string) + + // Usage is the one-line usage message. + UsageLine string + + // Description is the description of the command + Description string + + // Help is the full description of the command + Help string + + // Examples are some examples of the command + Examples string + + // Flag is a set of flags specific to this command. + Flag flag.FlagSet + + // Hidden is a flat to hide command from global help commands listing + Hidden bool + + // API is the interface used to communicate with Scaleway's API + API *api.ScalewayAPI +} + +// Name returns the command's name +func (c *Command) Name() string { + name := c.UsageLine + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name +} + +// PrintUsage prints a full command usage and exits +func (c *Command) PrintUsage() { + helpMessage, err := commandHelpMessage(c) + if err != nil { + log.Fatalf("%v", err) + } + fmt.Fprintf(os.Stderr, "%s\n", helpMessage) + os.Exit(1) +} + +// PrintShortUsage prints a short command usage and exits +func (c *Command) PrintShortUsage() { + fmt.Fprintf(os.Stderr, "usage: scw %s. See 'scw %s --help'.\n", c.UsageLine, c.Name()) + os.Exit(1) +} + +// Options returns a string describing options of the command +func (c *Command) Options() string { + var options string + visitor := func(flag *flag.Flag) { + name := strings.Join(flag.Names, ", -") + var optionUsage string + if flag.DefValue == "" { + optionUsage = fmt.Sprintf("%s=\"\"", name) + } else { + optionUsage = fmt.Sprintf("%s=%s", name, flag.DefValue) + } + options += fmt.Sprintf(" -%-20s %s\n", optionUsage, flag.Usage) + } + c.Flag.VisitAll(visitor) + if len(options) == 0 { + return "" + } + return fmt.Sprintf("Options:\n\n%s", options) +} + +// ExamplesHelp returns a string describing examples of the command +func (c *Command) ExamplesHelp() string { + if c.Examples == "" { + return "" + } + return fmt.Sprintf("Examples:\n\n%s", strings.Trim(c.Examples, "\n")) +} + +var fullHelpTemplate = ` +Usage: scw {{.UsageLine}} + +{{.Help}} + +{{.Options}} +{{.ExamplesHelp}} +` + +func commandHelpMessage(cmd *Command) (string, error) { + t := template.New("full") + template.Must(t.Parse(fullHelpTemplate)) + var output bytes.Buffer + err := t.Execute(&output, cmd) + if err != nil { + return "", err + } + return strings.Trim(output.String(), "\n"), nil +} diff --git a/version.go b/commands/version.go similarity index 82% rename from version.go rename to commands/version.go index 51b73f69e8..51e849a918 100644 --- a/version.go +++ b/commands/version.go @@ -1,13 +1,15 @@ -package main +package commands import ( "fmt" "runtime" "github.com/scaleway/scaleway-cli/scwversion" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdVersion = &Command{ +var cmdVersion = &types.Command{ Exec: runVersion, UsageLine: "version [OPTIONS]", Description: "Show the version information", @@ -21,7 +23,7 @@ func init() { // Flags var versionHelp bool // -h, --help flag -func runVersion(cmd *Command, args []string) { +func runVersion(cmd *types.Command, args []string) { if versionHelp { cmd.PrintUsage() } diff --git a/wait.go b/commands/wait.go similarity index 85% rename from wait.go rename to commands/wait.go index ef31024bba..40726b0664 100644 --- a/wait.go +++ b/commands/wait.go @@ -1,13 +1,15 @@ -package main +package commands import ( "os" "time" log "github.com/Sirupsen/logrus" + + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdWait = &Command{ +var cmdWait = &types.Command{ Exec: runWait, UsageLine: "wait [OPTIONS] SERVER [SERVER...]", Description: "Block until a server stops", @@ -21,7 +23,7 @@ func init() { // Flags var waitHelp bool // -h, --help flag -func runWait(cmd *Command, args []string) { +func runWait(cmd *types.Command, args []string) { if waitHelp { cmd.PrintUsage() } diff --git a/completion.go b/commands/x_completion.go similarity index 67% rename from completion.go rename to commands/x_completion.go index 9494a9913a..7bf5fe2f6c 100644 --- a/completion.go +++ b/commands/x_completion.go @@ -1,13 +1,16 @@ -package main +package commands import ( "fmt" "log" "sort" "strings" + + types "github.com/scaleway/scaleway-cli/commands/types" + utils "github.com/scaleway/scaleway-cli/utils" ) -var cmdCompletion = &Command{ +var cmdCompletion = &types.Command{ Exec: runCompletion, UsageLine: "_completion [OPTIONS] CATEGORY", Description: "Completion helper", @@ -29,7 +32,7 @@ func init() { // Flags var completionHelp bool // -h, --help flag -func runCompletion(cmd *Command, args []string) { +func runCompletion(cmd *types.Command, args []string) { if completionHelp { cmd.PrintUsage() } @@ -44,28 +47,28 @@ func runCompletion(cmd *Command, args []string) { switch category { case "servers-all": for identifier, name := range cmd.API.Cache.Servers { - elements = append(elements, identifier, wordify(name)) + elements = append(elements, identifier, utils.Wordify(name)) } case "images-all": for identifier, name := range cmd.API.Cache.Images { - elements = append(elements, identifier, wordify(name)) + elements = append(elements, identifier, utils.Wordify(name)) } case "volumes-all": for identifier, name := range cmd.API.Cache.Volumes { - elements = append(elements, identifier, wordify(name)) + elements = append(elements, identifier, utils.Wordify(name)) } case "snapshots-all": for identifier, name := range cmd.API.Cache.Snapshots { - elements = append(elements, identifier, wordify(name)) + elements = append(elements, identifier, utils.Wordify(name)) } case "bootscripts-all": for identifier, name := range cmd.API.Cache.Bootscripts { - elements = append(elements, identifier, wordify(name)) + elements = append(elements, identifier, utils.Wordify(name)) } default: log.Fatalf("Unhandled category of completion: %s", category) } sort.Strings(elements) - fmt.Println(strings.Join(RemoveDuplicates(elements), "\n")) + fmt.Println(strings.Join(utils.RemoveDuplicates(elements), "\n")) } diff --git a/patch.go b/commands/x_patch.go similarity index 78% rename from patch.go rename to commands/x_patch.go index 3c0bb29693..e71df98d3e 100644 --- a/patch.go +++ b/commands/x_patch.go @@ -1,13 +1,16 @@ -package main +package commands import ( "fmt" "strings" log "github.com/Sirupsen/logrus" + + api "github.com/scaleway/scaleway-cli/api" + types "github.com/scaleway/scaleway-cli/commands/types" ) -var cmdPatch = &Command{ +var cmdPatch = &types.Command{ Exec: runPatch, UsageLine: "_patch [OPTIONS] IDENTIFIER FIELD=VALUE", Description: "", @@ -25,7 +28,7 @@ func init() { // Flags var patchHelp bool // -h, --help flag -func runPatch(cmd *Command, args []string) { +func runPatch(cmd *types.Command, args []string) { if patchHelp { cmd.PrintUsage() } @@ -41,10 +44,10 @@ func runPatch(cmd *Command, args []string) { fieldName := updateParts[0] newValue := updateParts[1] - ident := getIdentifier(cmd.API, args[0]) + ident := api.GetIdentifier(cmd.API, args[0]) switch ident.Type { - case IdentifierServer: - var payload ScalewayServerPatchDefinition + case api.IdentifierServer: + var payload api.ScalewayServerPatchDefinition switch fieldName { case "state_detail": diff --git a/log.go b/log.go deleted file mode 100644 index a245aed1fb..0000000000 --- a/log.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "os" - - log "github.com/Sirupsen/logrus" -) - -func initLogging(debug bool) { - log.SetOutput(os.Stderr) - if debug { - log.SetLevel(log.DebugLevel) - } else { - log.SetLevel(log.InfoLevel) - } -} diff --git a/main.go b/main.go new file mode 100644 index 0000000000..48e888dd11 --- /dev/null +++ b/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + log "github.com/Sirupsen/logrus" + flag "github.com/docker/docker/pkg/mflag" + + cmds "github.com/scaleway/scaleway-cli/commands" +) + +func main() { + config, cfgErr := getConfig() + if cfgErr != nil && !os.IsNotExist(cfgErr) { + log.Fatalf("Unable to open .scwrc config file: %v", cfgErr) + } + + if config != nil { + flAPIEndPoint = flag.String([]string{"-api-endpoint"}, config.APIEndPoint, "Set the API endpoint") + } + flag.Parse() + + if *flVersion { + showVersion() + return + } + + if flAPIEndPoint != nil { + os.Setenv("scaleway_api_endpoint", *flAPIEndPoint) + } + + if *flDebug { + os.Setenv("DEBUG", "1") + } + + initLogging(os.Getenv("DEBUG") != "") + + args := flag.Args() + if len(args) < 1 { + usage() + } + name := args[0] + + args = args[1:] + + for _, cmd := range cmds.Commands { + if cmd.Name() == name { + cmd.Flag.SetOutput(ioutil.Discard) + err := cmd.Flag.Parse(args) + if err != nil { + log.Fatalf("usage: scw %s", cmd.UsageLine) + } + if cmd.Name() != "login" && cmd.Name() != "help" { + if cfgErr != nil { + if name != "login" && config == nil { + fmt.Fprintf(os.Stderr, "You need to login first: 'scw login'\n") + os.Exit(1) + } + } + api, err := getScalewayAPI() + if err != nil { + log.Fatalf("unable to initialize scw api: %s", err) + } + cmd.API = api + } + cmd.Exec(cmd, cmd.Flag.Args()) + if cmd.API != nil { + cmd.API.Sync() + } + os.Exit(0) + } + } + + log.Fatalf("scw: unknown subcommand %s\nRun 'scw help' for usage.", name) +} + +func initLogging(debug bool) { + log.SetOutput(os.Stderr) + if debug { + log.SetLevel(log.DebugLevel) + } else { + log.SetLevel(log.InfoLevel) + } +} diff --git a/utils.go b/utils/utils.go similarity index 68% rename from utils.go rename to utils/utils.go index 5dafcbe798..a03cb135bb 100644 --- a/utils.go +++ b/utils/utils.go @@ -1,4 +1,4 @@ -package main +package utils import ( "errors" @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "regexp" "strings" "time" @@ -14,7 +15,7 @@ import ( log "github.com/Sirupsen/logrus" ) -func sshExec(ipAddress string, command []string, checkConnection bool) error { +func SshExec(ipAddress string, command []string, checkConnection bool) error { if ipAddress == "" { return errors.New("server does not have public IP") } @@ -86,16 +87,16 @@ func IsTCPPortOpen(dest string) bool { return err == nil } -// truncIf ensures the input string does not exceed max size if cond is met -func truncIf(str string, max int, cond bool) string { +// TruncIf ensures the input string does not exceed max size if cond is met +func TruncIf(str string, max int, cond bool) string { if cond && len(str) > max { return str[:max] } return str } -// wordify convert complex name to a single word without special shell characters -func wordify(str string) string { +// Wordify convert complex name to a single word without special shell characters +func Wordify(str string) string { str = regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAllString(str, "_") str = regexp.MustCompile(`__+`).ReplaceAllString(str, "_") str = strings.Trim(str, "_") @@ -107,3 +108,33 @@ func PathToTARPathparts(fullPath string) (string, string) { fullPath = strings.TrimRight(fullPath, "/") return path.Dir(fullPath), path.Base(fullPath) } + +// RemoveDuplicates transforms an array into a unique array +func RemoveDuplicates(elements []string) []string { + encountered := map[string]bool{} + + // Create a map of all unique elements. + for v := range elements { + encountered[elements[v]] = true + } + + // Place all keys from the map into a slice. + result := []string{} + for key := range encountered { + result = append(result, key) + } + return result +} + +// GetConfigFilePath returns the path to the Scaleway CLI config file +func GetConfigFilePath() (string, error) { + homeDir := os.Getenv("HOME") // *nix + if homeDir == "" { // Windows + homeDir = os.Getenv("USERPROFILE") + } + if homeDir == "" { + return "", errors.New("user home directory not found") + } + + return filepath.Join(homeDir, ".scwrc"), nil +} diff --git a/utils_test.go b/utils/utils_test.go similarity index 99% rename from utils_test.go rename to utils/utils_test.go index 87a209b5e1..18a13e4fd1 100644 --- a/utils_test.go +++ b/utils/utils_test.go @@ -1,4 +1,4 @@ -package main +package utils import "testing" From 946070df5109ff8d3f58d2c4c1d7133a7f18930c Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Fri, 12 Jun 2015 17:47:49 +0200 Subject: [PATCH 2/7] Updated Makefile for multiple packages support --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index e55056cf8a..5c04cbca29 100644 --- a/Makefile +++ b/Makefile @@ -10,17 +10,18 @@ GOFMT ?= gofmt -w NAME = scw SRC = . +PACKAGES = api commands utils REV = $(shell git rev-parse HEAD || echo "nogit") TAG = $(shell git describe --tags --always || echo "nogit") BUILDER = scaleway-cli-builder BUILD_LIST = $(foreach int, $(SRC), $(int)_build) -CLEAN_LIST = $(foreach int, $(SRC), $(int)_clean) +CLEAN_LIST = $(foreach int, $(SRC) $(PACKAGES), $(int)_clean) INSTALL_LIST = $(foreach int, $(SRC), $(int)_install) -IREF_LIST = $(foreach int, $(SRC), $(int)_iref) -TEST_LIST = $(foreach int, $(SRC), $(int)_test) -FMT_LIST = $(foreach int, $(SRC), $(int)_fmt) +IREF_LIST = $(foreach int, $(SRC) $(PACKAGES), $(int)_iref) +TEST_LIST = $(foreach int, $(SRC) $(PACKAGES), $(int)_test) +FMT_LIST = $(foreach int, $(SRC) $(PACKAGES), $(int)_fmt) .PHONY: $(CLEAN_LIST) $(TEST_LIST) $(FMT_LIST) $(INSTALL_LIST) $(BUILD_LIST) $(IREF_LIST) From 2bbb7d321b77b4bd22ed6870b3c3e0d1095b2107 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Fri, 12 Jun 2015 17:48:31 +0200 Subject: [PATCH 3/7] Fixed tests --- utils/utils_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/utils_test.go b/utils/utils_test.go index 18a13e4fd1..ba62e086fa 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -3,7 +3,7 @@ package utils import "testing" func TestWordify(t *testing.T) { - actual := wordify("Hello World 42 !!") + actual := Wordify("Hello World 42 !!") expected := "Hello_World_42" if actual != expected { t.Errorf("returned value is invalid [actual: %s][expected: %s]", actual, expected) @@ -11,25 +11,25 @@ func TestWordify(t *testing.T) { } func TestTruncIf(t *testing.T) { - actual := truncIf("Hello World", 5, false) + actual := TruncIf("Hello World", 5, false) expected := "Hello World" if actual != expected { t.Errorf("returned value is invalid [actual: %s][expected: %s]", actual, expected) } - actual = truncIf("Hello World", 5, true) + actual = TruncIf("Hello World", 5, true) expected = "Hello" if actual != expected { t.Errorf("returned value is invalid [actual: %s][expected: %s]", actual, expected) } - actual = truncIf("Hello World", 50, false) + actual = TruncIf("Hello World", 50, false) expected = "Hello World" if actual != expected { t.Errorf("returned value is invalid [actual: %s][expected: %s]", actual, expected) } - actual = truncIf("Hello World", 50, true) + actual = TruncIf("Hello World", 50, true) expected = "Hello World" if actual != expected { t.Errorf("returned value is invalid [actual: %s][expected: %s]", actual, expected) From db8a846ad7241f8161cbfd19be340bd2982abf2e Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Fri, 12 Jun 2015 17:52:30 +0200 Subject: [PATCH 4/7] Updated CHANGELOG --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97c535348c..6d5dec0ff7 100644 --- a/README.md +++ b/README.md @@ -967,7 +967,7 @@ Development in progress #### Fixes -* The project is now `go get`-able +* The project is now `go get`-able and splitted into packages * Added timeout when polling SSH TCP port for `scw start -w` and `scw exec -w` ([#46](https://github.com/scaleway/scaleway-cli/issues/46)) * Improved resolver behavior for exact matching ([#53](https://github.com/scaleway/scaleway-cli/issues/53), [#55](https://github.com/scaleway/scaleway-cli/issues/55)) * Verbose error message when `scw exec` fails ([#42](https://github.com/scaleway/scaleway-cli/issues/42)) From 66e69e3d53d5c49e8f2c0be34aab838d9ad91673 Mon Sep 17 00:00:00 2001 From: "s. rannou" Date: Fri, 12 Jun 2015 17:53:51 +0200 Subject: [PATCH 5/7] Merged cli.go and main.go --- cli.go | 100 -------------------------------------------------------- main.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 100 deletions(-) delete mode 100644 cli.go diff --git a/cli.go b/cli.go deleted file mode 100644 index 262d15c7ea..0000000000 --- a/cli.go +++ /dev/null @@ -1,100 +0,0 @@ -// scw interract with Scaleway from the command line -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - - log "github.com/Sirupsen/logrus" - flag "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" -) - -// CommandListOpts holds a list of parameters -type CommandListOpts struct { - Values *[]string -} - -// NewListOpts create an empty CommandListOpts -func NewListOpts() CommandListOpts { - var values []string - return CommandListOpts{ - Values: &values, - } -} - -// String returns a string representation of a CommandListOpts object -func (opts *CommandListOpts) String() string { - return fmt.Sprintf("%v", []string((*opts.Values))) -} - -// Set appends a new value to a CommandListOpts -func (opts *CommandListOpts) Set(value string) error { - (*opts.Values) = append((*opts.Values), value) - return nil -} - -func commandUsage(name string) { -} - -var ( - flAPIEndPoint *string - flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") - flVersion = flag.Bool([]string{"v", "--version"}, false, "Print version information and quit") -) - -func usage() { - cmds.CmdHelp.Exec(cmds.CmdHelp, []string{}) - os.Exit(1) -} - -// getConfig returns the Scaleway CLI config file for the current user -func getConfig() (*api.Config, error) { - scwrcPath, err := utils.GetConfigFilePath() - if err != nil { - return nil, err - } - - stat, err := os.Stat(scwrcPath) - // we don't care if it fails, the user just won't see the warning - if err == nil { - mode := stat.Mode() - if mode&0066 != 0 { - log.Fatalf("Permissions %#o for .scwrc are too open.", mode) - } - } - - file, err := ioutil.ReadFile(scwrcPath) - if err != nil { - return nil, err - } - var config api.Config - err = json.Unmarshal(file, &config) - if err != nil { - return nil, err - } - if os.Getenv("scaleway_api_endpoint") == "" { - os.Setenv("scaleway_api_endpoint", config.APIEndPoint) - } - return &config, nil -} - -// getScalewayAPI returns a ScalewayAPI using the user config file -func getScalewayAPI() (*api.ScalewayAPI, error) { - // We already get config globally, but whis way we can get explicit error when trying to create a ScalewayAPI object - config, err := getConfig() - if err != nil { - return nil, err - } - return api.NewScalewayAPI(os.Getenv("scaleway_api_endpoint"), config.Organization, config.Token) -} - -func showVersion() { - fmt.Printf("scw version %s, build %s\n", scwversion.VERSION, scwversion.GITCOMMIT) -} diff --git a/main.go b/main.go index 48e888dd11..fc15c9c874 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -8,7 +9,43 @@ import ( log "github.com/Sirupsen/logrus" flag "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" +) + +// CommandListOpts holds a list of parameters +type CommandListOpts struct { + Values *[]string +} + +// NewListOpts create an empty CommandListOpts +func NewListOpts() CommandListOpts { + var values []string + return CommandListOpts{ + Values: &values, + } +} + +// String returns a string representation of a CommandListOpts object +func (opts *CommandListOpts) String() string { + return fmt.Sprintf("%v", []string((*opts.Values))) +} + +// Set appends a new value to a CommandListOpts +func (opts *CommandListOpts) Set(value string) error { + (*opts.Values) = append((*opts.Values), value) + return nil +} + +func commandUsage(name string) { +} + +var ( + flAPIEndPoint *string + flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") + flVersion = flag.Bool([]string{"v", "--version"}, false, "Print version information and quit") ) func main() { @@ -76,6 +113,56 @@ func main() { log.Fatalf("scw: unknown subcommand %s\nRun 'scw help' for usage.", name) } +func usage() { + cmds.CmdHelp.Exec(cmds.CmdHelp, []string{}) + os.Exit(1) +} + +// getConfig returns the Scaleway CLI config file for the current user +func getConfig() (*api.Config, error) { + scwrcPath, err := utils.GetConfigFilePath() + if err != nil { + return nil, err + } + + stat, err := os.Stat(scwrcPath) + // we don't care if it fails, the user just won't see the warning + if err == nil { + mode := stat.Mode() + if mode&0066 != 0 { + log.Fatalf("Permissions %#o for .scwrc are too open.", mode) + } + } + + file, err := ioutil.ReadFile(scwrcPath) + if err != nil { + return nil, err + } + var config api.Config + err = json.Unmarshal(file, &config) + if err != nil { + return nil, err + } + if os.Getenv("scaleway_api_endpoint") == "" { + os.Setenv("scaleway_api_endpoint", config.APIEndPoint) + } + return &config, nil +} + +// getScalewayAPI returns a ScalewayAPI using the user config file +func getScalewayAPI() (*api.ScalewayAPI, error) { + // We already get config globally, but whis way we can get explicit error when trying to create a ScalewayAPI object + config, err := getConfig() + if err != nil { + return nil, err + } + return api.NewScalewayAPI(os.Getenv("scaleway_api_endpoint"), config.Organization, config.Token) +} + +func showVersion() { + fmt.Printf("scw version %s, build %s\n", scwversion.VERSION, scwversion.GITCOMMIT) +} + func initLogging(debug bool) { log.SetOutput(os.Stderr) if debug { From 5010b110252bb8c7cbdea08f364045ce68e612ec Mon Sep 17 00:00:00 2001 From: "s. rannou" Date: Fri, 12 Jun 2015 18:02:58 +0200 Subject: [PATCH 6/7] Added dummy README for the API. --- api/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 api/README.md diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000000..18db554c14 --- /dev/null +++ b/api/README.md @@ -0,0 +1,6 @@ +# Scaleway's API + +This package contains facilities to play with the Scaleway API, in includes the following features: + +- dedicated configuration file containing credentials to deal with the API +- caching to resolve UUIDs without contacting the API From 463936d9eca99e44af9085b7169674e73af920b8 Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Fri, 12 Jun 2015 18:04:00 +0200 Subject: [PATCH 7/7] Golint fixes --- Makefile | 5 +++++ api/api.go | 1 + api/helpers.go | 3 +++ commands/commands.go | 1 + commands/cp.go | 2 ++ commands/exec.go | 2 +- commands/help.go | 1 + commands/logs.go | 2 +- commands/port.go | 2 +- commands/run.go | 4 ++-- scwversion/placeholder.go | 6 ++++-- utils/utils.go | 3 ++- 12 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 5c04cbca29..aa220e6400 100644 --- a/Makefile +++ b/Makefile @@ -85,3 +85,8 @@ travis_install: travis_run: build go test -v -covermode=count -coverprofile=profile.cov + + +golint: + @go get github.com/golang/lint/golint + @for dir in */; do golint $$dir; done diff --git a/api/api.go b/api/api.go index c75ed60bec..2bab20d761 100644 --- a/api/api.go +++ b/api/api.go @@ -452,6 +452,7 @@ type ScalewayImageDefinition struct { Arch string `json:"arch"` } +// FuncMap used for json inspection var FuncMap = template.FuncMap{ "json": func(v interface{}) string { a, _ := json.Marshal(v) diff --git a/api/helpers.go b/api/helpers.go index 57e05a5277..bd46a49b9f 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -199,6 +199,7 @@ func InspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj close(cj) } +// CreateServer creates a server using API based on typical server fields func CreateServer(api *ScalewayAPI, imageName string, name string, bootscript string, env string, additionalVolumes string) (string, error) { if name == "" { name = strings.Replace(namesgenerator.GetRandomName(0), "_", "-", -1) @@ -295,6 +296,7 @@ func (a ByCreationDate) Len() int { return len(a) } func (a ByCreationDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByCreationDate) Less(i, j int) bool { return a[j].CreationDate.Before(a[i].CreationDate) } +// StartServer start a server based on its needle, can optionaly block while server is booting func StartServer(api *ScalewayAPI, needle string, wait bool) error { server := api.GetServerID(needle) @@ -314,6 +316,7 @@ func StartServer(api *ScalewayAPI, needle string, wait bool) error { return nil } +// StartServerOnce wraps StartServer for golang channel func StartServerOnce(api *ScalewayAPI, needle string, wait bool, successChan chan bool, errChan chan error) { err := StartServer(api, needle, wait) diff --git a/commands/commands.go b/commands/commands.go index c442708c69..8bc5517fcf 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -2,6 +2,7 @@ package commands import types "github.com/scaleway/scaleway-cli/commands/types" +// Commands is the list of enabled CLI commands var Commands = []*types.Command{ CmdHelp, diff --git a/commands/cp.go b/commands/cp.go index 6c98909880..dca9051ad1 100644 --- a/commands/cp.go +++ b/commands/cp.go @@ -44,6 +44,7 @@ func init() { // Flags var cpHelp bool // -h, --help flag +// TarFromSource creates a stream buffer with the tarballed content of the user source func TarFromSource(api *api.ScalewayAPI, source string) (*io.ReadCloser, error) { var tarOutputStream io.ReadCloser @@ -132,6 +133,7 @@ func TarFromSource(api *api.ScalewayAPI, source string) (*io.ReadCloser, error) return &tarOutputStream, nil } +// UntarToDest writes to user destination the streamed tarball in input func UntarToDest(api *api.ScalewayAPI, sourceStream *io.ReadCloser, destination string) error { // destination is a server address + path (scp-like uri) if strings.Index(destination, ":") > -1 { diff --git a/commands/exec.go b/commands/exec.go index 2c3cff1f52..dbc72bcaf4 100644 --- a/commands/exec.go +++ b/commands/exec.go @@ -72,7 +72,7 @@ func runExec(cmd *types.Command, args []string) { }() } - err = utils.SshExec(server.PublicAddress.IP, args[1:], !execW) + err = utils.SSHExec(server.PublicAddress.IP, args[1:], !execW) if err != nil { log.Fatalf("%v", err) os.Exit(1) diff --git a/commands/help.go b/commands/help.go index 7be6f316fa..e559fccb08 100644 --- a/commands/help.go +++ b/commands/help.go @@ -8,6 +8,7 @@ import ( types "github.com/scaleway/scaleway-cli/commands/types" ) +// CmdHelp is the 'scw help' command var CmdHelp = &types.Command{ Exec: nil, UsageLine: "help [COMMAND]", diff --git a/commands/logs.go b/commands/logs.go index b3c3fbd2fa..9fe49a518c 100644 --- a/commands/logs.go +++ b/commands/logs.go @@ -38,7 +38,7 @@ func runLogs(cmd *types.Command, args []string) { // FIXME: switch to serial history when API is ready command := []string{"dmesg"} - err = utils.SshExec(server.PublicAddress.IP, command, true) + err = utils.SSHExec(server.PublicAddress.IP, command, true) if err != nil { log.Fatalf("Command execution failed: %v", err) } diff --git a/commands/port.go b/commands/port.go index d5e7620789..41010ac6f3 100644 --- a/commands/port.go +++ b/commands/port.go @@ -36,7 +36,7 @@ func runPort(cmd *types.Command, args []string) { } command := []string{"netstat -lutn 2>/dev/null | grep LISTEN"} - err = utils.SshExec(server.PublicAddress.IP, command, true) + err = utils.SSHExec(server.PublicAddress.IP, command, true) if err != nil { log.Fatalf("Command execution failed: %v", err) } diff --git a/commands/run.go b/commands/run.go index df2acb617c..bbe5f17e2f 100644 --- a/commands/run.go +++ b/commands/run.go @@ -75,9 +75,9 @@ func runRun(cmd *types.Command, args []string) { // exec -w SERVER COMMAND ARGS... log.Debugf("Executing command") if len(args) < 2 { - err = utils.SshExec(server.PublicAddress.IP, []string{"if [ -x /bin/bash ]; then /bin/bash; else /bin/sh; fi"}, false) + err = utils.SSHExec(server.PublicAddress.IP, []string{"if [ -x /bin/bash ]; then /bin/bash; else /bin/sh; fi"}, false) } else { - err = utils.SshExec(server.PublicAddress.IP, args[1:], false) + err = utils.SSHExec(server.PublicAddress.IP, args[1:], false) } if err != nil { log.Debugf("Command execution failed: %v", err) diff --git a/scwversion/placeholder.go b/scwversion/placeholder.go index ab1bd5d8e6..aa9d16ba3a 100644 --- a/scwversion/placeholder.go +++ b/scwversion/placeholder.go @@ -1,6 +1,8 @@ package scwversion var ( - VERSION string = "placeholder" - GITCOMMIT string = "placeholder" + // VERSION is the 'scw version', it's overriden on build by the Makefile + VERSION = "placeholder" + // GITCOMMIT is the commit uuid, it's overriden on build by the Makefile + GITCOMMIT = "placeholder" ) diff --git a/utils/utils.go b/utils/utils.go index a03cb135bb..f9367c2184 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -15,7 +15,8 @@ import ( log "github.com/Sirupsen/logrus" ) -func SshExec(ipAddress string, command []string, checkConnection bool) error { +// SSHExec executes a command over SSH and redirects file-descriptors +func SSHExec(ipAddress string, command []string, checkConnection bool) error { if ipAddress == "" { return errors.New("server does not have public IP") }