diff --git a/Makefile b/Makefile index 5589a38a5d..97306a0ed9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export VERSION=0.1.1 +export VERSION=0.1.2 .PHONY : build build: @@ -8,16 +8,22 @@ build: build_mac: GOOS=darwin GOARCH=amd64 go build -o out/ucloud main.go tar zcvf out/ucloud-cli-macosx-${VERSION}-amd64.tgz -C out ucloud + shasum -a 256 out/ucloud-cli-macosx-${VERSION}-amd64.tgz .PHONY : build_linux build_linux: GOOS=linux GOARCH=amd64 go build -o out/ucloud main.go tar zcvf out/ucloud-cli-linux-${VERSION}-amd64.tgz -C out ucloud + shasum -a 256 out/ucloud-cli-linux-${VERSION}-amd64.tgz .PHONY : build_windows build_windows: GOOS=windows GOARCH=amd64 go build -o out/ucloud.exe main.go zip -r out/ucloud-cli-windows-${VERSION}-amd64.zip out/ucloud.exe + shasum -a 256 out/ucloud-cli-windows-${VERSION}-amd64.zip + +.PHONY : build_all +build_all: build_mac build_linux build_windows .PHONY : install install: diff --git a/README.md b/README.md index 95c7c49473..857bbae113 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,21 @@ You can install UCloud CLI by downloading executable binary file or building fro ##### Download binary file Archive links: -[Mac](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-macosx-0.1.1-amd64.tgz) -[Linux](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-linux-0.1.1-amd64.tgz) -[Windows](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-windows-0.1.1-amd64.zip) +[Mac](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-macosx-0.1.2-amd64.tgz) +[Linux](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-linux-0.1.2-amd64.tgz) +[Windows](http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-windows-0.1.2-amd64.zip) -SHA-256 hashcode +SHA-256 checksum ``` -165f1ce4d413bf92e2794efe2722678eb80990602b81fd6e501d0c5f6bbf30bb ucloud-cli-linux-0.1.1-amd64.tgz -e174c2ef268f4b653062d0e1331bf642134a0fafbb745b407969a194d7c1bc0c ucloud-cli-macosx-0.1.1-amd64.tgz -75ff8741d9348881b3d992701590bc27f9278f207a3fb9a12ef0edfab19058d2 ucloud-cli-windows-0.1.1-amd64.zip +19b7a0803fc41ee689797a36fd67b288e993c383edf6087f56825a4d5bb17875 ucloud-cli-linux-0.1.2-amd64.tgz +ecc787f4045ea14d583801cd0cfa746be357d50756c2cf0ba879e405c2325d1c ucloud-cli-macosx-0.1.2-amd64.tgz +f48058ac96bb0283b18c660f0350eedba49d03a753775b0a2773b2081698b3f3 ucloud-cli-windows-0.1.2-amd64.zip ``` Download the binary file and extract to /usr/local/bin directory or add it to the $PATH. Take macOS as an example. ``` -$ curl -o ucloud-cli.tgz http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-macosx-0.1.1-amd64.tgz -$ echo "e174c2ef268f4b653062d0e1331bf642134a0fafbb745b407969a194d7c1bc0c *ucloud-cli-macosx-0.1.1-amd64.tgz" | shasum -a 256 -c +$ curl -o ucloud-cli.tgz http://ucloud-sdk.ufile.ucloud.com.cn/ucloud-cli-macosx-0.1.2-amd64.tgz +$ echo "ecc787f4045ea14d583801cd0cfa746be357d50756c2cf0ba879e405c2325d1c *ucloud-cli-macosx-0.1.2-amd64.tgz" | shasum -a 256 -c $ tar -zxf ucloud-cli.tgz $ cp ucloud /usr/local/bin ``` @@ -33,7 +33,7 @@ If you have installed golang, run the following commands to install the UCloud C ``` $ mkdir -p $GOPATH/src/github.com/ucloud $ cd $GOPATH/src/github.com/ucloud -$ git clone http://github.com/ucloud/ucloud-cli.git +$ git clone https://github.com/ucloud/ucloud-cli.git $ cd ucloud-cli $ make install ``` @@ -41,6 +41,8 @@ $ make install ### Config UCloud CLI After install the cli, run 'ucloud config' to complete the cli configuration following the tips. Local settings will be saved in directory $HOME/.ucloud +Command 'ucloud ls --object [region|project]' display all the regions and projects. You can change the default region and prject by runing 'ucloud config set [region|project] xxx'. +Execute 'ucloud config --help' for more information. ### Uninstall UCloud CLI diff --git a/cmd/completion.go b/cmd/completion.go index 93eb1ba05c..7ade3b5b3b 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -19,6 +19,9 @@ import ( "fmt" "io" "os" + "os/exec" + "regexp" + "runtime" "strings" "github.com/spf13/cobra" @@ -31,20 +34,17 @@ func NewCmdCompletion() *cobra.Command { On macOS, using bash On macOS, you will need to install bash-completion support via Homebrew first: - If running Bash 3.2 included with macOS - > brew install bash-completion - or, if running Bash 4.1+ - > brew install bash-completion@2 + $ ucloud completion + $ brew install bash-completion Follow the “caveats” section of brew’s output to add the appropriate bash completion path to your local .bash_profile. and then generate bash completion scripts for ucloud - > ucloud completion On Linux, using bash - On CentOS Linux, you may need to install the bash-completion package which is not installed by default. - > yum install bash-completion -y + On Linux, you may need to install the bash-completion package which is not installed by default. + $ ucloud completion + $ yum install bash-completion or apt-get install bash-completion and then genreate bash completion scripts for ucloud - > ucloud completion Using zsh > ucloud completion @@ -73,21 +73,65 @@ func NewCmdCompletion() *cobra.Command { return completionCmd } +var darwinBash = ` +Install bash-completion with command 'brew install bash-completion', and then append the following scripts to ~/.bash_profile + +if [ -f $(brew --prefix)/etc/bash_completion ]; then + . $(brew --prefix)/etc/bash_completion +fi + +source ~/.ucloud/ucloud.sh +` + +var linuxBash = ` +Ensure your have installed bash-completion, and then append the following scripts to ~/.bashrc + +if [ -f /etc/bash_completion ]; then + . /etc/bash_completion +fi + +source ~/.ucloud/ucloud.sh +` + +func getBashVersion() (version string, err error) { + lookupBashVersion := exec.Command("bash", "-version") + out, err := lookupBashVersion.Output() + if err != nil { + context.AppendError(err) + fmt.Println(err) + } + + // Example + // $ bash -version + // GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17) + // Copyright (C) 2007 Free Software Foundation, Inc. + versionStr := string(out) + re := regexp.MustCompile("(\\d)\\.\\d\\.") + strs := re.FindAllStringSubmatch(versionStr, -1) + if len(strs) >= 1 { + result := strs[0] + if len(result) >= 2 { + version = result[1] + } + } + if version == "" { + err = fmt.Errorf("lookup bash version failed") + } + return +} + func bashCompletion(cmd *cobra.Command) { home := util.GetHomePath() shellPath := home + "/" + util.ConfigPath + "/ucloud.sh" cmd.GenBashCompletionFile(shellPath) - for _, rc := range [...]string{".bashrc", ".bash_profile", ".bash_login", ".profile"} { - rcPath := home + "/" + rc - if _, err := os.Stat(rcPath); err == nil { - cmd := "source " + shellPath - if util.LineInFile(rcPath, cmd) == false { - util.AppendToFile(rcPath, cmd) - fmt.Println("Auto completion is on. Please install bash-completion on your platform using brew,yum or apt-get. ucloud completion --help for more information") - } else { - fmt.Println("Auto completion update. Restart session") - } - } + fmt.Printf("Completion scripts has been written to '~/%s/ucloud.sh'\n", util.ConfigPath) + + platform := runtime.GOOS + + if platform == "darwin" { + fmt.Println(darwinBash) + } else if platform == "linux" { + fmt.Println(linuxBash) } } @@ -97,22 +141,17 @@ func zshCompletion(cmd *cobra.Command) { file, err := os.Create(shellPath) if err != nil { fmt.Println(err) + context.AppendError(err) return } defer file.Close() + runCompletionZsh(file, cmd) + fmt.Printf("Completion scripts was written to '~/%s/_ucloud'\n", util.ConfigPath) - rcPath := home + "/.zshrc" - if _, err = os.Stat(rcPath); err == nil { - cmd := fmt.Sprintf("fpath=(%s/%s $fpath);", home, util.ConfigPath) - cmd += "autoload -U +X compinit && compinit" - if util.LineInFile(rcPath, cmd) == false { - util.AppendToFile(rcPath, cmd) - fmt.Println("Auto completion is on") - } else { - fmt.Println("Auto completion update. Restart session") - } - } + scripts := fmt.Sprintf("fpath=(~/%s $fpath)\n", util.ConfigPath) + scripts += "autoload -U +X compinit && compinit" + fmt.Printf("Please append the following scripts to your ~/.zshrc\n%s\n", scripts) } //参考自 k8s.io/kubernetes/pkg/kubectl/cmd/completion.go diff --git a/cmd/configure.go b/cmd/configure.go index fec9e1a72b..b119a009af 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -27,16 +27,7 @@ var config = model.ConfigInstance //NewCmdConfig ucloud config func NewCmdConfig() *cobra.Command { - - var configDesc = `Command 'ucloud config' is used to configure public-key,private-key and other settings. - -Public-key and private-key could be acquired from https://console.ucloud.cn/uapi/apikey. - -If you don’t have an UCloud account yet, run 'ucloud sign-up', and authenticate the account with your valid documentation. - -If you just want to configure default region or project, please run 'ucloud config set region/project xxx'. Run 'ucloud config --help' for more infomation. - -` + var configDesc = `Public-key and private-key could be acquired from https://console.ucloud.cn/uapi/apikey.` var helloUcloud = ` _ _ _ _ _ _ _____ _ _ | | | | | | | | | | / __ \ | | | @@ -49,10 +40,10 @@ If you just want to configure default region or project, please run 'ucloud conf var configCmd = &cobra.Command{ Use: "config", Short: "Config UCloud CLI options", - Long: `Config UCloud CLI options such as credentials and other settings.`, - Example: "ucloud config; ucloud config set region cn-bj2", + Long: `Config UCloud CLI options such as private-key,public-key,default region and default project-id.`, + Example: "ucloud config; ucloud config set region cn-bj2; ucloud config set project org-xxx", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf(configDesc) + fmt.Println(configDesc) if len(config.PrivateKey) != 0 && len(config.PublicKey) != 0 { fmt.Printf("Your have already configured public-key and private-key. Do you want to overwrite it? (y/n):") var overwrite string @@ -69,24 +60,32 @@ If you just want to configure default region or project, please run 'ucloud conf } config.ConfigPublicKey() config.ConfigPrivateKey() - fmt.Println("Fetching regions...") - err := listRegion() + + region, err := getDefaultRegion() if err != nil { + context.AppendError(err) fmt.Println(err) - return + } else { + config.Region = region + fmt.Printf("Configured default region:%s\n", region) } - config.ConfigRegion() - fmt.Println("Fetching projects...") - err = listProject() + project, err := getDefaultProject() if err != nil { + context.AppendError(err) fmt.Println(err) - return + } else { + config.ProjectID = project + fmt.Printf("Configured default project:%s\n", project) } - config.ConfigProjectID() config.SaveConfig() - certified, err := isUserCertified() + + userInfo, err := getUserInfo() + + fmt.Printf("You are logged in as: [%s]\n", userInfo.UserEmail) + + certified := isUserCertified(userInfo) if err != nil { fmt.Println(err) } else if certified == false { @@ -144,7 +143,7 @@ func NewCmdConfigSet() *cobra.Command { Use: "set", Short: "Set a config value", Long: "Set a config value, including private-key public-key region and project-id.", - Example: "ucloud configure set region cn-bj2", + Example: "ucloud config set region cn-bj2", Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { fmt.Printf("Error: accepts 2 arg(s), received %d\n", len(args)) diff --git a/cmd/eip.go b/cmd/eip.go index d235e71d98..6a3c0299c4 100644 --- a/cmd/eip.go +++ b/cmd/eip.go @@ -24,8 +24,8 @@ import ( func NewCmdEIP() *cobra.Command { var cmd = &cobra.Command{ Use: "eip", - Short: "EIP managment", - Long: `EIP managment, such as list,allocate and release`, + Short: "List,allocate and release EIP", + Long: `Manipulate EIP, such as list,allocate and release`, Args: cobra.NoArgs, } cmd.AddCommand(NewCmdEIPList()) diff --git a/cmd/globalssh.go b/cmd/globalssh.go index c19d273e28..843ca5da6d 100644 --- a/cmd/globalssh.go +++ b/cmd/globalssh.go @@ -25,8 +25,8 @@ import ( func NewCmdGssh() *cobra.Command { var cmd = &cobra.Command{ Use: "gssh", - Short: "GlobalSSH management", - Long: `GlobalSSH management, such as create,modify,list and delete`, + Short: "Create and manage globalssh instance", + Long: `Create and manage globalssh instance, such as create,modify,list and delete`, } cmd.AddCommand(NewCmdGsshList()) cmd.AddCommand(NewCmdGsshCreate()) @@ -86,7 +86,7 @@ func NewCmdGsshCreate() *cobra.Command { fmt.Println("Error:", err) return } - if port <= 1 || port >= 65535 || port == 80 || port == 443 { + if port < 1 || port > 65535 || port == 80 || port == 443 { fmt.Println("The port number should be between 1 and 65535, and cannot be equal to 80 or 443") return } diff --git a/cmd/list.go b/cmd/list.go index 56c8af5161..7c47b53eb6 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -50,6 +50,23 @@ func NewCmdList() *cobra.Command { return cmd } +func getDefaultRegion() (string, error) { + req := &uaccount.GetRegionRequest{} + resp, err := client.GetRegion(req) + if err != nil { + return "", err + } + if resp.RetCode != 0 { + return "", fmt.Errorf("Something wrong. RetCode:%d, Message:%s", resp.RetCode, resp.Message) + } + for _, region := range resp.Regions { + if region.IsDefault == true { + return region.Region, nil + } + } + return "", fmt.Errorf("No default region") +} + func listRegion() error { req := &uaccount.GetRegionRequest{} resp, err := client.GetRegion(req) @@ -59,12 +76,37 @@ func listRegion() error { if resp.RetCode != 0 { return fmt.Errorf("Something wrong. RetCode:%d, Message:%s", resp.RetCode, resp.Message) } + var regionMap = map[string]bool{} + var regionList []string for _, region := range resp.Regions { - fmt.Printf("Region: %s, Zone: %s\n", region.Region, region.Zone) + if _, ok := regionMap[region.Region]; !ok { + regionList = append(regionList, region.Region) + } + regionMap[region.Region] = true + } + for index, region := range regionList { + fmt.Printf("[%2d] %s\n", index, region) } return nil } +func getDefaultProject() (string, error) { + req := client.NewGetProjectListRequest() + resp, err := client.GetProjectList(req) + if err != nil { + return "", err + } + if resp.RetCode != 0 { + return "", fmt.Errorf("Something wrong. RetCode:%d, Message:%s", resp.RetCode, resp.Message) + } + for _, project := range resp.ProjectSet { + if project.IsDefault == true { + return project.ProjectId, nil + } + } + return "", fmt.Errorf("No default project") +} + func listProject() error { req := &uaccount.GetProjectListRequest{} resp, err := client.GetProjectList(req) @@ -80,12 +122,8 @@ func listProject() error { return nil } -func isUserCertified() (bool, error) { - userInfo, err := getUserInfo() - if err != nil { - return false, err - } - return userInfo.AuthState == "CERTIFIED", nil +func isUserCertified(userInfo *types.UserInfo) bool { + return userInfo.AuthState == "CERTIFIED" } func getUserInfo() (*types.UserInfo, error) { diff --git a/cmd/root.go b/cmd/root.go index 5d0c5ce6c8..9dc9c1475d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,24 +37,24 @@ var client = service.NewClient(model.ClientConfig, model.Credential) //NewCmdRoot 创建rootCmd rootCmd represents the base command when called without any subcommands func NewCmdRoot() *cobra.Command { var cmd = &cobra.Command{ - Use: "ucloud", - Short: "The UCloud Command Line Interface v" + version, - Long: `The UCloud Command Line Interface is a tool to manage your UCloud services`, + Use: "ucloud", + Short: "UCloud CLI v" + version, + Long: `UCloud CLI - manage UCloud resources and developer workflow`, BashCompletionFunction: "__ucloud_init_completion", } cmd.PersistentFlags().StringVarP(&global.region, "region", "r", "", "Assign region(override default region of your config)") - cmd.PersistentFlags().StringVarP(&global.projectID, "project-id", "p", "", "Assign projectId(override default projecId of your config)") + cmd.PersistentFlags().StringVarP(&global.projectID, "project-id", "p", "", "Assign project-id(override default projec-id of your config)") cmd.PersistentFlags().BoolVarP(&global.debug, "debug", "d", false, "Running in debug mode") - cmd.AddCommand(NewCmdVersion()) - cmd.AddCommand(NewCmdCompletion()) - cmd.AddCommand(NewCmdList()) - cmd.AddCommand(NewCmdConfig()) cmd.AddCommand(NewCmdSignup()) + cmd.AddCommand(NewCmdConfig()) + cmd.AddCommand(NewCmdList()) cmd.AddCommand(NewCmdUHost()) - cmd.AddCommand(NewCmdGssh()) cmd.AddCommand(NewCmdEIP()) + cmd.AddCommand(NewCmdGssh()) + cmd.AddCommand(NewCmdCompletion()) + cmd.AddCommand(NewCmdVersion()) return cmd } @@ -69,11 +69,15 @@ func Execute() { } } +var context *model.Context + func init() { + cobra.EnableCommandSorting = false + client = service.NewClient(model.ClientConfig, model.Credential) + context = model.GetContext(os.Stdout, model.ClientConfig) cobra.OnInitialize(initialize) model.ClientConfig.UserAgent = fmt.Sprintf("UCloud CLI v%s", version) model.ClientConfig.TracerData["command"] = fmt.Sprintf("%v", os.Args) - model.GetContext() } func initialize(cmd *cobra.Command) { @@ -81,7 +85,6 @@ func initialize(cmd *cobra.Command) { model.ClientConfig.LogLevel = 5 model.ClientConfig.Logger = nil } - client = service.NewClient(model.ClientConfig, model.Credential) userInfo, err := model.LoadUserInfo() if err == nil { @@ -89,13 +92,9 @@ func initialize(cmd *cobra.Command) { model.ClientConfig.TracerData["userID"] = userInfo.UserEmail model.ClientConfig.TracerData["companyName"] = userInfo.CompanyName } else { - errorStr, ok := model.ClientConfig.TracerData["error"].(string) - if ok { - model.ClientConfig.TracerData["error"] = errorStr + "->" + err.Error() - } else { - model.ClientConfig.TracerData["error"] = err.Error() - } + context.AppendError(err) } + //上报服务对Origin请求头有限制,必须以'.ucloud.cn'结尾,因此这里伪造了一个sdk.ucloud.cn,跟其他上报区分 model.ClientConfig.HTTPHeaders["Origin"] = "https://sdk.ucloud.cn" diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 0000000000..4529a1d7da --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,8 @@ +package cmd + +import "testing" + +func TestCmdRoot(t *testing.T) { + root := NewCmdRoot() + root.Execute() +} diff --git a/cmd/signup.go b/cmd/signup.go index b913671254..649434aa32 100644 --- a/cmd/signup.go +++ b/cmd/signup.go @@ -27,8 +27,8 @@ func NewCmdSignup() *cobra.Command { var cmd = &cobra.Command{ Use: "sign-up", - Short: "Open sign up page of ucloud in your browser", - Long: `Open sign up page of ucloud in your browser`, + Short: "Launch UCloud sign-up page in browser", + Long: `Launch UCloud sign-up page in browser`, Args: cobra.NoArgs, Example: "ucloud sign-up", Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/uhost.go b/cmd/uhost.go index 39c612468e..c4fea613e0 100644 --- a/cmd/uhost.go +++ b/cmd/uhost.go @@ -24,8 +24,8 @@ import ( func NewCmdUHost() *cobra.Command { var cmd = &cobra.Command{ Use: "uhost", - Short: "UHost managment", - Long: `UHost managment. Only list`, + Short: "List UHost instance", + Long: `List UHost instance`, Args: cobra.NoArgs, } cmd.AddCommand(NewCmdUHostList()) diff --git a/cmd/version.go b/cmd/version.go index ad3a1fc234..a7bcb1c8c8 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -20,14 +20,14 @@ import ( "github.com/spf13/cobra" ) -var version = "0.1.1" +var version = "0.1.2" //NewCmdVersion ucloud version func NewCmdVersion() *cobra.Command { return &cobra.Command{ Use: "version", - Short: "Show the version of ucloud", - Long: `Show the version of ucloud`, + Short: "Display UCloud CLI version", + Long: `Display UCloud CLI version`, Run: func(cmd *cobra.Command, args []string) { fmt.Println(version) }, diff --git a/model/config.go b/model/config.go index 8f8ae95103..7502354821 100644 --- a/model/config.go +++ b/model/config.go @@ -186,5 +186,4 @@ func init() { PublicKey: ConfigInstance.PublicKey, PrivateKey: ConfigInstance.PrivateKey, } - } diff --git a/model/context.go b/model/context.go index 747c906a40..dbac0eeff0 100644 --- a/model/context.go +++ b/model/context.go @@ -3,8 +3,9 @@ package model import ( "fmt" "io" - "os" "sync" + + "github.com/ucloud/ucloud-sdk-go/sdk" ) var context *Context @@ -12,7 +13,8 @@ var once sync.Once // Context 执行环境 type Context struct { - writer io.Writer + writer io.Writer + clientConfig *sdk.ClientConfig } // Println 在当前执行环境打印一行 @@ -25,10 +27,21 @@ func (p *Context) Print(a ...interface{}) (n int, err error) { return fmt.Fprint(p.writer, a...) } +//AppendError 添加上报的错误 +func (p *Context) AppendError(err error) { + tracerData := p.clientConfig.TracerData + errorStr, ok := tracerData["error"].(string) + if ok { + tracerData["error"] = errorStr + "->" + err.Error() + } else { + tracerData["error"] = err.Error() + } +} + // GetContext 创建一个单例的Context -func GetContext() *Context { +func GetContext(writer io.Writer, clientConfig *sdk.ClientConfig) *Context { once.Do(func() { - context = &Context{os.Stdout} + context = &Context{writer, clientConfig} }) return context } diff --git a/model/context_test.go b/model/context_test.go new file mode 100644 index 0000000000..d0aae65dc0 --- /dev/null +++ b/model/context_test.go @@ -0,0 +1,17 @@ +package model + +import ( + "os" + "testing" + + "github.com/ucloud/ucloud-sdk-go/sdk" +) + +var context_test = Context{ + os.Stdout, + &sdk.ClientConfig{}, +} + +func TestPrintln(t *testing.T) { + context_test.Println("test print") +} diff --git a/util/sdk_test.go b/util/sdk_test.go new file mode 100644 index 0000000000..4d5a8a3d2d --- /dev/null +++ b/util/sdk_test.go @@ -0,0 +1,10 @@ +package util + +import "testing" + +func TestGetHomePath(t *testing.T) { + home := GetHomePath() + if home == "" { + t.Errorf("home shoud not be empty") + } +}