mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
373 lines
11 KiB
Go
373 lines
11 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package entry
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/c-bata/go-prompt"
|
|
"github.com/cheggaaa/pb/v3"
|
|
"golang.org/x/net/http/httpproxy"
|
|
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/httputils"
|
|
"yunion.io/x/pkg/util/version"
|
|
"yunion.io/x/structarg"
|
|
|
|
"yunion.io/x/onecloud/cmd/climc/promputils"
|
|
"yunion.io/x/onecloud/cmd/climc/shell"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
)
|
|
|
|
type BaseOptions struct {
|
|
Debug bool `help:"Show debug information"`
|
|
Version bool `help:"Show version"`
|
|
Timeout int `default:"600" help:"Number of seconds to wait for a response"`
|
|
Insecure bool `default:"$YUNION_INSECURE|false" help:"Allow skip server cert verification if URL is https" short-token:"k"`
|
|
|
|
CertFile string `default:"$YUNION_CERT_FILE" help:"certificate file"`
|
|
KeyFile string `default:"$YUNION_KEY_FILE" help:"private key file"`
|
|
|
|
Completion string `default:"" help:"Generate climc auto complete script" choices:"bash|zsh"`
|
|
UseCachedToken bool `default:"$YUNION_USE_CACHED_TOKEN|false" help:"Use cached token"`
|
|
|
|
OsUsername string `default:"$OS_USERNAME" help:"Username, defaults to env[OS_USERNAME]"`
|
|
OsPassword string `default:"$OS_PASSWORD" help:"Password, defaults to env[OS_PASSWORD]"`
|
|
// OsProjectId string `default:"$OS_PROJECT_ID" help:"Proejct ID, defaults to env[OS_PROJECT_ID]"`
|
|
OsProjectName string `default:"$OS_PROJECT_NAME" help:"Project name, defaults to env[OS_PROJECT_NAME]"`
|
|
OsProjectDomain string `default:"$OS_PROJECT_DOMAIN" help:"Domain name of project, defaults to env[OS_PROJECT_DOMAIN]"`
|
|
OsDomainName string `default:"$OS_DOMAIN_NAME" help:"Domain name, defaults to env[OS_DOMAIN_NAME]"`
|
|
|
|
OsAccessKey string `default:"$OS_ACCESS_KEY" help:"ak/sk access key, defaults to env[OS_ACCESS_KEY]"`
|
|
OsSecretKey string `default:"$OS_SECRET_KEY" help:"ak/s secret, defaults to env[OS_SECRET_KEY]"`
|
|
|
|
OsAuthToken string `default:"$OS_AUTH_TOKEN" help:"token authenticate, defaults to env[OS_AUTH_TOKEN]"`
|
|
|
|
OsAuthURL string `default:"$OS_AUTH_URL" help:"Defaults to env[OS_AUTH_URL]"`
|
|
|
|
OsRegionName string `default:"$OS_REGION_NAME" help:"Defaults to env[OS_REGION_NAME]"`
|
|
OsZoneName string `default:"$OS_ZONE_NAME" help:"Defaults to env[OS_ZONE_NAME]"`
|
|
OsEndpointType string `default:"$OS_ENDPOINT_TYPE|internalURL" help:"Defaults to env[OS_ENDPOINT_TYPE] or internalURL" choices:"publicURL|internalURL|adminURL"`
|
|
// ApiVersion string `default:"$API_VERSION" help:"override default modules service api version"`
|
|
OutputFormat string `default:"$CLIMC_OUTPUT_FORMAT|table" choices:"table|kv|json|flatten-table|flatten-kv" help:"output format"`
|
|
|
|
ParallelRun int `help:"run in parallel to stess test the performance of server"`
|
|
|
|
SUBCOMMAND string `help:"climc subcommand" subcommand:"true"`
|
|
}
|
|
|
|
func getSubcommandsParser() (*structarg.ArgumentParser, error) {
|
|
var (
|
|
prog = "climc"
|
|
desc = `Command-line interface to the API server.`
|
|
)
|
|
parse, e := structarg.NewArgumentParserWithHelp(&BaseOptions{},
|
|
prog,
|
|
desc,
|
|
`See "climc help COMMAND" for help on a specific command.`)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
subcmd := parse.GetSubcommand()
|
|
if subcmd == nil {
|
|
return nil, fmt.Errorf("No subcommand argument")
|
|
}
|
|
|
|
promptRootCmd := promputils.InitRootCmd(prog, desc, parse.GetOptArgs(), parse.GetPosArgs())
|
|
for _, v := range shell.CommandTable {
|
|
_par, e := subcmd.AddSubParserWithHelp(v.Options, v.Command, v.Desc, v.Callback)
|
|
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
promputils.AppendCommand(promptRootCmd, v.Command, v.Desc)
|
|
cmd := v.Command
|
|
|
|
for _, v := range _par.GetOptArgs() {
|
|
text := v.String()
|
|
text = strings.TrimLeft(text, "[<")
|
|
text = strings.TrimRight(text, "]>")
|
|
promputils.AppendOpt(cmd, text, v.HelpString(""), v)
|
|
}
|
|
for _, v := range _par.GetPosArgs() {
|
|
text := v.String()
|
|
text = strings.TrimLeft(text, "[<")
|
|
text = strings.TrimRight(text, "]>")
|
|
promputils.AppendPos(cmd, text, v.HelpString(""), v)
|
|
}
|
|
}
|
|
return parse, nil
|
|
}
|
|
|
|
func showErrorAndExit(e error) {
|
|
fmt.Fprintf(os.Stderr, "%s", e)
|
|
fmt.Fprintln(os.Stderr)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) {
|
|
if len(options.OsAuthURL) == 0 {
|
|
return nil, fmt.Errorf("Missing OS_AUTH_URL")
|
|
}
|
|
if len(options.OsUsername) == 0 && len(options.OsAccessKey) == 0 && len(options.OsAuthToken) == 0 {
|
|
return nil, fmt.Errorf("Missing OS_USERNAME or OS_ACCESS_KEY or OS_AUTH_TOKEN")
|
|
}
|
|
if len(options.OsUsername) > 0 && len(options.OsPassword) == 0 {
|
|
return nil, fmt.Errorf("Missing OS_PASSWORD")
|
|
}
|
|
if len(options.OsAccessKey) > 0 && len(options.OsSecretKey) == 0 {
|
|
return nil, fmt.Errorf("Missing OS_SECRET_KEY")
|
|
}
|
|
|
|
logLevel := "info"
|
|
if options.Debug {
|
|
logLevel = "debug"
|
|
}
|
|
log.SetLogLevelByString(log.Logger(), logLevel)
|
|
|
|
client := mcclient.NewClient(options.OsAuthURL,
|
|
options.Timeout,
|
|
options.Debug,
|
|
options.Insecure,
|
|
options.CertFile,
|
|
options.KeyFile)
|
|
|
|
cfg := &httpproxy.Config{
|
|
HTTPProxy: os.Getenv("HTTP_PROXY"),
|
|
HTTPSProxy: os.Getenv("HTTPS_PROXY"),
|
|
NoProxy: os.Getenv("NO_PROXY"),
|
|
}
|
|
|
|
cfgProxyFunc := cfg.ProxyFunc()
|
|
proxyFunc := func(req *http.Request) (*url.URL, error) {
|
|
return cfgProxyFunc(req.URL)
|
|
}
|
|
httputils.SetClientProxyFunc(client.GetClient(), proxyFunc)
|
|
|
|
var cacheToken mcclient.TokenCredential
|
|
authUrlAlter := strings.Replace(options.OsAuthURL, "/", "", -1)
|
|
authUrlAlter = strings.Replace(authUrlAlter, ":", "", -1)
|
|
tokenCachePath := filepath.Join(os.TempDir(), fmt.Sprintf("OS_AUTH_CACHE_TOKEN-%s-%s-%s-%s", authUrlAlter, options.OsUsername, options.OsDomainName, options.OsProjectName))
|
|
if options.UseCachedToken {
|
|
cacheFile, err := os.Open(tokenCachePath)
|
|
if err == nil && cacheFile != nil {
|
|
fileInfo, _ := cacheFile.Stat()
|
|
dur, err := time.ParseDuration("-24h")
|
|
if fileInfo != nil && err == nil && fileInfo.ModTime().After(time.Now().Add(dur)) {
|
|
bytesToken, err := ioutil.ReadAll(cacheFile)
|
|
if err == nil {
|
|
token := client.NewAuthTokenCredential()
|
|
err := json.Unmarshal(bytesToken, token)
|
|
if err != nil {
|
|
fmt.Printf("Unmarshal token error:%s", err)
|
|
} else if token.IsValid() {
|
|
cacheToken = token
|
|
}
|
|
}
|
|
cacheFile.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
if cacheToken == nil {
|
|
var token mcclient.TokenCredential
|
|
var err error
|
|
if len(options.OsAuthToken) > 0 {
|
|
token, err = client.AuthenticateToken(options.OsAuthToken, options.OsProjectName,
|
|
options.OsProjectDomain,
|
|
mcclient.AuthSourceCli)
|
|
} else if len(options.OsAccessKey) > 0 {
|
|
token, err = client.AuthenticateByAccessKey(options.OsAccessKey,
|
|
options.OsSecretKey, mcclient.AuthSourceCli)
|
|
} else {
|
|
token, err = client.AuthenticateWithSource(options.OsUsername,
|
|
options.OsPassword,
|
|
options.OsDomainName,
|
|
options.OsProjectName,
|
|
options.OsProjectDomain,
|
|
mcclient.AuthSourceCli)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cacheToken = token
|
|
bytesCacheToken, err := json.Marshal(cacheToken)
|
|
if err != nil {
|
|
fmt.Printf("Marshal token error:%s", err)
|
|
} else {
|
|
fo, err := os.Create(tokenCachePath)
|
|
if err != nil {
|
|
fmt.Printf("Save token cache fail: %s", err)
|
|
} else {
|
|
fo.Write(bytesCacheToken)
|
|
fo.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
session := client.NewSession(
|
|
context.Background(),
|
|
options.OsRegionName,
|
|
options.OsZoneName,
|
|
options.OsEndpointType,
|
|
cacheToken,
|
|
)
|
|
return session, nil
|
|
}
|
|
|
|
func enterInteractiveMode(
|
|
parser *structarg.ArgumentParser,
|
|
sessionFactory func() *mcclient.ClientSession,
|
|
) {
|
|
promputils.InitEnv(parser, sessionFactory())
|
|
defer fmt.Println("Bye!")
|
|
p := prompt.New(
|
|
promputils.Executor,
|
|
promputils.Completer,
|
|
prompt.OptionPrefix("climc> "),
|
|
prompt.OptionTitle("Climc, a Command Line Interface to Manage Clouds"),
|
|
prompt.OptionMaxSuggestion(16),
|
|
)
|
|
p.Run()
|
|
}
|
|
|
|
func executeSubcommand(
|
|
subcmd *structarg.SubcommandArgument,
|
|
subparser *structarg.ArgumentParser,
|
|
options *BaseOptions,
|
|
sessionFactory func() *mcclient.ClientSession,
|
|
parallel int,
|
|
) {
|
|
suboptions := subparser.Options()
|
|
if subparser.IsHelpSet() {
|
|
helpStr, err := subcmd.SubHelpString(options.SUBCOMMAND)
|
|
if err != nil {
|
|
showErrorAndExit(err)
|
|
return
|
|
}
|
|
fmt.Println(helpStr)
|
|
return
|
|
}
|
|
if parallel <= 1 {
|
|
sess := sessionFactory()
|
|
err := subcmd.Invoke(sess, suboptions)
|
|
if err != nil {
|
|
showErrorAndExit(err)
|
|
return
|
|
}
|
|
} else {
|
|
fmt.Println("Authenticating...")
|
|
bar := pb.StartNew(parallel)
|
|
sess := make([]*mcclient.ClientSession, parallel)
|
|
for i := 0; i < parallel; i++ {
|
|
sess[i] = sessionFactory()
|
|
bar.Increment()
|
|
}
|
|
bar.Finish()
|
|
fmt.Println("Tokens are ready, start to request ...")
|
|
start := time.Now()
|
|
var wg sync.WaitGroup
|
|
var errs []error
|
|
for i := 0; i < parallel; i++ {
|
|
wg.Add(1)
|
|
s := sess[i]
|
|
go func() {
|
|
defer wg.Done()
|
|
err := subcmd.Invoke(s, suboptions)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if len(errs) > 0 {
|
|
showErrorAndExit(errors.NewAggregate(errs))
|
|
return
|
|
}
|
|
diff := time.Now().Sub(start)
|
|
fmt.Printf("cost: %f seconds %f qps\n", diff.Seconds(), float64(parallel)/diff.Seconds())
|
|
}
|
|
}
|
|
|
|
func ClimcMain() {
|
|
parser, e := getSubcommandsParser()
|
|
if e != nil {
|
|
showErrorAndExit(e)
|
|
return
|
|
}
|
|
e = parser.ParseArgs(os.Args[1:], false)
|
|
options := parser.Options().(*BaseOptions)
|
|
|
|
if len(options.Completion) > 0 {
|
|
completeScript := promputils.GenerateAutoCompleteCmds(promputils.GetRootCmd(), options.Completion)
|
|
if len(completeScript) > 0 {
|
|
fmt.Printf("%s", completeScript)
|
|
}
|
|
return
|
|
}
|
|
|
|
if parser.IsHelpSet() {
|
|
fmt.Print(parser.HelpString())
|
|
return
|
|
}
|
|
|
|
if options.Version {
|
|
fmt.Printf("Yunion API client version:\n %s\n", version.GetJsonString())
|
|
return
|
|
}
|
|
|
|
shell.OutputFormat(options.OutputFormat)
|
|
ensureSessionFactory := func() *mcclient.ClientSession {
|
|
session, err := newClientSession(options)
|
|
if err != nil {
|
|
showErrorAndExit(err)
|
|
return nil
|
|
}
|
|
return session
|
|
}
|
|
|
|
// enter interactive mode when not enough argument and SUBCOMMAND is empty
|
|
if _, ok := e.(*structarg.NotEnoughArgumentsError); ok && options.SUBCOMMAND == "" {
|
|
enterInteractiveMode(parser, ensureSessionFactory)
|
|
return
|
|
}
|
|
|
|
subcmd := parser.GetSubcommand()
|
|
subparser := subcmd.GetSubParser()
|
|
if e != nil {
|
|
if subparser != nil {
|
|
fmt.Print(subparser.Usage())
|
|
} else {
|
|
fmt.Print(parser.Usage())
|
|
}
|
|
showErrorAndExit(e)
|
|
return
|
|
}
|
|
|
|
// execute subcommand in non-interactive mode
|
|
executeSubcommand(subcmd, subparser, options, ensureSessionFactory, options.ParallelRun)
|
|
}
|