Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
964e557
tidb 支持
Jul 10, 2026
5cfdaac
Fix product/css Application version, e.g. 7.10.2
rocky-ucloud Jul 10, 2026
87948d4
[feature] #1 ukafka-cli
Jul 13, 2026
541c7ad
[feature] #1 ukafka ucloud-cli
Jul 13, 2026
d2bb83d
ukafka ucloud-cli
Jul 13, 2026
c88cb89
[feature] ukafka ucloud-cli
Jul 13, 2026
a5f20d7
utidb 接入cli
Jul 13, 2026
66db270
Update product.yaml
Jul 13, 2026
6f28814
feat(memcache): add resize command
ucloud-umem-qingpfang Jul 14, 2026
2341ba7
feat(redis): add proxy/flush/isolation/resize/modify commands and res…
ucloud-umem-qingpfang Jul 14, 2026
87e5181
feature: add uddos command
leas-li Jul 14, 2026
79c2bb7
Merge branch 'master' into feature/tidb
Jul 14, 2026
556c404
Update poll.go
Jul 14, 2026
2686324
feat(cli): make poller default timeout configurable via SetDefaultPol…
Episkey-G Jul 14, 2026
713c68c
feat(cli): add --wait-timeout-sec to override synchronous poll timeou…
Episkey-G Jul 14, 2026
658e494
fix(cli): accept --wait-timeout-sec=VALUE (equals form) in startup pr…
Episkey-G Jul 14, 2026
a6fec4b
fix
Jul 14, 2026
a28cf6d
补充 umongodb 命令
Jul 9, 2026
bfb9653
refactor(redis): consolidate type mapping onto resourceTypeToMode
ucloud-umem-qingpfang Jul 14, 2026
404929f
chore(memcache): update product owner
ucloud-umem-qingpfang Jul 14, 2026
e771594
feat(uclickhouse): add UClickhouse CLI commands
Ali1213 Jul 11, 2026
6efbec4
refactor(cli): three-tier poll timeout model + rename SetUserPollTimeout
Episkey-G Jul 14, 2026
96e9633
feat(cli): add WithTimeout option for per-command poll timeout
Episkey-G Jul 14, 2026
ce0fb76
feat(uddos): service create 补叙述与 --async 轮询,移除 ip bind/unbind
Jul 14, 2026
aec6daf
Merge pull request #121 from Episkey-G/feat/wait-timeout-config
Ali1213 Jul 14, 2026
a39e3b9
Merge pull request #122 from jukang-ucloud/feature/uclickhouse
Ali1213 Jul 14, 2026
bd8e26a
fix(umongodb): fix delete file split and stale golden file
Jul 14, 2026
ccc4041
Merge pull request #117 from ucloud-umem-qingpfang/feat/redis-commands
Ali1213 Jul 14, 2026
d320f6f
Merge pull request #116 from ucloud-umem-qingpfang/feat/memcache-resize
Ali1213 Jul 14, 2026
bc71838
Merge pull request #114 from xingxingso/feature/tidb
Ali1213 Jul 14, 2026
de1d53b
Merge pull request #110 from rocky-ucloud/master
Ali1213 Jul 14, 2026
88de4b7
feat: add nlb cli
Jul 14, 2026
9bee052
[feature] ucloud-cli
Jul 14, 2026
410f8c4
[feature] ukafka ucloud-cli
Jul 14, 2026
413e4df
Merge branch 'master' into feature/uddos
leas-li Jul 14, 2026
52435c9
修复cmd/products.gen.go冲突的问题
Jul 14, 2026
14c9b68
[feature] 删除根目录下的编译产物
Jul 14, 2026
c45ee16
fix(urocketmq): correct command behavior (#125)
lanfunoe Jul 14, 2026
205b70e
feat: modify nlb testdata
Jul 14, 2026
c10adc5
feat: auto product gen
Jul 14, 2026
3674725
Merge pull request #124 from Cavan-xu/feature-nlbCli
Episkey-G Jul 14, 2026
270fcff
Merge pull request #118 from leas-li/feature/uddos
Episkey-G Jul 14, 2026
6f38fc6
Merge pull request #113 from zxl-wangwang/feature/1-ukafka-product
Episkey-G Jul 14, 2026
d0ac3d2
Merge pull request #112 from jinfz12/feat/udb-mongodb
Episkey-G Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/internal/platform/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type GlobalFlag struct {
PrivateKey string
BaseURL string
Timeout int
WaitTimeout int // 同步轮询总超时(秒),0 表示用内置默认(600s)
MaxRetryTimes int
}

Expand Down
1 change: 1 addition & 0 deletions cmd/product_registration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestRegisteredProductsUseCommandDirectoryProducts(t *testing.T) {
{product: "ulb", commands: []string{"ulb"}},
{product: "umodelverse", commands: []string{"umodelverse"}},
{product: "uphost", commands: []string{"uphost"}},
{product: "utidb", commands: []string{"utidb"}},
{product: "vpc", commands: []string{"vpc"}},
} {
assertProductCommands(t, byName, tt.product, tt.commands)
Expand Down
12 changes: 12 additions & 0 deletions cmd/products.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"os"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -190,6 +191,7 @@ func applyGlobalOverrideFlags(root *cobra.Command) {
c.PersistentFlags().StringVar(&global.PrivateKey, "private-key", global.PrivateKey, "Set private-key to override the private-key in local config file")
c.PersistentFlags().StringVar(&global.BaseURL, "base-url", "", "Set base-url to override the base-url in local config file")
c.PersistentFlags().IntVar(&global.Timeout, "timeout-sec", 0, "Set timeout-sec to override the timeout-sec in local config file")
c.PersistentFlags().IntVar(&global.WaitTimeout, "wait-timeout-sec", 0, "Set the total timeout in seconds for synchronous wait/poll (e.g. cluster/host create). 0 uses the built-in default (600s)")
c.PersistentFlags().IntVar(&global.MaxRetryTimes, "max-retry-times", -1, "Set max-retry-times to override the max-retry-times in local config file")
}
}
Expand Down Expand Up @@ -241,6 +243,9 @@ func Execute() {
}
}
platform.InitConfig()
if global.WaitTimeout > 0 {
cli.SetUserPollTimeout(time.Duration(global.WaitTimeout) * time.Second)
}
setActiveRuntimeFromBaseGlobals()
mode := os.Getenv("UCLOUD_CLI_DEBUG")
if mode == "on" || global.Debug {
Expand Down Expand Up @@ -328,9 +333,40 @@ func init() {
}
}
}
if sec, found, err := parseWaitTimeoutSec(os.Args); found {
if err != nil {
fmt.Printf("parse wait-timeout-sec failed: %v\n", err)
} else {
global.WaitTimeout = sec
}
}
cobra.EnableCommandSorting = false
}

// parseWaitTimeoutSec scans args for --wait-timeout-sec in either
// `--wait-timeout-sec N` or `--wait-timeout-sec=N` form. found=true when
// the flag is present with a value; err is set when that value isn't an int.
func parseWaitTimeoutSec(args []string) (sec int, found bool, err error) {
const flag = "--wait-timeout-sec"
for idx, arg := range args {
if arg == flag {
if len(args) > idx+1 && args[idx+1] != "" {
sec, err = strconv.Atoi(args[idx+1])
return sec, true, err
}
continue
}
if val, ok := strings.CutPrefix(arg, flag+"="); ok {
if val == "" {
continue
}
sec, err = strconv.Atoi(val)
return sec, true, err
}
}
return 0, false, nil
}

func resetHelpFunc(cmd *cobra.Command) {
for _, a := range os.Args {
if a == "-h" {
Expand Down
83 changes: 83 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cmd

import "testing"

// TestParseWaitTimeoutSec covers the --wait-timeout-sec startup pre-scan
// helper, which must accept both the space form (--wait-timeout-sec 1800)
// and the equals form (--wait-timeout-sec=1800). See issue #119: the equals
// form was previously silently ignored because init()'s manual os.Args scan
// only matched arg == "--wait-timeout-sec" exactly.
func TestParseWaitTimeoutSec(t *testing.T) {
tests := []struct {
name string
args []string
wantSec int
wantFound bool
wantErr bool
}{
{
name: "space form",
args: []string{"ucloud", "uhost", "--wait-timeout-sec", "1800"},
wantSec: 1800,
wantFound: true,
wantErr: false,
},
{
name: "equals form",
args: []string{"ucloud", "uhost", "--wait-timeout-sec=1800"},
wantSec: 1800,
wantFound: true,
wantErr: false,
},
{
name: "absent",
args: []string{"ucloud", "uhost"},
wantSec: 0,
wantFound: false,
wantErr: false,
},
{
name: "equals with bad int",
args: []string{"ucloud", "--wait-timeout-sec=abc"},
wantSec: 0,
wantFound: true,
wantErr: true,
},
{
name: "space with bad int",
args: []string{"ucloud", "--wait-timeout-sec", "abc"},
wantSec: 0,
wantFound: true,
wantErr: true,
},
{
name: "trailing flag no value",
args: []string{"ucloud", "--wait-timeout-sec"},
wantSec: 0,
wantFound: false,
wantErr: false,
},
{
name: "empty equals",
args: []string{"ucloud", "--wait-timeout-sec="},
wantSec: 0,
wantFound: false,
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sec, found, err := parseWaitTimeoutSec(tt.args)
if sec != tt.wantSec {
t.Errorf("sec = %d, want %d", sec, tt.wantSec)
}
if found != tt.wantFound {
t.Errorf("found = %v, want %v", found, tt.wantFound)
}
if (err != nil) != tt.wantErr {
t.Errorf("err = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ require (
github.com/sirupsen/logrus v1.3.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/ucloud/ucloud-sdk-go v0.22.89
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
github.com/ucloud/ucloud-sdk-go v0.22.90
gopkg.in/yaml.v2 v2.2.2
)

Expand All @@ -25,5 +24,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ucloud/ucloud-sdk-go v0.22.89 h1:eCvCtkan5KIzzBv+QJgEQojCbwwS+hVtOq52hfGnWtg=
github.com/ucloud/ucloud-sdk-go v0.22.89/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ucloud/ucloud-sdk-go v0.22.90 h1:OYYPYlWZFQ5DB15akEkrd/ty28zkd6Als7PQ6shywzk=
github.com/ucloud/ucloud-sdk-go v0.22.90/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type Context struct {
logWarn func(io.Writer, ...string)
logError func(io.Writer, ...string)
logFilePath func() string
newPoller func(func(string, *request.CommonBase) (interface{}, error), io.Writer) Poller
newPoller func(func(string, *request.CommonBase) (interface{}, error), io.Writer, ...PollerOption) Poller

// errCount tallies HandleError calls this invocation so the host (cmd) can
// set a non-zero exit code when any product error occurred (aws/gcloud
Expand Down Expand Up @@ -88,7 +88,7 @@ type Deps struct {
LogWarn func(io.Writer, ...string)
LogError func(io.Writer, ...string)
LogFilePath func() string
NewPoller func(func(string, *request.CommonBase) (interface{}, error), io.Writer) Poller
NewPoller func(func(string, *request.CommonBase) (interface{}, error), io.Writer, ...PollerOption) Poller
}

// NewContext constructs a Context from the provided Deps.
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,14 @@ func (c *Context) LogFilePath() string { return c.logFilePath() }
func (c *Context) PickResourceID(s string) string { return PickResourceID(s) }

// Poller returns a platform poller bound to ctx's writer.
func (c *Context) Poller(describeFunc func(string, *request.CommonBase) (interface{}, error)) Poller {
return c.newPoller(describeFunc, c.out)
func (c *Context) Poller(describeFunc func(string, *request.CommonBase) (interface{}, error), opts ...PollerOption) Poller {
return c.newPoller(describeFunc, c.out, opts...)
}

// PollerTo wraps base.NewSpoller bound to an explicit writer, so callers can
// route progress narration to stderr (e.g. in json/yaml mode) while keeping
// machine output on stdout. Products cannot import base directly, so this
// exposes the writer-parameterized poller through the Context.
func (c *Context) PollerTo(w io.Writer, describeFunc func(string, *request.CommonBase) (interface{}, error)) Poller {
return c.newPoller(describeFunc, w)
func (c *Context) PollerTo(w io.Writer, describeFunc func(string, *request.CommonBase) (interface{}, error), opts ...PollerOption) Poller {
return c.newPoller(describeFunc, w, opts...)
}
4 changes: 2 additions & 2 deletions pkg/cli/forward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ func TestContextBindCommonParams(t *testing.T) {

func TestContextPollerToReturnsProductCompatiblePoller(t *testing.T) {
ctx := cli.NewContext(cli.Deps{
NewPoller: func(describe func(string, *request.CommonBase) (interface{}, error), out io.Writer) cli.Poller {
return cli.NewPoller(describe, out)
NewPoller: func(describe func(string, *request.CommonBase) (interface{}, error), out io.Writer, opts ...cli.PollerOption) cli.Poller {
return cli.NewPoller(describe, out, opts...)
},
})

Expand Down
59 changes: 52 additions & 7 deletions pkg/cli/poller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,64 @@ type Poller interface {
}

type poller struct {
describe func(string, *request.CommonBase) (interface{}, error)
out io.Writer
stateFields []string
timeout time.Duration
describe func(string, *request.CommonBase) (interface{}, error)
out io.Writer
stateFields []string
commandTimeout time.Duration
timeout time.Duration
}

func NewPoller(describe func(string, *request.CommonBase) (interface{}, error), out io.Writer) Poller {
return &poller{
// builtinPollTimeout 是同步轮询的内置兜底总超时。
const builtinPollTimeout = 10 * time.Minute

// userPollTimeout 是用户经 --wait-timeout-sec 指定的轮询超时(cmd 层启动时注入)。
// 0 表示用户未指定。它是全局最高优先级,覆盖任何命令自设的默认。
var userPollTimeout time.Duration

// SetUserPollTimeout 设置用户层轮询超时(cmd 层接 --wait-timeout-sec)。
// 非正值被忽略:SDK 的 waiter 在 Timeout==0 时直接报错(errTimeoutConf),
// 故此时保留其余层级,不将其置 0。
func SetUserPollTimeout(d time.Duration) {
if d > 0 {
userPollTimeout = d
}
}

// effectivePollTimeout 按 用户 > 命令自设 > 内置 的优先级裁决最终超时。
func effectivePollTimeout(commandTimeout time.Duration) time.Duration {
if userPollTimeout > 0 {
return userPollTimeout
}
if commandTimeout > 0 {
return commandTimeout
}
return builtinPollTimeout
}

// PollerOption 定制单个 poller 的创建。
type PollerOption func(*poller)

// WithTimeout 让单个命令声明自己的默认轮询超时。非正值被忽略。
// 优先级低于用户 --wait-timeout-sec、高于内置默认(见 effectivePollTimeout)。
func WithTimeout(d time.Duration) PollerOption {
return func(p *poller) {
if d > 0 {
p.commandTimeout = d
}
}
}

func NewPoller(describe func(string, *request.CommonBase) (interface{}, error), out io.Writer, opts ...PollerOption) Poller {
p := &poller{
describe: describe,
out: out,
stateFields: []string{"State", "Status"},
timeout: 10 * time.Minute,
}
for _, o := range opts {
o(p)
}
p.timeout = effectivePollTimeout(p.commandTimeout)
return p
}

func (p *poller) Spoll(resourceID, pollText string, targetStates []string) {
Expand Down
Loading