From b167bd311f2a7f0ca8dce53f845dd994ba190fa2 Mon Sep 17 00:00:00 2001 From: Episkey Date: Wed, 8 Jul 2026 03:39:16 -0700 Subject: [PATCH 1/3] refactor(products): split migrated command products --- .github/workflows/pr-gate.yml | 22 +- .github/workflows/release-drift.yml | 16 +- .github/workflows/release.yml | 18 +- .tool-versions | 1 + cmd/bandwidth.go | 407 ---- cmd/eip_compat.go | 202 -- cmd/ext.go | 145 +- cmd/ext_compat_test.go | 31 + cmd/globalssh.go | 332 ---- cmd/pathx.go | 1466 -------------- cmd/product_registration_test.go | 59 +- cmd/products.gen.go | 30 +- cmd/root.go | 9 - cmd/uhost_compat.go | 82 - cmd/uhost_create_image_test.go | 83 + cmd/ulb.go | 1700 ----------------- cmd/umem.go | 811 -------- cmd/unet.go | 262 --- cmd/util.go | 10 - cmd/vpc.go | 575 ------ hack/check-product/main.go | 23 +- hack/check-product/main_test.go | 126 +- hack/gen-products/main_test.go | 10 +- hack/owner-gate/main_test.go | 92 +- hack/snapshot/completion.go | 2 +- hack/snapshot/snapshot_test.go | 2 +- hack/snapshot/testdata/cmdtree.golden | 477 ----- hack/snapshot/testdata/completion.golden | 247 --- .../{unet => eip}/internal/eip/allocate.go | 0 products/{unet => eip}/internal/eip/bind.go | 0 products/{unet => eip}/internal/eip/cmd.go | 0 .../{unet => eip}/internal/eip/completion.go | 0 .../{unet => eip}/internal/eip/describe.go | 0 .../internal/eip/join_shared_bw.go | 0 .../internal/eip/leave_shared_bw.go | 0 products/{unet => eip}/internal/eip/list.go | 0 .../{unet => eip}/internal/eip/modify_bw.go | 0 .../internal/eip/modify_traffic_mode.go | 0 .../{unet => eip}/internal/eip/release.go | 0 products/{unet => eip}/internal/eip/rows.go | 0 products/{unet => eip}/internal/eip/status.go | 0 products/{unet => eip}/internal/eip/unbind.go | 0 products/{udb => eip}/product.go | 9 +- products/eip/product.yaml | 6 + products/eip/testdata/cmdtree.golden | 57 + .../{unet => eip}/testdata/completion.golden | 12 - .../internal/firewall/add_rule.go | 0 .../internal/firewall/apply.go | 0 .../internal/firewall/cmd.go | 0 .../internal/firewall/completion.go | 0 .../internal/firewall/copy.go | 0 .../internal/firewall/create.go | 0 .../internal/firewall/delete.go | 0 .../internal/firewall/describe.go | 0 .../internal/firewall/list.go | 0 .../internal/firewall/remove_rule.go | 0 .../internal/firewall/resource.go | 0 .../internal/firewall/rows.go | 0 .../internal/firewall/rules.go | 0 .../internal/firewall/update.go | 0 products/firewall/product.go | 20 + products/firewall/product.yaml | 6 + .../testdata/cmdtree.golden | 57 - products/firewall/testdata/completion.golden | 12 + products/globalssh/internal/gssh/area.go | 79 + products/globalssh/internal/gssh/cmd.go | 22 + .../globalssh/internal/gssh/completion.go | 88 + products/globalssh/internal/gssh/create.go | 77 + products/globalssh/internal/gssh/delete.go | 50 + products/globalssh/internal/gssh/list.go | 60 + products/globalssh/internal/gssh/modify.go | 80 + products/globalssh/internal/gssh/rows.go | 18 + products/globalssh/product.go | 20 + products/globalssh/product.yaml | 6 + products/globalssh/testdata/cmdtree.golden | 24 + products/globalssh/testdata/completion.golden | 11 + .../{uhost => image}/internal/image/cmd.go | 0 .../internal/image/completion.go | 0 .../{uhost => image}/internal/image/copy.go | 0 .../{uhost => image}/internal/image/create.go | 0 .../{uhost => image}/internal/image/delete.go | 0 .../internal/image/describe.go | 0 .../{uhost => image}/internal/image/list.go | 0 .../{uhost => image}/internal/image/rows.go | 0 .../{uhost => image}/internal/image/status.go | 0 products/image/product.go | 20 + products/image/product.yaml | 6 + products/image/testdata/cmdtree.golden | 33 + products/image/testdata/completion.golden | 9 + products/memcache/internal/memcache/cmd.go | 21 + .../memcache/internal/memcache/completion.go | 35 + products/memcache/internal/memcache/create.go | 75 + products/memcache/internal/memcache/delete.go | 56 + products/memcache/internal/memcache/list.go | 57 + products/memcache/internal/memcache/poll.go | 32 + .../memcache/internal/memcache/restart.go | 79 + products/memcache/internal/memcache/rows.go | 12 + products/memcache/internal/memcache/status.go | 6 + products/memcache/internal/memcache/vpc.go | 119 ++ products/memcache/product.go | 20 + products/memcache/product.yaml | 6 + products/memcache/testdata/cmdtree.golden | 29 + products/memcache/testdata/completion.golden | 18 + .../{udb => mysql}/internal/mysql/backup.go | 0 .../internal/mysql/backup_create.go | 0 .../internal/mysql/backup_delete.go | 0 .../internal/mysql/backup_download.go | 0 .../internal/mysql/backup_list.go | 0 products/{udb => mysql}/internal/mysql/cmd.go | 0 .../internal/mysql/completion.go | 0 .../{udb => mysql}/internal/mysql/conf.go | 0 .../internal/mysql/conf_apply.go | 0 .../internal/mysql/conf_clone.go | 0 .../internal/mysql/conf_delete.go | 0 .../internal/mysql/conf_describe.go | 0 .../internal/mysql/conf_download.go | 0 .../internal/mysql/conf_list.go | 0 .../internal/mysql/conf_update.go | 0 .../internal/mysql/conf_upload.go | 0 .../{udb => mysql}/internal/mysql/create.go | 0 .../internal/mysql/create_slave.go | 0 products/{udb => mysql}/internal/mysql/db.go | 0 .../{udb => mysql}/internal/mysql/delete.go | 0 .../{udb => mysql}/internal/mysql/list.go | 0 .../internal/mysql/list_machine_type.go | 0 .../{udb => mysql}/internal/mysql/logs.go | 0 .../internal/mysql/logs_archive.go | 0 .../internal/mysql/logs_delete.go | 0 .../internal/mysql/logs_download.go | 0 .../internal/mysql/logs_list.go | 0 .../{udb => mysql}/internal/mysql/poll.go | 0 .../internal/mysql/promote_slave.go | 0 .../internal/mysql/promote_to_ha.go | 0 .../internal/mysql/reset_password.go | 0 .../{udb => mysql}/internal/mysql/resize.go | 0 .../{udb => mysql}/internal/mysql/restart.go | 0 .../{udb => mysql}/internal/mysql/restore.go | 0 .../{udb => mysql}/internal/mysql/rows.go | 0 .../{udb => mysql}/internal/mysql/start.go | 0 .../{udb => mysql}/internal/mysql/status.go | 0 .../{udb => mysql}/internal/mysql/stop.go | 0 products/mysql/product.go | 20 + products/mysql/product.yaml | 6 + .../{udb => mysql}/testdata/cmdtree.golden | 0 .../{udb => mysql}/testdata/completion.golden | 0 products/pathx/internal/pathx/area.go | 18 + products/pathx/internal/pathx/area_list.go | 134 ++ products/pathx/internal/pathx/cmd.go | 25 + products/pathx/internal/pathx/completion.go | 78 + products/pathx/internal/pathx/create.go | 145 ++ products/pathx/internal/pathx/delete.go | 69 + products/pathx/internal/pathx/format.go | 152 ++ products/pathx/internal/pathx/list.go | 77 + products/pathx/internal/pathx/modify.go | 155 ++ products/pathx/internal/pathx/price.go | 18 + products/pathx/internal/pathx/price_list.go | 77 + products/pathx/internal/pathx/rows.go | 88 + products/pathx/internal/pathx/uga.go | 23 + products/pathx/internal/pathx/uga_add_port.go | 67 + products/pathx/internal/pathx/uga_create.go | 98 + products/pathx/internal/pathx/uga_delete.go | 47 + products/pathx/internal/pathx/uga_describe.go | 55 + products/pathx/internal/pathx/uga_list.go | 47 + .../pathx/internal/pathx/uga_remove_port.go | 67 + products/pathx/internal/pathx/upath.go | 18 + products/pathx/internal/pathx/upath_list.go | 49 + products/pathx/product.go | 20 + products/pathx/product.yaml | 6 + products/pathx/testdata/cmdtree.golden | 93 + products/pathx/testdata/completion.golden | 44 + products/redis/internal/redis/cmd.go | 21 + products/redis/internal/redis/completion.go | 34 + products/redis/internal/redis/create.go | 177 ++ products/redis/internal/redis/delete.go | 99 + products/redis/internal/redis/list.go | 88 + products/redis/internal/redis/poll.go | 32 + products/redis/internal/redis/restart.go | 79 + products/redis/internal/redis/rows.go | 20 + products/redis/internal/redis/status.go | 6 + products/redis/internal/redis/vpc.go | 119 ++ products/redis/product.go | 20 + products/redis/product.yaml | 6 + products/redis/testdata/cmdtree.golden | 34 + products/redis/testdata/completion.golden | 20 + products/sharedbw/internal/bw/cmd.go | 19 + products/sharedbw/internal/bw/completion.go | 90 + products/sharedbw/internal/bw/pkg.go | 20 + products/sharedbw/internal/bw/pkg_create.go | 85 + products/sharedbw/internal/bw/pkg_delete.go | 46 + products/sharedbw/internal/bw/pkg_list.go | 54 + products/sharedbw/internal/bw/rows.go | 18 + products/sharedbw/internal/bw/shared.go | 21 + .../sharedbw/internal/bw/shared_create.go | 49 + .../sharedbw/internal/bw/shared_delete.go | 57 + products/sharedbw/internal/bw/shared_list.go | 61 + .../sharedbw/internal/bw/shared_resize.go | 56 + products/sharedbw/internal/bw/status.go | 7 + products/sharedbw/product.go | 20 + products/sharedbw/product.yaml | 6 + products/sharedbw/testdata/cmdtree.golden | 41 + products/sharedbw/testdata/completion.golden | 19 + products/subnet/internal/subnet/cmd.go | 22 + products/subnet/internal/subnet/completion.go | 71 + products/subnet/internal/subnet/create.go | 67 + products/subnet/internal/subnet/delete.go | 54 + products/subnet/internal/subnet/list.go | 55 + .../subnet/internal/subnet/list_resource.go | 56 + products/subnet/internal/subnet/rows.go | 17 + products/subnet/product.go | 20 + products/subnet/product.yaml | 6 + products/subnet/testdata/cmdtree.golden | 28 + products/subnet/testdata/completion.golden | 12 + products/udb/product.yaml | 7 - products/udpn/internal/udpn/cmd.go | 23 + products/udpn/internal/udpn/completion.go | 43 + products/udpn/internal/udpn/create.go | 68 + products/udpn/internal/udpn/delete.go | 53 + products/udpn/internal/udpn/list.go | 58 + products/udpn/internal/udpn/modify_bw.go | 56 + products/udpn/internal/udpn/rows.go | 10 + products/udpn/product.go | 21 + products/udpn/product.yaml | 7 + products/udpn/testdata/cmdtree.golden | 22 + products/udpn/testdata/completion.golden | 10 + products/uhost/internal/uhost/create_image.go | 1 + products/uhost/product.go | 9 +- products/uhost/product.yaml | 2 - products/uhost/testdata/cmdtree.golden | 33 - products/uhost/testdata/completion.golden | 9 - products/ulb/internal/ulb/cmd.go | 23 + products/ulb/internal/ulb/completion.go | 156 ++ products/ulb/internal/ulb/create.go | 122 ++ products/ulb/internal/ulb/delete.go | 56 + products/ulb/internal/ulb/eip.go | 126 ++ products/ulb/internal/ulb/list.go | 76 + products/ulb/internal/ulb/read_file.go | 11 + products/ulb/internal/ulb/rows.go | 53 + products/ulb/internal/ulb/ssl.go | 23 + products/ulb/internal/ulb/ssl_add.go | 97 + products/ulb/internal/ulb/ssl_bind.go | 58 + products/ulb/internal/ulb/ssl_delete.go | 51 + products/ulb/internal/ulb/ssl_describe.go | 79 + products/ulb/internal/ulb/ssl_list.go | 59 + products/ulb/internal/ulb/ssl_unbind.go | 80 + products/ulb/internal/ulb/status.go | 3 + products/ulb/internal/ulb/update.go | 73 + products/ulb/internal/ulb/vpc.go | 70 + products/ulb/internal/ulb/vserver.go | 23 + products/ulb/internal/ulb/vserver_create.go | 104 + products/ulb/internal/ulb/vserver_delete.go | 63 + products/ulb/internal/ulb/vserver_list.go | 67 + products/ulb/internal/ulb/vserver_node.go | 21 + products/ulb/internal/ulb/vserver_node_add.go | 83 + .../ulb/internal/ulb/vserver_node_delete.go | 59 + .../ulb/internal/ulb/vserver_node_list.go | 81 + .../ulb/internal/ulb/vserver_node_update.go | 89 + products/ulb/internal/ulb/vserver_policy.go | 21 + .../ulb/internal/ulb/vserver_policy_create.go | 74 + .../ulb/internal/ulb/vserver_policy_delete.go | 51 + .../ulb/internal/ulb/vserver_policy_list.go | 69 + .../ulb/internal/ulb/vserver_policy_update.go | 133 ++ products/ulb/internal/ulb/vserver_update.go | 97 + products/ulb/product.go | 21 + products/ulb/product.yaml | 7 + products/ulb/testdata/cmdtree.golden | 172 ++ products/ulb/testdata/completion.golden | 104 + products/unet/product.go | 25 - products/unet/product.yaml | 8 - products/vpc/internal/vpc/cmd.go | 24 + products/vpc/internal/vpc/completion.go | 33 + products/vpc/internal/vpc/create.go | 51 + products/vpc/internal/vpc/delete.go | 56 + products/vpc/internal/vpc/list.go | 61 + products/vpc/internal/vpc/peer_create.go | 63 + products/vpc/internal/vpc/peer_delete.go | 55 + products/vpc/internal/vpc/peer_list.go | 58 + products/vpc/internal/vpc/rows.go | 19 + products/vpc/product.go | 20 + products/vpc/product.yaml | 6 + products/vpc/testdata/cmdtree.golden | 34 + products/vpc/testdata/completion.golden | 19 + 281 files changed, 8752 insertions(+), 6882 deletions(-) create mode 100644 .tool-versions delete mode 100644 cmd/bandwidth.go delete mode 100644 cmd/eip_compat.go create mode 100644 cmd/ext_compat_test.go delete mode 100644 cmd/globalssh.go delete mode 100644 cmd/pathx.go delete mode 100644 cmd/uhost_compat.go create mode 100644 cmd/uhost_create_image_test.go delete mode 100644 cmd/ulb.go delete mode 100644 cmd/umem.go delete mode 100644 cmd/unet.go delete mode 100644 cmd/vpc.go rename products/{unet => eip}/internal/eip/allocate.go (100%) rename products/{unet => eip}/internal/eip/bind.go (100%) rename products/{unet => eip}/internal/eip/cmd.go (100%) rename products/{unet => eip}/internal/eip/completion.go (100%) rename products/{unet => eip}/internal/eip/describe.go (100%) rename products/{unet => eip}/internal/eip/join_shared_bw.go (100%) rename products/{unet => eip}/internal/eip/leave_shared_bw.go (100%) rename products/{unet => eip}/internal/eip/list.go (100%) rename products/{unet => eip}/internal/eip/modify_bw.go (100%) rename products/{unet => eip}/internal/eip/modify_traffic_mode.go (100%) rename products/{unet => eip}/internal/eip/release.go (100%) rename products/{unet => eip}/internal/eip/rows.go (100%) rename products/{unet => eip}/internal/eip/status.go (100%) rename products/{unet => eip}/internal/eip/unbind.go (100%) rename products/{udb => eip}/product.go (50%) create mode 100644 products/eip/product.yaml create mode 100644 products/eip/testdata/cmdtree.golden rename products/{unet => eip}/testdata/completion.golden (68%) rename products/{unet => firewall}/internal/firewall/add_rule.go (100%) rename products/{unet => firewall}/internal/firewall/apply.go (100%) rename products/{unet => firewall}/internal/firewall/cmd.go (100%) rename products/{unet => firewall}/internal/firewall/completion.go (100%) rename products/{unet => firewall}/internal/firewall/copy.go (100%) rename products/{unet => firewall}/internal/firewall/create.go (100%) rename products/{unet => firewall}/internal/firewall/delete.go (100%) rename products/{unet => firewall}/internal/firewall/describe.go (100%) rename products/{unet => firewall}/internal/firewall/list.go (100%) rename products/{unet => firewall}/internal/firewall/remove_rule.go (100%) rename products/{unet => firewall}/internal/firewall/resource.go (100%) rename products/{unet => firewall}/internal/firewall/rows.go (100%) rename products/{unet => firewall}/internal/firewall/rules.go (100%) rename products/{unet => firewall}/internal/firewall/update.go (100%) create mode 100644 products/firewall/product.go create mode 100644 products/firewall/product.yaml rename products/{unet => firewall}/testdata/cmdtree.golden (50%) create mode 100644 products/firewall/testdata/completion.golden create mode 100644 products/globalssh/internal/gssh/area.go create mode 100644 products/globalssh/internal/gssh/cmd.go create mode 100644 products/globalssh/internal/gssh/completion.go create mode 100644 products/globalssh/internal/gssh/create.go create mode 100644 products/globalssh/internal/gssh/delete.go create mode 100644 products/globalssh/internal/gssh/list.go create mode 100644 products/globalssh/internal/gssh/modify.go create mode 100644 products/globalssh/internal/gssh/rows.go create mode 100644 products/globalssh/product.go create mode 100644 products/globalssh/product.yaml create mode 100644 products/globalssh/testdata/cmdtree.golden create mode 100644 products/globalssh/testdata/completion.golden rename products/{uhost => image}/internal/image/cmd.go (100%) rename products/{uhost => image}/internal/image/completion.go (100%) rename products/{uhost => image}/internal/image/copy.go (100%) rename products/{uhost => image}/internal/image/create.go (100%) rename products/{uhost => image}/internal/image/delete.go (100%) rename products/{uhost => image}/internal/image/describe.go (100%) rename products/{uhost => image}/internal/image/list.go (100%) rename products/{uhost => image}/internal/image/rows.go (100%) rename products/{uhost => image}/internal/image/status.go (100%) create mode 100644 products/image/product.go create mode 100644 products/image/product.yaml create mode 100644 products/image/testdata/cmdtree.golden create mode 100644 products/image/testdata/completion.golden create mode 100644 products/memcache/internal/memcache/cmd.go create mode 100644 products/memcache/internal/memcache/completion.go create mode 100644 products/memcache/internal/memcache/create.go create mode 100644 products/memcache/internal/memcache/delete.go create mode 100644 products/memcache/internal/memcache/list.go create mode 100644 products/memcache/internal/memcache/poll.go create mode 100644 products/memcache/internal/memcache/restart.go create mode 100644 products/memcache/internal/memcache/rows.go create mode 100644 products/memcache/internal/memcache/status.go create mode 100644 products/memcache/internal/memcache/vpc.go create mode 100644 products/memcache/product.go create mode 100644 products/memcache/product.yaml create mode 100644 products/memcache/testdata/cmdtree.golden create mode 100644 products/memcache/testdata/completion.golden rename products/{udb => mysql}/internal/mysql/backup.go (100%) rename products/{udb => mysql}/internal/mysql/backup_create.go (100%) rename products/{udb => mysql}/internal/mysql/backup_delete.go (100%) rename products/{udb => mysql}/internal/mysql/backup_download.go (100%) rename products/{udb => mysql}/internal/mysql/backup_list.go (100%) rename products/{udb => mysql}/internal/mysql/cmd.go (100%) rename products/{udb => mysql}/internal/mysql/completion.go (100%) rename products/{udb => mysql}/internal/mysql/conf.go (100%) rename products/{udb => mysql}/internal/mysql/conf_apply.go (100%) rename products/{udb => mysql}/internal/mysql/conf_clone.go (100%) rename products/{udb => mysql}/internal/mysql/conf_delete.go (100%) rename products/{udb => mysql}/internal/mysql/conf_describe.go (100%) rename products/{udb => mysql}/internal/mysql/conf_download.go (100%) rename products/{udb => mysql}/internal/mysql/conf_list.go (100%) rename products/{udb => mysql}/internal/mysql/conf_update.go (100%) rename products/{udb => mysql}/internal/mysql/conf_upload.go (100%) rename products/{udb => mysql}/internal/mysql/create.go (100%) rename products/{udb => mysql}/internal/mysql/create_slave.go (100%) rename products/{udb => mysql}/internal/mysql/db.go (100%) rename products/{udb => mysql}/internal/mysql/delete.go (100%) rename products/{udb => mysql}/internal/mysql/list.go (100%) rename products/{udb => mysql}/internal/mysql/list_machine_type.go (100%) rename products/{udb => mysql}/internal/mysql/logs.go (100%) rename products/{udb => mysql}/internal/mysql/logs_archive.go (100%) rename products/{udb => mysql}/internal/mysql/logs_delete.go (100%) rename products/{udb => mysql}/internal/mysql/logs_download.go (100%) rename products/{udb => mysql}/internal/mysql/logs_list.go (100%) rename products/{udb => mysql}/internal/mysql/poll.go (100%) rename products/{udb => mysql}/internal/mysql/promote_slave.go (100%) rename products/{udb => mysql}/internal/mysql/promote_to_ha.go (100%) rename products/{udb => mysql}/internal/mysql/reset_password.go (100%) rename products/{udb => mysql}/internal/mysql/resize.go (100%) rename products/{udb => mysql}/internal/mysql/restart.go (100%) rename products/{udb => mysql}/internal/mysql/restore.go (100%) rename products/{udb => mysql}/internal/mysql/rows.go (100%) rename products/{udb => mysql}/internal/mysql/start.go (100%) rename products/{udb => mysql}/internal/mysql/status.go (100%) rename products/{udb => mysql}/internal/mysql/stop.go (100%) create mode 100644 products/mysql/product.go create mode 100644 products/mysql/product.yaml rename products/{udb => mysql}/testdata/cmdtree.golden (100%) rename products/{udb => mysql}/testdata/completion.golden (100%) create mode 100644 products/pathx/internal/pathx/area.go create mode 100644 products/pathx/internal/pathx/area_list.go create mode 100644 products/pathx/internal/pathx/cmd.go create mode 100644 products/pathx/internal/pathx/completion.go create mode 100644 products/pathx/internal/pathx/create.go create mode 100644 products/pathx/internal/pathx/delete.go create mode 100644 products/pathx/internal/pathx/format.go create mode 100644 products/pathx/internal/pathx/list.go create mode 100644 products/pathx/internal/pathx/modify.go create mode 100644 products/pathx/internal/pathx/price.go create mode 100644 products/pathx/internal/pathx/price_list.go create mode 100644 products/pathx/internal/pathx/rows.go create mode 100644 products/pathx/internal/pathx/uga.go create mode 100644 products/pathx/internal/pathx/uga_add_port.go create mode 100644 products/pathx/internal/pathx/uga_create.go create mode 100644 products/pathx/internal/pathx/uga_delete.go create mode 100644 products/pathx/internal/pathx/uga_describe.go create mode 100644 products/pathx/internal/pathx/uga_list.go create mode 100644 products/pathx/internal/pathx/uga_remove_port.go create mode 100644 products/pathx/internal/pathx/upath.go create mode 100644 products/pathx/internal/pathx/upath_list.go create mode 100644 products/pathx/product.go create mode 100644 products/pathx/product.yaml create mode 100644 products/pathx/testdata/cmdtree.golden create mode 100644 products/pathx/testdata/completion.golden create mode 100644 products/redis/internal/redis/cmd.go create mode 100644 products/redis/internal/redis/completion.go create mode 100644 products/redis/internal/redis/create.go create mode 100644 products/redis/internal/redis/delete.go create mode 100644 products/redis/internal/redis/list.go create mode 100644 products/redis/internal/redis/poll.go create mode 100644 products/redis/internal/redis/restart.go create mode 100644 products/redis/internal/redis/rows.go create mode 100644 products/redis/internal/redis/status.go create mode 100644 products/redis/internal/redis/vpc.go create mode 100644 products/redis/product.go create mode 100644 products/redis/product.yaml create mode 100644 products/redis/testdata/cmdtree.golden create mode 100644 products/redis/testdata/completion.golden create mode 100644 products/sharedbw/internal/bw/cmd.go create mode 100644 products/sharedbw/internal/bw/completion.go create mode 100644 products/sharedbw/internal/bw/pkg.go create mode 100644 products/sharedbw/internal/bw/pkg_create.go create mode 100644 products/sharedbw/internal/bw/pkg_delete.go create mode 100644 products/sharedbw/internal/bw/pkg_list.go create mode 100644 products/sharedbw/internal/bw/rows.go create mode 100644 products/sharedbw/internal/bw/shared.go create mode 100644 products/sharedbw/internal/bw/shared_create.go create mode 100644 products/sharedbw/internal/bw/shared_delete.go create mode 100644 products/sharedbw/internal/bw/shared_list.go create mode 100644 products/sharedbw/internal/bw/shared_resize.go create mode 100644 products/sharedbw/internal/bw/status.go create mode 100644 products/sharedbw/product.go create mode 100644 products/sharedbw/product.yaml create mode 100644 products/sharedbw/testdata/cmdtree.golden create mode 100644 products/sharedbw/testdata/completion.golden create mode 100644 products/subnet/internal/subnet/cmd.go create mode 100644 products/subnet/internal/subnet/completion.go create mode 100644 products/subnet/internal/subnet/create.go create mode 100644 products/subnet/internal/subnet/delete.go create mode 100644 products/subnet/internal/subnet/list.go create mode 100644 products/subnet/internal/subnet/list_resource.go create mode 100644 products/subnet/internal/subnet/rows.go create mode 100644 products/subnet/product.go create mode 100644 products/subnet/product.yaml create mode 100644 products/subnet/testdata/cmdtree.golden create mode 100644 products/subnet/testdata/completion.golden delete mode 100644 products/udb/product.yaml create mode 100644 products/udpn/internal/udpn/cmd.go create mode 100644 products/udpn/internal/udpn/completion.go create mode 100644 products/udpn/internal/udpn/create.go create mode 100644 products/udpn/internal/udpn/delete.go create mode 100644 products/udpn/internal/udpn/list.go create mode 100644 products/udpn/internal/udpn/modify_bw.go create mode 100644 products/udpn/internal/udpn/rows.go create mode 100644 products/udpn/product.go create mode 100644 products/udpn/product.yaml create mode 100644 products/udpn/testdata/cmdtree.golden create mode 100644 products/udpn/testdata/completion.golden create mode 100644 products/ulb/internal/ulb/cmd.go create mode 100644 products/ulb/internal/ulb/completion.go create mode 100644 products/ulb/internal/ulb/create.go create mode 100644 products/ulb/internal/ulb/delete.go create mode 100644 products/ulb/internal/ulb/eip.go create mode 100644 products/ulb/internal/ulb/list.go create mode 100644 products/ulb/internal/ulb/read_file.go create mode 100644 products/ulb/internal/ulb/rows.go create mode 100644 products/ulb/internal/ulb/ssl.go create mode 100644 products/ulb/internal/ulb/ssl_add.go create mode 100644 products/ulb/internal/ulb/ssl_bind.go create mode 100644 products/ulb/internal/ulb/ssl_delete.go create mode 100644 products/ulb/internal/ulb/ssl_describe.go create mode 100644 products/ulb/internal/ulb/ssl_list.go create mode 100644 products/ulb/internal/ulb/ssl_unbind.go create mode 100644 products/ulb/internal/ulb/status.go create mode 100644 products/ulb/internal/ulb/update.go create mode 100644 products/ulb/internal/ulb/vpc.go create mode 100644 products/ulb/internal/ulb/vserver.go create mode 100644 products/ulb/internal/ulb/vserver_create.go create mode 100644 products/ulb/internal/ulb/vserver_delete.go create mode 100644 products/ulb/internal/ulb/vserver_list.go create mode 100644 products/ulb/internal/ulb/vserver_node.go create mode 100644 products/ulb/internal/ulb/vserver_node_add.go create mode 100644 products/ulb/internal/ulb/vserver_node_delete.go create mode 100644 products/ulb/internal/ulb/vserver_node_list.go create mode 100644 products/ulb/internal/ulb/vserver_node_update.go create mode 100644 products/ulb/internal/ulb/vserver_policy.go create mode 100644 products/ulb/internal/ulb/vserver_policy_create.go create mode 100644 products/ulb/internal/ulb/vserver_policy_delete.go create mode 100644 products/ulb/internal/ulb/vserver_policy_list.go create mode 100644 products/ulb/internal/ulb/vserver_policy_update.go create mode 100644 products/ulb/internal/ulb/vserver_update.go create mode 100644 products/ulb/product.go create mode 100644 products/ulb/product.yaml create mode 100644 products/ulb/testdata/cmdtree.golden create mode 100644 products/ulb/testdata/completion.golden delete mode 100644 products/unet/product.go delete mode 100644 products/unet/product.yaml create mode 100644 products/vpc/internal/vpc/cmd.go create mode 100644 products/vpc/internal/vpc/completion.go create mode 100644 products/vpc/internal/vpc/create.go create mode 100644 products/vpc/internal/vpc/delete.go create mode 100644 products/vpc/internal/vpc/list.go create mode 100644 products/vpc/internal/vpc/peer_create.go create mode 100644 products/vpc/internal/vpc/peer_delete.go create mode 100644 products/vpc/internal/vpc/peer_list.go create mode 100644 products/vpc/internal/vpc/rows.go create mode 100644 products/vpc/product.go create mode 100644 products/vpc/product.yaml create mode 100644 products/vpc/testdata/cmdtree.golden create mode 100644 products/vpc/testdata/completion.golden diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index 2b493d60f4..d2a363755b 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -4,7 +4,7 @@ # - owner-gate (product-autonomous vs platform; platform fail-closed) # - hold-gate (red while a do-not-merge/hold label is present) # - scope-gate (conventional commit title) -# - check-product (rule1–8 boundary checks) +# - check-product (generated registry drift + rule1–10 boundary checks) # - test (build + vet + offline tests) # - build-matrix (6 × platform) # @@ -21,7 +21,8 @@ # - Repository secret AUTO_MERGE_TOKEN is a bot/GitHub App token with # contents:write + pull-requests:write. Do NOT use GITHUB_TOKEN for # auto-merge: GitHub suppresses workflow runs triggered by GITHUB_TOKEN -# events, so a GITHUB_TOKEN-merged PR would not fire the master Release. +# events, which can hide post-merge CI/CD automation. Release is scheduled +# weekly and no longer depends on master push events. # - Required approvals = 0. Do NOT set a global "require N approvals" — that # would also block product autonomy. Platform approval is enforced by # owner-gate (admin review), not by branch protection's approval count. @@ -194,8 +195,8 @@ jobs: auto-merge: # Native GitHub auto-merge, only for product-autonomous eligible PRs. GitHub # enforces required checks + (any) required approvals before the actual merge. - # Use a real automation identity instead of GITHUB_TOKEN so the resulting - # master push can trigger Release. + # Use a real automation identity instead of GITHUB_TOKEN so post-merge + # repository workflows/events are not suppressed. if: github.event_name == 'pull_request' && needs.owner-gate.outputs.auto_merge == 'true' needs: [owner-gate] runs-on: ubuntu-latest @@ -213,7 +214,7 @@ jobs: run: | set -euo pipefail if [ -z "$AUTO_MERGE_TOKEN" ]; then - echo "::error::AUTO_MERGE_TOKEN is required for product auto-merge. Configure a bot/GitHub App token with contents:write and pull-requests:write so the post-merge master push triggers Release." + echo "::error::AUTO_MERGE_TOKEN is required for product auto-merge. Configure a bot/GitHub App token with contents:write and pull-requests:write so post-merge repository workflows/events are not suppressed." exit 1 fi - name: Enable auto-merge @@ -229,6 +230,17 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: { go-version: '1.25' } + - name: Verify generated product registry + run: | + set -euo pipefail + go run ./hack/gen-products + if ! git diff --quiet -- cmd/products.gen.go; then + echo "::error file=cmd/products.gen.go,line=1::cmd/products.gen.go is out of date. Run 'go run ./hack/gen-products' from the repo root and commit the result." + git diff -- cmd/products.gen.go + exit 1 + fi + echo "cmd/products.gen.go is up to date." + env: { GOPROXY: 'https://goproxy.cn,direct' } - run: go run ./hack/check-product env: { GOPROXY: 'https://goproxy.cn,direct' } diff --git a/.github/workflows/release-drift.yml b/.github/workflows/release-drift.yml index 661d70b688..e22e0d41c8 100644 --- a/.github/workflows/release-drift.yml +++ b/.github/workflows/release-drift.yml @@ -1,16 +1,18 @@ # Release Drift Guard # -# Detects the failure mode where master has releasable commits after the latest -# tag but no Release workflow produced the next patch version. This guard only +# Detects the failure mode where the weekly Release workflow misses its release +# window while master already had releasable commits waiting. This guard only # alerts; release.yml remains the single writer of tags and GitHub Releases. It -# skips while Release is already queued/running and gives fresh master commits a -# short grace window to avoid noisy failures during normal release serialization. +# runs after the weekly release window, skips while Release is already +# queued/running, and gives fresh master commits a grace window so changes that +# land after the release window wait for the next week instead of failing drift. name: Release Drift Guard on: schedule: - - cron: "17 * * * *" + # Tuesdays 03:00 Asia/Shanghai (Monday 19:00 UTC), four hours after Release. + - cron: "0 19 * * 1" workflow_dispatch: {} permissions: @@ -37,7 +39,7 @@ jobs: - name: Detect unreleased master changes env: GH_TOKEN: ${{ github.token }} - RELEASE_GRACE_SECONDS: "1200" + RELEASE_GRACE_SECONDS: "14400" run: | set -euo pipefail @@ -75,7 +77,7 @@ jobs: fi if [ "$DETECT" != "$CURRENT" ] || [ -n "$RELEASABLE" ]; then - echo "::error::master has releasable commits after $CURRENT, but no release tag covers HEAD. Run the Release workflow on master." + echo "::error::master has releasable commits after $CURRENT outside the weekly release grace window, but no release tag covers HEAD. Check the weekly Release workflow or run it manually." echo "current=$CURRENT" echo "detected=$DETECT" git log --oneline "$CURRENT"..HEAD diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f2d7e3138..b4ccc3883e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,8 @@ -# Merge-to-release automation +# Weekly release automation # # Flow: -# 1. A PR merges to master (or workflow_dispatch for the first guarded run). +# 1. Once per week (or workflow_dispatch for an urgent/manual run), the +# workflow checks master for releasable commits since the latest tag. # 2. Version policy: every release is a PATCH bump (0.3.3 → 0.3.4 → 0.3.5), # regardless of whether the commits are feat or fix — we intentionally do # NOT auto-minor on feat. svu is used in two roles here: @@ -19,16 +20,17 @@ # 5. If there are no new releasable commits (feat/fix/perf/refactor/breaking), # the job exits early without creating a release. # -# Release is intentionally automatic after master updates. Product-autonomous -# PRs are admitted by PR Gate, then the master push from the automation identity -# triggers this workflow. +# Release is intentionally batched weekly. PRs are still admitted by PR Gate and +# normal CI/CD checks before merge; this workflow only decides whether the +# already-merged master branch should be published during the release window. name: Release on: - push: - branches: [master] - workflow_dispatch: {} # manual trigger for the first guarded run + schedule: + # Mondays 23:00 Asia/Shanghai (15:00 UTC). + - cron: "0 15 * * 1" + workflow_dispatch: {} # manual trigger for urgent releases or guarded runs concurrency: group: release diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000000..7d8a6ba830 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.25.11 diff --git a/cmd/bandwidth.go b/cmd/bandwidth.go deleted file mode 100644 index 05eaa8dd55..0000000000 --- a/cmd/bandwidth.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - "strconv" - "strings" - "time" - - "github.com/spf13/cobra" - - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/model/status" - "github.com/ucloud/ucloud-cli/pkg/command" -) - -// NewCmdBandwidth ucloud bw -func NewCmdBandwidth() *cobra.Command { - cmd := &cobra.Command{ - Use: "bw", - Short: "Manipulate bandwidth package and shared bandwidth", - Long: "Manipulate bandwidth package and shared bandwidth", - } - cmd.AddCommand(NewCmdBandwidthPkg()) - cmd.AddCommand(NewCmdSharedBW()) - return cmd -} - -// NewCmdSharedBW ucloud shared-bw -func NewCmdSharedBW() *cobra.Command { - cmd := &cobra.Command{ - Use: "shared", - Short: "Create and manipulate shared bandwidth instances", - Long: "Create and manipulate shared bandwidth instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdSharedBWCreate()) - cmd.AddCommand(NewCmdSharedBWList(out)) - cmd.AddCommand(NewCmdSharedBWResize()) - cmd.AddCommand(NewCmdSharedBWDelete()) - return cmd -} - -// NewCmdSharedBWCreate ucloud shared-bw create -func NewCmdSharedBWCreate() *cobra.Command { - req := base.BizClient.NewAllocateShareBandwidthRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create shared bandwidth instance", - Long: "Create shared bandwidth instance", - Run: func(c *cobra.Command, args []string) { - if *req.ShareBandwidth < 20 || *req.ShareBandwidth > 5000 { - base.Cxt.Printf("bandwidth should be between 20 and 5000. received %d\n", *req.ShareBandwidth) - return - } - resp, err := base.BizClient.AllocateShareBandwidth(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("shared bandwidth[%s] created\n", resp.ShareBandwidthId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - req.Name = flags.String("name", "", "Required. Name of the shared bandwidth instance") - req.ShareBandwidth = flags.Int("bandwidth-mb", 20, "Optional. Unit:Mb. Bandwidth of the shared bandwidth. Range [20,5000]") - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.ChargeType = flags.String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") - req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") - command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") - - cmd.MarkFlagRequired("name") - - return cmd -} - -// SharedBWRow 表格行 -type SharedBWRow struct { - Name string - ResourceID string - ChargeType string - Bandwidth string - EIP string - ExpirationTime string -} - -// NewCmdSharedBWList ucloud shared-bw list -func NewCmdSharedBWList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeShareBandwidthRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List shared bandwidth instances", - Long: "List shared bandwidth instances", - Run: func(c *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeShareBandwidth(req) - if err != nil { - base.HandleError(err) - return - } - list := []SharedBWRow{} - for _, sb := range resp.DataSet { - row := SharedBWRow{} - row.Name = sb.Name - row.ResourceID = sb.ShareBandwidthId - row.ChargeType = sb.ChargeType - row.Bandwidth = strconv.Itoa(sb.ShareBandwidth) + "Mb" - row.ExpirationTime = common.FormatDate(sb.ExpireTime) - eipList := []string{} - for _, eip := range sb.EIPSet { - eipText := "" - eipText += eip.EIPId - for _, ip := range eip.EIPAddr { - eipText += fmt.Sprintf("/%s/%s", ip.IP, ip.OperatorName) - } - eipList = append(eipList, eipText) - } - row.EIP = strings.Join(eipList, "\n") - list = append(list, row) - } - base.PrintList(list, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - flags.StringSliceVar(&req.ShareBandwidthIds, "shared-bw-id", nil, "Resource ID of shared bandwidth instances to list") - - return cmd -} - -// NewCmdSharedBWResize ucloud shared-bw resize -func NewCmdSharedBWResize() *cobra.Command { - req := base.BizClient.NewResizeShareBandwidthRequest() - cmd := &cobra.Command{ - Use: "resize", - Short: "Resize shared bandwidth instance's bandwidth", - Long: "Resize shared bandwidth instance's bandwidth", - Run: func(c *cobra.Command, args []string) { - if *req.ShareBandwidth < 20 || *req.ShareBandwidth > 5000 { - base.Cxt.Printf("bandwidth should be between 20 and 5000. received %d\n", *req.ShareBandwidth) - return - } - req.ShareBandwidthId = sdk.String(base.PickResourceID(*req.ShareBandwidthId)) - _, err := base.BizClient.ResizeShareBandwidth(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("shared bandwidth[%s] resized to %dMb\n", *req.ShareBandwidthId, *req.ShareBandwidth) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ShareBandwidthId = flags.String("shared-bw-id", "", "Required. Resource ID of shared bandwidth instance to resize") - req.ShareBandwidth = flags.Int("bandwidth-mb", 0, "Required. Unit:Mb. resize to bandwidth value") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - command.SetCompletion(cmd, "shared-bw-id", func() []string { - list, _ := getAllSharedBW(*req.ProjectId, *req.Region) - return list - }) - - cmd.MarkFlagRequired("shared-bw-id") - cmd.MarkFlagRequired("bandwidth-mb") - - return cmd -} - -// NewCmdSharedBWDelete ucloud shared-bw delete -func NewCmdSharedBWDelete() *cobra.Command { - req := base.BizClient.NewReleaseShareBandwidthRequest() - ids := []string{} - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete shared bandwidth instance", - Long: "Delete shared bandwidth instance", - Run: func(c *cobra.Command, args []string) { - for _, id := range ids { - req.ShareBandwidthId = sdk.String(base.PickResourceID(id)) - _, err := base.BizClient.ReleaseShareBandwidth(req) - if err != nil { - base.HandleError(err) - continue - } - base.Cxt.Printf("shared bandwidth[%s] deleted\n", id) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&ids, "shared-bw-id", nil, "Required. Resource ID of shared bandwidth instances to delete") - req.EIPBandwidth = flags.Int("eip-bandwidth-mb", 1, "Optional. Bandwidth of the joined EIPs,after deleting the shared bandwidth instance") - req.PayMode = flags.String("traffic-mode", "", "Optional. The charge mode of joined EIPs after deleting the shared bandwidth. Accept values:Bandwidth,Traffic") - bindRegion(req, cmd) - bindProjectID(req, cmd) - command.SetCompletion(cmd, "shared-bw-id", func() []string { - list, _ := getAllSharedBW(*req.ProjectId, *req.Region) - return list - }) - command.SetFlagValues(cmd, "traffic-mode", "Bandwidth", "Traffic") - - cmd.MarkFlagRequired("shared-bw-id") - - return cmd -} - -func getAllSharedBW(project, region string) ([]string, error) { - req := base.BizClient.NewDescribeShareBandwidthRequest() - req.ProjectId = &project - req.Region = ®ion - resp, err := base.BizClient.DescribeShareBandwidth(req) - if err != nil { - return nil, err - } - list := []string{} - for _, item := range resp.DataSet { - list = append(list, item.ShareBandwidthId+"/"+item.Name) - } - return list, nil -} - -// NewCmdBandwidthPkg ucloud bw-pkg -func NewCmdBandwidthPkg() *cobra.Command { - cmd := &cobra.Command{ - Use: "pkg", - Short: "List, create and delete bandwidth package instances", - Long: "List, create and delete bandwidth package instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdBandwidthPkgCreate()) - cmd.AddCommand(NewCmdBandwidthPkgList(out)) - cmd.AddCommand(NewCmdBandwidthPkgDelete()) - return cmd -} - -// NewCmdBandwidthPkgCreate ucloud bw-pkg create -func NewCmdBandwidthPkgCreate() *cobra.Command { - var start, end *string - timeLayout := "2006-01-02/15:04:05" - ids := []string{} - req := base.BizClient.NewCreateBandwidthPackageRequest() - loc, _ := time.LoadLocation("Local") - cmd := &cobra.Command{ - Use: "create", - Short: "Create bandwidth package", - Long: "Create bandwidth package", - Example: "ucloud bw pkg create --eip-id eip-xxx --bandwidth-mb 20 --start-time 2018-12-15/09:20:00 --end-time 2018-12-16/09:20:00", - Run: func(c *cobra.Command, args []string) { - st, err := time.ParseInLocation(timeLayout, *start, loc) - if err != nil { - base.HandleError(err) - return - } - et, err := time.ParseInLocation(timeLayout, *end, loc) - if err != nil { - base.HandleError(err) - return - } - if st.Sub(time.Now()) < 0 { - base.Cxt.Println("start-time must be after the current time") - return - } - du := et.Unix() - st.Unix() - if du <= 0 { - base.Cxt.Println("end-time must be after the start-time") - return - } - req.EnableTime = sdk.Int(int(st.Unix())) - req.TimeRange = sdk.Int(int(du)) - - for _, id := range ids { - id = base.PickResourceID(id) - req.EIPId = &id - resp, err := base.BizClient.CreateBandwidthPackage(req) - if err != nil { - base.HandleError(err) - continue - } - base.Cxt.Printf("bandwidth package[%s] created for eip[%s]\n", resp.BandwidthPackageId, id) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - flags.StringSliceVar(&ids, "eip-id", nil, "Required. Resource ID of eip to be bound with created bandwidth package") - start = flags.String("start-time", "", "Required. The time to enable bandwidth package. Local time, for example '2018-12-25/08:30:00'") - end = flags.String("end-time", "", "Required. The time to disable bandwidth package. Local time, for example '2018-12-26/08:30:00'") - req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. bandwidth of the bandwidth package to create.Range [1,800]. Unit:'Mb'.") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - command.SetCompletion(cmd, "eip-id", func() []string { - return getAllEip(*req.ProjectId, *req.Region, []string{status.EIP_USED}, []string{status.EIP_CHARGE_BANDWIDTH}) - }) - - cmd.MarkFlagRequired("eip-id") - cmd.MarkFlagRequired("start-time") - cmd.MarkFlagRequired("end-time") - cmd.MarkFlagRequired("bandwidth-mb") - return cmd -} - -// BandwidthPkgRow 表格行 -type BandwidthPkgRow struct { - ResourceID string - EIP string - Bandwidth string - StartTime string - EndTime string -} - -// NewCmdBandwidthPkgList ucloud bw-pkg list -func NewCmdBandwidthPkgList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeBandwidthPackageRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List bandwidth packages", - Long: "List bandwidth packages", - Run: func(c *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeBandwidthPackage(req) - if err != nil { - base.HandleError(err) - return - } - list := []BandwidthPkgRow{} - for _, bp := range resp.DataSets { - row := BandwidthPkgRow{ - ResourceID: bp.BandwidthPackageId, - Bandwidth: strconv.Itoa(bp.Bandwidth) + "MB", - StartTime: common.FormatDateTime(bp.EnableTime), - EndTime: common.FormatDateTime(bp.DisableTime), - } - eip := bp.EIPId - for _, addr := range bp.EIPAddr { - eip += "/" + addr.IP + "/" + addr.OperatorName - } - row.EIP = eip - list = append(list, row) - } - base.PrintList(list, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset") - req.Limit = cmd.Flags().Int("limit", 50, "Optional. Limit range [0,10000000]") - - return cmd -} - -// NewCmdBandwidthPkgDelete ucloud bw-pkg delete -func NewCmdBandwidthPkgDelete() *cobra.Command { - ids := []string{} - req := base.BizClient.NewDeleteBandwidthPackageRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete bandwidth packages", - Long: "Delete bandwidth packages", - Example: "ucloud bw pkg delete --resource-id bwpack-xxx", - Run: func(c *cobra.Command, args []string) { - for _, id := range ids { - id := base.PickResourceID(id) - req.BandwidthPackageId = &id - _, err := base.BizClient.DeleteBandwidthPackage(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("bandwidth package[%s] deleted\n", id) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - flags.StringSliceVar(&ids, "resource-id", nil, "Required, Resource ID of bandwidth package to delete") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - return cmd -} diff --git a/cmd/eip_compat.go b/cmd/eip_compat.go deleted file mode 100644 index e20ad50e51..0000000000 --- a/cmd/eip_compat.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -// eip_compat.go is a cross-batch transition shim. The eip product moved to -// products/unet/internal/eip, but several commands still in package cmd call the -// old cmd-local eip helpers. These copies keep package cmd compiling until -// those consumers migrate. Logic is identical to the originals in the deleted -// cmd/eip.go (base.BizClient path). -// -// Consumers (kept until each migrates): -// - getAllEip: cmd/ulb.go, cmd/bandwidth.go, cmd/globalssh.go (batch2) -// - sbindEIP: cmd/ext.go (platform, batch2) -// - bindEIP: cmd/ulb.go (batch2) -// - unbindEIP: cmd/ext.go (platform, batch2) -// -// Private deps getEIPIDbyIP/fetchAllEip are included because the four public -// helpers above call them. getEIPLine stays in cmd/util.go (consumed by -// cmd/ulb.go), so it is intentionally NOT duplicated here. -// -// Remove this file once all the consumers above have migrated (batch2). - -import ( - "fmt" - "net" - "strings" - - "github.com/ucloud/ucloud-sdk-go/services/unet" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" -) - -func getEIPIDbyIP(ip net.IP, projectID, region string) (string, error) { - eipList, err := fetchAllEip(projectID, region) - if err != nil { - return "", err - } - for _, eip := range eipList { - for _, addr := range eip.EIPAddr { - if addr.IP == ip.String() { - return eip.EIPId, nil - } - } - } - return "", fmt.Errorf("IP[%s] not exist", ip.String()) -} - -func fetchAllEip(projectID, region string) ([]unet.UnetEIPSet, error) { - req := base.BizClient.NewDescribeEIPRequest() - list := []unet.UnetEIPSet{} - req.ProjectId = sdk.String(projectID) - req.Region = sdk.String(region) - for offset, step := 0, 100; ; offset += step { - req.Offset = &offset - req.Limit = &step - resp, err := base.BizClient.DescribeEIP(req) - if err != nil { - return nil, err - } - for i, size := 0, len(resp.EIPSet); i < size; i++ { - list = append(list, resp.EIPSet[i]) - } - if resp.TotalCount <= offset+step { - break - } - } - return list, nil -} - -// states,paymodes 为nil时,不作为过滤条件 -func getAllEip(projectID, region string, states, paymodes []string) []string { - list, err := fetchAllEip(projectID, region) - if err != nil { - return nil - } - strs := []string{} - for _, item := range list { - rightState := false - if states == nil { - rightState = true - } else { - for _, s := range states { - if item.Status == s { - rightState = true - } - } - } - - rightPayMode := false - if paymodes == nil { - rightPayMode = true - } else { - for _, m := range paymodes { - if item.PayMode == m { - rightPayMode = true - } - } - } - if !rightPayMode || !rightState { - continue - } - - ips := []string{} - for _, ip := range item.EIPAddr { - ips = append(ips, ip.IP) - } - strs = append(strs, item.EIPId+"/"+strings.Join(ips, ",")) - } - return strs -} - -func bindEIP(resourceID, resourceType, eipID, projectID, region *string) { - ip := net.ParseIP(*eipID) - if ip != nil { - id, err := getEIPIDbyIP(ip, *projectID, *region) - if err != nil { - base.HandleError(err) - } else { - *eipID = id - } - } - req := base.BizClient.NewBindEIPRequest() - req.ResourceId = resourceID - req.ResourceType = resourceType - req.EIPId = sdk.String(base.PickResourceID(*eipID)) - req.ProjectId = sdk.String(base.PickResourceID(*projectID)) - req.Region = region - _, err := base.BizClient.BindEIP(req) - if err != nil { - base.HandleError(err) - } else { - base.Cxt.Printf("bind EIP[%s] with %s[%s]\n", *req.EIPId, *req.ResourceType, *req.ResourceId) - } -} - -func sbindEIP(resourceID, resourceType, eipID, projectID, region *string) ([]string, error) { - logs := make([]string, 0) - ip := net.ParseIP(*eipID) - if ip != nil { - id, err := getEIPIDbyIP(ip, *projectID, *region) - if err != nil { - base.HandleError(err) - } else { - *eipID = id - } - } - req := base.BizClient.NewBindEIPRequest() - req.ResourceId = resourceID - req.ResourceType = resourceType - req.EIPId = sdk.String(base.PickResourceID(*eipID)) - req.ProjectId = sdk.String(base.PickResourceID(*projectID)) - req.Region = region - logs = append(logs, fmt.Sprintf("api: BindEIP, request: %v", base.ToQueryMap(req))) - _, err := base.BizClient.BindEIP(req) - if err != nil { - logs = append(logs, fmt.Sprintf("bind eip failed: %v", err)) - return logs, err - } - logs = append(logs, fmt.Sprintf("bind eip[%s] with %s[%s] successfully", *req.EIPId, *req.ResourceType, *req.ResourceId)) - return logs, nil -} - -func unbindEIP(resourceID, resourceType, eipID, projectID, region string) ([]string, error) { - logs := make([]string, 0) - eipID = base.PickResourceID(eipID) - ip := net.ParseIP(eipID) - if ip != nil { - id, err := getEIPIDbyIP(ip, projectID, region) - if err != nil { - base.HandleError(err) - } else { - eipID = id - } - } - req := base.BizClient.NewUnBindEIPRequest() - req.ResourceId = &resourceID - req.ResourceType = &resourceType - req.EIPId = &eipID - req.ProjectId = sdk.String(base.PickResourceID(projectID)) - req.Region = ®ion - logs = append(logs, fmt.Sprintf("api: UnBindEIP, request: %v", base.ToQueryMap(req))) - _, err := base.BizClient.UnBindEIP(req) - if err != nil { - logs = append(logs, fmt.Sprintf("unbind eip failed: %v", err)) - return logs, err - } - logs = append(logs, fmt.Sprintf("unbind eip[%s] with %s[%s] successfully", *req.EIPId, *req.ResourceType, *req.ResourceId)) - return logs, nil -} diff --git a/cmd/ext.go b/cmd/ext.go index c4fa656fe3..46ac7d36fc 100644 --- a/cmd/ext.go +++ b/cmd/ext.go @@ -17,6 +17,7 @@ package cmd import ( "errors" "fmt" + "net" "strings" "github.com/spf13/cobra" @@ -25,6 +26,7 @@ import ( "github.com/ucloud/ucloud-cli/model/status" "github.com/ucloud/ucloud-cli/pkg/command" "github.com/ucloud/ucloud-sdk-go/services/uhost" + "github.com/ucloud/ucloud-sdk-go/services/unet" sdk "github.com/ucloud/ucloud-sdk-go/ucloud" ) @@ -72,7 +74,7 @@ func NewCmdExtUHostSwitchEIP() *cobra.Command { for _, idname := range uhostIDs { uhostID := base.PickResourceID(idname) logs = append(logs, fmt.Sprintf("describe uhost instance by uhostID %s", uhostID)) - ins, err := describeUHostByID(uhostID, project, region, zone) + ins, err := extDescribeUHostByID(uhostID, project, region, zone) if err != nil { errStr := fmt.Sprintf("describe uhost %s failed: %v", uhostID, err) base.HandleError(errors.New(errStr)) @@ -136,7 +138,7 @@ func NewCmdExtUHostSwitchEIP() *cobra.Command { fmt.Println(eipRet) //绑定新EIP - slogs, err2 := sbindEIP(&uhostID, sdk.String("uhost"), &eipID, &project, ®ion) + slogs, err2 := extAttachEIPWithLogs(&uhostID, sdk.String("uhost"), &eipID, &project, ®ion) logs = append(logs, slogs...) if err2 != nil { base.HandleError(fmt.Errorf("bind new eip %s failed: %v", eipID, err2)) @@ -145,7 +147,7 @@ func NewCmdExtUHostSwitchEIP() *cobra.Command { fmt.Printf("bound eip %s with uhost %s\n", eipID, uhostID) if unbind { - slogs, err := unbindEIP(uhostID, "uhost", ip.IPId, project, region) + slogs, err := extDetachEIPWithLogs(uhostID, "uhost", ip.IPId, project, region) logs = append(logs, slogs...) if err != nil { base.HandleError(fmt.Errorf("unbind eip %s failed: %v", ip.IPId, err)) @@ -198,10 +200,145 @@ func NewCmdExtUHostSwitchEIP() *cobra.Command { bindZoneEmptyS(&zone, ®ion, cmd) command.SetCompletion(cmd, "uhost-id", func() []string { - return getUhostList([]string{status.HOST_RUNNING, status.HOST_STOPPED, status.HOST_FAIL}, project, region, zone) + return extUHostList([]string{status.HOST_RUNNING, status.HOST_STOPPED, status.HOST_FAIL}, project, region, zone) }) cmd.MarkFlagRequired("uhost-id") return cmd } + +func extDescribeUHostByID(uhostID, projectID, region, zone string) (interface{}, error) { + req := base.BizClient.NewDescribeUHostInstanceRequest() + req.UHostIds = []string{uhostID} + req.ProjectId = &projectID + req.Region = ®ion + req.Zone = &zone + + resp, err := base.BizClient.DescribeUHostInstance(req) + if err != nil { + return nil, err + } + if len(resp.UHostSet) < 1 { + return nil, fmt.Errorf("uhost [%s] does not exist", uhostID) + } + + return &resp.UHostSet[0], nil +} + +func extUHostList(states []string, project, region, zone string) []string { + req := base.BizClient.NewDescribeUHostInstanceRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + req.Limit = sdk.Int(50) + resp, err := base.BizClient.DescribeUHostInstance(req) + if err != nil { + return nil + } + list := []string{} + for _, host := range resp.UHostSet { + if states != nil { + for _, s := range states { + if host.State == s { + list = append(list, host.UHostId+"/"+strings.Replace(host.Name, " ", "-", -1)) + } + } + } else { + list = append(list, host.UHostId+"/"+strings.Replace(host.Name, " ", "-", -1)) + } + } + return list +} + +func extEIPIDByIP(ip net.IP, projectID, region string) (string, error) { + eipList, err := extFetchAllEIP(projectID, region) + if err != nil { + return "", err + } + for _, eip := range eipList { + for _, addr := range eip.EIPAddr { + if addr.IP == ip.String() { + return eip.EIPId, nil + } + } + } + return "", fmt.Errorf("IP[%s] not exist", ip.String()) +} + +func extFetchAllEIP(projectID, region string) ([]unet.UnetEIPSet, error) { + req := base.BizClient.NewDescribeEIPRequest() + list := []unet.UnetEIPSet{} + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + for offset, step := 0, 100; ; offset += step { + req.Offset = &offset + req.Limit = &step + resp, err := base.BizClient.DescribeEIP(req) + if err != nil { + return nil, err + } + for i, size := 0, len(resp.EIPSet); i < size; i++ { + list = append(list, resp.EIPSet[i]) + } + if resp.TotalCount <= offset+step { + break + } + } + return list, nil +} + +func extAttachEIPWithLogs(resourceID, resourceType, eipID, projectID, region *string) ([]string, error) { + logs := make([]string, 0) + ip := net.ParseIP(*eipID) + if ip != nil { + id, err := extEIPIDByIP(ip, *projectID, *region) + if err != nil { + base.HandleError(err) + } else { + *eipID = id + } + } + req := base.BizClient.NewBindEIPRequest() + req.ResourceId = resourceID + req.ResourceType = resourceType + req.EIPId = sdk.String(base.PickResourceID(*eipID)) + req.ProjectId = sdk.String(base.PickResourceID(*projectID)) + req.Region = region + logs = append(logs, fmt.Sprintf("api: BindEIP, request: %v", base.ToQueryMap(req))) + _, err := base.BizClient.BindEIP(req) + if err != nil { + logs = append(logs, fmt.Sprintf("bind eip failed: %v", err)) + return logs, err + } + logs = append(logs, fmt.Sprintf("bind eip[%s] with %s[%s] successfully", *req.EIPId, *req.ResourceType, *req.ResourceId)) + return logs, nil +} + +func extDetachEIPWithLogs(resourceID, resourceType, eipID, projectID, region string) ([]string, error) { + logs := make([]string, 0) + eipID = base.PickResourceID(eipID) + ip := net.ParseIP(eipID) + if ip != nil { + id, err := extEIPIDByIP(ip, projectID, region) + if err != nil { + base.HandleError(err) + } else { + eipID = id + } + } + req := base.BizClient.NewUnBindEIPRequest() + req.ResourceId = &resourceID + req.ResourceType = &resourceType + req.EIPId = &eipID + req.ProjectId = sdk.String(base.PickResourceID(projectID)) + req.Region = ®ion + logs = append(logs, fmt.Sprintf("api: UnBindEIP, request: %v", base.ToQueryMap(req))) + _, err := base.BizClient.UnBindEIP(req) + if err != nil { + logs = append(logs, fmt.Sprintf("unbind eip failed: %v", err)) + return logs, err + } + logs = append(logs, fmt.Sprintf("unbind eip[%s] with %s[%s] successfully", *req.EIPId, *req.ResourceType, *req.ResourceId)) + return logs, nil +} diff --git a/cmd/ext_compat_test.go b/cmd/ext_compat_test.go new file mode 100644 index 0000000000..bf65af6ea6 --- /dev/null +++ b/cmd/ext_compat_test.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "os" + "strings" + "testing" +) + +func TestExtCommandDoesNotDependOnCompatShims(t *testing.T) { + src, err := os.ReadFile("ext.go") + if err != nil { + t.Fatalf("read ext.go: %v", err) + } + for _, helper := range []string{ + "describeUHostByID(", + "getUhostList(", + "sbindEIP(", + "unbindEIP(", + } { + if strings.Contains(string(src), helper) { + t.Fatalf("ext.go must not call compat helper %s", helper) + } + } + for _, path := range []string{"eip_compat.go", "uhost_compat.go"} { + if _, err := os.Stat(path); err == nil { + t.Fatalf("%s must be removed after ext owns its SDK helpers", path) + } else if !os.IsNotExist(err) { + t.Fatalf("stat %s: %v", path, err) + } + } +} diff --git a/cmd/globalssh.go b/cmd/globalssh.go deleted file mode 100644 index 4970c67638..0000000000 --- a/cmd/globalssh.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - "net" - "strings" - - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-sdk-go/services/pathx" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/pkg/command" -) - -// NewCmdGssh ucloud gssh -func NewCmdGssh() *cobra.Command { - cmd := &cobra.Command{ - Use: "gssh", - Short: "Create,list,update and delete globalssh instance", - Long: `Create,list,update and delete globalssh instance`, - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdGsshList(out)) - cmd.AddCommand(NewCmdGsshCreate()) - cmd.AddCommand(NewCmdGsshDelete()) - cmd.AddCommand(NewCmdGsshModify()) - cmd.AddCommand(NewCmdGsshArea()) - return cmd -} - -// GSSHRow gssh表格行 -type GSSHRow struct { - ResourceID string - SSHServerIP string - AcceleratingDomain string - SSHServerLocation string - SSHPort int - GlobalSSHPort int - Remark string - InstanceType string -} - -// NewCmdGsshList ucloud gssh list -func NewCmdGsshList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeGlobalSSHInstanceRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List all GlobalSSH instances", - Long: `List all GlobalSSH instances`, - Example: "ucloud gssh list", - Run: func(cmd *cobra.Command, args []string) { - var areaMap = map[string]string{ - "洛杉矶": "LosAngeles", - "新加坡": "Singapore", - "香港": "HongKong", - "东京": "Tokyo", - "华盛顿": "Washington", - "法兰克福": "Frankfurt", - "拉各斯": "Lagos", - } - - resp, err := base.BizClient.DescribeGlobalSSHInstance(req) - if err != nil { - base.HandleError(err) - } else { - list := make([]GSSHRow, 0) - for _, gssh := range resp.InstanceSet { - row := GSSHRow{} - row.ResourceID = gssh.InstanceId - row.SSHServerIP = gssh.TargetIP - row.AcceleratingDomain = gssh.AcceleratingDomain - row.SSHPort = gssh.Port - row.GlobalSSHPort = gssh.GlobalSSHPort - row.Remark = gssh.Remark - row.InstanceType = gssh.InstanceType - if val, ok := areaMap[gssh.Area]; ok { - row.SSHServerLocation = val - } else { - row.SSHServerLocation = gssh.Area - } - list = append(list, row) - } - base.PrintList(list, out) - } - }, - } - cmd.Flags().SortFlags = false - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional. Assign region") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. Assign project-id") - return cmd -} - -// NewCmdGsshArea ucloud gssh area -func NewCmdGsshArea() *cobra.Command { - req := base.BizClient.NewDescribeGlobalSSHAreaRequest() - cmd := &cobra.Command{ - Use: "location", - Short: "List SSH server locations and covered areas", - Long: "List SSH server locations and covered areas", - Run: func(cmd *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeGlobalSSHArea(req) - if err != nil { - base.HandleError(err) - return - } - list := make([]GsshLocation, 0) - for _, item := range resp.AreaSet { - row := GsshLocation{ - AirportCode: item.AreaCode, - SSHServerLocation: areaCodeMap[item.AreaCode], - } - regionLabels := make([]string, 0) - for _, region := range item.RegionSet { - regionLabels = append(regionLabels, base.RegionLabel[region]) - } - row.CoveredArea = strings.Join(regionLabels, ",") - list = append(list, row) - } - - base.PrintTable(list, []string{"AirportCode", "SSHServerLocation", "CoveredArea"}) - }, - } - return cmd -} - -// GsshLocation 服务地点和覆盖区域 -type GsshLocation struct { - AirportCode string - SSHServerLocation string - CoveredArea string -} - -var areaCodeMap = map[string]string{ - "LAX": "LosAngeles", - "SIN": "Singapore", - "HKG": "HongKong", - "HND": "Tokyo", - "IAD": "Washington", - "FRA": "Frankfurt", - "LOS": "Lagos", -} - -// NewCmdGsshCreate ucloud gssh create -func NewCmdGsshCreate() *cobra.Command { - var targetIP *net.IP - req := base.BizClient.NewCreateGlobalSSHInstanceRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create GlobalSSH instance", - Long: "Create GlobalSSH instance", - Example: "ucloud gssh create --location Washington --target-ip 8.8.8.8", - Run: func(cmd *cobra.Command, args []string) { - port := *req.Port - for code, area := range areaCodeMap { - if area == *req.AreaCode { - *req.AreaCode = code - } - } - if port < 1 || port > 65535 || port == 80 || port == 443 || port == 65123 { - base.Cxt.Println("The port number should be between 1 and 65535, and cannot be 80, 443 or 65123") - return - } - req.TargetIP = sdk.String(targetIP.String()) - resp, err := base.BizClient.CreateGlobalSSHInstance(req) - if err != nil { - base.HandleError(err) - } else { - base.Cxt.Printf("gssh[%s] created\n", resp.InstanceId) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.AreaCode = cmd.Flags().String("location", "", "Required. Location of the source server. See 'ucloud gssh location'") - targetIP = cmd.Flags().IP("target-ip", nil, "Required. IP of the source server. Required") - bindProjectID(req, cmd) - req.Port = cmd.Flags().Int("port", 22, "Optional. Port of The SSH service between 1 and 65535. Do not use ports such as 80, 443 or 65123.") - req.Remark = cmd.Flags().String("remark", "", "Optional. Remark of your GlobalSSH.") - req.ChargeType = cmd.Flags().String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly(requires access)") - req.Quantity = cmd.Flags().Int("quantity", 1, "Optional. The duration of the instance. N years/months.") - req.InstanceType = cmd.Flags().String("instance-type", "", "Optional. Possible values: 'Ultimate','Enterprise', 'Basic', 'Free'(Default value)") - req.ForwardRegion = cmd.Flags().String("forward-region", "", "Optional. You can select one of 'cn-bj2','cn-sh2','cn-gd' When instance-type is 'Basic'") - req.BandwidthPackage = cmd.Flags().Int("bandwidth-package", 0, "Optional. You can set one of 0, 20, 40 When instance-type is 'Ultimate'") - cmd.MarkFlagRequired("location") - cmd.MarkFlagRequired("target-ip") - command.SetFlagValues(cmd, "location", "LosAngeles", "Singapore", "Lagos", "HongKong", "Tokyo", "Washington", "Frankfurt") - command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic", "Trial") - command.SetFlagValues(cmd, "bandwidth-package", "0", "20", "40") - command.SetFlagValues(cmd, "forward-region", "cn-bj2", "cn-sh2", "cn-gd") - command.SetFlagValues(cmd, "instance-type", "Free", "Basic", "Enterprise", "Ultimate") - command.SetCompletion(cmd, "target-ip", func() []string { - eips := getAllEip(*req.ProjectId, base.ConfigIns.Region, nil, nil) - for idx, eip := range eips { - eips[idx] = strings.SplitN(eip, "/", 2)[1] - } - return eips - }) - return cmd -} - -// NewCmdGsshDelete ucloud gssh delete -func NewCmdGsshDelete() *cobra.Command { - var req = base.BizClient.NewDeleteGlobalSSHInstanceRequest() - var gsshIds *[]string - var cmd = &cobra.Command{ - Use: "delete", - Short: "Delete GlobalSSH instance", - Long: "Delete GlobalSSH instance", - Example: "ucloud gssh delete --gssh-id uga-xx1 --id uga-xx2", - Run: func(cmd *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, id := range *gsshIds { - req.InstanceId = sdk.String(base.PickResourceID(id)) - _, err := base.BizClient.DeleteGlobalSSHInstance(req) - if err != nil { - base.HandleError(err) - } else { - base.Cxt.Printf("gssh[%s] deleted\n", id) - } - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - gsshIds = cmd.Flags().StringSlice("gssh-id", make([]string, 0), "Required. ID of the GlobalSSH instances you want to delete. Multiple values specified by multiple commas") - bindProjectID(req, cmd) - cmd.MarkFlagRequired("gssh-id") - command.SetCompletion(cmd, "gssh-id", func() []string { - return getAllGsshIDNames(*req.ProjectId) - }) - return cmd -} - -// NewCmdGsshModify ucloud gssh modify -func NewCmdGsshModify() *cobra.Command { - gsshModifyPortReq := base.BizClient.NewModifyGlobalSSHPortRequest() - gsshModifyRemarkReq := base.BizClient.NewModifyGlobalSSHRemarkRequest() - project := base.ConfigIns.ProjectID - gsshIDs := []string{} - cmd := &cobra.Command{ - Use: "update", - Short: "Update GlobalSSH instance", - Long: "Update GlobalSSH instance, including port and remark attribute", - Example: "ucloud gssh update --gssh-id uga-xxx --port 22", - Run: func(cmd *cobra.Command, args []string) { - gsshModifyPortReq.ProjectId = sdk.String(project) - gsshModifyRemarkReq.ProjectId = sdk.String(project) - if *gsshModifyPortReq.Port == 0 && *gsshModifyRemarkReq.Remark == "" { - base.Cxt.Println("Error, port or remark required") - } - if *gsshModifyPortReq.Port != 0 { - port := *gsshModifyPortReq.Port - if port <= 1 || port >= 65535 || port == 80 || port == 443 || port == 65123 { - base.Cxt.Println("The port number should be between 1 and 65535, and cannot be equal to 80, 443 or 65123") - return - } - for _, idname := range gsshIDs { - gsshModifyPortReq.InstanceId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.ModifyGlobalSSHPort(gsshModifyPortReq) - if err != nil { - base.HandleError(err) - } else { - base.Cxt.Printf("gssh[%s]'s port updated\n", *gsshModifyPortReq.InstanceId) - } - } - } - if *gsshModifyRemarkReq.Remark != "" { - for _, idname := range gsshIDs { - gsshModifyRemarkReq.InstanceId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.ModifyGlobalSSHRemark(gsshModifyRemarkReq) - if err != nil { - base.HandleError(err) - } else { - base.Cxt.Printf("gssh[%s]'s remark updated\n", *gsshModifyRemarkReq.InstanceId) - } - } - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&gsshIDs, "gssh-id", nil, "Required. ResourceID of your GlobalSSH instances") - bindProjectIDS(&project, cmd) - gsshModifyPortReq.Port = cmd.Flags().Int("port", 0, "Optional. Port of SSH service.") - gsshModifyRemarkReq.Remark = cmd.Flags().String("remark", "", "Optional. Remark of your GlobalSSH.") - cmd.MarkFlagRequired("gssh-id") - command.SetCompletion(cmd, "gssh-id", func() []string { - return getAllGsshIDNames(project) - }) - return cmd -} - -func getAllGssh(project string) ([]pathx.GlobalSSHInfo, error) { - req := base.BizClient.NewDescribeGlobalSSHInstanceRequest() - req.ProjectId = &project - resp, err := base.BizClient.DescribeGlobalSSHInstance(req) - if err != nil { - return nil, err - } - return resp.InstanceSet, nil -} - -func getAllGsshIDNames(project string) []string { - gsshs, err := getAllGssh(project) - if err != nil { - return nil - } - list := []string{} - for _, gssh := range gsshs { - list = append(list, fmt.Sprintf("%s/%s", gssh.InstanceId, gssh.TargetIP)) - } - return list -} diff --git a/cmd/pathx.go b/cmd/pathx.go deleted file mode 100644 index 8cea32f7c8..0000000000 --- a/cmd/pathx.go +++ /dev/null @@ -1,1466 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - "strconv" - "strings" - - ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" - "github.com/ucloud/ucloud-sdk-go/services/pathx" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error" - - "github.com/spf13/cobra" - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/pkg/command" - "github.com/ucloud/ucloud-cli/ux" -) - -// NewCmdPathx ucloud pathx -func NewCmdPathx() *cobra.Command { - cmd := &cobra.Command{ - Use: "pathx", - Short: "Manipulate uga and upath instances", - Long: "Manipulate uga and upath instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdUGA()) - cmd.AddCommand(NewCmdUpath()) - cmd.AddCommand(NewCmdUGA3Create(out)) - cmd.AddCommand(NewCmdUGA3Delete(out)) - cmd.AddCommand(NewCmdUGA3Modify(out)) - cmd.AddCommand(NewCmdUGA3List(out)) - cmd.AddCommand(NewCmdPathxPrice(out)) - cmd.AddCommand(NewCmdPathxArea(out)) - - return cmd -} - -// create pathx instance -func NewCmdUGA3Create(out io.Writer) *cobra.Command { - createPathxReq := base.BizClient.NewCreateUGA3InstanceRequest() - createPathxPortReq := base.BizClient.NewCreateUGA3PortRequest() - spinner := ux.NewDotSpinner(out) - var ports, originPorts []string - protocol := "tcp" - createCmd := &cobra.Command{ - Use: "create", - Short: "Create the pathx resource and port", - Long: "Create global unified access acceleration configuration item", - Example: "ucloud pathx create --bandwidth 10 --area-code DXB" + - "--charge-type Month --quantity 4 --accel Global --origin-ip 110.111.111.111" + - "--protocol TCP --port 30654 --origin-port 30564", - Run: func(cmd *cobra.Command, args []string) { - spinner.Start("The pathx resource creating") - if *createPathxReq.OriginIPList == "" && *createPathxReq.OriginDomain == "" { - spinner.Fail(fmt.Errorf("The origin-ip and origin-domain cannot be empty at the same time")) - return - } - portIntList := make([]int, 0) - originPortIntList := make([]int, 0) - if len(ports) > 0 || len(originPorts) > 0 { - if len(ports) == 0 { - spinner.Fail(fmt.Errorf("The port cannot be empty.")) - return - } else if len(originPorts) == 0 { - spinner.Fail(fmt.Errorf("The origin-port cannot be empty.")) - return - } - if strings.EqualFold(protocol, "UDP") { - spinner.Fail(fmt.Errorf("The udp protocol is temporarily not supported for create")) - return - } else if !strings.EqualFold(protocol, "TCP") && - !strings.EqualFold(protocol, "UDP") { - spinner.Fail(fmt.Errorf("The value of protocol input error,please input 'TCP' or 'UDP',and the value entered is not case sensitive")) - return - } - tcpPortList, err := formatPortList(ports) - if err != nil { - spinner.Fail(err) - return - } - // tcpPorts convert to []int - for _, tcpPort := range tcpPortList { - port, _ := strconv.Atoi(tcpPort) - portIntList = append(portIntList, port) - } - rsTcpPortList, err := formatPortList(originPorts) - if err != nil { - spinner.Fail(err) - return - } - // rsTcpPorts convert to []int - for _, rsTcpPort := range rsTcpPortList { - rsPort, _ := strconv.Atoi(rsTcpPort) - originPortIntList = append(originPortIntList, rsPort) - } - if len(portIntList) != len(originPortIntList) { - spinner.Fail(fmt.Errorf("The number of port must be consistent with the number of origin-port.")) - return - } else if len(portIntList) >= 10 { - spinner.Fail(fmt.Errorf("The number of port cannot greater than or equals to 10")) - return - } - } - if strings.EqualFold(*createPathxReq.ChargeType, "Month") { - *createPathxReq.Quantity = 0 - } else if *createPathxReq.Quantity <= 0 { - spinner.Fail(fmt.Errorf("If the value of charge-type is 'Year' or 'Hour',the value of quantity must be greater than 0")) - return - } - - switch strings.ToLower(*createPathxReq.ChargeType) { - case "hour": - *createPathxReq.ChargeType = "Dynamic" - case "month": - *createPathxReq.ChargeType = "Month" - case "year": - *createPathxReq.ChargeType = "Year" - } - // post create pathx resource - createUGA3InstanceResp, err := base.BizClient.CreateUGA3Instance(createPathxReq) - if err != nil { - spinner.Fail(err) - return - } - if createUGA3InstanceResp == nil || createUGA3InstanceResp.InstanceId == "" || - &createUGA3InstanceResp.InstanceId == nil { - spinner.Fail(fmt.Errorf("An unknown error occurred and could not be created successfully.")) - return - } - spinner.Stop() - - // CreatePathxPort - if len(portIntList) > 0 && len(originPortIntList) > 0 { - createPathxPortReq.InstanceId = &createUGA3InstanceResp.InstanceId - createPathxPortReq.SetRegionRef(createPathxReq.GetRegionRef()) - createPathxPortReq.SetProjectIdRef(createPathxReq.GetProjectIdRef()) - createPathxPortReq.SetZoneRef(createPathxReq.GetZoneRef()) - spinner.Start("The pathx port creating") - // Temporary support tcp protocol - if strings.EqualFold(protocol, "TCP") { - createPathxPortReq.TCP = portIntList - createPathxPortReq.TCPRS = originPortIntList - } - _, err := base.BizClient.CreateUGA3Port(createPathxPortReq) - if err != nil { - spinner.Fail(err) - return - } - spinner.Stop() - } - - fmt.Fprintf(out, "The resource is created, and the resource ID is: %s\n", createUGA3InstanceResp.InstanceId) - }, - } - flags := createCmd.Flags() - flags.SortFlags = false - - bindProjectID(createPathxReq, createCmd) - bindRegion(createPathxReq, createCmd) - bindZone(createPathxReq, createCmd) - - createPathxReq.Bandwidth = flags.String("bandwidth", "0", - "Required. Shared bandwidth of the resource") - flags.String("area-code", "", - "Optional. When it is empty,the nearest zone will be selected based on the origin-domain and origin-ip. "+ - "Acceptable values:'BKK'(曼谷),'DXB'(迪拜),'FRA'(法兰克福),'SGN'(胡志明市),'HKG'(香港),'CGK'(雅加达),'LOS'(拉各斯),'LHR'(伦敦),'LAX'(洛杉矶),"+ - "'MNL'(马尼拉),'DME'(莫斯科),'BOM'(孟买),'MSP'(圣保罗),'ICN'(首尔),'PVG'(上海),'SIN'(新加坡),'NRT'(东京),'IAD'(华盛顿),'TPE'(台北)") - - createPathxReq.ChargeType = flags.String("charge-type", "", - "Optional. Payment method,its value is not case sensitive,acceptable values:'Year',pay yearly;'Month',pay monthly;'Hour', pay hourly") - createPathxReq.Quantity = flags.Int("quantity", 1, - "Optional. The duration of the pathx resource, the value cannot be less than or equal to 0. N years/months") - createPathxReq.AccelerationArea = flags.String("accel", "", - "Optional. The default value is 'Global'(全球). "+ - "Other acceptable values:'AP'(亚太);'EU'(欧洲);'ME'(中东);'OA'(大洋洲);'AF'(非洲);'NA'(北美洲);'SA'(南美洲)") - createPathxReq.OriginIPList = flags.String("origin-ip", "", - "Optional. But when the origin-domain is empty,it cannot be empty. If multiple values exist,please split by ','. For example '0.0.0.0,110.110.100.100'") - createPathxReq.OriginDomain = flags.String("origin-domain", "", - "Optional. But when the origin-ip is empty,it cannot be empty") - - flags.StringSliceVar(&ports, "port", nil, - "Optional. Disable 65123 port,the port can be multiple,please split by ',' for example 80,3000-3010. "+ - "The number of port must be consistent with the number of origin-port,and the number cannot greater than or equals to 10") - flags.StringSliceVar(&originPorts, "origin-port", nil, - "Optional. The origin-port can be multiple,please split by ',' for example 80,3000-3010."+ - "The number of origin-port must be consistent with the number of port") - flags.StringVar(&protocol, "protocol", "TCP", "Its values can be TCP and UDP, but currently only supports TCP") - - createCmd.MarkFlagRequired("bandwidth") - command.SetFlagValues(createCmd, "area-code", - "BKK", "DXB", "FRA", "SGN", "HKG", "CGK", "LOS", "LHR", "LAX", "MNL", "DME", "BOM", "MSP", "ICN", "PVG", "SIN", "NRT", "IAD", "TPE") - command.SetFlagValues(createCmd, "charge-type", "Month", "Year", "Hour") - command.SetFlagValues(createCmd, "accel", "Global", "AP", "EU", "ME", "OA", "AF", "NA", "SA") - command.SetFlagValues(createCmd, "protocol", "TCP", "UDP") - return createCmd -} - -// delete pathx instance -func NewCmdUGA3Delete(out io.Writer) *cobra.Command { - deleteUga3Req := base.BizClient.NewDeleteUGA3InstanceRequest() - deleteUga3PortReq := base.BizClient.NewDeleteUGA3PortRequest() - spinner := ux.NewDotSpinner(out) - var yes *bool - var instanceId string - removeCmd := &cobra.Command{ - Use: "delete", - Short: "Delete the pathx resource and port", - Long: "Delete the pathx resource and port", - Example: "ucloud pathx delete --id uga3-xxx", - Run: func(cmd *cobra.Command, args []string) { - if !*yes { - sure, err := ux.Prompt("Are you sure you want to delete this resource ?") - if err != nil { - base.Cxt.Println(err) - return - } - if !sure { - return - } - } - spinner.Start(fmt.Sprintf("Starting delete the pathx[%s] resource port", instanceId)) - deleteUga3PortReq.InstanceId = &instanceId - _, deletePortErr := base.BizClient.DeleteUGA3Port(deleteUga3PortReq) - if deletePortErr != nil { - spinner.Fail(deletePortErr) - return - } - spinner.Stop() - - spinner.Start(fmt.Sprintf("Starting delete the pathx[%s] resource", instanceId)) - deleteUga3Req.InstanceId = &instanceId - deleteUga3Req.SetProjectIdRef(deleteUga3PortReq.GetProjectIdRef()) - deleteUga3Req.SetRegionRef(deleteUga3PortReq.GetRegionRef()) - deleteUga3Req.SetZoneRef(deleteUga3PortReq.GetZoneRef()) - _, err := base.BizClient.DeleteUGA3Instance(deleteUga3Req) - if err != nil { - spinner.Fail(err) - return - } - spinner.Stop() - }, - } - flags := removeCmd.Flags() - flags.SortFlags = false - flags.StringVar(&instanceId, "id", "", - "Required. It is the resource ID of pathx, and the deletion will be performed according to this") - - bindProjectID(deleteUga3PortReq, removeCmd) - bindRegion(deleteUga3PortReq, removeCmd) - bindZone(deleteUga3PortReq, removeCmd) - - removeCmd.MarkFlagRequired("id") - yes = removeCmd.Flags().BoolP("yes", "y", false, "Optional. Do not prompt for confirmation.") - command.SetCompletion(removeCmd, "id", func() []string { - return getPathxList(*deleteUga3PortReq.ProjectId, *deleteUga3PortReq.Region, *deleteUga3PortReq.Zone) - }) - return removeCmd -} - -func getPathxList(project, region, zone string) []string { - getInstanceReq := base.BizClient.NewDescribeUGA3InstanceRequest() - getInstanceReq.ProjectId = sdk.String(project) - getInstanceReq.Region = sdk.String(region) - getInstanceReq.Zone = sdk.String(zone) - getInstanceReq.Limit = sdk.Int(50) - resp, err := base.BizClient.DescribeUGA3Instance(getInstanceReq) - if err != nil { - base.HandleError(err) - return nil - } - list := make([]string, 0) - for _, item := range resp.ForwardInstanceInfos { - list = append(list, item.InstanceId) - } - return list -} - -// modify UGA3 instance -func NewCmdUGA3Modify(out io.Writer) *cobra.Command { - modifyBandwidthReq := base.BizClient.NewModifyUGA3BandwidthRequest() - modifyOriginInfoReq := base.BizClient.NewModifyUGA3OriginInfoRequest() - modifyInstanceReq := base.BizClient.NewModifyUGA3InstanceRequest() - modifyPortReq := base.BizClient.NewModifyUGA3PortRequest() - - spinner := ux.NewDotSpinner(out) - var tcpPorts, rsTcpPorts []string - var instanceId string - protocol := "TCP" - modifyCmd := &cobra.Command{ - Use: "modify", - Short: "Modify the pathx associated information. Example bandwidth or origin information or resource information", - Long: "Support modify bandwidth,origin information,resource information,port", - Example: "ucloud pathx modify --id uga3-xxx --bandwidth 1 --origin-ip 127.0.0.1 --name Pathx测试 --remark 加速资源 --protocol TCP --port 30010 --origin-port 39999", - Run: func(cmd *cobra.Command, args []string) { - modifyBandwidthReq.InstanceId = &instanceId - modifyInstanceReq.InstanceId = &instanceId - modifyOriginInfoReq.InstanceId = &instanceId - modifyPortReq.InstanceId = &instanceId - if *modifyBandwidthReq.Bandwidth != 0 { - spinner.Start(fmt.Sprintf("Starting modify the pathx[%s] bandwidth", instanceId)) - if *modifyBandwidthReq.Bandwidth < 1 || *modifyBandwidthReq.Bandwidth > 100 { - spinner.Fail(fmt.Errorf("The value of bandwidth size cannot be less than 1 and cannot be greater than 100")) - return - } - modifyBandwidthReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) - modifyBandwidthReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) - modifyBandwidthReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) - _, err := base.BizClient.ModifyUGA3Bandwidth(modifyBandwidthReq) - if err != nil { - spinner.Fail(err) - return - } - spinner.Stop() - } - if *modifyOriginInfoReq.OriginIPList != "" || *modifyOriginInfoReq.OriginDomain != "" { - spinner.Start(fmt.Sprintf("Starting modify the pathx[%s] origin information", instanceId)) - modifyOriginInfoReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) - modifyOriginInfoReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) - modifyOriginInfoReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) - _, err := base.BizClient.ModifyUGA3OriginInfo(modifyOriginInfoReq) - if err != nil { - spinner.Fail(err) - return - } - spinner.Stop() - } - if *modifyInstanceReq.Name != "" || *modifyInstanceReq.Remark != "" { - spinner.Start(fmt.Sprintf("Starting modify the pathx[%s] resource information", instanceId)) - _, err := base.BizClient.ModifyUGA3Instance(modifyInstanceReq) - if err != nil { - spinner.Fail(err) - return - } - spinner.Stop() - } - - // modify port - tcpPortIntList := make([]int, 0) - rsTcpPortIntList := make([]int, 0) - if len(tcpPorts) > 0 || len(rsTcpPorts) > 0 { - spinner.Start(fmt.Sprintf("Starting modify the pathx[%s] port", instanceId)) - if len(tcpPorts) == 0 { - spinner.Fail(fmt.Errorf("The port cannot be empty.")) - return - } else if len(rsTcpPorts) == 0 { - spinner.Fail(fmt.Errorf("The origin-port cannot be empty.")) - return - } - if strings.EqualFold(protocol, "UDP") { - spinner.Fail(fmt.Errorf("The udp protocol is temporarily not supported for create")) - return - } else if !strings.EqualFold(protocol, "TCP") && - !strings.EqualFold(protocol, "UDP") { - spinner.Fail(fmt.Errorf("The value of protocol input error,please input 'TCP' or 'UDP',and the value entered is not case sensitive")) - return - } - tcpPortList, err := formatPortList(tcpPorts) - if err != nil { - spinner.Fail(err) - return - } - // tcpPorts convert to []int - for _, tcpPort := range tcpPortList { - port, _ := strconv.Atoi(tcpPort) - tcpPortIntList = append(tcpPortIntList, port) - } - rsTcpPortList, err := formatPortList(rsTcpPorts) - if err != nil { - spinner.Fail(err) - return - } - // rsTcpPorts convert to []int - for _, rsTcpPort := range rsTcpPortList { - rsPort, _ := strconv.Atoi(rsTcpPort) - rsTcpPortIntList = append(rsTcpPortIntList, rsPort) - } - if len(tcpPortIntList) != len(rsTcpPortIntList) { - spinner.Fail(fmt.Errorf("The number of port must be consistent with the number of origin-port.")) - return - } else if len(tcpPortIntList) >= 10 { - spinner.Fail(fmt.Errorf("The number of port cannot greater than or equals to 10")) - return - } - } - // ModifyUGA3Port - if len(tcpPortIntList) > 0 && len(rsTcpPortIntList) > 0 { - if strings.EqualFold(protocol, "TCP") { - modifyPortReq.TCP = tcpPortIntList - modifyPortReq.TCPRS = rsTcpPortIntList - } - modifyPortReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) - modifyPortReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) - modifyPortReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) - _, err := base.BizClient.ModifyUGA3Port(modifyPortReq) - if err != nil { - base.HandleError(err) - return - } - spinner.Stop() - } - }, - } - flags := modifyCmd.Flags() - flags.SortFlags = false - - bindProjectID(modifyInstanceReq, modifyCmd) - bindRegion(modifyInstanceReq, modifyCmd) - bindZone(modifyInstanceReq, modifyCmd) - - flags.StringVar(&instanceId, "id", "", - "Required. It is the resource ID of the pathx") - modifyBandwidthReq.Bandwidth = flags.Int("bandwidth", 0, - "Optional. The bandwidth size. Its value range [1-100],no update if no value is specified") - modifyOriginInfoReq.OriginIPList = flags.String("origin-ip", "", - "Optional. Acceleration source IP. If multiple values exist,please split by ','") - modifyOriginInfoReq.OriginDomain = flags.String("origin-domain", "", - "Optional. Acceleration source domain name. Only 1 domain is supported") - modifyInstanceReq.Name = flags.String("name", "", - "Optional. Accelerate configuration resource name. If its value is not filled in or an empty string is not updated") - modifyInstanceReq.Remark = flags.String("remark", "", - "Optional. It will be modified if its value is not empty") - - flags.StringSliceVar(&tcpPorts, "port", nil, - "Optional. Disable 65123 port,the port can be multiple,please split by ',' for example 80,3000-3010. "+ - "The number of port must be consistent with the number of origin-port,and the number cannot greater than or equals to 10") - flags.StringSliceVar(&rsTcpPorts, "origin-port", nil, - "Optional. The origin-port can be multiple,please split by ',' for example 80,3000-3010."+ - "The number of origin-port must be consistent with the number of port") - flags.StringVar(&protocol, "protocol", "TCP", "Its values can be TCP and UDP, but currently only supports TCP") - - modifyCmd.MarkFlagRequired("id") - command.SetCompletion(modifyCmd, "id", func() []string { - return getPathxList(*modifyInstanceReq.ProjectId, *modifyInstanceReq.Region, *modifyInstanceReq.Zone) - }) - return modifyCmd -} - -// ucloud pathx list -func NewCmdUGA3List(out io.Writer) *cobra.Command { - getPathxListReq := base.BizClient.NewDescribeUGA3InstanceRequest() - var instanceId string - var detail bool - listCmd := &cobra.Command{ - Use: "list", - Short: "List all the pathx resource of project", - Long: "List all the pathx resource of project", - Example: "'ucloud pathx list or ucloud pathx list --id uga-xxx or ucloud pathx list --id uga-xxx --detail", - Run: func(cmd *cobra.Command, args []string) { - if len(instanceId) > 0 { - getPathxListReq.InstanceId = &instanceId - } - resp, err := base.BizClient.DescribeUGA3Instance(getPathxListReq) - if err != nil { - base.HandleError(err) - return - } - forwardInfos := resp.ForwardInstanceInfos - // may be no UGA3 instance under the current project - if len(forwardInfos) == 0 { - base.HandleError(fmt.Errorf("No pathx resource found under the current project.")) - return - } - // print pathx detail information - if detail && len(instanceId) > 0 { - instanceInfo := forwardInfos[0] - printPathxDetail(instanceInfo, out) - return - } - list := make([]Uga3DescribeRow, 0) - for _, item := range forwardInfos { - row := Uga3DescribeRow{} - row.ResourceID = item.InstanceId - row.CName = item.CName - row.Name = item.Name - row.AccelerationArea = item.AccelerationArea - row.Bandwidth = item.Bandwidth - row.OriginAreaCode = item.OriginAreaCode - row.IPList = strings.Join(item.IPList, ",") - row.Domain = item.Domain - row.CreateTime = common.FormatDate(item.CreateTime) - - var egressIps []string - for _, egressIp := range item.EgressIpList { - egressIps = append(egressIps, fmt.Sprintf("%s:%s", egressIp.Area, egressIp.IP)) - } - row.EgressIpList = strings.Join(egressIps, "|") - - list = append(list, row) - } - base.PrintTable(list, []string{ - "ResourceID", "CName", "Name", "AccelerationArea", "OriginAreaCode", - "Bandwidth", "EgressIpList", "IPList", "Domain", "CreateTime"}) - }, - } - flags := listCmd.Flags() - flags.SortFlags = false - - bindProjectID(getPathxListReq, listCmd) - bindRegion(getPathxListReq, listCmd) - bindZone(getPathxListReq, listCmd) - - flags.StringVar(&instanceId, "id", "", "Required. It is the resource ID of pathx resource") - flags.BoolVar(&detail, "detail", false, "Optional. If it is specified,the details will be printed") - command.SetCompletion(listCmd, "id", func() []string { - return getPathxList(*getPathxListReq.ProjectId, *getPathxListReq.Region, *getPathxListReq.Zone) - }) - return listCmd -} - -func printPathxDetail(instanceInfo pathx.ForwardInfo, out io.Writer) { - attrs := []base.DescribeTableRow{ - {Attribute: "ResourceID", Content: instanceInfo.InstanceId}, - {Attribute: "CName", Content: instanceInfo.CName}, - {Attribute: "Name", Content: instanceInfo.Name}, - {Attribute: "AccelerationArea", Content: instanceInfo.AccelerationArea}, - {Attribute: "AccelerationAreaName", Content: instanceInfo.AccelerationAreaName}, - {Attribute: "OriginAreaCode", Content: instanceInfo.OriginAreaCode}, - {Attribute: "OriginArea", Content: instanceInfo.OriginArea}, - {Attribute: "Bandwidth", Content: strconv.Itoa(instanceInfo.Bandwidth)}, - {Attribute: "ChargeType", Content: instanceInfo.ChargeType}, - {Attribute: "IPList", Content: strings.Join(instanceInfo.IPList, ",")}, - {Attribute: "Domain", Content: instanceInfo.Domain}, - {Attribute: "Remark", Content: instanceInfo.Remark}, - {Attribute: "CreateTime", Content: common.FormatDateTime(instanceInfo.CreateTime)}, - {Attribute: "ExpireTime", Content: common.FormatDateTime(instanceInfo.ExpireTime)}, - } - for _, attr := range attrs { - fmt.Fprintf(out, "%-22s: %s", attr.Attribute, attr.Content) - fmt.Println() - } - // 加速节点列表 - if len(instanceInfo.AccelerationAreaInfos) > 0 { - fmt.Println() - fmt.Fprintln(out, "Acceleration area list:") - for _, area := range instanceInfo.AccelerationAreaInfos { - fmt.Fprintf(out, "%s:%5s\n", "Area", area.AccelerationArea) - areaList := make([]PathxOptionalAreaRow, 0) - for _, node := range area.AccelerationNodes { - row := PathxOptionalAreaRow{ - AreaCode: node.AreaCode, - Area: node.Area, - FlagUnicode: node.FlagUnicode, - FlagEmoji: node.FlagEmoji, - } - areaList = append(areaList, row) - } - base.PrintTable(areaList, []string{"AreaCode", "Area", "FlagUnicode", "FlagEmoji"}) - } - } - // 回源出口IP地址 - if len(instanceInfo.EgressIpList) > 0 { - fmt.Println() - fmt.Fprintln(out, "Egress ip list:") - egressIpList := make([]EgressIpInfoRow, 0) - for _, egressIp := range instanceInfo.EgressIpList { - row := EgressIpInfoRow{ - IP: egressIp.IP, - Area: egressIp.Area, - } - egressIpList = append(egressIpList, row) - } - base.PrintTable(egressIpList, []string{"Area", "IP"}) - } - if len(instanceInfo.PortSets) > 0 { - fmt.Println() - fmt.Fprintln(out, "Port list:") - portList := make([]Uga3PortRow, 0) - for _, portItem := range instanceInfo.PortSets { - row := Uga3PortRow{ - Protocol: portItem.Protocol, - Port: portItem.Port, - RSPort: portItem.RSPort, - } - portList = append(portList, row) - } - base.PrintTable(portList, []string{"Protocol", "Port", "RSPort"}) - } -} - -// ucloud pathx-price -func NewCmdPathxPrice(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "price", - Short: "List all the acceleration area price", - Long: "List all the acceleration area price", - } - cmd.AddCommand(NewPathxPriceList(out)) - // temporary not supports - //cmd.AddCommand(NewPathxPriceUpgradeInfo()) - return cmd -} - -// ucloud pathx price list -func NewPathxPriceList(out io.Writer) *cobra.Command { - priceReq := base.BizClient.NewGetUGA3PriceRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List all the pathx acceleration area price", - Long: "List all the pathx acceleration area price", - Example: "ucloud pathx price list --bandwidth 10 --area-code BKK --charge-type Month", - Run: func(cmd *cobra.Command, args []string) { - if strings.EqualFold(*priceReq.ChargeType, "Month") { - *priceReq.Quantity = 0 - } else if *priceReq.Quantity <= 0 { - base.HandleError(fmt.Errorf("If the value of charge-type is 'Year' or 'Hour',its value must be greater than 0")) - return - } - - switch strings.ToLower(*priceReq.ChargeType) { - case "hour": - *priceReq.ChargeType = "Dynamic" - case "month": - *priceReq.ChargeType = "Month" - case "year": - *priceReq.ChargeType = "Year" - } - - response, err := base.BizClient.GetUGA3Price(priceReq) - if err != nil { - base.HandleError(err) - return - } - list := make([]UGA3PriceRow, 0) - priceList := response.UGA3Price - if len(priceList) == 0 { - base.HandleError(fmt.Errorf("Not found acceleration area price information.")) - return - } - //fmt.Fprintf(out,"Aceeleration area price information (unit:¥) :") - for _, info := range priceList { - row := UGA3PriceRow{ - AccelerationBandwidthPrice: fmt.Sprintf("%s%s", "¥", strconv.FormatFloat(info.AccelerationBandwidthPrice, 'g', 12, 64)), - //AccelerationAreaName: info.AccelerationAreaName, - AccelerationForwarderPrice: fmt.Sprintf("%s%s", "¥", strconv.FormatFloat(info.AccelerationForwarderPrice, 'g', 12, 64)), - AccelerationArea: info.AccelerationArea, - } - list = append(list, row) - } - base.PrintTable(list, []string{"AccelerationArea", "AccelerationBandwidthPrice", "AccelerationForwarderPrice"}) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(priceReq, cmd) - bindRegion(priceReq, cmd) - bindZone(priceReq, cmd) - - priceReq.Bandwidth = flags.Int("bandwidth", 1, - "Required. The bandwidth of acceleration area to get price") - priceReq.AreaCode = flags.String("area-code", "", - "Required. The area-code of acceleration area to get price") - priceReq.Quantity = flags.Int("quantity", 1, - "Optional. When the value of the charge-type is 'Month',its default value is 0,"+ - "if the value of charge-type is 'Year' or 'Hour',its value must be greater than 0") - priceReq.ChargeType = flags.String("charge-type", "", - "Optional. Its value is not case sensitive,acceptable values:'Year',pay yearly;'Month',pay monthly;'Hour',pay hourly") - priceReq.AccelerationArea = flags.String("accel", "", - "Optional. The acceleration-area to get price") - - _ = cmd.MarkFlagRequired("bandwidth") - _ = cmd.MarkFlagRequired("area-code") - command.SetFlagValues(cmd, "area-code", "BKK", "DXB", "FRA", "SGN", "HKG", "CGK", "LOS", "LHR", "LAX", "MNL", "DME", "BOM", "MSP", "ICN", "PVG", "SIN", "NRT", "IAD", "TPE") - command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Hour") - command.SetFlagValues(cmd, "accel", "Global", "AP", "EU", "ME", "OA", "AF", "NA", "SA") - return cmd -} - -// ucloud pathx-area -func NewCmdPathxArea(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "area", - Short: "List origin area or acceleration area information", - Long: "List origin area or acceleration area information", - } - cmd.AddCommand(NewCmdPathxAreaList(out)) - return cmd -} - -// ucloud pathx area list -func NewCmdPathxAreaList(out io.Writer) *cobra.Command { - areaGetReq := base.BizClient.NewDescribeUGA3AreaRequest() - optimizationReq := base.BizClient.NewDescribeUGA3OptimizationRequest() - var timeRange, accelerationArea, originDomain, originIp string - var noAccel bool - cmd := &cobra.Command{ - Use: "list", - Short: "List origin area or acceleration area information", - Long: "Provide optional flags to get the optional list of global access source stations", - Example: "ucloud pathx area list --origin-ip 0.0.0.0 --origin-domain test.com", - Run: func(cmd *cobra.Command, args []string) { - if len(originDomain) == 0 && len(originIp) == 0 { - response, err := base.BizClient.DescribeUGA3Area(areaGetReq) - if err != nil { - base.HandleError(err) - return - } - forwardAreas := response.AreaSet - if len(forwardAreas) == 0 { - base.HandleError(fmt.Errorf("Not found the origin area list")) - return - } - areasGroup := make(map[string][]PathxOptionalAreaRow) - for _, item := range forwardAreas { - if areasGroup[item.ContinentCode] == nil { - areasGroup[item.ContinentCode] = make([]PathxOptionalAreaRow, 0) - } - areasGroup[item.ContinentCode] = append(areasGroup[item.ContinentCode], PathxOptionalAreaRow{ - AreaCode: item.AreaCode, - Area: item.Area, - CountryCode: item.CountryCode, - FlagUnicode: item.FlagUnicode, - FlagEmoji: item.FlagEmoji, - }) - } - fmt.Fprintln(out, "Origin areas :") - for area := range areasGroup { - fmt.Fprintf(out, "ContinentCode: %s\n", area) - rows := areasGroup[area] - base.PrintTable(rows, []string{"AreaCode", "Area", "CountryCode", "FlagUnicode", "FlagEmoji"}) - fmt.Println() - } - return - } - areaGetReq.Domain = &originDomain - areaGetReq.IPList = &originIp - response, err := base.BizClient.DescribeUGA3Area(areaGetReq) - if err != nil { - base.HandleError(err) - return - } - forwardAreas := response.AreaSet - if len(forwardAreas) == 0 { - base.HandleError(fmt.Errorf("Not found the origin area list")) - return - } - // recommend one area for user - forwardArea := forwardAreas[0] - - fmt.Fprintf(out, "Recommend origin area:(%s)\n", forwardArea.ContinentCode) - areas := make([]PathxOptionalAreaRow, 0) - areas = append(areas, PathxOptionalAreaRow{ - AreaCode: forwardArea.AreaCode, - Area: forwardArea.Area, - CountryCode: forwardArea.CountryCode, - FlagUnicode: forwardArea.FlagUnicode, - FlagEmoji: forwardArea.FlagEmoji, - }) - base.PrintTable(areas, []string{"AreaCode", "Area", "CountryCode", "FlagUnicode", "FlagEmoji"}) - fmt.Println() - - // display acceleration areas - if !noAccel { - areaCode := forwardAreas[0].AreaCode - optimizationReq.AreaCode = &areaCode - optimizationReq.AccelerationArea = &accelerationArea - optimizationReq.TimeRange = &timeRange - optimizationReq.SetProjectIdRef(areaGetReq.GetProjectIdRef()) - optimizationReq.SetRegionRef(areaGetReq.GetRegionRef()) - optimizationReq.SetZoneRef(areaGetReq.GetZoneRef()) - optimizationResponse, err := base.BizClient.DescribeUGA3Optimization(optimizationReq) - if err != nil { - base.HandleError(err) - return - } - accelerationInfos := optimizationResponse.AccelerationInfos - if len(accelerationInfos) == 0 { - base.HandleError(fmt.Errorf("Not found the acceleration area information.")) - return - } - fmt.Fprintf(out, "Acceleration areas :\n") - for _, item := range accelerationInfos { - // User did not provide acceleration-area flag - if len(accelerationArea) == 0 { - fmt.Fprintf(out, "%s(%s):\n", item.AccelerationName, item.AccelerationArea) - } - list := make([]PathxOptimizationRow, 0) - nodeDelays := item.NodeInfo - for _, node := range nodeDelays { - row := PathxOptimizationRow{} - row.Area = node.Area - row.AreaCode = node.AreaCode - row.CountryCode = node.CountryCode - row.FlagUnicode = node.FlagUnicode - row.FlagEmoji = node.FlagEmoji - row.Latency = fmt.Sprintf("%s%s", strconv.FormatFloat(node.Latency, 'g', 12, 64), "ms") - row.LatencyWAN = fmt.Sprintf("%s%s", strconv.FormatFloat(node.LatencyInternet, 'g', 12, 64), "ms") - row.LatencyPathX = fmt.Sprintf("%s%s", strconv.FormatFloat(node.LatencyOptimization, 'g', 12, 64), "%") - row.Loss = fmt.Sprintf("%s%s", strconv.FormatFloat(node.Loss, 'g', 12, 64), "%") - row.LossWAN = fmt.Sprintf("%s%s", strconv.FormatFloat(node.LossInternet, 'g', 12, 64), "%") - row.LossPathx = fmt.Sprintf("%s%s", strconv.FormatFloat(node.LossOptimization, 'g', 12, 64), "%") - list = append(list, row) - } - base.PrintTable(list, []string{"AreaCode", "Area", "CountryCode", "FlagUnicode", "FlagEmoji", - "Latency", "LatencyWAN", "LatencyPathX", "Loss", "LossWAN", "LossPathx"}) - } - return - } - - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(areaGetReq, cmd) - bindRegion(areaGetReq, cmd) - bindZone(areaGetReq, cmd) - - flags.StringVar(&timeRange, "time-range", "", - "Optional. The default value is 1 day. Acceptable values:'Hour','Day','Week',and its value is not case sensitive") - flags.StringVar(&accelerationArea, "accel", "", - "Optional. The acceleration area,acceptable values:'Global','AP','EU','ME','OA','AF','NA','SA'") - flags.StringVar(&originDomain, "origin-domain", "", - "Optional. If you fill in the IP or domain name, a region will be recommended as the first in the return list") - flags.StringVar(&originIp, "origin-ip", "", - "Optional. If you fill in the IP or domain name, a region will be recommended as the first IP collection of the source station in the return list, "+ - "split by ',' example:110.10.10.1,111.100.0.10 ") - flags.BoolVar(&noAccel, "no-accel", false, - "Optional. If it is specified,the print result will not be displayed acceleration areas") - - return cmd -} - -type UGA3PriceRow struct { - // 加速大区代码 - AccelerationArea string - // 加速大区名称 - AccelerationAreaName string - // 转发配置价格 - AccelerationForwarderPrice string - // 加速配置带宽价格 - AccelerationBandwidthPrice string -} - -// describe UGA3 instance information row -type Uga3DescribeRow struct { - // 加速配置ID - ResourceID string - // 加速域名 - CName string - // 加速实例名称 - Name string - // 加速区域 - AccelerationArea string - // 加速区域名称 - AccelerationAreaName string - // 回源出口IP地址 - EgressIpList string - // 购买的带宽值 - Bandwidth int - // 备注 - Remark string - // 源站中文名 - OriginArea string - // 源站AreaCode - OriginAreaCode string - // 资源创建时间 - CreateTime string - // 资源过期时间 - ExpireTime string - // 计费方式 - ChargeType string - // 源站IP列表,多个值由半角英文逗号相隔 - IPList string - // 源站域名 - Domain string -} - -// pathx port print row -type Uga3PortRow struct { - // 转发协议,枚举值["TCP","UDP","HTTPHTTP","HTTPSHTTP","HTTPSHTTPS","WSWS","WSSWS","WSSWSS"]。TCP和UDP代表四层转发,其余为七层转发。 - Protocol string - // 源站服务器监听的端口号 - RSPort int - // 加速端口 - Port int -} - -// pathx price upgrade-info print row -type PathxUpdatePriceRow struct { - // 实例ID - InstanceId string - // 带宽 - Bandwidth int - // 更新价格 - UpdatePrice float64 -} - -// pathx optimization print row -type PathxOptimizationRow struct { - // 加速大区名称 - AccelerationName string - // 加速大区代码 - AccelerationArea string - // 加速区域 - Area string - // 加速区域Code - AreaCode string - // 国家代码 - CountryCode string - // 国旗Code - FlagUnicode string - // 国旗Emoji - FlagEmoji string - // 加速延迟 - Latency string - // 公网延迟 - LatencyWAN string - // 加速提升比例 - LatencyPathX string - // 加速后丢包率 - Loss string - // 原始丢包率 - LossWAN string - // 丢包下降比例 - LossPathx string -} - -// row for print of pathx area -type PathxOptionalAreaRow struct { - AreaCode string - Area string - CountryCode string - FlagUnicode string - FlagEmoji string - ContinentCode string -} - -// row for print of egressIpList -type EgressIpInfoRow struct { - // 线路出口EIP - IP string - // 线路出口机房代号 - Area string -} - -// NewCmdUpath ucloud pathx upath -func NewCmdUpath() *cobra.Command { - cmd := &cobra.Command{ - Use: "upath", - Short: "List pathx upath instances", - Long: "List pathx upath instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdUpathList(out)) - return cmd -} - -type upathRow struct { - ResourceID string - UPathName string - AcceleratedPath string - BoundUGA string -} - -// NewCmdUpathList ucloud pathx upath list -func NewCmdUpathList(out io.Writer) *cobra.Command { - req := base.BizClient.PrivatePathxClient.NewDescribeUPathRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "list upath instances", - Long: "list upath instances", - Run: func(c *cobra.Command, args []string) { - resp, err := base.BizClient.PrivatePathxClient.DescribeUPath(req) - if err != nil { - base.HandleError(err) - return - } - list := make([]upathRow, 0) - for _, ins := range resp.UPathSet { - row := upathRow{ - ResourceID: ins.UPathId, - UPathName: ins.Name, - AcceleratedPath: fmt.Sprintf("%s->%s %dM", ins.LineFromName, ins.LineToName, ins.Bandwidth), - } - ids := []string{} - for _, ga := range ins.UGAList { - ids = append(ids, ga.UGAId) - } - row.BoundUGA = strings.Join(ids, ",") - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(req, cmd) - req.UPathId = flags.String("upath-id", "", "Optional. Resource ID of upath instance to list") - - return cmd -} - -// NewCmdUGA ucloud uga -func NewCmdUGA() *cobra.Command { - cmd := &cobra.Command{ - Use: "uga", - Short: "Create,list,update and delete pathx uga instances", - Long: `Create,list,update and delete pathx uga instances`, - } - - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdUGAList(out)) - cmd.AddCommand(NewCmdUGADescribe(out)) - cmd.AddCommand(NewCmdUGACreate(out)) - cmd.AddCommand(NewCmdUGADelete(out)) - cmd.AddCommand(NewCmdUGAAddPort(out)) - cmd.AddCommand(NewCmdUGARemovePort(out)) - - return cmd -} - -// UGARow 表格行 -type UGARow struct { - ResourceID string - UGAName string - CName string - Origin string - AcceleratedPath string -} - -var protocols = []string{"tcp", "udp"} - -// NewCmdUGAList ucloud uga list -func NewCmdUGAList(out io.Writer) *cobra.Command { - req := base.BizClient.PrivatePathxClient.NewDescribeUGAInstanceRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "list uga instances", - Long: "list uga instances", - Run: func(c *cobra.Command, args []string) { - *req.UGAId = base.PickResourceID(*req.UGAId) - resp, err := base.BizClient.PrivatePathxClient.DescribeUGAInstance(req) - if err != nil { - base.HandleError(err) - return - } - - list := make([]UGARow, 0) - for _, ins := range resp.UGAList { - row := UGARow{ - ResourceID: ins.UGAId, - UGAName: ins.UGAName, - CName: ins.CName, - Origin: fmt.Sprintf("%s%s", strings.Join(ins.IPList, ","), ins.Domain), - } - row.AcceleratedPath = getUpathStr(ins.UPathSet) - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.UGAId = flags.String("uga-id", "", "Optional. Resource ID of uga instance") - bindProjectID(req, cmd) - - return cmd -} - -func getUpathStr(list []ppathx.UPathSet) string { - paths := make([]string, 0) - for _, p := range list { - paths = append(paths, fmt.Sprintf("%s->%s %dM", p.LineFromName, p.LineToName, p.Bandwidth)) - } - return strings.Join(paths, "\n") -} - -func getOutIPStr(list []ppathx.OutPublicIpInfo) string { - strs := make([]string, 0) - for _, p := range list { - strs = append(strs, fmt.Sprintf("%s %s", p.IP, base.RegionLabel[p.Area])) - } - return strings.Join(strs, "\n") -} - -func getPortStr(list []ppathx.UGAATask) string { - strs := make([]string, 0) - for _, t := range list { - strs = append(strs, fmt.Sprintf("%s %d", t.Protocol, t.Port)) - } - return strings.Join(strs, "\n") -} - -// NewCmdUGADescribe ucloud uga describe -func NewCmdUGADescribe(out io.Writer) *cobra.Command { - req := base.BizClient.PrivatePathxClient.NewDescribeUGAInstanceRequest() - cmd := &cobra.Command{ - Use: "describe", - Short: "Display detail informations about uga instances", - Long: "Display detail informations about uga instances", - Run: func(c *cobra.Command, args []string) { - *req.UGAId = base.PickResourceID(*req.UGAId) - resp, err := base.BizClient.PrivatePathxClient.DescribeUGAInstance(req) - if err != nil { - base.HandleError(err) - return - } - if len(resp.UGAList) != 1 { - base.HandleError(fmt.Errorf("uga[%s] may not exist", *req.UGAId)) - return - } - - ins := resp.UGAList[0] - list := []base.DescribeTableRow{ - {Attribute: "ResourceID", Content: ins.UGAId}, - {Attribute: "UGAName", Content: ins.UGAName}, - {Attribute: "Origin", Content: fmt.Sprintf("%s%s", ins.Domain, strings.Join(ins.IPList, ","))}, - {Attribute: "CName", Content: ins.CName}, - {Attribute: "AcceleratedPath", Content: getUpathStr(ins.UPathSet)}, - {Attribute: "OutIP", Content: getOutIPStr(ins.OutPublicIpList)}, - {Attribute: "Port", Content: getPortStr(ins.TaskSet)}, - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance") - bindProjectID(req, cmd) - - cmd.MarkFlagRequired("uga-id") - command.SetCompletion(cmd, "uga-id", func() []string { - return getUGAIDList(*req.ProjectId) - }) - - return cmd -} - -func formatPortList(userPorts []string) ([]string, error) { - portList := make([]string, 0) - for _, port := range userPorts { - if strings.Contains(port, "-") { - portRange := strings.Split(port, "-") - if len(portRange) != 2 { - return nil, fmt.Errorf("port %s is invalid, it's pattern should be like 3000-3100", port) - } - min, err := strconv.Atoi(portRange[0]) - if err != nil { - return nil, fmt.Errorf("parse port failed: %v", err) - } - max, err := strconv.Atoi(portRange[1]) - if err != nil { - return nil, fmt.Errorf("parse port failed: %v", err) - } - - for i := min; i <= max; i++ { - portList = append(portList, strconv.Itoa(i)) - } - } else { - portList = append(portList, port) - } - } - return portList, nil -} - -// NewCmdUGACreate ucloud uga create -func NewCmdUGACreate(out io.Writer) *cobra.Command { - var protocol string - var ports, lines []string - req := base.BizClient.PrivatePathxClient.NewCreateUGAInstanceRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create uga instance", - Long: "Create uga instance", - Example: "ucloud pathx uga create --name testcli1 --protocol tcp --origin-location 中国 --origin-domain lixiaojun.xyz --upath-id upath-auvfexxx/test_0 --port 80-90,100,110-115", - Run: func(c *cobra.Command, args []string) { - if *req.IPList == "" && *req.Domain == "" { - fmt.Fprintln(out, "origin-ip and origin-domain can not be both empty") - return - } - - portList, err := formatPortList(ports) - if err != nil { - base.HandleError(err) - return - } - - switch strings.ToLower(protocol) { - case "tcp": - req.TCP = portList - case "udp": - req.UDP = portList - case "http": - req.HTTP = portList - case "https": - req.HTTPS = portList - default: - fmt.Fprintf(out, "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) - } - - resp, err := base.BizClient.PrivatePathxClient.CreateUGAInstance(req) - if err != nil { - if uErr, ok := err.(uerr.Error); ok && uErr.Code() == 33756 { - fmt.Fprintf(out, "The number of ports added exceeds the limit(50). We recommend that you could reduce the number of ports, then create an uga instance, \nand then add the remaining ports by executing 'ucloud pathx uga add-port --protocol %s --uga-id --port '\n", protocol) - } - return - } - - fmt.Fprintf(out, "uga[%s] created\n", resp.UGAId) - - for _, path := range lines { - p := base.PickResourceID(path) - bindReq := base.BizClient.PrivatePathxClient.NewUGABindUPathRequest() - bindReq.ProjectId = req.ProjectId - bindReq.UGAId = sdk.String(resp.UGAId) - bindReq.UPathId = &p - _, err := base.BizClient.PrivatePathxClient.UGABindUPath(bindReq) - if err != nil { - fmt.Fprintf(out, "bind uga[%s] and upath[%s] failed: %v\n", resp.UGAId, p, err) - } else { - fmt.Fprintf(out, "bound uga[%s] and upath[%s]\n", resp.UGAId, p) - } - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(req, cmd) - req.Name = flags.String("name", "", "Required. Name of uga instance to create") - req.IPList = flags.String("origin-ip", "", "Required if origin-domain is empty. IP address of origin. multiple IP address separated by ','") - req.Domain = flags.String("origin-domain", "", "Required if origin-ip is empty.") - req.Location = flags.String("origin-location", "", "Required. Location of origin ip or domain. accpet valeus:'中国','洛杉矶','法兰克福','中国香港','雅加达','孟买','东京','莫斯科','新加坡','曼谷','中国台北','华盛顿','首尔'") - flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) - flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") - flags.StringSliceVar(&lines, "upath-id", nil, "Required. Accelerated path to bind with the uga instance to create. multiple upath-id separated by ','; see 'ucloud pathx upath list") - - cmd.MarkFlagRequired("name") - cmd.MarkFlagRequired("origin-location") - cmd.MarkFlagRequired("protocol") - cmd.MarkFlagRequired("port") - cmd.MarkFlagRequired("upath-id") - - command.SetFlagValues(cmd, "origin-location", "中国", "洛杉矶", "法兰克福", "中国香港", "雅加达", "孟买", "东京", "莫斯科", "新加坡", "曼谷", "中国台北", "华盛顿", "首尔") - command.SetFlagValues(cmd, "protocol", protocols...) - command.SetCompletion(cmd, "upath-id", func() []string { - return getUpathIDList(*req.ProjectId) - }) - - return cmd -} - -// NewCmdUGADelete ucloud uga delete -func NewCmdUGADelete(out io.Writer) *cobra.Command { - idNames := []string{} - req := base.BizClient.PrivatePathxClient.NewDeleteUGAInstanceRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete uga instances", - Long: "Delete uga instances", - Run: func(c *cobra.Command, args []string) { - for _, idname := range idNames { - id := base.PickResourceID(idname) - req.UGAId = &id - _, err := base.BizClient.PrivatePathxClient.DeleteUGAInstance(req) - if err != nil { - base.HandleError(err) - } else { - fmt.Fprintf(out, "uga[%s] deleted\n", id) - } - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(req, cmd) - flags.StringSliceVar(&idNames, "uga-id", nil, "Required. Resource ID of uga instances to delete. Multiple resource ids separated by comma") - - cmd.MarkFlagRequired("uga-id") - command.SetCompletion(cmd, "uga-id", func() []string { - return getUGAIDList(*req.ProjectId) - }) - - return cmd -} - -// NewCmdUGAAddPort ucloud pathx uga add-port -func NewCmdUGAAddPort(out io.Writer) *cobra.Command { - var ports []string - var protocol string - req := base.BizClient.NewAddUGATaskRequest() - cmd := &cobra.Command{ - Use: "add-port", - Short: "Add port for uga instance", - Long: "Add port for uga instance", - Run: func(c *cobra.Command, args []string) { - portList, err := formatPortList(ports) - if err != nil { - base.HandleError(err) - return - } - - switch strings.ToLower(protocol) { - case "tcp": - req.TCP = portList - case "udp": - req.UDP = portList - case "http": - req.HTTP = portList - case "https": - req.HTTPS = portList - default: - fmt.Fprintf(out, "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) - } - - *req.UGAId = base.PickResourceID(*req.UGAId) - _, err = base.BizClient.AddUGATask(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "port %v added\n", ports) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(req, cmd) - req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance to add port") - flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) - flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") - - cmd.MarkFlagRequired("protocol") - cmd.MarkFlagRequired("uga-id") - cmd.MarkFlagRequired("port") - - command.SetFlagValues(cmd, "protocol", protocols...) - command.SetCompletion(cmd, "uga-id", func() []string { - return getUGAIDList(*req.ProjectId) - }) - - return cmd -} - -// NewCmdUGARemovePort ucloud pathx uga delete-port -func NewCmdUGARemovePort(out io.Writer) *cobra.Command { - var ports []string - var protocol string - req := base.BizClient.NewDeleteUGATaskRequest() - cmd := &cobra.Command{ - Use: "delete-port", - Short: "Delete port for uga instance", - Long: "Delete port for uga instance", - Run: func(c *cobra.Command, args []string) { - portList, err := formatPortList(ports) - if err != nil { - base.HandleError(err) - return - } - - switch strings.ToLower(protocol) { - case "tcp": - req.TCP = portList - case "udp": - req.UDP = portList - case "http": - req.HTTP = portList - case "https": - req.HTTPS = portList - default: - fmt.Fprintf(out, "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) - } - - *req.UGAId = base.PickResourceID(*req.UGAId) - _, err = base.BizClient.DeleteUGATask(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "port %v deleted\n", ports) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindProjectID(req, cmd) - req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance to delete port") - flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) - flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") - - cmd.MarkFlagRequired("protocol") - cmd.MarkFlagRequired("uga-id") - cmd.MarkFlagRequired("port") - - command.SetFlagValues(cmd, "protocol", protocols...) - command.SetCompletion(cmd, "uga-id", func() []string { - return getUGAIDList(*req.ProjectId) - }) - - return cmd -} - -func getUGAList(project string) ([]ppathx.UGAAInfo, error) { - req := base.BizClient.PrivatePathxClient.NewDescribeUGAInstanceRequest() - req.ProjectId = &project - resp, err := base.BizClient.PrivatePathxClient.DescribeUGAInstance(req) - if err != nil { - return nil, err - } - return resp.UGAList, nil -} - -func getUGAIDList(project string) []string { - list, err := getUGAList(project) - if err != nil { - base.LogError(fmt.Sprintf("getUDGAIDList filed:%v", err)) - return nil - } - strs := make([]string, 0) - for _, ins := range list { - strs = append(strs, fmt.Sprintf("%s/%s", ins.UGAId, ins.UGAName)) - } - return strs -} - -func getUpathList(project string) ([]ppathx.UPathInfo, error) { - req := base.BizClient.PrivatePathxClient.NewDescribeUPathRequest() - req.ProjectId = &project - resp, err := base.BizClient.PrivatePathxClient.DescribeUPath(req) - if err != nil { - return nil, err - } - return resp.UPathSet, nil -} - -func getUpathIDList(project string) []string { - list, err := getUpathList(project) - if err != nil { - base.LogError(fmt.Sprintf("getUpathIDList failed:%v", err)) - return nil - } - strs := make([]string, 0) - for _, ins := range list { - strs = append(strs, fmt.Sprintf("%s/%s", ins.UPathId, ins.Name)) - } - return strs -} diff --git a/cmd/product_registration_test.go b/cmd/product_registration_test.go index 60d49af6f4..cca6ce4356 100644 --- a/cmd/product_registration_test.go +++ b/cmd/product_registration_test.go @@ -1,7 +1,9 @@ package cmd import ( + "os" "sort" + "strings" "testing" "github.com/spf13/cobra" @@ -22,7 +24,7 @@ func (p multiCommandProductForTest) NewCommand(ctx *cli.Context) []*cobra.Comman } } -func TestRegisteredProductsUseRealProductDomains(t *testing.T) { +func TestRegisteredProductsUseCommandDirectoryProducts(t *testing.T) { products := registeredProducts() byName := make(map[string]cli.Product, len(products)) @@ -30,14 +32,35 @@ func TestRegisteredProductsUseRealProductDomains(t *testing.T) { byName[p.Metadata().Name] = p } - for _, oldName := range []string{"eip", "firewall", "image"} { - if _, ok := byName[oldName]; ok { - t.Fatalf("registeredProducts includes command-level product %q; want it merged into its real product domain", oldName) - } + for _, tt := range []struct { + product string + commands []string + }{ + {product: "sharedbw", commands: []string{"bw"}}, + {product: "eip", commands: []string{"eip"}}, + {product: "firewall", commands: []string{"firewall"}}, + {product: "globalssh", commands: []string{"gssh"}}, + {product: "image", commands: []string{"image"}}, + {product: "memcache", commands: []string{"memcache"}}, + {product: "mysql", commands: []string{"mysql"}}, + {product: "pathx", commands: []string{"pathx"}}, + {product: "redis", commands: []string{"redis"}}, + {product: "subnet", commands: []string{"subnet"}}, + {product: "udisk", commands: []string{"udisk"}}, + {product: "udpn", commands: []string{"udpn"}}, + {product: "uhost", commands: []string{"uhost"}}, + {product: "ulb", commands: []string{"ulb"}}, + {product: "uphost", commands: []string{"uphost"}}, + {product: "vpc", commands: []string{"vpc"}}, + } { + assertProductCommands(t, byName, tt.product, tt.commands) } - assertProductCommands(t, byName, "unet", []string{"eip", "firewall"}) - assertProductCommands(t, byName, "uhost", []string{"image", "uhost"}) + for _, removedName := range []string{"bw", "gssh", "udb", "umem", "unet"} { + if _, ok := byName[removedName]; ok { + t.Fatalf("registeredProducts includes grouped/stale product %q; want existing top-level commands in independent directories", removedName) + } + } } func TestAddProductCommandsRegistersAllProductCommands(t *testing.T) { @@ -52,6 +75,28 @@ func TestAddProductCommandsRegistersAllProductCommands(t *testing.T) { } } +func TestAddPlatformCommandsExcludesMigratedProductCommands(t *testing.T) { + src, err := os.ReadFile("root.go") + if err != nil { + t.Fatalf("read root.go: %v", err) + } + for _, constructor := range []string{ + "NewCmdUDPN(", + "NewCmdGssh(", + "NewCmdPathx(", + "NewCmdBandwidth(", + "NewCmdRedis(", + "NewCmdMemcache(", + "NewCmdULB(", + "NewCmdSubnet(", + "NewCmdVpc(", + } { + if strings.Contains(string(src), constructor) { + t.Fatalf("addPlatformCommands must not register %s after product migration", constructor) + } + } +} + func assertProductCommands(t *testing.T, products map[string]cli.Product, name string, want []string) { t.Helper() diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 40a32aa6a5..e361499bb7 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -3,20 +3,42 @@ package cmd import ( "github.com/ucloud/ucloud-cli/pkg/cli" - "github.com/ucloud/ucloud-cli/products/udb" + "github.com/ucloud/ucloud-cli/products/eip" + "github.com/ucloud/ucloud-cli/products/firewall" + "github.com/ucloud/ucloud-cli/products/globalssh" + "github.com/ucloud/ucloud-cli/products/image" + "github.com/ucloud/ucloud-cli/products/memcache" + "github.com/ucloud/ucloud-cli/products/mysql" + "github.com/ucloud/ucloud-cli/products/pathx" + "github.com/ucloud/ucloud-cli/products/redis" + "github.com/ucloud/ucloud-cli/products/sharedbw" + "github.com/ucloud/ucloud-cli/products/subnet" "github.com/ucloud/ucloud-cli/products/udisk" + "github.com/ucloud/ucloud-cli/products/udpn" "github.com/ucloud/ucloud-cli/products/uhost" - "github.com/ucloud/ucloud-cli/products/unet" + "github.com/ucloud/ucloud-cli/products/ulb" "github.com/ucloud/ucloud-cli/products/uphost" + "github.com/ucloud/ucloud-cli/products/vpc" ) // registeredProducts returns the platform-registered products. func registeredProducts() []cli.Product { return []cli.Product{ - udb.New(), + eip.New(), + firewall.New(), + globalssh.New(), + image.New(), + memcache.New(), + mysql.New(), + pathx.New(), + redis.New(), + sharedbw.New(), + subnet.New(), udisk.New(), + udpn.New(), uhost.New(), - unet.New(), + ulb.New(), uphost.New(), + vpc.New(), } } diff --git a/cmd/root.go b/cmd/root.go index a155322df2..ee20193f66 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -162,15 +162,6 @@ func addPlatformCommands(root *cobra.Command) { root.AddCommand(NewCmdRegion(out)) root.AddCommand(NewCmdProject()) // uhost migrated to products/uhost (Part 6); registered via products.gen.go. - root.AddCommand(NewCmdSubnet()) - root.AddCommand(NewCmdVpc()) - root.AddCommand(NewCmdBandwidth()) - root.AddCommand(NewCmdUDPN(out)) - root.AddCommand(NewCmdULB()) - root.AddCommand(NewCmdGssh()) - root.AddCommand(NewCmdPathx()) - root.AddCommand(NewCmdRedis()) - root.AddCommand(NewCmdMemcache()) root.AddCommand(NewCmdExt()) root.AddCommand(NewCmdAPI(out)) root.AddCommand(NewCmdSignature()) diff --git a/cmd/uhost_compat.go b/cmd/uhost_compat.go deleted file mode 100644 index b33c5586a9..0000000000 --- a/cmd/uhost_compat.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "strings" - - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" -) - -// uhost_compat.go is a cross-batch transition shim. uhost migrated to -// products/uhost (Part 6), but cmd/ext.go (the `ext uhost switch-eip` command, -// still in package cmd — platform/ext, batch2) calls two cmd-local uhost -// helpers: -// - describeUHostByID: cmd/ext.go ~:75 (lookup uhost before switching EIP) -// - getUhostList: cmd/ext.go ~:201 (--uhost-id completion) -// These copies keep the SAME names+signatures as the originals in the deleted -// cmd/uhost.go (base.BizClient path), so package cmd keeps compiling. Remove this -// file once cmd/ext.go migrates (batch2). - -// describeUHostByID looks up a single uhost by id (cmd-local, base.BizClient). -// Same signature as the original cmd/uhost.go describeUHostByID. -func describeUHostByID(uhostID, projectID, region, zone string) (interface{}, error) { - req := base.BizClient.NewDescribeUHostInstanceRequest() - req.UHostIds = []string{uhostID} - req.ProjectId = &projectID - req.Region = ®ion - req.Zone = &zone - - resp, err := base.BizClient.DescribeUHostInstance(req) - if err != nil { - return nil, err - } - if len(resp.UHostSet) < 1 { - return nil, fmt.Errorf("uhost [%s] does not exist", uhostID) - } - - return &resp.UHostSet[0], nil -} - -// getUhostList returns "UHostId/Name" completion candidates filtered by states -// (nil = all). Same signature as the original cmd/uhost.go getUhostList. -func getUhostList(states []string, project, region, zone string) []string { - req := base.BizClient.NewDescribeUHostInstanceRequest() - req.ProjectId = sdk.String(project) - req.Region = sdk.String(region) - req.Zone = sdk.String(zone) - req.Limit = sdk.Int(50) - resp, err := base.BizClient.DescribeUHostInstance(req) - if err != nil { - //todo runtime log - return nil - } - list := []string{} - for _, host := range resp.UHostSet { - if states != nil { - for _, s := range states { - if host.State == s { - list = append(list, host.UHostId+"/"+strings.Replace(host.Name, " ", "-", -1)) - } - } - } else { - list = append(list, host.UHostId+"/"+strings.Replace(host.Name, " ", "-", -1)) - } - } - return list -} diff --git a/cmd/uhost_create_image_test.go b/cmd/uhost_create_image_test.go new file mode 100644 index 0000000000..a0bedcd4cc --- /dev/null +++ b/cmd/uhost_create_image_test.go @@ -0,0 +1,83 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/ucloud/ucloud-cli/base" + "github.com/ucloud/ucloud-cli/pkg/cli" + productuhost "github.com/ucloud/ucloud-cli/products/uhost" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" +) + +func saveBaseGlobalsForCreateImage(t *testing.T) { + t.Helper() + oldClientConfig, oldAuthCredential, oldConfigIns := base.ClientConfig, base.AuthCredential, base.ConfigIns + t.Cleanup(func() { + base.ClientConfig = oldClientConfig + base.AuthCredential = oldAuthCredential + base.ConfigIns = oldConfigIns + }) +} + +func TestUhostCreateImageJSONEmitsStructuredResult(t *testing.T) { + saveBaseGlobalsForCreateImage(t) + + const imageID = "uimage-json-contract" + api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + t.Fatalf("parse request form: %v", err) + } + if got := r.Form.Get("Action"); got != "CreateCustomImage" { + t.Fatalf("Action = %q, want CreateCustomImage", got) + } + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"RetCode":0,"Action":"CreateCustomImageResponse","ImageId":%q}`, imageID) + })) + defer api.Close() + + cfg := sdk.NewConfig() + cfg.BaseUrl = api.URL + cfg.Region = "cn-bj2" + cfg.Zone = "cn-bj2-03" + cfg.ProjectId = "org-test" + base.ClientConfig = &cfg + base.AuthCredential = &base.CredentialConfig{PublicKey: "public", PrivateKey: "private"} + base.ConfigIns = &base.AggConfig{ProjectID: "org-test", Region: "cn-bj2", Zone: "cn-bj2-03"} + + var stdout, stderr bytes.Buffer + ctx := cli.NewContext(cli.Deps{ + Out: &stdout, + Err: &stderr, + Format: cli.OutputJSON, + Config: base.ConfigIns, + }) + root := topLevelCmd(t, productuhost.New().NewCommand(ctx), "uhost") + root.SetArgs([]string{ + "create-image", + "--uhost-id", "uhost-for-image", + "--image-name", "contract-image", + "--async", + }) + + if err := root.Execute(); err != nil { + t.Fatalf("create-image command failed: %v", err) + } + if !strings.Contains(stderr.String(), "iamge["+imageID+"] is making") { + t.Fatalf("stderr progress = %q, want progress for %s", stderr.String(), imageID) + } + + var rows []cli.OpResultRow + if err := json.Unmarshal(stdout.Bytes(), &rows); err != nil { + t.Fatalf("stdout must be JSON result rows, got %q: %v", stdout.String(), err) + } + want := []cli.OpResultRow{{ResourceID: imageID, Action: "create", Status: "Making"}} + if len(rows) != 1 || rows[0] != want[0] { + t.Fatalf("result rows = %#v, want %#v", rows, want) + } +} diff --git a/cmd/ulb.go b/cmd/ulb.go deleted file mode 100644 index 18f039f77d..0000000000 --- a/cmd/ulb.go +++ /dev/null @@ -1,1700 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - "io/ioutil" - "strings" - - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-sdk-go/services/ulb" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/model/status" - "github.com/ucloud/ucloud-cli/pkg/command" -) - -// NewCmdULB ucloud ulb -func NewCmdULB() *cobra.Command { - cmd := &cobra.Command{ - Use: "ulb", - Short: "List and manipulate ULB instances", - Long: "List and manipulate ULB instances", - } - out := base.Cxt.GetWriter() - - cmd.AddCommand(NewCmdULBList(out)) - cmd.AddCommand(NewCmdULBCreate(out)) - cmd.AddCommand(NewCmdULBUpdate(out)) - cmd.AddCommand(NewCmdULBDelete(out)) - cmd.AddCommand(NewCmdULBVserver()) - cmd.AddCommand(NewCmdULBSSL()) - - return cmd -} - -// ULBRow 表格行 -type ULBRow struct { - Name string - ResourceID string - Group string - Network string - VserverCount int - VPC string - CreationTime string -} - -// NewCmdULBList ucloud ulb list -func NewCmdULBList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeULBRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List ULB instances", - Long: "List ULB instances", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - resp, err := base.BizClient.DescribeULB(req) - if err != nil { - base.HandleError(err) - return - } - list := []ULBRow{} - for _, ulb := range resp.DataSet { - row := ULBRow{} - row.ResourceID = ulb.ULBId - row.Name = ulb.Name - row.Group = ulb.BusinessId - row.VserverCount = len(ulb.VServerSet) - row.VPC = ulb.VPCId - row.CreationTime = common.FormatDate(ulb.CreateTime) - if ulb.ULBType == "OuterMode" { - ips := []string{} - for _, ip := range ulb.IPSet { - ips = append(ips, fmt.Sprintf("%s(%s)", ip.EIP, ip.EIPId)) - } - row.Network = strings.Join(ips, ",") - } else { - row.Network = ulb.PrivateIP - } - list = append(list, row) - } - - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - - req.ULBId = flags.String("ulb-id", "", "Optional. Resource ID of ULB instance to list") - req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC which the ULB instances to list belong to") - req.SubnetId = flags.String("subnet-id", "", "Optional. Resource ID of subnet which the ULB instances to list belong to") - req.BusinessId = flags.String("group", "", "Optional. Business group of ULB instances to list") - req.Offset = flags.Int("offset", 0, "Optional. Offset") - req.Limit = flags.Int("limit", 50, "Optional. Limit") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdULBCreate ucloud ulb create -func NewCmdULBCreate(out io.Writer) *cobra.Command { - var bindEipID *string - mode := "outer" - req := base.BizClient.NewCreateULBRequest() - eipReq := base.BizClient.NewAllocateEIPRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create ULB instance", - Long: "Create ULB instance", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - if mode == "outer" { - if *bindEipID == "" && *eipReq.Bandwidth == 0 { - fmt.Fprintln(out, "Outer mode ULB need a eip to bind, please assign eip by flag 'bind-eip' or create eip by 'create-eip-bandwidth-mb'") - return - } - if *eipReq.OperatorName == "" { - *eipReq.OperatorName = getEIPLine(*req.Region) - } - req.OuterMode = sdk.String("Yes") - } else if mode == "inner" { - req.InnerMode = sdk.String("Yes") - } else { - fmt.Fprintln(out, "Error, flag mode should be 'outer' or 'inner'") - return - } - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - req.SubnetId = sdk.String(base.PickResourceID(*req.SubnetId)) - resp, err := base.BizClient.CreateULB(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ulb[%s] created\n", resp.ULBId) - if mode == "inner" { - return - } - bindEipID = sdk.String(base.PickResourceID(*bindEipID)) - if *bindEipID != "" { - bindEIP(sdk.String(resp.ULBId), sdk.String("ulb"), bindEipID, req.ProjectId, req.Region) - return - } - if *eipReq.OperatorName != "" && *eipReq.Bandwidth != 0 { - eipReq.ChargeType = req.ChargeType - eipReq.Tag = req.Tag - eipReq.Region = req.Region - eipReq.ProjectId = req.ProjectId - eipResp, err := base.BizClient.AllocateEIP(eipReq) - - if err != nil { - base.HandleError(err) - return - } - - for _, eip := range eipResp.EIPSet { - base.Cxt.Printf("allocate EIP[%s] ", eip.EIPId) - for _, ip := range eip.EIPAddr { - base.Cxt.Printf("IP:%s Line:%s \n", ip.IP, ip.OperatorName) - } - bindEIP(sdk.String(resp.ULBId), sdk.String("ulb"), sdk.String(eip.EIPId), req.ProjectId, req.Region) - } - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBName = flags.String("name", "", "Required. Name of ULB instance to create") - flags.StringVar(&mode, "mode", "outer", "Required. Network mode of ULB instance, outer or inner.") - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC which the ULB to create belong to. See 'ucloud vpc list'") - req.SubnetId = flags.String("subnet-id", "", "Optional. Resource ID of subnet. This flag will be discarded when you are creating an outter mode ULB. See 'ucloud subnet list'") - req.ChargeType = flags.String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") - req.Tag = flags.String("group", "Default", "Optional. Business group") - req.Remark = flags.String("remark", "", "Optional. Remark of instance to create.") - bindEipID = flags.String("bind-eip", "", "Optional. Resource ID or IP Address of eip that will be bound to the new created outer mode ulb") - eipReq.Bandwidth = cmd.Flags().Int("create-eip-bandwidth-mb", 0, "Optional. Required if you want to create new EIP. Bandwidth(Unit:Mbps).The range of value related to network charge mode. By traffic [1, 300]; by bandwidth [1,800] (Unit: Mbps); it could be 0 if the eip belong to the shared bandwidth") - eipReq.OperatorName = flags.String("create-eip-line", "", "Optional. Line of created eip to bind with the new created outer mode ulb") - eipReq.PayMode = cmd.Flags().String("create-eip-traffic-mode", "Bandwidth", "Optional. 'Traffic','Bandwidth' or 'ShareBandwidth'") - eipReq.Name = flags.String("create-eip-name", "", "Optional. Name of created eip to bind with the new created outer mode ulb") - eipReq.Remark = cmd.Flags().String("create-eip-remark", "", "Optional. Remark of your EIP.") - - command.SetFlagValues(cmd, "mode", "outer", "inner") - command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") - command.SetFlagValues(cmd, "create-eip-line", "BGP", "International") - command.SetFlagValues(cmd, "create-eip-traffic-mode", "Bandwidth", "Traffic", "ShareBandwidth") - command.SetCompletion(cmd, "bind-eip", func() []string { - return getAllEip(*req.ProjectId, *req.Region, []string{status.EIP_FREE}, nil) - }) - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "subnet-id", func() []string { - return getAllSubnetIDNames(*req.VPCId, *req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("mode") - cmd.MarkFlagRequired("name") - - return cmd -} - -// NewCmdULBDelete ucloud ulb delete -func NewCmdULBDelete(out io.Writer) *cobra.Command { - idNames := []string{} - req := base.BizClient.NewDeleteULBRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete ULB instances by resource ID", - Long: "Delete ULB instances by resource ID", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, idname := range idNames { - req.ULBId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.DeleteULB(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ulb[%s] deleted\n", idname) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "ulb-id", nil, "Required. Resource ID of the ULB instances to delete") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - - return cmd -} - -// NewCmdULBUpdate ucloud ulb update -func NewCmdULBUpdate(out io.Writer) *cobra.Command { - var name, group, remark string - idNames := []string{} - req := base.BizClient.NewUpdateULBAttributeRequest() - cmd := &cobra.Command{ - Use: "update", - Short: "Update ULB instance", - Long: "Update ULB instance", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, idname := range idNames { - req.ULBId = sdk.String(base.PickResourceID(idname)) - if name == "" && group == "" && remark == "" { - fmt.Fprintln(out, "Error, name, remark and group can't be all empty") - return - } - if name != "" { - req.Name = &name - } - if group != "" { - req.Tag = &group - } - if remark != "" { - req.Remark = &remark - } - _, err := base.BizClient.UpdateULBAttribute(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "ulb[%s] updated\n", *req.ULBId) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - flags.StringSliceVar(&idNames, "ulb-id", nil, "Required. Resource ID of ULB instances to update") - flags.StringVar(&name, "name", "", "Optional, Name of ULB instance") - flags.StringVar(&remark, "remark", "", "Optional, Remark of ULB instance") - flags.StringVar(&group, "group", "", "Optional, Business group of ULB instance") - // bindGroup(&group, flags) - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - - return cmd -} - -func getAllULB(project, region string) ([]ulb.ULBSet, error) { - list := []ulb.ULBSet{} - req := base.BizClient.NewDescribeULBRequest() - req.ProjectId = &project - req.Region = ®ion - - for offset, limit := 0, 50; ; offset += limit { - req.Offset = sdk.Int(offset) - req.Limit = sdk.Int(limit) - resp, err := base.BizClient.DescribeULB(req) - - if err != nil { - return nil, err - } - list = append(list, resp.DataSet...) - - if resp.TotalCount < offset+limit { - break - } - } - return list, nil -} - -func getAllULBIDNames(project, region string) []string { - list := []string{} - ulbList, err := getAllULB(project, region) - if err != nil { - return nil - } - for _, ulb := range ulbList { - list = append(list, fmt.Sprintf("%s/%s", ulb.ULBId, ulb.Name)) - } - return list -} - -// NewCmdULBVserver ucloud ulb-vserver -func NewCmdULBVserver() *cobra.Command { - cmd := &cobra.Command{ - Use: "vserver", - Short: "List and manipulate ULB Vserver instances", - Long: "List and manipulate ULB Vserver instances", - } - out := base.Cxt.GetWriter() - - cmd.AddCommand(NewCmdULBVServerList(out)) - cmd.AddCommand(NewCmdULBVServerCreate(out)) - cmd.AddCommand(NewCmdULBVServerUpdate(out)) - cmd.AddCommand(NewCmdULBVServerDelete(out)) - cmd.AddCommand(NewCmdULBVServerNode()) - cmd.AddCommand(NewCmdULBVServerPolicy()) - - return cmd -} - -// ULBVServerRow 表格行 -type ULBVServerRow struct { - VServerName string - ResourceID string - ListenType string - Protocol string - Port int - LBMethod string - SessionMaintainMode string - SessionMaintainKey string - ClientTimeout string - HealthCheckMode string - HealthCheckDomain string - HealthCheckPath string -} - -// NewCmdULBVServerList ucloud ulb-vserver list -func NewCmdULBVServerList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeVServerRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List ULB Vserver instances", - Long: "List ULB Vserver instances", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - resp, err := base.BizClient.DescribeVServer(req) - if err != nil { - base.HandleError(err) - return - } - list := []ULBVServerRow{} - for _, vs := range resp.DataSet { - row := ULBVServerRow{} - row.VServerName = vs.VServerName - row.ResourceID = vs.VServerId - row.ListenType = vs.ListenType - row.Protocol = vs.Protocol - row.Port = vs.FrontendPort - row.LBMethod = vs.Method - row.ClientTimeout = fmt.Sprintf("%ds", vs.ClientTimeout) - row.SessionMaintainMode = vs.PersistenceType - row.SessionMaintainKey = vs.PersistenceInfo - row.HealthCheckMode = vs.MonitorType - row.HealthCheckDomain = vs.Domain - row.HealthCheckPath = vs.Path - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") - req.VServerId = flags.String("vserver-id", "", "Optional. Resource ID of vserver to list") - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - - return cmd -} - -// NewCmdULBVServerCreate ucloud ulb-vserver create -func NewCmdULBVServerCreate(out io.Writer) *cobra.Command { - sslID := "" - req := base.BizClient.NewCreateVServerRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create ULB VServer instance", - Long: "Create ULB VServer instance", - Run: func(c *cobra.Command, args []string) { - if *req.ListenType == "RequestProxy" && (*req.ClientTimeout <= 0 || *req.ClientTimeout > 86400) { - fmt.Println("Error, client-timeout-seconds in the range of (0,86400]") - return - } - if *req.ListenType == "PacketsTransmit" && (*req.ClientTimeout <= 0 || *req.ClientTimeout > 86400) { - fmt.Println("Error, client-timeout-seconds in the range of [60,900]") - return - } - if *req.Protocol == "HTTPS" && sslID == "" { - fmt.Println("Error, SSL Certificate is needed when you choose HTTPS") - return - } - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - resp, err := base.BizClient.CreateVServer(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ulb-vserver[%s] created\n", resp.VServerId) - if *req.Protocol == "HTTPS" && sslID != "" { - bindReq := base.BizClient.NewBindSSLRequest() - bindReq.Region = req.Region - bindReq.ProjectId = req.ProjectId - bindReq.SSLId = sdk.String(base.PickResourceID(sslID)) - bindReq.VServerId = sdk.String(resp.VServerId) - bindReq.ULBId = req.ULBId - _, err := base.BizClient.BindSSL(bindReq) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ssl certificate[%s] bind with vserver[%s] of ulb[%s]\n", sslID, *bindReq.VServerId, *bindReq.ULBId) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.VServerName = flags.String("name", "", "Optional. Name of VServer to create") - req.ListenType = flags.String("listen-type", "RequestProxy", "Optional. Listen type, 'RequestProxy' or 'PacketsTransmit'") - req.Protocol = flags.String("protocol", "HTTP", "Optional. Protocol of VServer instance, 'HTTP','HTTPS','TCP' for listen type 'RequestProxy' and 'TCP','UDP' for listen type 'PacketsTransmit'") - req.FrontendPort = flags.Int("port", 80, "Optional. Port of VServer instance") - flags.StringVar(&sslID, "ssl-id", "", "Optional. Required if you choose HTTPS, Resource ID of SSL Certificate") - req.Method = flags.String("lb-method", "Roundrobin", "Optional. LB methods, accept values:Roundrobin,Source,ConsistentHash,SourcePort,ConsistentHashPort,WeightRoundrobin and Leastconn. \nConsistentHash,SourcePort and ConsistentHashPort are effective for listen type PacketsTransmit only;\nLeastconn is effective for listen type RequestProxy only;\nRoundrobin,Source and WeightRoundrobin are effective for both listen types") - req.PersistenceType = flags.String("session-maintain-mode", "None", "Optional. The method of maintaining user's session. Accept values: 'None','ServerInsert' and 'UserDefined'. 'None' meaning don't maintain user's session'; 'ServerInsert' meaning auto create session key; 'UserDefined' meaning specify session key which accpeted by flag seesion-maintain-key by yourself") - req.PersistenceInfo = flags.String("session-maintain-key", "", "Optional. Specify a key for maintaining session") - req.ClientTimeout = flags.Int("client-timeout-seconds", 60, "Optional.Unit seconds. For 'RequestProxy', it's lifetime for idle connections, range (0,86400]. For 'PacketsTransmit', it's the duration of the connection is maintained, range [60,900]") - req.MonitorType = flags.String("health-check-mode", "Port", "Optional. Method of checking real server's status of health. Accept values:'Port','Path'") - req.Domain = flags.String("health-check-domain", "", "Optional. Skip this flag if health-check-mode is assigned Port") - req.Path = flags.String("health-check-path", "", "Optional. Skip this flags if health-check-mode is assigned Port") - - command.SetFlagValues(cmd, "listen-type", "RequestProxy", "PacketsTransmit") - command.SetFlagValues(cmd, "protocol", "HTTP", "HTTPS", "TCP", "UDP") - command.SetCompletion(cmd, "lb-method", func() []string { - if *req.ListenType == "RequestProxy" { - return []string{"Roundrobin", "Source", "WeightRoundrobin", "Leastconn"} - } else if *req.ListenType == "PacketsTransmit" { - return []string{"Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort"} - } - return []string{"Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort", "Leastconn"} - }) - command.SetFlagValues(cmd, "session-maintain-mode", "None", "ServerInsert", "UserDefined") - command.SetFlagValues(cmd, "health-check-mode", "Port", "Path") - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "ssl-id", func() []string { - return getAllSSLCertIDNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - - return cmd -} - -// NewCmdULBVServerUpdate ucloud ulb-vserver update -func NewCmdULBVServerUpdate(out io.Writer) *cobra.Command { - req := base.BizClient.NewUpdateVServerAttributeRequest() - vserverIDs := []string{} - cmd := &cobra.Command{ - Use: "update", - Short: "Update attributes of VServer instances", - Long: "Update attributes of VServer instances", - Run: func(c *cobra.Command, args []string) { - if *req.VServerName == "" { - req.VServerName = nil - } - if *req.Method == "" { - req.Method = nil - } - if *req.PersistenceType == "" { - req.PersistenceType = nil - } - if *req.PersistenceInfo == "" { - req.PersistenceInfo = nil - } - if *req.ClientTimeout == -1 { - req.ClientTimeout = nil - } - if *req.MonitorType == "" { - req.MonitorType = nil - } - if *req.Domain == "" { - req.Domain = nil - } - if *req.Path == "" { - req.Path = nil - } - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - for _, idname := range vserverIDs { - req.VServerId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.UpdateVServerAttribute(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ulb-vserver[%s] updated\n", *req.VServerId) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") - flags.StringSliceVar(&vserverIDs, "vserver-id", nil, "Required. Resource ID of Vserver to update") - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.VServerName = flags.String("name", "", "Optional. Name of VServer") - req.Method = flags.String("lb-method", "", "Optional. LB methods, accept values:Roundrobin,Source,ConsistentHash,SourcePort,ConsistentHashPort,WeightRoundrobin and Leastconn. \nConsistentHash,SourcePort and ConsistentHashPort are effective for listen type PacketsTransmit only;\nLeastconn is effective for listen type RequestProxy only;\nRoundrobin,Source and WeightRoundrobin are effective for both listen types") - req.PersistenceType = flags.String("session-maintain-mode", "", "Optional. The method of maintaining user's session. Accept values: 'None','ServerInsert' and 'UserDefined'. 'None' meaning don't maintain user's session'; 'ServerInsert' meaning auto create session key; 'UserDefined' meaning specify session key which accpeted by flag seesion-maintain-key by yourself") - req.PersistenceInfo = flags.String("session-maintain-key", "", "Optional. Specify a key for maintaining session") - req.ClientTimeout = flags.Int("client-timeout-seconds", -1, "Optional.Unit seconds. For 'RequestProxy', it's lifetime for idle connections, range (0,86400]. For 'PacketsTransmit', it's the duration of the connection is maintained, range [60,900]") - req.MonitorType = flags.String("health-check-mode", "", "Optional. Method of checking real server's status of health. Accept values:'Port','Path'") - req.Domain = flags.String("health-check-domain", "", "Optional. Skip this flag if health-check-mode is assigned Port") - req.Path = flags.String("health-check-path", "", "Optional. Skip this flags if health-check-mode is assigned Port") - - command.SetFlagValues(cmd, "lb-method", "Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort", "Leastconn") - command.SetFlagValues(cmd, "session-maintain-mode", "None", "ServerInsert", "UserDefined") - command.SetFlagValues(cmd, "health-check-mode", "Port", "Path") - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - ulbID := base.PickResourceID(*req.ULBId) - return getAllULBVServerIDNames(ulbID, *req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - - return cmd -} - -// NewCmdULBVServerDelete ucloud ulb-vserver delete -func NewCmdULBVServerDelete(out io.Writer) *cobra.Command { - vserverIDs := []string{} - req := base.BizClient.NewDeleteVServerRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete ULB VServer instances", - Long: "Delete ULB VServer instances", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - for _, idname := range vserverIDs { - vsid := base.PickResourceID(idname) - req.VServerId = sdk.String(vsid) - _, err := base.BizClient.DeleteVServer(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ulb-vserver[%s] deleted\n", idname) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") - flags.StringSliceVar(&vserverIDs, "vserver-id", nil, "Required. Resource ID of Vserver to update") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - ulbID := base.PickResourceID(*req.ULBId) - return getAllULBVServerIDNames(ulbID, *req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdULBVServerNode ucloud ulb vserver node -func NewCmdULBVServerNode() *cobra.Command { - out := base.Cxt.GetWriter() - cmd := &cobra.Command{ - Use: "backend", - Short: "List and manipulate VServer backend nodes", - Long: "List and manipulate VServer backend nodes", - } - cmd.AddCommand(NewCmdULBVServerListNode(out)) - cmd.AddCommand(NewCmdULBVServerAddNode(out)) - cmd.AddCommand(NewCmdULBVServerUpdateNode(out)) - cmd.AddCommand(NewCmdULBVServerDeleteNode(out)) - return cmd -} - -// ULBVServerNode 表格行 -type ULBVServerNode struct { - Name string - ResourceID string - BackendID string - PrivateIP string - Port int - HealthCheck string - NodeMode string - Weight int -} - -// NewCmdULBVServerListNode ucloud ulb-vserver list-node -func NewCmdULBVServerListNode(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeVServerRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List ULB VServer backend nodes", - Long: "List ULB VServer backend nodes", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - resp, err := base.BizClient.DescribeVServer(req) - if err != nil { - base.HandleError(err) - return - } - if len(resp.DataSet) != 1 { - fmt.Fprintf(out, "ulb[%s] or vserver[%s] may not exist\n", *req.ULBId, *req.VServerId) - return - } - vs := resp.DataSet[0] - list := []ULBVServerNode{} - for _, node := range vs.BackendSet { - row := ULBVServerNode{} - row.Name = node.ResourceName - row.ResourceID = node.ResourceId - row.BackendID = node.BackendId - row.PrivateIP = node.PrivateIP - row.Weight = node.Weight - row.Port = node.Port - if node.Status == 0 { - row.HealthCheck = "Normal" - } else if node.Status == 1 { - row.HealthCheck = "Failed" - } - if node.Enabled == 1 { - row.NodeMode = "enable" - } else if node.Enabled == 0 { - row.NodeMode = "disable" - } - list = append(list, row) - } - base.PrintList(list, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer which the backend nodes belong to") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - ulbID := base.PickResourceID(*req.ULBId) - return getAllULBVServerIDNames(ulbID, *req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdULBVServerAddNode ucloud ulb-vserver add-node -func NewCmdULBVServerAddNode(out io.Writer) *cobra.Command { - var enable *string - var weight *int - var ids []string - req := base.BizClient.NewAllocateBackendRequest() - cmd := &cobra.Command{ - Use: "add", - Short: "Add backend nodes for ULB Vserver instance", - Long: "Add backend nodes for ULB Vserver instance", - Run: func(c *cobra.Command, args []string) { - if *enable == "enable" { - req.Enabled = sdk.Int(1) - } else if *enable == "disable" { - req.Enabled = sdk.Int(0) - } else { - fmt.Fprintln(out, "Error, backend-mode must be enable or disable") - return - } - if *weight < 0 || *weight > 100 { - fmt.Fprintln(out, "Error, weight must be between 0 and 100") - return - } - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - for _, id := range ids { - req.ResourceId = sdk.String(id) - resp, err := base.BizClient.AllocateBackend(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "backend node[%s] added, backend-id:%s\n", *req.ResourceId, resp.BackendId) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer which the backend nodes belong to") - flags.StringSliceVar(&ids, "resource-id", nil, "Required. Resource ID of the backend nodes to add") - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.ResourceType = flags.String("resource-type", "UHost", "Optional. Resource type of the backend node to add. Accept values: UHost,UPM,UDHost,UDocker") - req.Port = flags.Int("port", 80, "Optional. The port of your real server on the backend node listening on") - enable = flags.String("backend-mode", "enable", "Optional. Enable backend node or not. Accept values: enable, disable") - weight = flags.Int("weight", 1, "Optional. effective for lb-method WeightRoundrobin. Rnage [0,100]") - - command.SetFlagValues(cmd, "resource-type", "Uhost", "UPM", "UDHost", "UDocker") - command.SetFlagValues(cmd, "backend-mode", "enable", "disable") - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - ulbID := base.PickResourceID(*req.ULBId) - return getAllULBVServerIDNames(ulbID, *req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - cmd.MarkFlagRequired("resource-id") - return cmd -} - -// NewCmdULBVServerUpdateNode ucloud ulb-vserver update-node -func NewCmdULBVServerUpdateNode(out io.Writer) *cobra.Command { - var mode *string - var weight *int - backendIDs := []string{} - req := base.BizClient.NewUpdateBackendAttributeRequest() - cmd := &cobra.Command{ - Use: "update", - Short: "Update attributes of ULB backend nodes", - Long: "Update attributes of ULB backend nodes", - Run: func(c *cobra.Command, args []string) { - if *mode == "enable" { - req.Enabled = sdk.Int(1) - } else if *mode == "disable" { - req.Enabled = sdk.Int(0) - } else if *mode == "" { - req.Enabled = nil - } else { - fmt.Fprintln(out, "Error, backend-mode must be enable or disable") - return - } - if *weight != -1 && (*weight < 0 || *weight > 100) { - fmt.Fprintln(out, "Error, weight must be between 0 and 100") - return - } - if *weight != -1 { - req.Weight = weight - } - - if *req.Port == 0 { - req.Port = nil - } - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, bid := range backendIDs { - req.BackendId = sdk.String(base.PickResourceID(bid)) - _, err := base.BizClient.UpdateBackendAttribute(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "backend node[%s] updated\n", bid) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") - flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of backend nodes to update") - req.Port = flags.Int("port", 0, "Optional. Port of your real server listening on backend nodes to update. Rnage [1,65535]") - mode = flags.String("backend-mode", "", "Optional. Enable backend node or not. Accept values: enable, disable") - weight = flags.Int("weight", -1, "Optional. effective for lb-method WeightRoundrobin. Rnage [0,100], -1 meaning no update") - - bindRegion(req, cmd) - bindProjectID(req, cmd) - - command.SetFlagValues(cmd, "backend-mode", "enable", "disable") - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, "", *req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("backend-id") - - return cmd -} - -// NewCmdULBVServerDeleteNode ucloud ulb-vserver delete-node -func NewCmdULBVServerDeleteNode(out io.Writer) *cobra.Command { - backendIDs := []string{} - req := base.BizClient.NewReleaseBackendRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete ULB VServer backend nodes", - Long: "Delete ULB VServer backend nodes", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - for _, idname := range backendIDs { - req.BackendId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.ReleaseBackend(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "backend node[%s] deleted\n", idname) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") - flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of backend nodes to update") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("backend-id") - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, "", *req.ProjectId, *req.Region) - }) - return cmd -} - -// NewCmdULBVServerPolicy ucloud ulb vserver policy -func NewCmdULBVServerPolicy() *cobra.Command { - out := base.Cxt.GetWriter() - cmd := &cobra.Command{ - Use: "policy", - Short: "List and manipulate forward policy for VServer", - Long: "List and manipulate forward policy for VServer", - } - cmd.AddCommand(NewCmdULBVServerCreatePolicy(out)) - cmd.AddCommand(NewCmdULBVServerListPolicy(out)) - cmd.AddCommand(NewCmdULBVServerUpdatePolicy(out)) - cmd.AddCommand(NewCmdULBVServerDeletePolicy(out)) - return cmd -} - -// NewCmdULBVServerCreatePolicy ucloud ulb-vserver create-policy -func NewCmdULBVServerCreatePolicy(out io.Writer) *cobra.Command { - backendIDs := []string{} - req := base.BizClient.NewCreatePolicyRequest() - cmd := &cobra.Command{ - Use: "add", - Short: "Add content forward policy for VServer", - Long: "Add content forward policy for VServer", - Run: func(c *cobra.Command, args []string) { - if *req.Type != "Domain" && *req.Type != "Path" { - fmt.Fprintln(out, "Error, forward method must be Domain or Path") - return - } - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - for _, idname := range backendIDs { - req.BackendId = append(req.BackendId, base.PickResourceID(idname)) - } - resp, err := base.BizClient.CreatePolicy(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "policy[%s] created\n", resp.PolicyId) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") - flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of the VServer's backend nodes") - req.Type = flags.String("forward-method", "", "Required. Forward method, accept values:Domain and Path; Both forwarding methods can be described by using regular expressions or wildcards") - req.Match = flags.String("expression", "", "Required. Expression of domain or path, such as \"www.[123].demo.com\" or \"/path/img/*.jpg\"") - bindRegion(req, cmd) - bindProjectID(req, cmd) - - command.SetFlagValues(cmd, "forward-method", "Domain", "Path") - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - return getAllULBVServerIDNames(*req.ULBId, *req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - cmd.MarkFlagRequired("backend-id") - cmd.MarkFlagRequired("forward-method") - cmd.MarkFlagRequired("expression") - - return cmd -} - -// ULBVServerPolicy 表格行 -type ULBVServerPolicy struct { - ForwardMethod string - Expression string - PolicyID string - PolicyType string - Backends string -} - -// NewCmdULBVServerListPolicy ucloud ulb-vserver list-policy -func NewCmdULBVServerListPolicy(out io.Writer) *cobra.Command { - var ulbID, vserverID *string - region := base.ConfigIns.Region - project := base.ConfigIns.ProjectID - cmd := &cobra.Command{ - Use: "list", - Short: "List content forward policies of the VServer instance", - Long: "List content forward policies of the VServer instance", - Run: func(c *cobra.Command, args []string) { - ulbID = sdk.String(base.PickResourceID(*ulbID)) - vserverID = sdk.String(base.PickResourceID(*vserverID)) - vsList, err := getAllULBVServer(*ulbID, *vserverID, project, region) - if err != nil { - base.HandleError(err) - return - } - if len(vsList) == 1 { - vs := vsList[0] - list := []ULBVServerPolicy{} - for _, p := range vs.PolicySet { - row := ULBVServerPolicy{} - row.ForwardMethod = p.Type - row.Expression = p.Match - row.PolicyID = p.PolicyId - row.PolicyType = p.PolicyType - nodes := []string{} - for _, b := range p.BackendSet { - nodes = append(nodes, fmt.Sprintf("%s|%s:%d|%s", b.BackendId, b.PrivateIP, b.Port, b.ResourceName)) - } - row.Backends = strings.Join(nodes, ",") - list = append(list, row) - } - base.PrintList(list, out) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - bindRegionS(®ion, cmd) - bindProjectIDS(&project, cmd) - - ulbID = flags.String("ulb-id", "", "Required. Resource ID of ULB") - vserverID = flags.String("vserver-id", "", "Required. Resource ID of VServer") - - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(project, region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - ulb := base.PickResourceID(*ulbID) - return getAllULBVServerIDNames(ulb, project, region) - }) - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - return cmd -} - -// NewCmdULBVServerUpdatePolicy ucloud ulb-vserver update-policy -func NewCmdULBVServerUpdatePolicy(out io.Writer) *cobra.Command { - policyIDs := []string{} - backendIDs := []string{} - addBackendIDs := []string{} - removeBackendIDs := []string{} - req := base.BizClient.NewUpdatePolicyRequest() - cmd := &cobra.Command{ - Use: "update", - Short: "Update content forward policies of ULB VServer", - Long: "Update content forward policies ULB VServer", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - - vsList, err := getAllULBVServer(*req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) - if err != nil { - base.HandleError(err) - return - } - vs := vsList[0] - - for _, policyID := range policyIDs { - var policy *ulb.ULBPolicySet - for _, p := range vs.PolicySet { - if p.PolicyId == policyID { - policy = &p - break - } - } - if policy == nil { - fmt.Fprintf(out, "policy[%s] not found\n", *req.PolicyId) - continue - } - req.PolicyId = sdk.String(policyID) - if *req.Type == "" { - req.Type = sdk.String(policy.Type) - } else if *req.Type != "Domain" && *req.Type != "Path" { - fmt.Fprintf(out, "Error, forward-method must be Domain or Path") - continue - } - if *req.Match == "" { - req.Match = sdk.String(policy.Match) - } - backendIDMap := map[string]bool{} - if backendIDs == nil { - for _, b := range policy.BackendSet { - backendIDMap[b.BackendId] = true - } - } else { - for _, bid := range backendIDs { - backendIDMap[base.PickResourceID(bid)] = true - } - } - for _, bid := range addBackendIDs { - backendIDMap[base.PickResourceID(bid)] = true - } - for _, bid := range removeBackendIDs { - backendIDMap[base.PickResourceID(bid)] = false - } - for bid, ok := range backendIDMap { - if ok { - req.BackendId = append(req.BackendId, bid) - } - } - resp, err := base.BizClient.UpdatePolicy(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "policy[%s] updated\n", resp.PolicyId) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") - flags.StringSliceVar(&policyIDs, "policy-id", nil, "Required. PolicyID of policies to update") - flags.StringSliceVar(&backendIDs, "backend-id", nil, "Optional. BackendID of backend nodes. If assign this flag, it will rewrite all backend nodes of the policy") - flags.StringSliceVar(&addBackendIDs, "add-backend-id", nil, "Optional. BackendID of backend nodes. Add backend nodes to the policy") - flags.StringSliceVar(&removeBackendIDs, "remove-backend-id", nil, "Optional. BackendID of backend nodes. Remove those backend nodes from the policy") - req.Type = flags.String("forward-method", "", "Optional. Forward method of policy, accept values:Domain and Path") - req.Match = flags.String("expression", "", "Optional. Expression of domain or path, such as \"www.[123].demo.com\" or \"/path/img/*.jpg\"") - - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - cmd.MarkFlagRequired("policy-id") - - command.SetFlagValues(cmd, "forward-method", "Domain", "Path") - command.SetCompletion(cmd, "ulb-id", func() []string { - project := base.PickResourceID(*req.ProjectId) - return getAllULBIDNames(project, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - return getAllULBVServerIDNames(*req.ULBId, *req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "add-backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "remove-backend-id", func() []string { - return getAllULBVServerNodeIDNames(*req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdULBVServerDeletePolicy ucloud ulb-vserver delete-policy -func NewCmdULBVServerDeletePolicy(out io.Writer) *cobra.Command { - policyIDs := []string{} - req := base.BizClient.NewDeletePolicyRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete content forward policies of ULB VServer", - Long: "Delete content forward policies of ULB VServer", - Run: func(c *cobra.Command, args []string) { - for _, p := range policyIDs { - req.PolicyId = sdk.String(p) - _, err := base.BizClient.DeletePolicy(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "policy[%s] deleted\n", p) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - - flags.StringSliceVar(&policyIDs, "policy-id", nil, "Required. PolicyID of policies to delete") - req.VServerId = flags.String("vserver-id", "", "Optional. Resource ID of VServer") - - cmd.MarkFlagRequired("policy-id") - - return cmd -} - -// NewCmdULBSSL ucloud ulb-ssl-certificate -func NewCmdULBSSL() *cobra.Command { - cmd := &cobra.Command{ - Use: "ssl", - Short: "List and manipulate SSL Certificates for ULB", - Long: "List and manipulate SSL Certificates for ULB", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdSSLList(out)) - cmd.AddCommand(NewCmdSSLDescribe(out)) - cmd.AddCommand(NewCmdSSLAdd(out)) - cmd.AddCommand(NewCmdSSLDelete(out)) - cmd.AddCommand(NewCmdSSLBind(out)) - cmd.AddCommand(NewCmdSSLUnbind(out)) - return cmd -} - -// SSLCertificate 表格行 -type SSLCertificate struct { - Name string - ResourceID string - MD5 string - BindResource string - UploadTime string -} - -// NewCmdSSLList ucloud ulb-ssl-certificate list -func NewCmdSSLList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeSSLRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List SSL Certificates", - Long: "List SSL Certificates", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - resp, err := base.BizClient.DescribeSSL(req) - if err != nil { - base.HandleError(err) - return - } - rows := []SSLCertificate{} - for _, ssl := range resp.DataSet { - row := SSLCertificate{} - row.Name = ssl.SSLName - row.ResourceID = ssl.SSLId - row.MD5 = ssl.HashValue - row.UploadTime = common.FormatDateTime(ssl.CreateTime) - targets := []string{} - for _, t := range ssl.BindedTargetSet { - item := fmt.Sprintf("%s/%s(%s/%s)", t.VServerId, t.VServerName, t.ULBId, t.ULBName) - targets = append(targets, item) - } - row.BindResource = strings.Join(targets, ",") - rows = append(rows, row) - } - base.PrintList(rows, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.SSLId = flags.String("ssl-id", "", "Optional. ResouceID of ssl certificate to list") - bindLimit(req, flags) - bindOffset(req, flags) - - return cmd -} - -// NewCmdSSLDescribe ucloud ulb-ssl-certificate describe -func NewCmdSSLDescribe(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeSSLRequest() - cmd := &cobra.Command{ - Use: "describe", - Short: "Display all data associated with SSL Certificate", - Long: "Display all data associated with SSL Certificate", - Run: func(c *cobra.Command, args []string) { - req.SSLId = sdk.String(base.PickResourceID(*req.SSLId)) - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - resp, err := base.BizClient.DescribeSSL(req) - if err != nil { - base.HandleError(err) - return - } - if len(resp.DataSet) <= 0 { - fmt.Fprintf(out, "ssl certificate[%s] is not exists\n", *req.SSLId) - return - } - - sslcf := resp.DataSet[0] - targets := []string{} - for _, t := range sslcf.BindedTargetSet { - item := fmt.Sprintf("%s/%s-%s/%s", t.ULBId, t.ULBName, t.VServerId, t.VServerName) - targets = append(targets, item) - } - rows := []base.DescribeTableRow{ - { - Attribute: "ResourceID", - Content: sslcf.SSLId, - }, - { - Attribute: "Name", - Content: sslcf.SSLName, - }, - { - Attribute: "Type", - Content: sslcf.SSLType, - }, - { - Attribute: "UploadTime", - Content: common.FormatDateTime(sslcf.CreateTime), - }, - { - Attribute: "BindResource", - Content: strings.Join(targets, ","), - }, - { - Attribute: "MD5", - Content: sslcf.HashValue, - }, - { - Attribute: "Content", - Content: sslcf.SSLContent, - }, - } - base.PrintDescribe(rows, global.JSON) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.SSLId = flags.String("ssl-id", "", "Required. ResouceID of ssl certificate to describe") - bindRegion(req, cmd) - bindProjectID(req, cmd) - command.SetCompletion(cmd, "ssl-id", func() []string { - return getAllSSLCertIDNames(*req.ProjectId, *req.Region) - }) - cmd.MarkFlagRequired("ssl-id") - return cmd -} - -// NewCmdSSLAdd ucloud ulb-ssl-certificate add -func NewCmdSSLAdd(out io.Writer) *cobra.Command { - var allPath, sitePath, keyPath, caPath *string - req := base.BizClient.NewCreateSSLRequest() - cmd := &cobra.Command{ - Use: "add", - Short: "Add SSL Certificate", - Long: "Add SSL Certificate", - Run: func(c *cobra.Command, args []string) { - if *allPath == "" && (*sitePath == "" || *keyPath == "") { - fmt.Fprintln(out, "if all-in-one-file is omitted, site-certificate-file and private-key-file can't be empty") - return - } - if *allPath != "" { - content, err := readFile(*allPath) - if err != nil { - base.HandleError(err) - return - } - req.SSLContent = &content - } - if *sitePath != "" { - content, err := readFile(*sitePath) - if err != nil { - base.HandleError(err) - return - } - req.UserCert = &content - } - if *keyPath != "" { - content, err := readFile(*keyPath) - if err != nil { - base.HandleError(err) - return - } - req.PrivateKey = &content - } - if *caPath != "" { - content, err := readFile(*caPath) - if err != nil { - base.HandleError(err) - return - } - req.CaCert = &content - } - - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - resp, err := base.BizClient.CreateSSL(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ssl certificate[%s] added\n", resp.SSLId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.SSLName = flags.String("name", "", "Required. Name of ssl certificate to add") - req.SSLType = flags.String("format", "Pem", "Optional. Format of ssl certificate") - allPath = flags.String("all-in-one-file", "", "Optional. Path of file which contain the complete content of the SSL certificate, including the content of site certificate, the private key which encrypted the site certificate, and the CA certificate. ") - sitePath = flags.String("site-certificate-file", "", "Optional. Path of user's certificate file, *.crt. Required if all-in-one-file is omitted") - keyPath = flags.String("private-key-file", "", "Optional. Path of private key file, *.key. Required if all-in-one-file is omitted") - caPath = flags.String("ca-certificate-file", "", "Optional. Path of CA certificate file, *.crt") - cmd.MarkFlagRequired("name") - command.SetCompletion(cmd, "all-in-one-file", func() []string { - return common.GetFileList("") - }) - command.SetCompletion(cmd, "private-key-file", func() []string { - return common.GetFileList(".key") - }) - command.SetCompletion(cmd, "ca-certificate-file", func() []string { - return common.GetFileList(".crt") - }) - command.SetCompletion(cmd, "site-certificate-file", func() []string { - return common.GetFileList(".crt") - }) - return cmd -} - -// NewCmdSSLDelete ucloud ulb-ssl-certificate delete -func NewCmdSSLDelete(out io.Writer) *cobra.Command { - var idNames []string - req := base.BizClient.NewDeleteSSLRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete SSL Certificates by resource id(ssl id)", - Long: "Delete SSL Certificates by resource id(ssl id)", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, idname := range idNames { - req.SSLId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.DeleteSSL(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "ssl certificate[%s] deleted\n", idname) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - flags.StringSliceVar(&idNames, "ssl-id", nil, "Required. Resource ID of SSL Certificates to delete") - command.SetCompletion(cmd, "ssl-id", func() []string { - return getAllSSLCertIDNames(*req.ProjectId, *req.Region) - }) - return cmd -} - -// NewCmdSSLBind ucloud ulb-ssl-certificate bind -func NewCmdSSLBind(out io.Writer) *cobra.Command { - req := base.BizClient.NewBindSSLRequest() - cmd := &cobra.Command{ - Use: "bind", - Short: "Bind SSL Certificate with VServer", - Long: "Bind SSL Certificate with VServer", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - req.SSLId = sdk.String(base.PickResourceID(*req.SSLId)) - _, err := base.BizClient.BindSSL(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ssl certificate[%s] bind with vserver[%s] of ulb[%s]\n", *req.SSLId, *req.VServerId, *req.ULBId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.SSLId = flags.String("ssl-id", "", "Required. Resource ID of SSL Certificate to bind") - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") - command.SetCompletion(cmd, "ssl-id", func() []string { - return getAllSSLCertIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "ulb-id", func() []string { - return getAllULBIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - return getAllULBVServerIDNames(*req.ULBId, *req.ProjectId, *req.Region) - }) - cmd.MarkFlagRequired("ssl-id") - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - return cmd -} - -// NewCmdSSLUnbind ucloud ulb-ssl-certificate unbind -func NewCmdSSLUnbind(out io.Writer) *cobra.Command { - req := base.BizClient.NewUnbindSSLRequest() - cmd := &cobra.Command{ - Use: "unbind", - Short: "Unbind SSL Certificate with VServer", - Long: "Unbind SSL Certificate with VServer", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.ULBId = sdk.String(base.PickResourceID(*req.ULBId)) - req.VServerId = sdk.String(base.PickResourceID(*req.VServerId)) - req.SSLId = sdk.String(base.PickResourceID(*req.SSLId)) - _, err := base.BizClient.UnbindSSL(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "ssl certificate[%s] unbind with vserver[%s] of ulb[%s]\n", *req.SSLId, *req.VServerId, *req.ULBId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - bindRegion(req, cmd) - bindProjectID(req, cmd) - req.SSLId = flags.String("ssl-id", "", "Required. Resource ID of SSL Certificate to unbind") - req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") - req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") - command.SetCompletion(cmd, "ssl-id", func() []string { - return getAllSSLCertIDNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "ulb-id", func() []string { - if *req.SSLId == "" { - return getAllULBIDNames(*req.ProjectId, *req.Region) - } - cert, err := getSSLCertByID(*req.SSLId, *req.ProjectId, *req.Region) - if err != nil { - return nil - } - ulbs := []string{} - for _, b := range cert.BindedTargetSet { - ulbs = append(ulbs, fmt.Sprintf("%s/%s", b.ULBId, b.ULBName)) - } - return ulbs - }) - command.SetCompletion(cmd, "vserver-id", func() []string { - if *req.SSLId == "" { - return getAllULBVServerIDNames(*req.ULBId, *req.ProjectId, *req.Region) - } - cert, err := getSSLCertByID(*req.SSLId, *req.ProjectId, *req.Region) - if err != nil { - return nil - } - vservers := []string{} - for _, b := range cert.BindedTargetSet { - vservers = append(vservers, fmt.Sprintf("%s/%s", b.VServerId, b.VServerName)) - } - return vservers - }) - cmd.MarkFlagRequired("ssl-id") - cmd.MarkFlagRequired("ulb-id") - cmd.MarkFlagRequired("vserver-id") - return cmd -} - -func readFile(file string) (string, error) { - byts, err := ioutil.ReadFile(file) - if err != nil { - return "", err - } - return string(byts), nil -} - -func getAllULBVServerNodes(ulbID, vserverID, project, region string) ([]ulb.ULBBackendSet, error) { - vsList, err := getAllULBVServer(ulbID, vserverID, project, region) - if err != nil { - return nil, err - } - nodeList := []ulb.ULBBackendSet{} - for _, vs := range vsList { - nodeList = append(nodeList, vs.BackendSet...) - } - return nodeList, nil -} - -func getAllULBVServerNodeIDNames(ulbID, vserverID, project, region string) []string { - nodeList, err := getAllULBVServerNodes(ulbID, vserverID, project, region) - if err != nil { - return nil - } - idNames := []string{} - for _, node := range nodeList { - idNames = append(idNames, fmt.Sprintf("%s/%s", node.BackendId, node.ResourceName)) - } - return idNames -} - -func getAllSSLCertIDNames(project, region string) []string { - sslcs, err := getAllSSLCerts(project, region) - if err != nil { - return nil - } - idNames := []string{} - for _, ssl := range sslcs { - idNames = append(idNames, fmt.Sprintf("%s/%s", ssl.SSLId, ssl.SSLName)) - } - return idNames -} - -func getAllSSLCerts(project, region string) ([]ulb.ULBSSLSet, error) { - req := base.BizClient.NewDescribeSSLRequest() - req.ProjectId = sdk.String(base.PickResourceID(project)) - req.Region = sdk.String(region) - list := []ulb.ULBSSLSet{} - for offset, limit := 0, 50; ; offset += limit { - req.Offset = sdk.Int(offset) - req.Limit = sdk.Int(limit) - resp, err := base.BizClient.DescribeSSL(req) - if err != nil { - return nil, err - } - list = append(list, resp.DataSet...) - if resp.TotalCount <= offset+limit { - break - } - } - return list, nil -} - -func getSSLCertByID(sslID, project, region string) (*ulb.ULBSSLSet, error) { - if sslID == "" { - return nil, fmt.Errorf("ssl certificate resource id can't be empty") - } - req := base.BizClient.NewDescribeSSLRequest() - req.ProjectId = sdk.String(base.PickResourceID(project)) - req.Region = sdk.String(region) - req.SSLId = sdk.String(base.PickResourceID(sslID)) - resp, err := base.BizClient.DescribeSSL(req) - if err != nil { - return nil, err - } - if len(resp.DataSet) <= 0 { - return nil, fmt.Errorf("ssl certificate[%s] is not exists", sslID) - } - return &resp.DataSet[0], nil -} - -func getAllULBVServer(ulbID, vserverID, project, region string) ([]ulb.ULBVServerSet, error) { - req := base.BizClient.NewDescribeVServerRequest() - req.ULBId = sdk.String(base.PickResourceID(ulbID)) - req.ProjectId = sdk.String(base.PickResourceID(project)) - req.Region = ®ion - if vserverID != "" { - req.VServerId = sdk.String(base.PickResourceID(vserverID)) - } - resp, err := base.BizClient.DescribeVServer(req) - if err != nil { - return nil, err - } - if vserverID != "" { - if len(resp.DataSet) < 1 { - return nil, fmt.Errorf("VServer[%s] may not exist", vserverID) - } else if len(resp.DataSet) > 1 { - return nil, fmt.Errorf("Internal Error, too many vserver:%#v", resp.DataSet) - } - } - return resp.DataSet, nil -} - -func getAllULBVServerIDNames(ulbID, project, region string) []string { - vservers, err := getAllULBVServer(ulbID, "", project, region) - if err != nil { - return nil - } - idNames := []string{} - for _, vs := range vservers { - idNames = append(idNames, fmt.Sprintf("%s/%s", vs.VServerId, vs.VServerName)) - } - return idNames -} diff --git a/cmd/umem.go b/cmd/umem.go deleted file mode 100644 index f65ec76afd..0000000000 --- a/cmd/umem.go +++ /dev/null @@ -1,811 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - "strings" - "unicode/utf8" - - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-sdk-go/services/umem" - "github.com/ucloud/ucloud-sdk-go/services/vpc" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - "github.com/ucloud/ucloud-sdk-go/ucloud/request" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/model/status" - "github.com/ucloud/ucloud-cli/pkg/command" - "github.com/ucloud/ucloud-cli/ux" -) - -// NewCmdRedis ucloud redis -func NewCmdRedis() *cobra.Command { - cmd := &cobra.Command{ - Use: "redis", - Short: "List and manipulate redis instances", - Long: "List and manipulate redis instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdRedisList(out)) - cmd.AddCommand(NewCmdRedisCreate(out)) - cmd.AddCommand(NewCmdRedisDelete(out)) - cmd.AddCommand(NewCmdRedisRestart(out)) - return cmd -} - -// NewCmdMemcache ucloud memcache -func NewCmdMemcache() *cobra.Command { - cmd := &cobra.Command{ - Use: "memcache", - Short: "List and manipulate memcache instances", - Long: "List and manipulate memcache instances", - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdMemcacheList(out)) - cmd.AddCommand(NewCmdMemcacheCreate(out)) - cmd.AddCommand(NewCmdMemcacheDelete(out)) - cmd.AddCommand(NewCmdMemcacheRestart(out)) - return cmd -} - -// UMemRedisRow 表格行 -type UMemRedisRow struct { - ResourceID string - Name string - Role string - Type string - Address string - Size string - UsedSize string - State string - Group string - Zone string - CreateTime string -} - -var redisTypeMap = map[string]string{ - "single": "master-replica", - "distributed": "distributed", -} - -// NewCmdRedisList ucloud redis list -func NewCmdRedisList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeUMemRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List redis instances", - Long: "List redis instances", - Run: func(c *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeUMem(req) - if err != nil { - base.HandleError(err) - return - } - list := []UMemRedisRow{} - for _, ins := range resp.DataSet { - row := UMemRedisRow{ - ResourceID: ins.ResourceId, - Name: ins.Name, - Role: ins.Role, - Type: redisTypeMap[ins.ResourceType], - Group: ins.Tag, - Size: fmt.Sprintf("%dGB", ins.Size), - UsedSize: fmt.Sprintf("%dMB", ins.UsedSize), - State: ins.State, - Zone: ins.Zone, - CreateTime: common.FormatDate(ins.CreateTime), - } - addrs := []string{} - for _, addr := range ins.Address { - addrs = append(addrs, fmt.Sprintf("%s:%d", addr.IP, addr.Port)) - } - row.Address = strings.Join(addrs, "|") - list = append(list, row) - for _, slave := range ins.DataSet { - srow := UMemRedisRow{ - ResourceID: slave.GroupId, - Name: slave.Name, - Role: fmt.Sprintf("\u2b91 %s", slave.Role), - Type: redisTypeMap[slave.ResourceType], - Group: slave.Tag, - Size: fmt.Sprintf("%dGB", slave.Size), - UsedSize: fmt.Sprintf("%dMB", slave.UsedSize), - State: slave.State, - Zone: slave.Zone, - Address: fmt.Sprintf("%s:%d", slave.VirtualIP, slave.Port), - CreateTime: common.FormatDate(slave.CreateTime), - } - list = append(list, srow) - } - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.ResourceId = flags.String("umem-id", "", "Optional. Resource ID of the redis to list") - bindRegion(req, cmd) - bindZoneEmpty(req, cmd) - bindProjectID(req, cmd) - bindOffset(req, flags) - bindLimit(req, flags) - req.Protocol = sdk.String("redis") - - command.SetCompletion(cmd, "umem-id", func() []string { - return getRedisIDList(*req.ProjectId, *req.Region) - }) - - return cmd -} - -// redisCreateParams holds the shared flag values for redis create commands. -type redisCreateParams struct { - name string - password string - size int - region string - zone string - projectID string - chargeType string - quantity int - group string - vpcID string - subnetID string - version string - blockCnt int - proxySize int -} - -// NewCmdRedisCreate ucloud redis create -func NewCmdRedisCreate(out io.Writer) *cobra.Command { - var redisType string - var p redisCreateParams - cmd := &cobra.Command{ - Use: "create", - Short: "Create redis instance", - Long: "Create redis instance", - Run: func(c *cobra.Command, args []string) { - // Validate name,support Chinese name - if l := utf8.RuneCountInString(p.name); l < 6 || l > 63 { - fmt.Fprintln(out, "length of name should be between 6 and 63") - return - } - // Validate password - if p.password != "" { - if l := len(p.password); l < 6 || l > 36 { - fmt.Fprintln(out, "length of password should be between 6 and 36") - return - } - } - if err := fillDefaultVPCAndSubnet(&p.vpcID, &p.subnetID, p.projectID, p.region, p.zone); err != nil { - fmt.Fprintln(out, err) - return - } - switch redisType { - case "master-replica": - createMasterReplicaRedis(out, &p) - case "distributed": - createDistributedRedis(out, &p) - default: - fmt.Printf("unknow redis type[%s], it's should be 'master-replica' or 'distributed'\n", redisType) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringVar(&p.name, "name", "", "Required. Name of the redis to create. Range of the name length is [6,63]") - flags.StringVar(&redisType, "type", "", "Required. Type of the redis. Accept values:'master-replica','distributed'") - flags.IntVar(&p.size, "size-gb", 2, "Optional. Memory size. Default value 2GB. Unit GB") - flags.StringVar(&p.version, "version", "6.0", "Optional. Version of redis. Accept values: '4.0', '5.0', '6.0', '7.0'") - flags.StringVar(&p.vpcID, "vpc-id", "", "Optional. VPC ID. This field is required under VPC2.0. See 'ucloud vpc list'") - flags.StringVar(&p.subnetID, "subnet-id", "", "Optional. Subnet ID. This field is required under VPC2.0. See 'ucloud subnet list'") - flags.StringVar(&p.password, "password", "", "Optional. Password of redis to create. Range of the password length is [6,36] and the password can only contain letters and numbers") - - //distributed optional params - flags.IntVar(&p.blockCnt, "block-cnt", 2, "Optional. Block count. Default value 2(for distributed redis type).") - flags.IntVar(&p.proxySize, "proxy-size", 2, "Optional. Proxy size. Default value 2(for distributed redis type) Unit Core") - - bindRegionS(&p.region, cmd) - bindZoneS(&p.zone, &p.region, cmd) - bindProjectIDS(&p.projectID, cmd) - flags.StringVar(&p.chargeType, "charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") - flags.IntVar(&p.quantity, "quantity", 1, "Optional. The duration of the instance. N years/months.") - flags.StringVar(&p.group, "group", "", "Optional. Business group") - - command.SetFlagValues(cmd, "version", "4.0", "5.0", "6.0", "7.0") - command.SetFlagValues(cmd, "type", "master-replica", "distributed") - command.SetFlagValues(cmd, "charge-type", "Month", "Dynamic", "Year") - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(p.projectID, p.region) - }) - command.SetCompletion(cmd, "subnet-id", func() []string { - return getAllSubnetIDNames(p.vpcID, p.projectID, p.region) - }) - - cmd.MarkFlagRequired("name") - cmd.MarkFlagRequired("type") - - return cmd -} - -func createMasterReplicaRedis(out io.Writer, p *redisCreateParams) { - req := base.BizClient.NewCreateURedisGroupRequest() - req.Region = &p.region - req.Zone = &p.zone - req.ProjectId = &p.projectID - req.Name = &p.name - req.HighAvailability = sdk.String("enable") - req.Size = &p.size - req.Version = &p.version - req.VPCId = &p.vpcID - req.SubnetId = &p.subnetID - req.ChargeType = &p.chargeType - req.Quantity = &p.quantity - req.Tag = &p.group - if p.password != "" { - req.Password = &p.password - } - - resp, err := base.BizClient.CreateURedisGroup(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "redis[%s] created\n", resp.GroupId) -} - -func createDistributedRedis(out io.Writer, p *redisCreateParams) { - req := base.BizClient.NewCreateUMemSpaceRequest() - req.Region = &p.region - req.Zone = &p.zone - req.ProjectId = &p.projectID - req.Name = &p.name - req.Protocol = sdk.String("redis") - - // Validate block-cnt - if p.blockCnt <= 0 { - fmt.Fprintln(out, "block-cnt should be greater than 0") - return - } - - // Validate size is divisible by block-cnt - if p.size%p.blockCnt != 0 { - fmt.Fprintf(out, "size-gb(%d) should be divisible by block-cnt(%d)\n", p.size, p.blockCnt) - return - } - - // Validate proxy-size is a multiple of 2 - if p.proxySize%2 != 0 { - fmt.Fprintf(out, "proxy-size(%d) should be a multiple of 2\n", p.proxySize) - return - } - - req.BlockCnt = &p.blockCnt - req.ProxySize = &p.proxySize - req.Size = &p.size - req.Version = &p.version - req.VPCId = &p.vpcID - req.SubnetId = &p.subnetID - req.ChargeType = &p.chargeType - req.Quantity = &p.quantity - req.Tag = &p.group - if p.password != "" { - req.Password = &p.password - } - - resp, err := base.BizClient.CreateUMemSpace(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "redis[%s] created\n", resp.SpaceId) -} - -// redisDeleteParams holds the shared flag values for redis delete commands. -type redisDeleteParams struct { - region string - zone string - projectID string -} - -// NewCmdRedisDelete ucloud redis delete -func NewCmdRedisDelete(out io.Writer) *cobra.Command { - var idNames []string - var p redisDeleteParams - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete redis instances", - Long: "Delete redis instances", - Example: "ucloud redis delete --umem-id uredis-rl5xuxx/testcli1,uredis-xsdfa/testcli2", - Run: func(c *cobra.Command, args []string) { - for _, idname := range idNames { - id := base.PickResourceID(idname) - if strings.HasPrefix(id, "uredis") || strings.HasPrefix(id, "uhredis") || strings.HasPrefix(id, "uregionredis") { - deleteMasterReplicaRedis(out, &p, id) - } else if strings.HasPrefix(id, "udredis") { - deleteDistributedRedis(out, &p, id) - } else { - fmt.Fprintf(out, "redis[%s] unknown id prefix, skip\n", idname) - } - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis instances to delete") - bindRegionS(&p.region, cmd) - bindZoneS(&p.zone, &p.region, cmd) - bindProjectIDS(&p.projectID, cmd) - - cmd.MarkFlagRequired("umem-id") - - command.SetCompletion(cmd, "umem-id", func() []string { - return getRedisIDList(p.projectID, p.region) - }) - - return cmd -} - -func deleteMasterReplicaRedis(out io.Writer, p *redisDeleteParams, id string) { - req := base.BizClient.NewDeleteURedisGroupRequest() - req.Region = &p.region - req.Zone = &p.zone - req.ProjectId = &p.projectID - req.GroupId = &id - _, err := base.BizClient.DeleteURedisGroup(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "redis[%s] deleted\n", id) -} - -func deleteDistributedRedis(out io.Writer, p *redisDeleteParams, id string) { - req := base.BizClient.NewDeleteUMemSpaceRequest() - req.Region = &p.region - req.Zone = &p.zone - req.ProjectId = &p.projectID - req.SpaceId = &id - _, err := base.BizClient.DeleteUMemSpace(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "redis[%s] deleted\n", id) -} - -// NewCmdRedisRestart ucloud redis restart -func NewCmdRedisRestart(out io.Writer) *cobra.Command { - idNames := make([]string, 0) - req := base.BizClient.UMemClient.NewRestartURedisGroupRequest() - cmd := &cobra.Command{ - Use: "restart", - Short: "Restart redis instances of master-replica type", - Long: "Restart redis instances of master-replica type", - Run: func(c *cobra.Command, args []string) { - reqs := make([]request.Common, len(idNames)) - for idx, idname := range idNames { - id := base.PickResourceID(idname) - _req := *req - _req.GroupId = &id - reqs[idx] = &_req - } - coAction := newConcurrentAction(reqs, 10, restartRedis) - coAction.Do() - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis instances to restart") - bindProjectID(req, cmd) - bindRegion(req, cmd) - bindZone(req, cmd) - - cmd.MarkFlagRequired("umem-id") - command.SetCompletion(cmd, "umem-id", func() []string { - return getRedisIDList(*req.ProjectId, *req.Region) - }) - - return cmd -} - -func restartRedis(creq request.Common) (bool, []string) { - req := creq.(*umem.RestartURedisGroupRequest) - block := ux.NewBlock() - ux.Doc.Append(block) - logs := make([]string, 0) - logs = append(logs, fmt.Sprintf("api:RestartURedisGroup, request:%v", base.ToQueryMap(req))) - _, err := base.BizClient.UMemClient.RestartURedisGroup(req) - if err != nil { - block.Append(base.ParseError(err)) - logs = append(logs, fmt.Sprintf("restart redis[%s] failed: %s", *req.GroupId, base.ParseError(err))) - return false, logs - } - poller := base.NewSpoller(describeRedisByID, base.Cxt.GetWriter()) - text := fmt.Sprintf("redis[%s] is restarting", *req.GroupId) - ret := poller.Sspoll(*req.GroupId, text, []string{status.UMEM_RUNNING, status.UMEM_FAIL}, block, nil) - if ret.Err != nil { - block.Append(base.ParseError(err)) - logs = append(logs, ret.Err.Error()) - } - if ret.Timeout { - logs = append(logs, "poll redis[%s] timeout", *req.GroupId) - } - return ret.Done, logs -} - -func getRedisIDList(project, region string) []string { - req := base.BizClient.NewDescribeURedisGroupRequest() - req.ProjectId = &project - req.Region = ®ion - list := []string{} - - for limit, offset := 50, 0; ; offset += limit { - req.Limit = sdk.Int(limit) - req.Offset = sdk.Int(offset) - resp, err := base.BizClient.DescribeURedisGroup(req) - if err != nil { - return nil - } - for _, ins := range resp.DataSet { - list = append(list, fmt.Sprintf("%s/%s", ins.GroupId, ins.Name)) - } - if offset+limit >= resp.TotalCount { - break - } - } - return list -} - -// UMemMemcacheRow 表格行 -type UMemMemcacheRow struct { - ResourceID string - Name string - Address string - Size string - UsedSize string - State string - Group string - CreateTime string -} - -// NewCmdMemcacheList ucloud memcache list -func NewCmdMemcacheList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeUMemcacheGroupRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List memcache instances", - Long: "List memcache instances", - Run: func(c *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeUMemcacheGroup(req) - if err != nil { - base.HandleError(err) - return - } - list := []UMemMemcacheRow{} - for _, ins := range resp.DataSet { - row := UMemMemcacheRow{ - ResourceID: ins.GroupId, - Name: ins.Name, - Group: ins.Tag, - Size: fmt.Sprintf("%dGB", ins.Size), - UsedSize: fmt.Sprintf("%dMB", ins.UsedSize), - State: ins.State, - CreateTime: common.FormatDate(ins.CreateTime), - Address: fmt.Sprintf("%s:%d", ins.VirtualIP, ins.Port), - } - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.GroupId = flags.String("umem-id", "", "Optional. Resource ID of the redis to list") - bindRegion(req, cmd) - bindZoneEmpty(req, cmd) - bindProjectID(req, cmd) - bindOffset(req, flags) - bindLimit(req, flags) - - return cmd -} - -// NewCmdMemcacheCreate ucloud memcache create -func NewCmdMemcacheCreate(out io.Writer) *cobra.Command { - req := base.BizClient.NewCreateUMemcacheGroupRequest() - var region, zone, projectID string - cmd := &cobra.Command{ - Use: "create", - Short: "Create memcache instance", - Long: "Create memcache instance", - Run: func(c *cobra.Command, args []string) { - if *req.Size > 32 || *req.Size < 1 { - fmt.Fprintln(out, "size-gb should be between 1 and 32") - return - } - if err := fillDefaultVPCAndSubnet(req.VPCId, req.SubnetId, *req.ProjectId, *req.Region, *req.Zone); err != nil { - fmt.Fprintln(out, err) - return - } - resp, err := base.BizClient.CreateUMemcacheGroup(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "memcache[%s] created\n", resp.GroupId) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.Name = flags.String("name", "", "Required. Name of memcache instance to create") - req.Size = flags.Int("size-gb", 1, "Optional. Memory size of memcache instance. Unit GB. Accpet values:1,2,4,8,16,32") - req.VPCId = flags.String("vpc-id", "", "Optional. VPC ID. See 'ucloud vpc list'") - req.SubnetId = flags.String("subnet-id", "", "Optional. Subnet ID. See 'ucloud subnet list'") - bindRegionS(®ion, cmd) - bindZoneS(&zone, ®ion, cmd) - bindProjectIDS(&projectID, cmd) - req.ChargeType = flags.String("charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") - req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") - req.Tag = flags.String("group", "", "Optional. Business group") - - // Set region/zone/projectID to request after flag parsing - req.Region = ®ion - req.Zone = &zone - req.ProjectId = &projectID - - command.SetFlagValues(cmd, "size-gb", "1", "2", "4", "8", "16", "32") - command.SetFlagValues(cmd, "charge-type", "Month", "Dynamic", "Year") - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(projectID, region) - }) - command.SetCompletion(cmd, "subnet-id", func() []string { - return getAllSubnetIDNames(*req.VPCId, projectID, region) - }) - - cmd.MarkFlagRequired("name") - - return cmd -} - -// NewCmdMemcacheDelete ucloud memcache delete -func NewCmdMemcacheDelete(out io.Writer) *cobra.Command { - var idNames []string - req := base.BizClient.NewDeleteUMemcacheGroupRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete memcache instances", - Long: "Delete memcache instances", - Example: "ucloud memcache delete --umem-id umemcache-rl5xuxx/testcli1,umemcache-xsdfa/testcli2", - Run: func(c *cobra.Command, args []string) { - for _, idname := range idNames { - id := base.PickResourceID(idname) - req.GroupId = &id - _, err := base.BizClient.DeleteUMemcacheGroup(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "memcache[%s] deleted\n", idname) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of memcache intances to delete") - bindProjectID(req, cmd) - bindRegion(req, cmd) - bindZoneEmpty(req, cmd) - - cmd.MarkFlagRequired("umem-id") - - command.SetCompletion(cmd, "umem-id", func() []string { - return getMemcacheIDList(*req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdMemcacheRestart ucloud memcache restart -func NewCmdMemcacheRestart(out io.Writer) *cobra.Command { - idNames := make([]string, 0) - req := base.BizClient.NewRestartUMemcacheGroupRequest() - cmd := &cobra.Command{ - Use: "restart", - Short: "Restart memcache instances", - Long: "Restart memcache instances", - Run: func(c *cobra.Command, args []string) { - reqs := make([]request.Common, len(idNames)) - for idx, idname := range idNames { - id := base.PickResourceID(idname) - _req := *req - _req.GroupId = &id - reqs[idx] = &_req - } - coAction := newConcurrentAction(reqs, 10, restartMemcache) - coAction.Do() - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of memcache to restart") - bindRegion(req, cmd) - bindZone(req, cmd) - bindProjectID(req, cmd) - - command.SetCompletion(cmd, "umem-id", func() []string { - return getMemcacheIDList(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("umem-id") - return cmd -} - -func restartMemcache(creq request.Common) (bool, []string) { - req := creq.(*umem.RestartUMemcacheGroupRequest) - block := ux.NewBlock() - ux.Doc.Append(block) - logs := make([]string, 0) - logs = append(logs, fmt.Sprintf("api:RestartUMemcacheGroup, request:%v", base.ToQueryMap(req))) - _, err := base.BizClient.RestartUMemcacheGroup(req) - if err != nil { - block.Append(base.ParseError(err)) - logs = append(logs, fmt.Sprintf("restart memcache[%s] failed: %s", *req.GroupId, base.ParseError(err))) - return false, logs - } - poller := base.NewSpoller(describeMemcacheByID, base.Cxt.GetWriter()) - text := fmt.Sprintf("memcache[%s] is restarting", *req.GroupId) - ret := poller.Sspoll(*req.GroupId, text, []string{status.UMEM_RUNNING, status.UMEM_FAIL}, block, nil) - if ret.Err != nil { - block.Append(base.ParseError(err)) - logs = append(logs, ret.Err.Error()) - } - if ret.Timeout { - logs = append(logs, "poll memcache[%s] timeout", *req.GroupId) - } - return ret.Done, logs -} - -func describeMemcacheByID(memcacheID string, commonBase *request.CommonBase) (interface{}, error) { - req := base.BizClient.NewDescribeUMemRequest() - if commonBase != nil { - req.CommonBase = *commonBase - } - req.Protocol = sdk.String("memcache") - req.ResourceId = &memcacheID - - resp, err := base.BizClient.DescribeUMem(req) - if err != nil { - return nil, err - } - if len(resp.DataSet) < 1 { - return nil, fmt.Errorf("resource [%s] may not exist", memcacheID) - } - return &resp.DataSet[0], nil -} -func describeRedisByID(redisID string, commonBase *request.CommonBase) (interface{}, error) { - req := base.BizClient.NewDescribeUMemRequest() - if commonBase != nil { - req.CommonBase = *commonBase - } - req.Protocol = sdk.String("redis") - req.ResourceId = &redisID - - resp, err := base.BizClient.DescribeUMem(req) - if err != nil { - return nil, err - } - if len(resp.DataSet) < 1 { - return nil, fmt.Errorf("resource [%s] may not exist", redisID) - } - return &resp.DataSet[0], nil -} - -func fillDefaultVPCAndSubnet(vpcID, subnetID *string, projectID, region, zone string) error { - if *vpcID != "" && *subnetID != "" { - return nil - } - vpcs, err := getAllVPCIns(projectID, region) - if err != nil { - return fmt.Errorf("failed to get vpc list: %s", err) - } - if len(vpcs) == 0 { - return fmt.Errorf("no vpc found in region[%s], please specify --vpc-id and --subnet-id", region) - } - - // Find the default VPC - var defaultVPC *vpc.VPCInfo - for i := range vpcs { - if vpcs[i].VPCType == "DefaultVPC" { - defaultVPC = &vpcs[i] - break - } - } - // Fallback to the first VPC if no DefaultVPC found - if defaultVPC == nil { - defaultVPC = &vpcs[0] - } - - if *vpcID == "" { - *vpcID = defaultVPC.VPCId - } - - if *subnetID == "" { - subnets, err := getAllSubnets(*vpcID, projectID, region) - if err != nil { - return fmt.Errorf("failed to get subnet list: %s", err) - } - if len(subnets) == 0 { - return fmt.Errorf("no subnet found in vpc[%s], please specify --subnet-id", *vpcID) - } - // Filter subnets by zone if specified - if zone != "" { - for _, sn := range subnets { - if sn.Zone == zone { - *subnetID = sn.SubnetId - return nil - } - } - } - // Fallback to the first subnet - *subnetID = subnets[0].SubnetId - } - - return nil -} - -func getMemcacheIDList(project, region string) []string { - req := base.BizClient.NewDescribeUMemcacheGroupRequest() - req.ProjectId = &project - req.Region = ®ion - list := []string{} - - for limit, offset := 50, 0; ; offset += limit { - req.Limit = sdk.Int(limit) - req.Offset = sdk.Int(offset) - resp, err := base.BizClient.DescribeUMemcacheGroup(req) - if err != nil { - fmt.Println(err) - return nil - } - for _, ins := range resp.DataSet { - list = append(list, fmt.Sprintf("%s/%s", ins.GroupId, ins.Name)) - } - if offset+limit >= resp.TotalCount { - break - } - } - return list -} diff --git a/cmd/unet.go b/cmd/unet.go deleted file mode 100644 index 2d8f5a8c5f..0000000000 --- a/cmd/unet.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright © 2018 NAME HERE tony.li@ucloud.cn -// -// 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 cmd - -import ( - "fmt" - "io" - - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-sdk-go/services/udpn" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/pkg/command" -) - -// NewCmdUDPN ucloud udpn -func NewCmdUDPN(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "udpn", - Short: "List and manipulate udpn instances", - Long: "List and manipulate udpn instances", - } - - cmd.AddCommand(NewCmdUDPNCreate(out)) - cmd.AddCommand(NewCmdUDPNList(out)) - cmd.AddCommand(NewCmdUdpnDelete(out)) - cmd.AddCommand(NewCmdUdpnModifyBW(out)) - - return cmd -} - -// NewCmdUDPNCreate ucloud udpn create -func NewCmdUDPNCreate(out io.Writer) *cobra.Command { - req := base.BizClient.NewAllocateUDPNRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create UDPN tunnel", - Long: "Create UDPN tunnel", - Run: func(c *cobra.Command, args []string) { - if *req.Peer1 == *req.Peer2 { - fmt.Fprintln(out, "Error, flags peer1 and peer2 can't be equal") - return - } - resp, err := base.BizClient.AllocateUDPN(req) - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "udpn[%s] created\n", resp.UDPNId) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.Peer1 = flags.String("peer1", base.ConfigIns.Region, "Required. One end of the tunnel to create") - req.Peer2 = flags.String("peer2", "", "Required. The other end of the tunnel create") - req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. Bandwidth of the tunnel to create. Unit:Mb. Rnange [2,1000]") - req.ChargeType = flags.String("charge-type", "", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") - req.Quantity = cmd.Flags().Int("quantity", 1, "Optional. The duration of the instance. N years/months.") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - - command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") - command.SetCompletion(cmd, "project-id", getProjectList) - command.SetCompletion(cmd, "peer1", getRegionList) - //peer1和peer2不相等 - command.SetCompletion(cmd, "peer2", func() []string { - regions := getRegionList() - list := []string{} - for _, r := range regions { - if r != *req.Peer1 { - list = append(list, r) - } - } - return list - }) - - cmd.MarkFlagRequired("peer1") - cmd.MarkFlagRequired("peer2") - cmd.MarkFlagRequired("bandwidth-mb") - - return cmd -} - -// UDPNRow 表格行 -type UDPNRow struct { - ResourceID string - Peers string - Bandwidth string - ChargeType string - CreationTime string -} - -// NewCmdUDPNList ucloud udpn list -func NewCmdUDPNList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeUDPNRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List udpn instances", - Long: "List udpn instances", - Run: func(c *cobra.Command, args []string) { - req.UDPNId = sdk.String(base.PickResourceID(*req.UDPNId)) - resp, err := base.BizClient.DescribeUDPN(req) - if err != nil { - base.HandleError(err) - return - } - list := []UDPNRow{} - for _, udpn := range resp.DataSet { - row := UDPNRow{} - row.ResourceID = udpn.UDPNId - row.Peers = fmt.Sprintf("%s <--> %s", udpn.Peer1, udpn.Peer2) - row.Bandwidth = fmt.Sprintf("%dMb", udpn.Bandwidth) - row.ChargeType = udpn.ChargeType - row.CreationTime = common.FormatDate(udpn.CreateTime) - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - req.UDPNId = flags.String("udpn-id", "", "Optional. Resource ID of udpn instances to list") - req.Offset = flags.Int("offset", 0, "Optional. Offset") - req.Limit = flags.Int("limit", 50, "Optional. Limit") - req.Region = flags.String("region", base.ConfigIns.Region, "Optional. Region, see 'ucloud region'") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - - command.SetCompletion(cmd, "region", getRegionList) - command.SetCompletion(cmd, "project-id", getRegionList) - command.SetCompletion(cmd, "udpn-id", func() []string { - return getAllUDPNIdNames(*req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdUdpnDelete ucloud udpn delete -func NewCmdUdpnDelete(out io.Writer) *cobra.Command { - idNames := []string{} - req := base.BizClient.NewReleaseUDPNRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "delete udpn instances", - Long: "delete udpn instances", - Run: func(c *cobra.Command, args []string) { - for _, idname := range idNames { - req.UDPNId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.ReleaseUDPN(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "udpn[%s] deleted\n", idname) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "udpn-id", nil, "Required. Resource ID of udpn instances to delete") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - - command.SetCompletion(cmd, "project-id", getRegionList) - command.SetCompletion(cmd, "udpn-id", func() []string { - return getAllUDPNIdNames(*req.ProjectId, base.ConfigIns.Region) - }) - - cmd.MarkFlagRequired("udpn-id") - - return cmd -} - -// NewCmdUdpnModifyBW ucloud udpn modify-bw -func NewCmdUdpnModifyBW(out io.Writer) *cobra.Command { - idNames := []string{} - req := base.BizClient.NewModifyUDPNBandwidthRequest() - cmd := &cobra.Command{ - Use: "modify-bw", - Short: "Modify bandwidth of UDPN tunnel", - Long: "Modify bandwidth of UDPN tunnel", - Run: func(c *cobra.Command, args []string) { - for _, idname := range idNames { - req.UDPNId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.ModifyUDPNBandwidth(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Fprintf(out, "udpn[%s]'s bandwidth modified\n", idname) - } - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "udpn-id", nil, "Required. Resource ID of UDPN to modify bandwidth") - req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. Bandwidth of UDPN tunnel. Unit:Mb. Range [2,1000]") - req.Region = flags.String("region", base.ConfigIns.Region, "Optional. Region, see 'ucloud region'") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - - command.SetCompletion(cmd, "udpn-id", func() []string { - return getAllUDPNIdNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("udpn-id") - cmd.MarkFlagRequired("bandwidth-mb") - - return cmd -} - -func getAllUDPNIns(project, region string) ([]udpn.UDPNData, error) { - req := base.BizClient.NewDescribeUDPNRequest() - req.ProjectId = sdk.String(project) - req.Region = sdk.String(region) - list := make([]udpn.UDPNData, 0) - for offset, limit := 0, 50; ; offset += limit { - req.Offset = sdk.Int(offset) - req.Limit = sdk.Int(limit) - resp, err := base.BizClient.DescribeUDPN(req) - if err != nil { - return nil, err - } - for _, u := range resp.DataSet { - list = append(list, u) - } - if offset+limit > resp.TotalCount { - break - } - } - return list, nil -} - -func getAllUDPNIdNames(project, region string) []string { - udpnInsList, err := getAllUDPNIns(project, region) - if err != nil { - return nil - } - idNameList := []string{} - for _, udpn := range udpnInsList { - idNameList = append(idNameList, fmt.Sprintf("%s/%s:%s", udpn.UDPNId, udpn.Peer1, udpn.Peer2)) - } - return idNameList -} diff --git a/cmd/util.go b/cmd/util.go index 4c8639164a..8dd86b2c91 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "reflect" - "strings" "sync" "time" @@ -112,15 +111,6 @@ func bindQuantity(req interface{}, flags *pflag.FlagSet) { f.Set(reflect.ValueOf(quanitiy)) } -func getEIPLine(region string) (line string) { - if strings.HasPrefix(region, "cn") { - line = "BGP" - } else { - line = "International" - } - return -} - type concurrentAction struct { reqs []request.Common actionFunc func(request.Common) (bool, []string) diff --git a/cmd/vpc.go b/cmd/vpc.go deleted file mode 100644 index 1e72a11ebf..0000000000 --- a/cmd/vpc.go +++ /dev/null @@ -1,575 +0,0 @@ -package cmd - -import ( - "fmt" - "io" - "net" - "strconv" - "strings" - - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-sdk-go/services/vpc" - sdk "github.com/ucloud/ucloud-sdk-go/ucloud" - - "github.com/ucloud/ucloud-cli/base" - "github.com/ucloud/ucloud-cli/internal/common" - "github.com/ucloud/ucloud-cli/pkg/command" -) - -// NewCmdVpc ucloud vpc -func NewCmdVpc() *cobra.Command { - cmd := &cobra.Command{ - Use: "vpc", - Short: "List and manipulate VPC instances", - Long: "List and manipulate VPC instances", - Args: cobra.NoArgs, - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdVpcCreate()) - cmd.AddCommand(NewCmdVPCList(out)) - cmd.AddCommand(NewCmdVpcDelete()) - cmd.AddCommand(NewCmdVpcCreatePeer()) - cmd.AddCommand(NewCmdVpcListPeer(out)) - cmd.AddCommand(NewCmdVpcDeletePeer()) - return cmd -} - -// VPCRow 表格行 -type VPCRow struct { - VPCName string - ResourceID string - Group string - NetworkSegment string - SubnetCount int - CreationTime string -} - -// NewCmdVPCList ucloud vpc list -func NewCmdVPCList(out io.Writer) *cobra.Command { - vpcIDs := []string{} - req := base.BizClient.NewDescribeVPCRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List vpc", - Long: "List vpc", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - for _, id := range vpcIDs { - req.VPCIds = append(req.VPCIds, base.PickResourceID(id)) - } - resp, err := base.BizClient.DescribeVPC(req) - if err != nil { - base.HandleError(err) - return - } - list := []VPCRow{} - for _, vpc := range resp.DataSet { - row := VPCRow{} - row.VPCName = vpc.Name - row.ResourceID = vpc.VPCId - row.Group = vpc.Tag - row.NetworkSegment = strings.Join(vpc.Network, ",") - row.SubnetCount = vpc.SubnetCount - row.CreationTime = common.FormatDate(vpc.CreateTime) - list = append(list, row) - } - base.PrintList(list, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - req.Region = flags.String("region", base.ConfigIns.Region, "Optional. Region, see 'ucloud region'") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - req.Tag = flags.String("group", "", "Optional. Group") - flags.StringSliceVar(&vpcIDs, "vpc-id", []string{}, "Optional. Multiple values separated by commas") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - - return cmd -} - -// NewCmdVpcCreate ucloud vpc create -func NewCmdVpcCreate() *cobra.Command { - var segments *[]string - req := base.BizClient.NewCreateVPCRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create vpc network", - Long: "Create vpc network", - Example: "ucloud vpc create --name xxx --segment 192.168.0.0/16", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - req.Network = *segments - resp, err := base.BizClient.CreateVPC(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("vpc[%s] created\n", resp.VPCId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.Name = cmd.Flags().String("name", "", "Required. Name of the vpc network.") - segments = cmd.Flags().StringSlice("segment", nil, "Required. The segment for private network.") - req.Tag = cmd.Flags().String("group", "", "Optional. Business group.") - req.Remark = cmd.Flags().String("remark", "", "Optional. The description of the vpc.") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional. Assign the region of the VPC") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. Assign the project-id") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("name") - cmd.MarkFlagRequired("segment") - - return cmd -} - -// NewCmdVpcDelete ucloud vpc delete -func NewCmdVpcDelete() *cobra.Command { - idNames := []string{} - req := base.BizClient.NewDeleteVPCRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete vpc network", - Long: "Delete vpc network", - Example: "ucloud vpc delete --vpc-id uvnet-xxx", - Run: func(cmd *cobra.Command, args []string) { - for _, idname := range idNames { - req.VPCId = sdk.String(base.PickResourceID(idname)) - _, err := base.BizClient.DeleteVPC(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("vpc[%s] deleted\n", idname) - } - }, - } - - cmd.Flags().SortFlags = false - - cmd.Flags().StringSliceVar(&idNames, "vpc-id", nil, "Required. Resource ID of the vpc network to delete") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional. Region of the vpc") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. Project id of the vpc") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("vpc-id") - - return cmd -} - -// NewCmdVpcCreatePeer ucloud vpc peer -func NewCmdVpcCreatePeer() *cobra.Command { - req := base.BizClient.NewCreateVPCIntercomRequest() - cmd := &cobra.Command{ - Use: "create-intercome", - Short: "Create intercome with other vpc", - Long: "Create intercome with other vpc", - Example: "ucloud vpc create-intercome --vpc-id xx --dst-vpc-id xx --dst-region xx", - Run: func(cmd *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - req.DstProjectId = sdk.String(base.PickResourceID(*req.DstProjectId)) - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - req.DstVPCId = sdk.String(base.PickResourceID(*req.DstVPCId)) - _, err := base.BizClient.CreateVPCIntercom(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("intercome [%s<-->%s] establish", *req.VPCId, *req.DstVPCId) - }, - } - - cmd.Flags().SortFlags = false - - req.VPCId = cmd.Flags().String("vpc-id", "", "Required. The source vpc you want to establish the intercome") - req.DstVPCId = cmd.Flags().String("dst-vpc-id", "", "Required. The target vpc you want to establish the intercome") - req.DstRegion = cmd.Flags().String("dst-region", base.ConfigIns.Region, "Required. If the intercome established across different regions") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optioanl. The region of source vpc which will establish the intercome") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. The project id of the source vpc") - req.DstProjectId = cmd.Flags().String("dst-project-id", base.ConfigIns.ProjectID, "Optional. The project id of the source vpc") - - cmd.MarkFlagRequired("vpc-id") - cmd.MarkFlagRequired("dst-vpc-id") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "dst-vpc-id", func() []string { - return getAllVPCIdNames(*req.DstProjectId, *req.DstRegion) - }) - command.SetCompletion(cmd, "region", getRegionList) - command.SetCompletion(cmd, "dst-region", getRegionList) - command.SetCompletion(cmd, "project-id", getProjectList) - command.SetCompletion(cmd, "dst-project-id", getProjectList) - - return cmd -} - -// VPCIntercomRow 表格行 -type VPCIntercomRow struct { - VPCName string - ResourceID string - Segments string - ProjectID string - DstRegion string - Group string -} - -// NewCmdVpcListPeer ucloud vpc list-intercome -func NewCmdVpcListPeer(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeVPCIntercomRequest() - cmd := &cobra.Command{ - Use: "list-intercome", - Short: "list intercome ", - Long: "list intercome", - Example: "ucloud vpc list-intercome --vpc-id xx", - Run: func(cmd *cobra.Command, args []string) { - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - resp, err := base.BizClient.DescribeVPCIntercom(req) - if err != nil { - base.HandleError(err) - return - } - list := make([]VPCIntercomRow, 0) - for _, VPCIntercom := range resp.DataSet { - row := VPCIntercomRow{} - row.ProjectID = VPCIntercom.ProjectId - row.Segments = strings.Join(VPCIntercom.Network, ",") - row.DstRegion = VPCIntercom.DstRegion - row.VPCName = VPCIntercom.Name - row.ResourceID = VPCIntercom.VPCId - row.Group = VPCIntercom.Tag - list = append(list, row) - } - base.PrintList(list, out) - }, - } - req.VPCId = cmd.Flags().String("vpc-id", "", "Required. The vpc id which you wnat to describe the information") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. The project id of source vpc") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional, The region of source vpc") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "region", getRegionList) - command.SetCompletion(cmd, "project-id", getProjectList) - - cmd.MarkFlagRequired("vpc-id") - - return cmd -} - -// NewCmdVpcDeletePeer ucloud vpc delete-intercome -func NewCmdVpcDeletePeer() *cobra.Command { - req := base.BizClient.NewDeleteVPCIntercomRequest() - cmd := &cobra.Command{ - Use: "delete-intercome", - Short: "delete the vpc intercome", - Long: "delete the vpc intercome", - Example: "ucloud vpc delete-intercome --vpc-id xxx --dst-vpc-id xxx", - Run: func(cmd *cobra.Command, args []string) { - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - req.DstVPCId = sdk.String(base.PickResourceID(*req.DstVPCId)) - _, err := base.BizClient.DeleteVPCIntercom(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("intercome [%s<-->%s] deleted\n", *req.VPCId, *req.DstVPCId) - }, - } - - cmd.Flags().SortFlags = false - - req.VPCId = cmd.Flags().String("vpc-id", "", "Required. Resource ID of source VPC to disconnect with destination VPC") - req.DstVPCId = cmd.Flags().String("dst-vpc-id", "", "Required. Resource ID of destination VPC to disconnect with source VPC") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. The project id of source vpc") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional. The region of source vpc to disconnect") - req.DstRegion = cmd.Flags().String("dst-region", "", "Optional. The region of dest vpc to disconnect") - - cmd.MarkFlagRequired("vpc-id") - cmd.MarkFlagRequired("dst-vpc-id") - cmd.MarkFlagRequired("dst-region") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - command.SetCompletion(cmd, "dst-region", getRegionList) - - return cmd -} - -func getAllVPCIns(project, region string) ([]vpc.VPCInfo, error) { - req := base.BizClient.NewDescribeVPCRequest() - req.ProjectId = &project - req.Region = ®ion - resp, err := base.BizClient.DescribeVPC(req) - if err != nil { - return nil, err - } - return resp.DataSet, nil -} - -func getAllVPCIdNames(project, region string) []string { - vpcInsList, err := getAllVPCIns(project, region) - list := []string{} - if err != nil { - return nil - } - for _, vpc := range vpcInsList { - list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) - } - return list -} - -// NewCmdSubnet ucloud subnet -func NewCmdSubnet() *cobra.Command { - cmd := &cobra.Command{ - Use: "subnet", - Short: "List, create and delete subnet", - Long: "List, create and delete subnet", - Args: cobra.NoArgs, - } - out := base.Cxt.GetWriter() - cmd.AddCommand(NewCmdSubnetList(out)) - cmd.AddCommand(NewCmdSubnetCreate()) - cmd.AddCommand(NewCmdSubnetDelete(out)) - cmd.AddCommand(NewCmdSubnetListResource(out)) - - return cmd -} - -// SubnetRow 表格行 -type SubnetRow struct { - SubnetName string - ResourceID string - Group string - AffiliatedVPC string - NetworkSegment string - CreationTime string -} - -// NewCmdSubnetList ucloud subnet list -func NewCmdSubnetList(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeSubnetRequest() - cmd := &cobra.Command{ - Use: "list", - Short: "List subnet", - Long: `List subnet`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - resp, err := base.BizClient.DescribeSubnet(req) - if err != nil { - base.HandleError(err) - return - } - list := make([]SubnetRow, 0) - for _, sn := range resp.DataSet { - row := SubnetRow{} - row.SubnetName = sn.SubnetName - row.ResourceID = sn.SubnetId - row.Group = sn.Tag - row.AffiliatedVPC = fmt.Sprintf("%s/%s", sn.VPCId, sn.VPCName) - row.NetworkSegment = fmt.Sprintf("%s/%s", sn.Subnet, sn.Netmask) - row.CreationTime = common.FormatDate(sn.CreateTime) - list = append(list, row) - } - base.PrintList(list, out) - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - req.Region = flags.String("region", base.ConfigIns.Region, "Optional. Region, see 'ucloud region'") - req.ProjectId = flags.String("project-id", base.ConfigIns.ProjectID, "Optional. Project-id, see 'ucloud project list'") - flags.StringSliceVar(&req.SubnetIds, "subnet-id", []string{}, "Optional. Multiple values separated by commas") - req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC") - req.Tag = flags.String("group", "", "Optional. Group") - req.Offset = flags.Int("offset", 0, "Optional. Offset") - req.Limit = flags.Int("limit", 50, "Optional. Limit") - - return cmd -} - -// NewCmdSubnetCreate ucloud subnet create -func NewCmdSubnetCreate() *cobra.Command { - var segment *net.IPNet - req := base.BizClient.NewCreateSubnetRequest() - cmd := &cobra.Command{ - Use: "create", - Short: "Create subnet of vpc network", - Long: "Create subnet of vpc network", - Example: "ucloud subnet create --vpc-id uvnet-vpcxid --name testName --segment 192.168.2.0/24", - Run: func(cmd *cobra.Command, args []string) { - ipMaskStrs := strings.SplitN(segment.String(), "/", 2) - req.Subnet = sdk.String(ipMaskStrs[0]) - mask, err := strconv.Atoi(ipMaskStrs[1]) - if err != nil { - base.HandleError(err) - return - } - req.Netmask = sdk.Int(mask) - req.VPCId = sdk.String(base.PickResourceID(*req.VPCId)) - resp, err := base.BizClient.CreateSubnet(req) - if err != nil { - base.HandleError(err) - return - } - base.Cxt.Printf("subnet[%s] created\n", resp.SubnetId) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - - req.VPCId = cmd.Flags().String("vpc-id", "", "Required. Assign the VPC network of the subnet") - segment = cmd.Flags().IPNet("segment", net.IPNet{}, "Required. Segment of subnet. For example '192.168.0.0/24'") - req.SubnetName = cmd.Flags().String("name", "Subnet", "Optional. Name of subnet to create") - req.Region = cmd.Flags().String("region", base.ConfigIns.Region, "Optional. The region of the subnet") - req.ProjectId = cmd.Flags().String("project-id", base.ConfigIns.ProjectID, "Optional. The project id of the subnet") - req.Tag = cmd.Flags().String("group", "", "Optional. Business group") - req.Remark = cmd.Flags().String("remark", "", "Optional. Remark of subnet to create") - - command.SetCompletion(cmd, "vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) - }) - - cmd.MarkFlagRequired("vpc-id") - cmd.MarkFlagRequired("segment") - - return cmd -} - -// NewCmdSubnetDelete ucloud subnet delete -func NewCmdSubnetDelete(out io.Writer) *cobra.Command { - idNames := []string{} - req := base.BizClient.NewDeleteSubnetRequest() - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete subnet", - Long: "Delete subnet", - Run: func(c *cobra.Command, args []string) { - req.ProjectId = sdk.String(base.PickResourceID(*req.ProjectId)) - for _, id := range idNames { - req.SubnetId = sdk.String(base.PickResourceID(id)) - _, err := base.BizClient.DeleteSubnet(req) - if err != nil { - base.HandleError(err) - continue - } - fmt.Fprintf(out, "subnet[%s] deleted\n", id) - } - }, - } - - flags := cmd.Flags() - flags.SortFlags = false - - flags.StringSliceVar(&idNames, "subnet-id", nil, "Required. Resource ID of subent") - bindRegion(req, cmd) - bindProjectID(req, cmd) - cmd.MarkFlagRequired("subnet-id") - command.SetCompletion(cmd, "subnet-id", func() []string { - return getAllSubnetIDNames("", *req.ProjectId, *req.Region) - }) - - return cmd -} - -// SubnetResourceRow 表格行 -type SubnetResourceRow struct { - ResourceName string - ResourceID string - ResourceType string - PrivateIP string -} - -// NewCmdSubnetListResource ucloud subnet list-resource -func NewCmdSubnetListResource(out io.Writer) *cobra.Command { - req := base.BizClient.NewDescribeSubnetResourceRequest() - cmd := &cobra.Command{ - Use: "list-resource", - Short: "List resources belong to subnet", - Long: "List resources belong to subnet", - Run: func(c *cobra.Command, args []string) { - req.SubnetId = sdk.String(base.PickResourceID(*req.SubnetId)) - resp, err := base.BizClient.DescribeSubnetResource(req) - if err != nil { - base.HandleError(err) - return - } - list := []SubnetResourceRow{} - for _, r := range resp.DataSet { - row := SubnetResourceRow{ - ResourceName: r.Name, - ResourceID: r.ResourceId, - ResourceType: r.ResourceType, - PrivateIP: r.IP, - } - list = append(list, row) - } - base.PrintList(list, out) - }, - } - flags := cmd.Flags() - flags.SortFlags = false - req.SubnetId = flags.String("subnet-id", "", "Required. Resource ID of subnet which resources to list belong to") - req.ResourceType = flags.String("resource-type", "", "Optional. Resource type of resources to list. Accept values:'uhost','phost','ulb','uhadoophost','ufortresshost','unatgw','ukafka','umem','docker','udb','udw' and 'vip'") - bindRegion(req, cmd) - bindProjectID(req, cmd) - bindLimit(req, flags) - bindOffset(req, flags) - cmd.MarkFlagRequired("subnet-id") - command.SetCompletion(cmd, "subnet-id", func() []string { - return getAllSubnetIDNames("", *req.ProjectId, *req.Region) - }) - command.SetFlagValues(cmd, "resource-type", "uhost", "phost", "ulb", "uhadoophost", "ufortresshost", "unatgw", "ukafka", "umem", "docker", "udb", "udw", "vip") - - return cmd -} - -func getAllSubnets(vpcID, project, region string) ([]vpc.SubnetInfo, error) { - req := base.BizClient.NewDescribeSubnetRequest() - req.ProjectId = sdk.String(base.PickResourceID(project)) - req.Region = sdk.String(region) - if vpcID != "" { - req.VPCId = sdk.String(base.PickResourceID(vpcID)) - } - subnets := []vpc.SubnetInfo{} - for limit, offset := 50, 0; ; offset += limit { - req.Limit = sdk.Int(limit) - req.Offset = sdk.Int(offset) - resp, err := base.BizClient.DescribeSubnet(req) - if err != nil { - base.HandleError(err) - return nil, err - } - subnets = append(subnets, resp.DataSet...) - if limit+offset >= resp.TotalCount { - break - } - } - return subnets, nil -} - -func getAllSubnetIDNames(vpcID, project, region string) []string { - subnets, err := getAllSubnets(vpcID, project, region) - if err != nil { - return nil - } - list := []string{} - for _, s := range subnets { - list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) - } - return list -} diff --git a/hack/check-product/main.go b/hack/check-product/main.go index 57d63e5759..5956cb0157 100644 --- a/hack/check-product/main.go +++ b/hack/check-product/main.go @@ -379,22 +379,13 @@ func checkConsistency(products []Product, dirs []string) (violations, warnings [ // set to match. var reservedCommands = map[string]bool{ // addPlatformCommands (cmd/root.go), in registration order: - "init": true, // NewCmdInit - "auth": true, // NewCmdAuth - "gendoc": true, // NewCmdDoc (doc-gen command, Use: "gendoc") - "config": true, // NewCmdConfig - "region": true, // NewCmdRegion - "project": true, // NewCmdProject + "init": true, // NewCmdInit + "auth": true, // NewCmdAuth + "gendoc": true, // NewCmdDoc (doc-gen command, Use: "gendoc") + "config": true, // NewCmdConfig + "region": true, // NewCmdRegion + "project": true, // NewCmdProject // uhost migrated to products/uhost (Part 6) — no longer platform-reserved. - "subnet": true, // NewCmdSubnet - "vpc": true, // NewCmdVpc - "bandwidth": true, // NewCmdBandwidth - "udpn": true, // NewCmdUDPN - "ulb": true, // NewCmdULB - "gssh": true, // NewCmdGssh - "pathx": true, // NewCmdPathx - "redis": true, // NewCmdRedis - "memcache": true, // NewCmdMemcache "ext": true, // NewCmdExt "api": true, // NewCmdAPI "signature": true, // NewCmdSignature @@ -507,7 +498,7 @@ func main() { } // Determine which product this file belongs to. - // path is like "products/udb/internal/mysql/cmd.go" + // path is like "products/mysql/internal/mysql/cmd.go" rel := strings.TrimPrefix(path, productsRoot+string(filepath.Separator)) parts := strings.SplitN(rel, string(filepath.Separator), 2) productName := parts[0] diff --git a/hack/check-product/main_test.go b/hack/check-product/main_test.go index 997f3e9036..696efcd51f 100644 --- a/hack/check-product/main_test.go +++ b/hack/check-product/main_test.go @@ -50,11 +50,11 @@ func TestCheckFile_Rule1_SameProductImport_Clean(t *testing.T) { src := `package cmd import ( - _ "github.com/ucloud/ucloud-cli/products/udb/internal/helper" + _ "github.com/ucloud/ucloud-cli/products/mysql/internal/helper" ) ` - path := writeFile(t, dir, "udb/cmd.go", src) - got := checkFile(path, "udb") + path := writeFile(t, dir, "mysql/cmd.go", src) + got := checkFile(path, "mysql") for _, v := range got { if strings.Contains(v, "rule1") { t.Errorf("unexpected rule1 violation for same-product import: %v", v) @@ -243,8 +243,8 @@ func setup(ctx *cli.Context, f someFlag) { // -------------------------------------------------------------------------- func TestCheckConsistency_DirWithoutYAML_Violation(t *testing.T) { - products := []Product{{Name: "udb", Dir: "products/udb", Enabled: true}} - dirs := []string{"udb", "mystery"} // mystery 无 product.yaml + products := []Product{{Name: "mysql", Dir: "products/mysql", Enabled: true}} + dirs := []string{"mysql", "mystery"} // mystery 无 product.yaml violations, _ := checkConsistency(products, dirs) found := false for _, v := range violations { @@ -258,8 +258,8 @@ func TestCheckConsistency_DirWithoutYAML_Violation(t *testing.T) { } func TestCheckConsistency_AllHaveYAML_Clean(t *testing.T) { - products := []Product{{Name: "udb", Dir: "products/udb", Enabled: true}} - dirs := []string{"udb"} + products := []Product{{Name: "mysql", Dir: "products/mysql", Enabled: true}} + dirs := []string{"mysql"} violations, _ := checkConsistency(products, dirs) if len(violations) != 0 { t.Errorf("expected no violations, got: %v", violations) @@ -268,13 +268,13 @@ func TestCheckConsistency_AllHaveYAML_Clean(t *testing.T) { func TestLoadProducts(t *testing.T) { dir := t.TempDir() - writeFile(t, dir, "products/udb/product.yaml", "name: udb\nowners: [Episkey-G]\ncommands: [mysql]\nenabled: true\n") + writeFile(t, dir, "products/mysql/product.yaml", "name: mysql\nowners: [Episkey-G]\ncommands: [mysql]\nenabled: true\n") t.Chdir(dir) got, err := loadProducts() if err != nil { t.Fatalf("loadProducts: %v", err) } - if len(got) != 1 || got[0].Name != "udb" || got[0].Dir != "products/udb" || len(got[0].Commands) != 1 { + if len(got) != 1 || got[0].Name != "mysql" || got[0].Dir != "products/mysql" || len(got[0].Commands) != 1 { t.Fatalf("unexpected: %+v", got) } } @@ -306,9 +306,9 @@ func TestCheckReservedCommands_ReservedName_Violation(t *testing.T) { } func TestCheckReservedCommands_RealRegistry_Clean(t *testing.T) { - // The real registry (udb → mysql) declares no reserved name → clean. + // The real registry declares no reserved name → clean. products := []Product{ - {Name: "udb", Dir: "products/udb", Commands: []string{"mysql"}, Enabled: true}, + {Name: "mysql", Dir: "products/mysql", Commands: []string{"mysql"}, Enabled: true}, } violations := checkReservedCommands(products) @@ -317,6 +317,66 @@ func TestCheckReservedCommands_RealRegistry_Clean(t *testing.T) { } } +func TestReservedCommands_ExcludesStaleBandwidthImplementationName(t *testing.T) { + if reservedCommands["bandwidth"] { + t.Fatal("reservedCommands must not include stale implementation name \"bandwidth\"") + } +} + +func TestReservedCommands_ExcludesMigratedUDPNProduct(t *testing.T) { + if reservedCommands["udpn"] { + t.Fatal("reservedCommands must not include udpn after it migrates to products/udpn") + } +} + +func TestReservedCommands_ExcludesMigratedGSSHProductCommand(t *testing.T) { + if reservedCommands["gssh"] { + t.Fatal("reservedCommands must not include gssh after it migrates to products/globalssh") + } +} + +func TestReservedCommands_ExcludesMigratedPathXProductCommand(t *testing.T) { + if reservedCommands["pathx"] { + t.Fatal("reservedCommands must not include pathx after it migrates to products/pathx") + } +} + +func TestReservedCommands_ExcludesMigratedBandwidthProductCommand(t *testing.T) { + if reservedCommands["bw"] { + t.Fatal("reservedCommands must not include bw after it migrates to products/sharedbw") + } +} + +func TestReservedCommands_ExcludesMigratedRedisProductCommand(t *testing.T) { + if reservedCommands["redis"] { + t.Fatal("reservedCommands must not include redis after it migrates to products/redis") + } +} + +func TestReservedCommands_ExcludesMigratedMemcacheProductCommand(t *testing.T) { + if reservedCommands["memcache"] { + t.Fatal("reservedCommands must not include memcache after it migrates to products/memcache") + } +} + +func TestReservedCommands_ExcludesMigratedULBProductCommand(t *testing.T) { + if reservedCommands["ulb"] { + t.Fatal("reservedCommands must not include ulb after it migrates to products/ulb") + } +} + +func TestReservedCommands_ExcludesMigratedVPCProductCommand(t *testing.T) { + if reservedCommands["vpc"] { + t.Fatal("reservedCommands must not include vpc after it migrates to products/vpc") + } +} + +func TestReservedCommands_ExcludesMigratedSubnetProductCommand(t *testing.T) { + if reservedCommands["subnet"] { + t.Fatal("reservedCommands must not include subnet after it migrates to products/subnet") + } +} + // -------------------------------------------------------------------------- // checkCommandCollisions tests (rule7) // -------------------------------------------------------------------------- @@ -341,7 +401,7 @@ func TestCheckCommandCollisions_Duplicate_Violation(t *testing.T) { func TestCheckCommandCollisions_UniqueRegistry_Clean(t *testing.T) { products := []Product{ - {Name: "udb", Dir: "products/udb", Commands: []string{"mysql"}, Enabled: true}, + {Name: "mysql", Dir: "products/mysql", Commands: []string{"mysql"}, Enabled: true}, {Name: "uhost", Dir: "products/uhost", Commands: []string{"uhost"}, Enabled: true}, } violations := checkCommandCollisions(products) @@ -386,14 +446,14 @@ func TestSameStringSet(t *testing.T) { func TestExtractMetadataCommands(t *testing.T) { dir := t.TempDir() - writeFile(t, dir, "product.go", `package udb + writeFile(t, dir, "product.go", `package mysql import "github.com/ucloud/ucloud-cli/pkg/cli" type product struct{} func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "udb", Commands: []string{"mysql"}} + return cli.Metadata{Name: "mysql", Commands: []string{"mysql"}} } `) got, err := extractMetadataCommands(dir) @@ -407,24 +467,24 @@ func (product) Metadata() cli.Metadata { func TestCheckCommandsConsistency_Mismatch_Violation(t *testing.T) { dir := t.TempDir() - writeFile(t, dir, "products/udb/product.go", `package udb + writeFile(t, dir, "products/mysql/product.go", `package mysql import "github.com/ucloud/ucloud-cli/pkg/cli" type product struct{} func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "udb", Commands: []string{"mysql", "extra"}} + return cli.Metadata{Name: "mysql", Commands: []string{"mysql", "extra"}} } `) t.Chdir(dir) // Go 1.24+: chdir for this test, auto-restored products := []Product{ - {Name: "udb", Dir: "products/udb", Commands: []string{"mysql"}, Enabled: true}, + {Name: "mysql", Dir: "products/mysql", Commands: []string{"mysql"}, Enabled: true}, } violations := checkCommandsConsistency(products) found := false for _, v := range violations { - if strings.Contains(v, "rule8") && strings.Contains(v, "udb") { + if strings.Contains(v, "rule8") && strings.Contains(v, "mysql") { found = true break } @@ -436,19 +496,19 @@ func (product) Metadata() cli.Metadata { func TestCheckCommandsConsistency_Match_Clean(t *testing.T) { dir := t.TempDir() - writeFile(t, dir, "products/udb/product.go", `package udb + writeFile(t, dir, "products/mysql/product.go", `package mysql import "github.com/ucloud/ucloud-cli/pkg/cli" type product struct{} func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "udb", Commands: []string{"mysql"}} + return cli.Metadata{Name: "mysql", Commands: []string{"mysql"}} } `) t.Chdir(dir) products := []Product{ - {Name: "udb", Dir: "products/udb", Commands: []string{"mysql"}, Enabled: true}, + {Name: "mysql", Dir: "products/mysql", Commands: []string{"mysql"}, Enabled: true}, } violations := checkCommandsConsistency(products) if len(violations) != 0 { @@ -463,18 +523,18 @@ func (product) Metadata() cli.Metadata { func TestCheckFile_Rule9_ImportWhitelist(t *testing.T) { src := `package p -import ( - _ "fmt" - _ "github.com/spf13/cobra" - _ "github.com/ucloud/ucloud-cli/internal/common" - _ "github.com/ucloud/ucloud-cli/model/status" - _ "github.com/ucloud/ucloud-cli/pkg/cli" - _ "github.com/ucloud/ucloud-cli/products/udb/internal/mysql" - _ "github.com/ucloud/ucloud-sdk-go/private/services/uhost" - _ "github.com/fatih/color" -) + import ( + _ "fmt" + _ "github.com/spf13/cobra" + _ "github.com/ucloud/ucloud-cli/internal/common" + _ "github.com/ucloud/ucloud-cli/model/status" + _ "github.com/ucloud/ucloud-cli/pkg/cli" + _ "github.com/ucloud/ucloud-cli/products/mysql/internal/mysql" + _ "github.com/ucloud/ucloud-sdk-go/private/services/uhost" + _ "github.com/fatih/color" + ) ` - joined := strings.Join(checkFile(writeFile(t, t.TempDir(), "x.go", src), "udb"), "\n") + joined := strings.Join(checkFile(writeFile(t, t.TempDir(), "x.go", src), "mysql"), "\n") for _, bad := range []string{ `rule9: import "github.com/ucloud/ucloud-cli/model/status"`, @@ -487,7 +547,7 @@ import ( for _, ok := range []string{`"fmt"`, `"github.com/spf13/cobra"`, `"github.com/ucloud/ucloud-cli/internal/common"`, `"github.com/ucloud/ucloud-cli/pkg/cli"`, - `"github.com/ucloud/ucloud-cli/products/udb/internal/mysql"`, + `"github.com/ucloud/ucloud-cli/products/mysql/internal/mysql"`, `"github.com/ucloud/ucloud-sdk-go/private/services/uhost"`} { if strings.Contains(joined, "rule9: import "+ok) { t.Fatalf("false positive on %s in:\n%s", ok, joined) diff --git a/hack/gen-products/main_test.go b/hack/gen-products/main_test.go index 0b3f5ce73a..b8d1439d0e 100644 --- a/hack/gen-products/main_test.go +++ b/hack/gen-products/main_test.go @@ -40,7 +40,7 @@ func TestGenerateEmpty(t *testing.T) { // emits the correct import path and constructor call. func TestGenerateWithProduct(t *testing.T) { products := []Product{ - {Name: "udb", Dir: "products/udb", Enabled: true}, + {Name: "mysql", Dir: "products/mysql", Enabled: true}, } src, err := generate(products) @@ -50,11 +50,11 @@ func TestGenerateWithProduct(t *testing.T) { s := string(src) - if !strings.Contains(s, `"github.com/ucloud/ucloud-cli/products/udb"`) { - t.Errorf("missing import path for products/udb; got:\n%s", s) + if !strings.Contains(s, `"github.com/ucloud/ucloud-cli/products/mysql"`) { + t.Errorf("missing import path for products/mysql; got:\n%s", s) } - if !strings.Contains(s, "udb.New()") { - t.Errorf("missing udb.New() in return slice; got:\n%s", s) + if !strings.Contains(s, "mysql.New()") { + t.Errorf("missing mysql.New() in return slice; got:\n%s", s) } if !strings.Contains(s, "func registeredProducts() []cli.Product") { t.Errorf("missing registeredProducts signature; got:\n%s", s) diff --git a/hack/owner-gate/main_test.go b/hack/owner-gate/main_test.go index 99926fd2ca..374279f006 100644 --- a/hack/owner-gate/main_test.go +++ b/hack/owner-gate/main_test.go @@ -54,8 +54,8 @@ func TestProductOf(t *testing.T) { want string ok bool }{ - {"products/udb/internal/mysql/cmd.go", "udb", true}, - {"products/udb/product.yaml", "udb", true}, + {"products/mysql/internal/mysql/cmd.go", "mysql", true}, + {"products/mysql/product.yaml", "mysql", true}, {"products/eip/x.go", "eip", true}, {"products/README.md", "", false}, // products/ 直属文件不属任何产品 {"pkg/cli/context.go", "", false}, @@ -76,9 +76,9 @@ func TestProductOf(t *testing.T) { func TestDecide_ProductAutonomous(t *testing.T) { d := decide( - []changedFile{cf("products/udb/internal/mysql/cmd.go")}, + []changedFile{cf("products/mysql/internal/mysql/cmd.go")}, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "product" || !d.AutoMergeEligible || d.Blocking { t.Fatalf("expected product+eligible+nonblocking, got %+v", d) @@ -89,7 +89,7 @@ func TestDecide_PlatformFile(t *testing.T) { d := decide( []changedFile{cf("pkg/cli/context.go")}, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || d.AutoMergeEligible || !d.Blocking { t.Fatalf("expected platform+ineligible+blocking, got %+v", d) @@ -98,9 +98,9 @@ func TestDecide_PlatformFile(t *testing.T) { func TestDecide_CrossProduct(t *testing.T) { d := decide( - []changedFile{cf("products/udb/x.go"), cf("products/eip/y.go")}, + []changedFile{cf("products/mysql/x.go"), cf("products/eip/y.go")}, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}, "eip": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}, "eip": {"Episkey-G"}}), ) if d.Type != "platform" || d.AutoMergeEligible || !d.Blocking { t.Fatalf("expected platform (cross-product) blocking, got %+v", d) @@ -109,9 +109,9 @@ func TestDecide_CrossProduct(t *testing.T) { func TestDecide_NonOwnerEdit(t *testing.T) { d := decide( - []changedFile{cf("products/udb/internal/mysql/cmd.go")}, + []changedFile{cf("products/mysql/internal/mysql/cmd.go")}, "mallory", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || d.AutoMergeEligible || !d.Blocking { t.Fatalf("expected platform (non-owner) blocking, got %+v", d) @@ -121,9 +121,9 @@ func TestDecide_NonOwnerEdit(t *testing.T) { // base-vs-head 提权反例:HEAD 把 mallory 加进 owners,但 base 版只有 Episkey-G。 func TestDecide_SelfPromotionRejectedByBase(t *testing.T) { d := decide( - []changedFile{cf("products/udb/product.yaml")}, + []changedFile{cf("products/mysql/product.yaml")}, "mallory", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), // base: 无 mallory + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), // base: 无 mallory ) if d.Type != "platform" || d.AutoMergeEligible || !d.Blocking { t.Fatalf("expected platform (self-promotion blocked by base), got %+v", d) @@ -133,9 +133,9 @@ func TestDecide_SelfPromotionRejectedByBase(t *testing.T) { // 改自己 owners:已是 base 版 owner 的人改 product.yaml(如加 co-owner)仍自治。 func TestDecide_OwnerEditsOwnOwners(t *testing.T) { d := decide( - []changedFile{cf("products/udb/product.yaml")}, + []changedFile{cf("products/mysql/product.yaml")}, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "product" || !d.AutoMergeEligible || d.Blocking { t.Fatalf("expected product+eligible (owner edits own owners), got %+v", d) @@ -157,9 +157,9 @@ func TestDecide_NewProductOnboarding(t *testing.T) { // 删产品:即便 author 是 owner,删除 product.yaml 也走平台审批(下线)。 func TestDecide_ProductOffboarding(t *testing.T) { d := decide( - []changedFile{cfDel("products/udb/product.yaml"), cfDel("products/udb/internal/mysql/cmd.go")}, + []changedFile{cfDel("products/mysql/product.yaml"), cfDel("products/mysql/internal/mysql/cmd.go")}, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || d.AutoMergeEligible || !d.Blocking { t.Fatalf("expected platform (offboarding) blocking, got %+v", d) @@ -175,7 +175,7 @@ func TestDecide_PlatformClearedReleases(t *testing.T) { d := decide( []changedFile{cf("base/biz_client.go")}, "carol", true, // platformCleared - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || d.Blocking || d.AutoMergeEligible { t.Fatalf("expected platform+nonblocking+ineligible (cleared), got %+v", d) @@ -185,9 +185,9 @@ func TestDecide_PlatformClearedReleases(t *testing.T) { // cleared 只解硬拦,不把 non-owner 升级成 product 自治、不开 auto-merge。 func TestDecide_ClearedDoesNotPromoteNonOwner(t *testing.T) { d := decide( - []changedFile{cf("products/udb/internal/mysql/cmd.go")}, + []changedFile{cf("products/mysql/internal/mysql/cmd.go")}, "mallory", true, // cleared - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || d.AutoMergeEligible || d.Blocking { t.Fatalf("expected platform (non-owner) nonblocking+ineligible, got %+v", d) @@ -198,9 +198,9 @@ func TestDecide_ClearedDoesNotPromoteNonOwner(t *testing.T) { func TestDecide_ProductIgnoresCleared(t *testing.T) { for _, cleared := range []bool{false, true} { d := decide( - []changedFile{cf("products/udb/internal/mysql/cmd.go")}, + []changedFile{cf("products/mysql/internal/mysql/cmd.go")}, "Episkey-G", cleared, - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "product" || !d.AutoMergeEligible || d.Blocking { t.Fatalf("cleared=%v: expected product+eligible+nonblocking, got %+v", cleared, d) @@ -212,11 +212,11 @@ func TestDecide_ProductIgnoresCleared(t *testing.T) { func TestDecide_CrossProductRename(t *testing.T) { d := decide( []changedFile{ - {Path: "products/udb/cmd/shared.go", Deleted: true}, // rename old 端 + {Path: "products/mysql/cmd/shared.go", Deleted: true}, // rename old 端 {Path: "products/uhost/cmd/shared.go", Deleted: false}, }, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}, "uhost": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}, "uhost": {"Episkey-G"}}), ) if d.Type != "platform" || !d.Blocking { t.Fatalf("expected platform (cross-product rename) blocking, got %+v", d) @@ -227,11 +227,11 @@ func TestDecide_CrossProductRename(t *testing.T) { func TestDecide_RenameAwayProductYAMLIsOffboarding(t *testing.T) { d := decide( []changedFile{ - {Path: "products/udb/product.yaml", Deleted: true}, // rename old 端 - {Path: "products/udb/product.yaml.bak", Deleted: false}, + {Path: "products/mysql/product.yaml", Deleted: true}, // rename old 端 + {Path: "products/mysql/product.yaml.bak", Deleted: false}, }, "Episkey-G", false, // 即便作者是 owner - staticOwners(map[string][]string{"udb": {"Episkey-G"}}), + staticOwners(map[string][]string{"mysql": {"Episkey-G"}}), ) if d.Type != "platform" || !d.Blocking { t.Fatalf("expected platform (rename-away offboarding) blocking, got %+v", d) @@ -241,7 +241,7 @@ func TestDecide_RenameAwayProductYAMLIsOffboarding(t *testing.T) { // 空 diff(仅 merge commit / 无改动)→ noop,不当平台拦。 func TestDecide_EmptyDiffNoop(t *testing.T) { d := decide(nil, "Episkey-G", false, - staticOwners(map[string][]string{"udb": {"Episkey-G"}})) + staticOwners(map[string][]string{"mysql": {"Episkey-G"}})) if d.Type != "noop" || d.Blocking || d.AutoMergeEligible { t.Fatalf("expected noop+nonblocking+ineligible, got %+v", d) } @@ -250,9 +250,9 @@ func TestDecide_EmptyDiffNoop(t *testing.T) { // base 版 product.yaml 解析失败 → 独立平台文案(不误称 non-owner)。 func TestDecide_ParseFailureDistinctReason(t *testing.T) { d := decide( - []changedFile{cf("products/udb/internal/mysql/cmd.go")}, + []changedFile{cf("products/mysql/internal/mysql/cmd.go")}, "Episkey-G", false, - staticOwnersParseErr("udb"), + staticOwnersParseErr("mysql"), ) if d.Type != "platform" || !d.Blocking { t.Fatalf("expected platform (parse error) blocking, got %+v", d) @@ -267,10 +267,10 @@ func TestDecide_ParseFailureDistinctReason(t *testing.T) { // -------------------------------------------------------------------------- func TestParseNameStatus(t *testing.T) { - in := "M\tproducts/udb/product.go\n" + - "A\tproducts/udb/internal/mysql/new.go\n" + - "D\tproducts/udb/internal/mysql/old.go\n" + - "R100\tproducts/udb/a.go\tproducts/udb/b.go\n" + + in := "M\tproducts/mysql/product.go\n" + + "A\tproducts/mysql/internal/mysql/new.go\n" + + "D\tproducts/mysql/internal/mysql/old.go\n" + + "R100\tproducts/mysql/a.go\tproducts/mysql/b.go\n" + "\n" // 空行应被跳过 got, err := parseNameStatus(strings.NewReader(in)) if err != nil { @@ -281,26 +281,26 @@ func TestParseNameStatus(t *testing.T) { t.Fatalf("expected 5 entries, got %d: %+v", len(got), got) } // 普通改动非删除 - if got[0].Deleted || got[0].Path != "products/udb/product.go" { + if got[0].Deleted || got[0].Path != "products/mysql/product.go" { t.Errorf("entry[0] should be modified product.go, got %+v", got[0]) } // 删除标记 - if !got[2].Deleted || got[2].Path != "products/udb/internal/mysql/old.go" { + if !got[2].Deleted || got[2].Path != "products/mysql/internal/mysql/old.go" { t.Errorf("entry[2] should be deleted old.go, got %+v", got[2]) } // rename old 端:标记 Deleted - if !got[3].Deleted || got[3].Path != "products/udb/a.go" { + if !got[3].Deleted || got[3].Path != "products/mysql/a.go" { t.Errorf("entry[3] should be rename-old a.go (deleted), got %+v", got[3]) } // rename new 端:非删除 - if got[4].Deleted || got[4].Path != "products/udb/b.go" { + if got[4].Deleted || got[4].Path != "products/mysql/b.go" { t.Errorf("entry[4] should be rename-new b.go (not deleted), got %+v", got[4]) } } // 跨产品 rename 行:old/new 两端分属不同产品都要计入。 func TestParseNameStatus_CrossProductRename(t *testing.T) { - in := "R100\tproducts/udb/cmd/shared.go\tproducts/uhost/cmd/shared.go\n" + in := "R100\tproducts/mysql/cmd/shared.go\tproducts/uhost/cmd/shared.go\n" got, err := parseNameStatus(strings.NewReader(in)) if err != nil { t.Fatalf("parseNameStatus: %v", err) @@ -314,8 +314,8 @@ func TestParseNameStatus_CrossProductRename(t *testing.T) { seen[x] = true } } - if !seen["udb"] || !seen["uhost"] { - t.Fatalf("expected both udb and uhost in productSet, got %v", seen) + if !seen["mysql"] || !seen["uhost"] { + t.Fatalf("expected both mysql and uhost in productSet, got %v", seen) } } @@ -343,21 +343,21 @@ func TestGitShowOwners_ReadsBaseNotHead(t *testing.T) { gitRun(t, repo, "init", "-q", "-b", "main") // base commit: owners = [alice] - writeFile(t, repo, "products/udb/product.yaml", - "name: udb\nowners:\n - alice\ncommands: [mysql]\nenabled: true\n") + writeFile(t, repo, "products/mysql/product.yaml", + "name: mysql\nowners:\n - alice\ncommands: [mysql]\nenabled: true\n") gitRun(t, repo, "add", "-A") gitRun(t, repo, "commit", "-qm", "base") baseSHA := gitRun(t, repo, "rev-parse", "HEAD") // head commit: PR 把 bob 加进 owners(自我提权尝试) - writeFile(t, repo, "products/udb/product.yaml", - "name: udb\nowners:\n - alice\n - bob\ncommands: [mysql]\nenabled: true\n") + writeFile(t, repo, "products/mysql/product.yaml", + "name: mysql\nowners:\n - alice\n - bob\ncommands: [mysql]\nenabled: true\n") gitRun(t, repo, "add", "-A") gitRun(t, repo, "commit", "-qm", "head") t.Chdir(repo) // gitShowOwners 在进程 cwd 下跑 git - owners, exists, parseErr := gitShowOwners(baseSHA, "udb") + owners, exists, parseErr := gitShowOwners(baseSHA, "mysql") if !exists || parseErr { t.Fatalf("expected base product.yaml to exist and parse, got exists=%v parseErr=%v", exists, parseErr) } @@ -376,13 +376,13 @@ func TestGitShowOwners_ParseError(t *testing.T) { repo := t.TempDir() gitRun(t, repo, "init", "-q", "-b", "main") // 未定义 anchor 的别名引用 → yaml.v2 解析报错。 - writeFile(t, repo, "products/udb/product.yaml", "owners: *nope\n") + writeFile(t, repo, "products/mysql/product.yaml", "owners: *nope\n") gitRun(t, repo, "add", "-A") gitRun(t, repo, "commit", "-qm", "base") baseSHA := gitRun(t, repo, "rev-parse", "HEAD") t.Chdir(repo) - owners, exists, parseErr := gitShowOwners(baseSHA, "udb") + owners, exists, parseErr := gitShowOwners(baseSHA, "mysql") if !exists || !parseErr { t.Fatalf("expected exists=true parseErr=true, got exists=%v parseErr=%v", exists, parseErr) } diff --git a/hack/snapshot/completion.go b/hack/snapshot/completion.go index ff29538be8..2ff7ad631c 100644 --- a/hack/snapshot/completion.go +++ b/hack/snapshot/completion.go @@ -21,7 +21,7 @@ type completionResult struct { // // Dynamic detection: SetCompletion closures touch the network-backing globals // which the test nils after tree construction, causing a nil-pointer panic. -// Platform (cmd) closures dereference base.BizClient; product (products/udb) +// Platform (cmd) closures dereference base.BizClient; product (products/mysql) // closures go through cli.NewServiceClient, which builds an SDK client from // base.ClientConfig — so the test nils both (see TestWriteCompletionBaseline). // We recover from the panic and mark the flag dynamic. A closure may also diff --git a/hack/snapshot/snapshot_test.go b/hack/snapshot/snapshot_test.go index d6a10b0936..4fa1c563be 100644 --- a/hack/snapshot/snapshot_test.go +++ b/hack/snapshot/snapshot_test.go @@ -39,7 +39,7 @@ func TestWriteCompletionBaseline(t *testing.T) { // (SetFlagValues closures are immune; SetCompletion closures dereference them). // // Platform (cmd) dynamic completions dereference base.BizClient directly, so - // nil-ing it makes them panic. Product (products/udb) dynamic completions go + // nil-ing it makes them panic. Product (products/mysql) dynamic completions go // through cli.NewServiceClient → ctor(base.ClientConfig, base.BuildCredential()) // → SDK call, which ignores base.BizClient; nil-ing base.ClientConfig makes the // SDK request build panic instead of issuing a real (non-deterministic, slow) diff --git a/hack/snapshot/testdata/cmdtree.golden b/hack/snapshot/testdata/cmdtree.golden index e7d08d6b68..440b159f99 100644 --- a/hack/snapshot/testdata/cmdtree.golden +++ b/hack/snapshot/testdata/cmdtree.golden @@ -10,47 +10,6 @@ ucloud auth login use=login short=Log in to UCloud via browser (OAuth) flag=no-browser short= default=false required= flag=oauth-base-url short= default= required= ucloud auth logout use=logout short=Log out: remove local OAuth tokens of the current profile -ucloud bw use=bw short=Manipulate bandwidth package and shared bandwidth -ucloud bw pkg use=pkg short=List, create and delete bandwidth package instances -ucloud bw pkg create use=create short=Create bandwidth package - flag=bandwidth-mb short= default=0 required=true - flag=eip-id short= default=[] required=true - flag=end-time short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=start-time short= default= required=true -ucloud bw pkg delete use=delete short=Delete bandwidth packages - flag=project-id short= default= required= - flag=region short= default= required= - flag=resource-id short= default=[] required= -ucloud bw pkg list use=list short=List bandwidth packages - flag=limit short= default=50 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= -ucloud bw shared use=shared short=Create and manipulate shared bandwidth instances -ucloud bw shared create use=create short=Create shared bandwidth instance - flag=bandwidth-mb short= default=20 required= - flag=charge-type short= default=Month required= - flag=name short= default= required=true - flag=project-id short= default= required= - flag=quantity short= default=1 required= - flag=region short= default= required= -ucloud bw shared delete use=delete short=Delete shared bandwidth instance - flag=eip-bandwidth-mb short= default=1 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=shared-bw-id short= default=[] required=true - flag=traffic-mode short= default= required= -ucloud bw shared list use=list short=List shared bandwidth instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=shared-bw-id short= default=[] required= -ucloud bw shared resize use=resize short=Resize shared bandwidth instance's bandwidth - flag=bandwidth-mb short= default=0 required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=shared-bw-id short= default= required=true ucloud config use=config short=add or update configurations flag=active short= default= required= flag=agree-upload-log short= default=false required= @@ -108,153 +67,7 @@ ucloud ext uhost switch-eip use=switch-eip short=Switch EIP for UHost instances ucloud gendoc use=gendoc short=Generate documents for all commands flag=dir short= default= required=true flag=format short= default=douku required= -ucloud gssh use=gssh short=Create,list,update and delete globalssh instance -ucloud gssh create use=create short=Create GlobalSSH instance - flag=bandwidth-package short= default=0 required= - flag=charge-type short= default=Month required= - flag=forward-region short= default= required= - flag=instance-type short= default= required= - flag=location short= default= required=true - flag=port short= default=22 required= - flag=project-id short= default= required= - flag=quantity short= default=1 required= - flag=remark short= default= required= - flag=target-ip short= default= required=true -ucloud gssh delete use=delete short=Delete GlobalSSH instance - flag=gssh-id short= default=[] required=true - flag=project-id short= default= required= -ucloud gssh list use=list short=List all GlobalSSH instances - flag=project-id short= default= required= - flag=region short= default= required= -ucloud gssh location use=location short=List SSH server locations and covered areas -ucloud gssh update use=update short=Update GlobalSSH instance - flag=gssh-id short= default=[] required=true - flag=port short= default=0 required= - flag=project-id short= default= required= - flag=remark short= default= required= ucloud init use=init short=Initialize UCloud CLI options -ucloud memcache use=memcache short=List and manipulate memcache instances -ucloud memcache create use=create short=Create memcache instance - flag=charge-type short= default=Month required= - flag=group short= default= required= - flag=name short= default= required=true - flag=project-id short= default= required= - flag=quantity short= default=1 required= - flag=region short= default= required= - flag=size-gb short= default=1 required= - flag=subnet-id short= default= required= - flag=vpc-id short= default= required= - flag=zone short= default= required= -ucloud memcache delete use=delete short=Delete memcache instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default=[] required=true - flag=zone short= default= required= -ucloud memcache list use=list short=List memcache instances - flag=limit short= default=100 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default= required= - flag=zone short= default= required= -ucloud memcache restart use=restart short=Restart memcache instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default=[] required=true - flag=zone short= default= required= -ucloud pathx use=pathx short=Manipulate uga and upath instances -ucloud pathx area use=area short=List origin area or acceleration area information -ucloud pathx area list use=list short=List origin area or acceleration area information - flag=accel short= default= required= - flag=no-accel short= default=false required= - flag=origin-domain short= default= required= - flag=origin-ip short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=time-range short= default= required= - flag=zone short= default= required= -ucloud pathx create use=create short=Create the pathx resource and port - flag=accel short= default= required= - flag=area-code short= default= required= - flag=bandwidth short= default=0 required=true - flag=charge-type short= default= required= - flag=origin-domain short= default= required= - flag=origin-ip short= default= required= - flag=origin-port short= default=[] required= - flag=port short= default=[] required= - flag=project-id short= default= required= - flag=protocol short= default=TCP required= - flag=quantity short= default=1 required= - flag=region short= default= required= - flag=zone short= default= required= -ucloud pathx delete use=delete short=Delete the pathx resource and port - flag=id short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=yes short=y default=false required= - flag=zone short= default= required= -ucloud pathx list use=list short=List all the pathx resource of project - flag=detail short= default=false required= - flag=id short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= -ucloud pathx modify use=modify short=Modify the pathx associated information. Example bandwidth or origin information or resource information - flag=bandwidth short= default=0 required= - flag=id short= default= required=true - flag=name short= default= required= - flag=origin-domain short= default= required= - flag=origin-ip short= default= required= - flag=origin-port short= default=[] required= - flag=port short= default=[] required= - flag=project-id short= default= required= - flag=protocol short= default=TCP required= - flag=region short= default= required= - flag=remark short= default= required= - flag=zone short= default= required= -ucloud pathx price use=price short=List all the acceleration area price -ucloud pathx price list use=list short=List all the pathx acceleration area price - flag=accel short= default= required= - flag=area-code short= default= required=true - flag=bandwidth short= default=1 required=true - flag=charge-type short= default= required= - flag=project-id short= default= required= - flag=quantity short= default=1 required= - flag=region short= default= required= - flag=zone short= default= required= -ucloud pathx uga use=uga short=Create,list,update and delete pathx uga instances -ucloud pathx uga add-port use=add-port short=Add port for uga instance - flag=port short= default=[] required=true - flag=project-id short= default= required= - flag=protocol short= default= required=true - flag=uga-id short= default= required=true -ucloud pathx uga create use=create short=Create uga instance - flag=name short= default= required=true - flag=origin-domain short= default= required= - flag=origin-ip short= default= required= - flag=origin-location short= default= required=true - flag=port short= default=[] required=true - flag=project-id short= default= required= - flag=protocol short= default= required=true - flag=upath-id short= default=[] required=true -ucloud pathx uga delete use=delete short=Delete uga instances - flag=project-id short= default= required= - flag=uga-id short= default=[] required=true -ucloud pathx uga delete-port use=delete-port short=Delete port for uga instance - flag=port short= default=[] required=true - flag=project-id short= default= required= - flag=protocol short= default= required=true - flag=uga-id short= default= required=true -ucloud pathx uga describe use=describe short=Display detail informations about uga instances - flag=project-id short= default= required= - flag=uga-id short= default= required=true -ucloud pathx uga list use=list short=list uga instances - flag=project-id short= default= required= - flag=uga-id short= default= required= -ucloud pathx upath use=upath short=List pathx upath instances -ucloud pathx upath list use=list short=list upath instances - flag=project-id short= default= required= - flag=upath-id short= default= required= ucloud project use=project short=List,create,update and delete project ucloud project create use=create short=Create project flag=name short= default= required=true @@ -265,298 +78,8 @@ ucloud project list use=list short=List project ucloud project update use=update short=Update project name flag=id short= default= required=true flag=name short= default= required=true -ucloud redis use=redis short=List and manipulate redis instances -ucloud redis create use=create short=Create redis instance - flag=block-cnt short= default=2 required= - flag=charge-type short= default=Month required= - flag=group short= default= required= - flag=name short= default= required=true - flag=password short= default= required= - flag=project-id short= default= required= - flag=proxy-size short= default=2 required= - flag=quantity short= default=1 required= - flag=region short= default= required= - flag=size-gb short= default=2 required= - flag=subnet-id short= default= required= - flag=type short= default= required=true - flag=version short= default=6.0 required= - flag=vpc-id short= default= required= - flag=zone short= default= required= -ucloud redis delete use=delete short=Delete redis instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default=[] required=true - flag=zone short= default= required= -ucloud redis list use=list short=List redis instances - flag=limit short= default=100 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default= required= - flag=zone short= default= required= -ucloud redis restart use=restart short=Restart redis instances of master-replica type - flag=project-id short= default= required= - flag=region short= default= required= - flag=umem-id short= default=[] required=true - flag=zone short= default= required= ucloud region use=region short=List all region and zone ucloud signature use=signature short=Calculate ucloud signature flag=param short=m default=[] required= flag=private-key short=k default= required=true flag=url short=u default= required= -ucloud subnet use=subnet short=List, create and delete subnet -ucloud subnet create use=create short=Create subnet of vpc network - flag=group short= default= required= - flag=name short= default=Subnet required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=remark short= default= required= - flag=segment short= default= required=true - flag=vpc-id short= default= required=true -ucloud subnet delete use=delete short=Delete subnet - flag=project-id short= default= required= - flag=region short= default= required= - flag=subnet-id short= default=[] required=true -ucloud subnet list use=list short=List subnet - flag=group short= default= required= - flag=limit short= default=50 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=subnet-id short= default=[] required= - flag=vpc-id short= default= required= -ucloud subnet list-resource use=list-resource short=List resources belong to subnet - flag=limit short= default=100 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=resource-type short= default= required= - flag=subnet-id short= default= required=true -ucloud udpn use=udpn short=List and manipulate udpn instances -ucloud udpn create use=create short=Create UDPN tunnel - flag=bandwidth-mb short= default=0 required=true - flag=charge-type short= default= required= - flag=peer1 short= default= required=true - flag=peer2 short= default= required=true - flag=project-id short= default= required= - flag=quantity short= default=1 required= -ucloud udpn delete use=delete short=delete udpn instances - flag=project-id short= default= required= - flag=udpn-id short= default=[] required=true -ucloud udpn list use=list short=List udpn instances - flag=limit short= default=50 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=udpn-id short= default= required= -ucloud udpn modify-bw use=modify-bw short=Modify bandwidth of UDPN tunnel - flag=bandwidth-mb short= default=0 required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=udpn-id short= default=[] required=true -ucloud ulb use=ulb short=List and manipulate ULB instances -ucloud ulb create use=create short=Create ULB instance - flag=bind-eip short= default= required= - flag=charge-type short= default=Month required= - flag=create-eip-bandwidth-mb short= default=0 required= - flag=create-eip-line short= default= required= - flag=create-eip-name short= default= required= - flag=create-eip-remark short= default= required= - flag=create-eip-traffic-mode short= default=Bandwidth required= - flag=group short= default=Default required= - flag=mode short= default=outer required=true - flag=name short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=remark short= default= required= - flag=subnet-id short= default= required= - flag=vpc-id short= default= required= -ucloud ulb delete use=delete short=Delete ULB instances by resource ID - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default=[] required=true -ucloud ulb list use=list short=List ULB instances - flag=group short= default= required= - flag=limit short= default=50 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=subnet-id short= default= required= - flag=ulb-id short= default= required= - flag=vpc-id short= default= required= -ucloud ulb ssl use=ssl short=List and manipulate SSL Certificates for ULB -ucloud ulb ssl add use=add short=Add SSL Certificate - flag=all-in-one-file short= default= required= - flag=ca-certificate-file short= default= required= - flag=format short= default=Pem required= - flag=name short= default= required=true - flag=private-key-file short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=site-certificate-file short= default= required= -ucloud ulb ssl bind use=bind short=Bind SSL Certificate with VServer - flag=project-id short= default= required= - flag=region short= default= required= - flag=ssl-id short= default= required=true - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb ssl delete use=delete short=Delete SSL Certificates by resource id(ssl id) - flag=project-id short= default= required= - flag=region short= default= required= - flag=ssl-id short= default=[] required= -ucloud ulb ssl describe use=describe short=Display all data associated with SSL Certificate - flag=project-id short= default= required= - flag=region short= default= required= - flag=ssl-id short= default= required=true -ucloud ulb ssl list use=list short=List SSL Certificates - flag=limit short= default=100 required= - flag=offset short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=ssl-id short= default= required= -ucloud ulb ssl unbind use=unbind short=Unbind SSL Certificate with VServer - flag=project-id short= default= required= - flag=region short= default= required= - flag=ssl-id short= default= required=true - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb update use=update short=Update ULB instance - flag=group short= default= required= - flag=name short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=remark short= default= required= - flag=ulb-id short= default=[] required=true -ucloud ulb vserver use=vserver short=List and manipulate ULB Vserver instances -ucloud ulb vserver backend use=backend short=List and manipulate VServer backend nodes -ucloud ulb vserver backend add use=add short=Add backend nodes for ULB Vserver instance - flag=backend-mode short= default=enable required= - flag=port short= default=80 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=resource-id short= default=[] required=true - flag=resource-type short= default=UHost required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true - flag=weight short= default=1 required= -ucloud ulb vserver backend delete use=delete short=Delete ULB VServer backend nodes - flag=backend-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true -ucloud ulb vserver backend list use=list short=List ULB VServer backend nodes - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb vserver backend update use=update short=Update attributes of ULB backend nodes - flag=backend-id short= default=[] required=true - flag=backend-mode short= default= required= - flag=port short= default=0 required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=weight short= default=-1 required= -ucloud ulb vserver create use=create short=Create ULB VServer instance - flag=client-timeout-seconds short= default=60 required= - flag=health-check-domain short= default= required= - flag=health-check-mode short= default=Port required= - flag=health-check-path short= default= required= - flag=lb-method short= default=Roundrobin required= - flag=listen-type short= default=RequestProxy required= - flag=name short= default= required= - flag=port short= default=80 required= - flag=project-id short= default= required= - flag=protocol short= default=HTTP required= - flag=region short= default= required= - flag=session-maintain-key short= default= required= - flag=session-maintain-mode short= default=None required= - flag=ssl-id short= default= required= - flag=ulb-id short= default= required=true -ucloud ulb vserver delete use=delete short=Delete ULB VServer instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default=[] required=true -ucloud ulb vserver list use=list short=List ULB Vserver instances - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required= -ucloud ulb vserver policy use=policy short=List and manipulate forward policy for VServer -ucloud ulb vserver policy add use=add short=Add content forward policy for VServer - flag=backend-id short= default=[] required=true - flag=expression short= default= required=true - flag=forward-method short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb vserver policy delete use=delete short=Delete content forward policies of ULB VServer - flag=policy-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=vserver-id short= default= required= -ucloud ulb vserver policy list use=list short=List content forward policies of the VServer instance - flag=project-id short= default= required= - flag=region short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb vserver policy update use=update short=Update content forward policies of ULB VServer - flag=add-backend-id short= default=[] required= - flag=backend-id short= default=[] required= - flag=expression short= default= required= - flag=forward-method short= default= required= - flag=policy-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=remove-backend-id short= default=[] required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default= required=true -ucloud ulb vserver update use=update short=Update attributes of VServer instances - flag=client-timeout-seconds short= default=-1 required= - flag=health-check-domain short= default= required= - flag=health-check-mode short= default= required= - flag=health-check-path short= default= required= - flag=lb-method short= default= required= - flag=name short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=session-maintain-key short= default= required= - flag=session-maintain-mode short= default= required= - flag=ulb-id short= default= required=true - flag=vserver-id short= default=[] required=true -ucloud vpc use=vpc short=List and manipulate VPC instances -ucloud vpc create use=create short=Create vpc network - flag=group short= default= required= - flag=name short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=remark short= default= required= - flag=segment short= default=[] required=true -ucloud vpc create-intercome use=create-intercome short=Create intercome with other vpc - flag=dst-project-id short= default= required= - flag=dst-region short= default= required= - flag=dst-vpc-id short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=vpc-id short= default= required=true -ucloud vpc delete use=delete short=Delete vpc network - flag=project-id short= default= required= - flag=region short= default= required= - flag=vpc-id short= default=[] required=true -ucloud vpc delete-intercome use=delete-intercome short=delete the vpc intercome - flag=dst-region short= default= required=true - flag=dst-vpc-id short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=vpc-id short= default= required=true -ucloud vpc list use=list short=List vpc - flag=group short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=vpc-id short= default=[] required= -ucloud vpc list-intercome use=list-intercome short=list intercome - flag=project-id short= default= required= - flag=region short= default= required= - flag=vpc-id short= default= required=true diff --git a/hack/snapshot/testdata/completion.golden b/hack/snapshot/testdata/completion.golden index 0c12d326e3..0e9e97807f 100644 --- a/hack/snapshot/testdata/completion.golden +++ b/hack/snapshot/testdata/completion.golden @@ -1,22 +1,3 @@ -ucloud bw pkg create eip-id dynamic -ucloud bw pkg create project-id dynamic -ucloud bw pkg create region dynamic -ucloud bw pkg delete project-id dynamic -ucloud bw pkg delete region dynamic -ucloud bw pkg list project-id dynamic -ucloud bw pkg list region dynamic -ucloud bw shared create charge-type static Dynamic,Month,Year -ucloud bw shared create project-id dynamic -ucloud bw shared create region dynamic -ucloud bw shared delete project-id dynamic -ucloud bw shared delete region dynamic -ucloud bw shared delete shared-bw-id dynamic -ucloud bw shared delete traffic-mode static Bandwidth,Traffic -ucloud bw shared list project-id dynamic -ucloud bw shared list region dynamic -ucloud bw shared resize project-id dynamic -ucloud bw shared resize region dynamic -ucloud bw shared resize shared-bw-id dynamic ucloud config active static false,true ucloud config agree-upload-log static false,true ucloud config profile static @@ -44,231 +25,3 @@ ucloud ext uhost switch-eip uhost-id dynamic ucloud ext uhost switch-eip zone dynamic ucloud gendoc dir static ucloud gendoc format static douku,markdown,rst -ucloud gssh create bandwidth-package static 0,20,40 -ucloud gssh create charge-type static Dynamic,Month,Trial,Year -ucloud gssh create forward-region static cn-bj2,cn-gd,cn-sh2 -ucloud gssh create instance-type static Basic,Enterprise,Free,Ultimate -ucloud gssh create location static Frankfurt,HongKong,Lagos,LosAngeles,Singapore,Tokyo,Washington -ucloud gssh create project-id dynamic -ucloud gssh create target-ip dynamic -ucloud gssh delete gssh-id dynamic -ucloud gssh delete project-id dynamic -ucloud gssh update gssh-id dynamic -ucloud gssh update project-id dynamic -ucloud memcache create charge-type static Dynamic,Month,Year -ucloud memcache create project-id dynamic -ucloud memcache create region dynamic -ucloud memcache create size-gb static 1,16,2,32,4,8 -ucloud memcache create subnet-id dynamic -ucloud memcache create vpc-id dynamic -ucloud memcache create zone dynamic -ucloud memcache delete project-id dynamic -ucloud memcache delete region dynamic -ucloud memcache delete umem-id dynamic -ucloud memcache delete zone dynamic -ucloud memcache list project-id dynamic -ucloud memcache list region dynamic -ucloud memcache list zone dynamic -ucloud memcache restart project-id dynamic -ucloud memcache restart region dynamic -ucloud memcache restart umem-id dynamic -ucloud memcache restart zone dynamic -ucloud pathx area list project-id dynamic -ucloud pathx area list region dynamic -ucloud pathx area list zone dynamic -ucloud pathx create accel static AF,AP,EU,Global,ME,NA,OA,SA -ucloud pathx create area-code static BKK,BOM,CGK,DME,DXB,FRA,HKG,IAD,ICN,LAX,LHR,LOS,MNL,MSP,NRT,PVG,SGN,SIN,TPE -ucloud pathx create charge-type static Hour,Month,Year -ucloud pathx create project-id dynamic -ucloud pathx create protocol static TCP,UDP -ucloud pathx create region dynamic -ucloud pathx create zone dynamic -ucloud pathx delete id dynamic -ucloud pathx delete project-id dynamic -ucloud pathx delete region dynamic -ucloud pathx delete zone dynamic -ucloud pathx list id dynamic -ucloud pathx list project-id dynamic -ucloud pathx list region dynamic -ucloud pathx list zone dynamic -ucloud pathx modify id dynamic -ucloud pathx modify project-id dynamic -ucloud pathx modify region dynamic -ucloud pathx modify zone dynamic -ucloud pathx price list accel static AF,AP,EU,Global,ME,NA,OA,SA -ucloud pathx price list area-code static BKK,BOM,CGK,DME,DXB,FRA,HKG,IAD,ICN,LAX,LHR,LOS,MNL,MSP,NRT,PVG,SGN,SIN,TPE -ucloud pathx price list charge-type static Hour,Month,Year -ucloud pathx price list project-id dynamic -ucloud pathx price list region dynamic -ucloud pathx price list zone dynamic -ucloud pathx uga add-port project-id dynamic -ucloud pathx uga add-port protocol static tcp,udp -ucloud pathx uga add-port uga-id dynamic -ucloud pathx uga create origin-location static 东京,中国,中国台北,中国香港,华盛顿,孟买,新加坡,曼谷,法兰克福,洛杉矶,莫斯科,雅加达,首尔 -ucloud pathx uga create project-id dynamic -ucloud pathx uga create protocol static tcp,udp -ucloud pathx uga create upath-id dynamic -ucloud pathx uga delete project-id dynamic -ucloud pathx uga delete uga-id dynamic -ucloud pathx uga delete-port project-id dynamic -ucloud pathx uga delete-port protocol static tcp,udp -ucloud pathx uga delete-port uga-id dynamic -ucloud pathx uga describe project-id dynamic -ucloud pathx uga describe uga-id dynamic -ucloud pathx uga list project-id dynamic -ucloud pathx upath list project-id dynamic -ucloud redis create charge-type static Dynamic,Month,Year -ucloud redis create project-id dynamic -ucloud redis create region dynamic -ucloud redis create subnet-id dynamic -ucloud redis create type static distributed,master-replica -ucloud redis create version static 4.0,5.0,6.0,7.0 -ucloud redis create vpc-id dynamic -ucloud redis create zone dynamic -ucloud redis delete project-id dynamic -ucloud redis delete region dynamic -ucloud redis delete umem-id dynamic -ucloud redis delete zone dynamic -ucloud redis list project-id dynamic -ucloud redis list region dynamic -ucloud redis list umem-id dynamic -ucloud redis list zone dynamic -ucloud redis restart project-id dynamic -ucloud redis restart region dynamic -ucloud redis restart umem-id dynamic -ucloud redis restart zone dynamic -ucloud subnet create vpc-id dynamic -ucloud subnet delete project-id dynamic -ucloud subnet delete region dynamic -ucloud subnet delete subnet-id dynamic -ucloud subnet list-resource project-id dynamic -ucloud subnet list-resource region dynamic -ucloud subnet list-resource resource-type static docker,phost,udb,udw,ufortresshost,uhadoophost,uhost,ukafka,ulb,umem,unatgw,vip -ucloud subnet list-resource subnet-id dynamic -ucloud udpn create charge-type static Dynamic,Month,Year -ucloud udpn create peer1 dynamic -ucloud udpn create peer2 dynamic -ucloud udpn create project-id dynamic -ucloud udpn delete project-id dynamic -ucloud udpn delete udpn-id dynamic -ucloud udpn list project-id dynamic -ucloud udpn list region dynamic -ucloud udpn list udpn-id dynamic -ucloud udpn modify-bw udpn-id dynamic -ucloud ulb create bind-eip dynamic -ucloud ulb create charge-type static Dynamic,Month,Year -ucloud ulb create create-eip-line static BGP,International -ucloud ulb create create-eip-traffic-mode static Bandwidth,ShareBandwidth,Traffic -ucloud ulb create mode static inner,outer -ucloud ulb create project-id dynamic -ucloud ulb create region dynamic -ucloud ulb create subnet-id dynamic -ucloud ulb create vpc-id dynamic -ucloud ulb delete project-id dynamic -ucloud ulb delete region dynamic -ucloud ulb delete ulb-id dynamic -ucloud ulb list project-id dynamic -ucloud ulb list region dynamic -ucloud ulb list vpc-id dynamic -ucloud ulb ssl add all-in-one-file static -ucloud ulb ssl add ca-certificate-file static -ucloud ulb ssl add private-key-file static -ucloud ulb ssl add project-id dynamic -ucloud ulb ssl add region dynamic -ucloud ulb ssl add site-certificate-file static -ucloud ulb ssl bind project-id dynamic -ucloud ulb ssl bind region dynamic -ucloud ulb ssl bind ssl-id dynamic -ucloud ulb ssl bind ulb-id dynamic -ucloud ulb ssl bind vserver-id dynamic -ucloud ulb ssl delete project-id dynamic -ucloud ulb ssl delete region dynamic -ucloud ulb ssl delete ssl-id dynamic -ucloud ulb ssl describe project-id dynamic -ucloud ulb ssl describe region dynamic -ucloud ulb ssl describe ssl-id dynamic -ucloud ulb ssl list project-id dynamic -ucloud ulb ssl list region dynamic -ucloud ulb ssl unbind project-id dynamic -ucloud ulb ssl unbind region dynamic -ucloud ulb ssl unbind ssl-id dynamic -ucloud ulb ssl unbind ulb-id dynamic -ucloud ulb ssl unbind vserver-id dynamic -ucloud ulb update project-id dynamic -ucloud ulb update region dynamic -ucloud ulb update ulb-id dynamic -ucloud ulb vserver backend add backend-mode static disable,enable -ucloud ulb vserver backend add project-id dynamic -ucloud ulb vserver backend add region dynamic -ucloud ulb vserver backend add resource-type static UDHost,UDocker,UPM,Uhost -ucloud ulb vserver backend add ulb-id dynamic -ucloud ulb vserver backend add vserver-id dynamic -ucloud ulb vserver backend delete backend-id dynamic -ucloud ulb vserver backend delete project-id dynamic -ucloud ulb vserver backend delete region dynamic -ucloud ulb vserver backend delete ulb-id dynamic -ucloud ulb vserver backend list project-id dynamic -ucloud ulb vserver backend list region dynamic -ucloud ulb vserver backend list ulb-id dynamic -ucloud ulb vserver backend list vserver-id dynamic -ucloud ulb vserver backend update backend-id dynamic -ucloud ulb vserver backend update backend-mode static disable,enable -ucloud ulb vserver backend update project-id dynamic -ucloud ulb vserver backend update region dynamic -ucloud ulb vserver backend update ulb-id dynamic -ucloud ulb vserver create health-check-mode static Path,Port -ucloud ulb vserver create lb-method static Leastconn,Roundrobin,Source,WeightRoundrobin -ucloud ulb vserver create listen-type static PacketsTransmit,RequestProxy -ucloud ulb vserver create project-id dynamic -ucloud ulb vserver create protocol static HTTP,HTTPS,TCP,UDP -ucloud ulb vserver create region dynamic -ucloud ulb vserver create session-maintain-mode static None,ServerInsert,UserDefined -ucloud ulb vserver create ssl-id dynamic -ucloud ulb vserver create ulb-id dynamic -ucloud ulb vserver delete project-id dynamic -ucloud ulb vserver delete region dynamic -ucloud ulb vserver delete ulb-id dynamic -ucloud ulb vserver delete vserver-id dynamic -ucloud ulb vserver list project-id dynamic -ucloud ulb vserver list region dynamic -ucloud ulb vserver list ulb-id dynamic -ucloud ulb vserver policy add backend-id dynamic -ucloud ulb vserver policy add forward-method static Domain,Path -ucloud ulb vserver policy add project-id dynamic -ucloud ulb vserver policy add region dynamic -ucloud ulb vserver policy add ulb-id dynamic -ucloud ulb vserver policy add vserver-id dynamic -ucloud ulb vserver policy delete project-id dynamic -ucloud ulb vserver policy delete region dynamic -ucloud ulb vserver policy list project-id dynamic -ucloud ulb vserver policy list region dynamic -ucloud ulb vserver policy list ulb-id dynamic -ucloud ulb vserver policy list vserver-id dynamic -ucloud ulb vserver policy update add-backend-id dynamic -ucloud ulb vserver policy update backend-id dynamic -ucloud ulb vserver policy update forward-method static Domain,Path -ucloud ulb vserver policy update project-id dynamic -ucloud ulb vserver policy update region dynamic -ucloud ulb vserver policy update remove-backend-id dynamic -ucloud ulb vserver policy update ulb-id dynamic -ucloud ulb vserver policy update vserver-id dynamic -ucloud ulb vserver update health-check-mode static Path,Port -ucloud ulb vserver update lb-method static ConsistentHash,ConsistentHashPort,Leastconn,Roundrobin,Source,SourcePort,WeightRoundrobin -ucloud ulb vserver update project-id dynamic -ucloud ulb vserver update region dynamic -ucloud ulb vserver update session-maintain-mode static None,ServerInsert,UserDefined -ucloud ulb vserver update ulb-id dynamic -ucloud ulb vserver update vserver-id dynamic -ucloud vpc create-intercome dst-project-id dynamic -ucloud vpc create-intercome dst-region dynamic -ucloud vpc create-intercome dst-vpc-id dynamic -ucloud vpc create-intercome project-id dynamic -ucloud vpc create-intercome region dynamic -ucloud vpc create-intercome vpc-id dynamic -ucloud vpc delete vpc-id dynamic -ucloud vpc delete-intercome dst-region dynamic -ucloud vpc delete-intercome vpc-id dynamic -ucloud vpc list vpc-id dynamic -ucloud vpc list-intercome project-id dynamic -ucloud vpc list-intercome region dynamic -ucloud vpc list-intercome vpc-id dynamic diff --git a/products/unet/internal/eip/allocate.go b/products/eip/internal/eip/allocate.go similarity index 100% rename from products/unet/internal/eip/allocate.go rename to products/eip/internal/eip/allocate.go diff --git a/products/unet/internal/eip/bind.go b/products/eip/internal/eip/bind.go similarity index 100% rename from products/unet/internal/eip/bind.go rename to products/eip/internal/eip/bind.go diff --git a/products/unet/internal/eip/cmd.go b/products/eip/internal/eip/cmd.go similarity index 100% rename from products/unet/internal/eip/cmd.go rename to products/eip/internal/eip/cmd.go diff --git a/products/unet/internal/eip/completion.go b/products/eip/internal/eip/completion.go similarity index 100% rename from products/unet/internal/eip/completion.go rename to products/eip/internal/eip/completion.go diff --git a/products/unet/internal/eip/describe.go b/products/eip/internal/eip/describe.go similarity index 100% rename from products/unet/internal/eip/describe.go rename to products/eip/internal/eip/describe.go diff --git a/products/unet/internal/eip/join_shared_bw.go b/products/eip/internal/eip/join_shared_bw.go similarity index 100% rename from products/unet/internal/eip/join_shared_bw.go rename to products/eip/internal/eip/join_shared_bw.go diff --git a/products/unet/internal/eip/leave_shared_bw.go b/products/eip/internal/eip/leave_shared_bw.go similarity index 100% rename from products/unet/internal/eip/leave_shared_bw.go rename to products/eip/internal/eip/leave_shared_bw.go diff --git a/products/unet/internal/eip/list.go b/products/eip/internal/eip/list.go similarity index 100% rename from products/unet/internal/eip/list.go rename to products/eip/internal/eip/list.go diff --git a/products/unet/internal/eip/modify_bw.go b/products/eip/internal/eip/modify_bw.go similarity index 100% rename from products/unet/internal/eip/modify_bw.go rename to products/eip/internal/eip/modify_bw.go diff --git a/products/unet/internal/eip/modify_traffic_mode.go b/products/eip/internal/eip/modify_traffic_mode.go similarity index 100% rename from products/unet/internal/eip/modify_traffic_mode.go rename to products/eip/internal/eip/modify_traffic_mode.go diff --git a/products/unet/internal/eip/release.go b/products/eip/internal/eip/release.go similarity index 100% rename from products/unet/internal/eip/release.go rename to products/eip/internal/eip/release.go diff --git a/products/unet/internal/eip/rows.go b/products/eip/internal/eip/rows.go similarity index 100% rename from products/unet/internal/eip/rows.go rename to products/eip/internal/eip/rows.go diff --git a/products/unet/internal/eip/status.go b/products/eip/internal/eip/status.go similarity index 100% rename from products/unet/internal/eip/status.go rename to products/eip/internal/eip/status.go diff --git a/products/unet/internal/eip/unbind.go b/products/eip/internal/eip/unbind.go similarity index 100% rename from products/unet/internal/eip/unbind.go rename to products/eip/internal/eip/unbind.go diff --git a/products/udb/product.go b/products/eip/product.go similarity index 50% rename from products/udb/product.go rename to products/eip/product.go index 20130c490b..fe90d88059 100644 --- a/products/udb/product.go +++ b/products/eip/product.go @@ -1,21 +1,20 @@ -package udb +package eip import ( "github.com/spf13/cobra" "github.com/ucloud/ucloud-cli/pkg/cli" - "github.com/ucloud/ucloud-cli/products/udb/internal/mysql" + internaleip "github.com/ucloud/ucloud-cli/products/eip/internal/eip" ) type product struct{} -// New returns the udb product (registered via hack/gen-products). func New() cli.Product { return product{} } func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "udb", Commands: []string{"mysql"}} + return cli.Metadata{Name: "eip", Commands: []string{"eip"}} } func (product) NewCommand(ctx *cli.Context) []*cobra.Command { - return []*cobra.Command{mysql.NewCommand(ctx)} + return []*cobra.Command{internaleip.NewCommand(ctx)} } diff --git a/products/eip/product.yaml b/products/eip/product.yaml new file mode 100644 index 0000000000..33fd319721 --- /dev/null +++ b/products/eip/product.yaml @@ -0,0 +1,6 @@ +name: eip +owners: + - Episkey-G +commands: + - eip +enabled: true diff --git a/products/eip/testdata/cmdtree.golden b/products/eip/testdata/cmdtree.golden new file mode 100644 index 0000000000..1cb6ab5372 --- /dev/null +++ b/products/eip/testdata/cmdtree.golden @@ -0,0 +1,57 @@ +ucloud eip use=eip short=List,allocate and release EIP +ucloud eip allocate use=allocate short=Allocate EIP + flag=bandwidth-mb short= default=0 required=true + flag=charge-type short= default=Month required= + flag=count short= default=1 required= + flag=group short= default=Default required= + flag=line short= default= required= + flag=name short= default=EIP required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=remark short= default= required= + flag=share-bandwidth-id short= default= required= + flag=traffic-mode short= default=Bandwidth required= +ucloud eip bind use=bind short=Bind EIP with uhost + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=resource-id short= default= required=true + flag=resource-type short= default=uhost required= +ucloud eip join-shared-bw use=join-shared-bw short=Join shared bandwidth + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=shared-bw-id short= default= required=true +ucloud eip leave-shared-bw use=leave-shared-bw short=Leave shared bandwidth + flag=bandwidth-mb short= default=1 required= + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=shared-bw-id short= default= required= + flag=traffic-mode short= default=Bandwidth required= +ucloud eip list use=list short=List all EIP instances + flag=limit short= default=50 required= + flag=list-all short= default=false required= + flag=offset short= default=0 required= + flag=page-off short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= +ucloud eip modify-bw use=modify-bw short=Modify bandwith of EIP instances + flag=bandwidth-mb short= default=0 required=true + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= +ucloud eip modify-traffic-mode use=modify-traffic-mode short=Modify charge mode of EIP instances + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=traffic-mode short= default= required=true +ucloud eip release use=release short=Release EIP + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= +ucloud eip unbind use=unbind short=Unbind EIP with uhost + flag=eip-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= diff --git a/products/unet/testdata/completion.golden b/products/eip/testdata/completion.golden similarity index 68% rename from products/unet/testdata/completion.golden rename to products/eip/testdata/completion.golden index 480488403d..d2f9868b60 100644 --- a/products/unet/testdata/completion.golden +++ b/products/eip/testdata/completion.golden @@ -22,15 +22,3 @@ ucloud eip release region dynamic ucloud eip unbind eip-id dynamic ucloud eip unbind project-id dynamic ucloud eip unbind region dynamic -ucloud firewall add-rule fw-id dynamic -ucloud firewall add-rule rules-file static -ucloud firewall apply fw-id dynamic -ucloud firewall apply resource-type static dbaudit,fortresshost,hadoophost,udhost,udockhost,uhost,unatgw,upm -ucloud firewall copy region dynamic -ucloud firewall copy src-fw dynamic -ucloud firewall copy target-region dynamic -ucloud firewall create rules-file static -ucloud firewall delete fw-id dynamic -ucloud firewall remove-rule fw-id dynamic -ucloud firewall resource fw-id dynamic -ucloud firewall update fw-id dynamic diff --git a/products/unet/internal/firewall/add_rule.go b/products/firewall/internal/firewall/add_rule.go similarity index 100% rename from products/unet/internal/firewall/add_rule.go rename to products/firewall/internal/firewall/add_rule.go diff --git a/products/unet/internal/firewall/apply.go b/products/firewall/internal/firewall/apply.go similarity index 100% rename from products/unet/internal/firewall/apply.go rename to products/firewall/internal/firewall/apply.go diff --git a/products/unet/internal/firewall/cmd.go b/products/firewall/internal/firewall/cmd.go similarity index 100% rename from products/unet/internal/firewall/cmd.go rename to products/firewall/internal/firewall/cmd.go diff --git a/products/unet/internal/firewall/completion.go b/products/firewall/internal/firewall/completion.go similarity index 100% rename from products/unet/internal/firewall/completion.go rename to products/firewall/internal/firewall/completion.go diff --git a/products/unet/internal/firewall/copy.go b/products/firewall/internal/firewall/copy.go similarity index 100% rename from products/unet/internal/firewall/copy.go rename to products/firewall/internal/firewall/copy.go diff --git a/products/unet/internal/firewall/create.go b/products/firewall/internal/firewall/create.go similarity index 100% rename from products/unet/internal/firewall/create.go rename to products/firewall/internal/firewall/create.go diff --git a/products/unet/internal/firewall/delete.go b/products/firewall/internal/firewall/delete.go similarity index 100% rename from products/unet/internal/firewall/delete.go rename to products/firewall/internal/firewall/delete.go diff --git a/products/unet/internal/firewall/describe.go b/products/firewall/internal/firewall/describe.go similarity index 100% rename from products/unet/internal/firewall/describe.go rename to products/firewall/internal/firewall/describe.go diff --git a/products/unet/internal/firewall/list.go b/products/firewall/internal/firewall/list.go similarity index 100% rename from products/unet/internal/firewall/list.go rename to products/firewall/internal/firewall/list.go diff --git a/products/unet/internal/firewall/remove_rule.go b/products/firewall/internal/firewall/remove_rule.go similarity index 100% rename from products/unet/internal/firewall/remove_rule.go rename to products/firewall/internal/firewall/remove_rule.go diff --git a/products/unet/internal/firewall/resource.go b/products/firewall/internal/firewall/resource.go similarity index 100% rename from products/unet/internal/firewall/resource.go rename to products/firewall/internal/firewall/resource.go diff --git a/products/unet/internal/firewall/rows.go b/products/firewall/internal/firewall/rows.go similarity index 100% rename from products/unet/internal/firewall/rows.go rename to products/firewall/internal/firewall/rows.go diff --git a/products/unet/internal/firewall/rules.go b/products/firewall/internal/firewall/rules.go similarity index 100% rename from products/unet/internal/firewall/rules.go rename to products/firewall/internal/firewall/rules.go diff --git a/products/unet/internal/firewall/update.go b/products/firewall/internal/firewall/update.go similarity index 100% rename from products/unet/internal/firewall/update.go rename to products/firewall/internal/firewall/update.go diff --git a/products/firewall/product.go b/products/firewall/product.go new file mode 100644 index 0000000000..0bdfcafade --- /dev/null +++ b/products/firewall/product.go @@ -0,0 +1,20 @@ +package firewall + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalfirewall "github.com/ucloud/ucloud-cli/products/firewall/internal/firewall" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "firewall", Commands: []string{"firewall"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalfirewall.NewCommand(ctx)} +} diff --git a/products/firewall/product.yaml b/products/firewall/product.yaml new file mode 100644 index 0000000000..921c40ceeb --- /dev/null +++ b/products/firewall/product.yaml @@ -0,0 +1,6 @@ +name: firewall +owners: + - Episkey-G +commands: + - firewall +enabled: true diff --git a/products/unet/testdata/cmdtree.golden b/products/firewall/testdata/cmdtree.golden similarity index 50% rename from products/unet/testdata/cmdtree.golden rename to products/firewall/testdata/cmdtree.golden index 392f903068..0bf9fd86dd 100644 --- a/products/unet/testdata/cmdtree.golden +++ b/products/firewall/testdata/cmdtree.golden @@ -1,60 +1,3 @@ -ucloud eip use=eip short=List,allocate and release EIP -ucloud eip allocate use=allocate short=Allocate EIP - flag=bandwidth-mb short= default=0 required=true - flag=charge-type short= default=Month required= - flag=count short= default=1 required= - flag=group short= default=Default required= - flag=line short= default= required= - flag=name short= default=EIP required= - flag=project-id short= default= required= - flag=quantity short= default=1 required= - flag=region short= default= required= - flag=remark short= default= required= - flag=share-bandwidth-id short= default= required= - flag=traffic-mode short= default=Bandwidth required= -ucloud eip bind use=bind short=Bind EIP with uhost - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=resource-id short= default= required=true - flag=resource-type short= default=uhost required= -ucloud eip join-shared-bw use=join-shared-bw short=Join shared bandwidth - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=shared-bw-id short= default= required=true -ucloud eip leave-shared-bw use=leave-shared-bw short=Leave shared bandwidth - flag=bandwidth-mb short= default=1 required= - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=shared-bw-id short= default= required= - flag=traffic-mode short= default=Bandwidth required= -ucloud eip list use=list short=List all EIP instances - flag=limit short= default=50 required= - flag=list-all short= default=false required= - flag=offset short= default=0 required= - flag=page-off short= default=false required= - flag=project-id short= default= required= - flag=region short= default= required= -ucloud eip modify-bw use=modify-bw short=Modify bandwith of EIP instances - flag=bandwidth-mb short= default=0 required=true - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= -ucloud eip modify-traffic-mode use=modify-traffic-mode short=Modify charge mode of EIP instances - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=traffic-mode short= default= required=true -ucloud eip release use=release short=Release EIP - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= -ucloud eip unbind use=unbind short=Unbind EIP with uhost - flag=eip-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= ucloud firewall use=firewall short=List and manipulate extranet firewall ucloud firewall add-rule use=add-rule short=Add rule to firewall instance flag=fw-id short= default=[] required=true diff --git a/products/firewall/testdata/completion.golden b/products/firewall/testdata/completion.golden new file mode 100644 index 0000000000..7113e91d9a --- /dev/null +++ b/products/firewall/testdata/completion.golden @@ -0,0 +1,12 @@ +ucloud firewall add-rule fw-id dynamic +ucloud firewall add-rule rules-file static +ucloud firewall apply fw-id dynamic +ucloud firewall apply resource-type static dbaudit,fortresshost,hadoophost,udhost,udockhost,uhost,unatgw,upm +ucloud firewall copy region dynamic +ucloud firewall copy src-fw dynamic +ucloud firewall copy target-region dynamic +ucloud firewall create rules-file static +ucloud firewall delete fw-id dynamic +ucloud firewall remove-rule fw-id dynamic +ucloud firewall resource fw-id dynamic +ucloud firewall update fw-id dynamic diff --git a/products/globalssh/internal/gssh/area.go b/products/globalssh/internal/gssh/area.go new file mode 100644 index 0000000000..df48d5d4ea --- /dev/null +++ b/products/globalssh/internal/gssh/area.go @@ -0,0 +1,79 @@ +package gssh + +import ( + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +var areaCodeMap = map[string]string{ + "LAX": "LosAngeles", + "SIN": "Singapore", + "HKG": "HongKong", + "HND": "Tokyo", + "IAD": "Washington", + "FRA": "Frankfurt", + "LOS": "Lagos", +} + +var regionLabel = map[string]string{ + "cn-bj1": "Beijing1", + "cn-bj2": "Beijing2", + "cn-sh2": "Shanghai2", + "cn-gd": "Guangzhou", + "cn-qz": "Quanzhou", + "hk": "Hongkong", + "us-ca": "LosAngeles", + "us-ws": "Washington", + "ge-fra": "Frankfurt", + "th-bkk": "Bangkok", + "kr-seoul": "Seoul", + "sg": "Singapore", + "tw-kh": "Kaohsiung", + "rus-mosc": "Moscow", + "jpn-tky": "Tokyo", + "tw-tp": "TaiPei", + "uae-dubai": "Dubai", + "idn-jakarta": "Jakarta", + "ind-mumbai": "Bombay", + "bra-saopaulo": "SaoPaulo", + "uk-london": "London", + "afr-nigeria": "Lagos", +} + +// newArea ucloud gssh location +func newArea(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewDescribeGlobalSSHAreaRequest() + cmd := &cobra.Command{ + Use: "location", + Short: "List SSH server locations and covered areas", + Long: "List SSH server locations and covered areas", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.DescribeGlobalSSHArea(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]GsshLocation, 0) + for _, item := range resp.AreaSet { + row := GsshLocation{ + AirportCode: item.AreaCode, + SSHServerLocation: areaCodeMap[item.AreaCode], + } + regionLabels := make([]string, 0) + for _, region := range item.RegionSet { + regionLabels = append(regionLabels, regionLabel[region]) + } + row.CoveredArea = strings.Join(regionLabels, ",") + list = append(list, row) + } + ctx.PrintList(list) + }, + } + return cmd +} diff --git a/products/globalssh/internal/gssh/cmd.go b/products/globalssh/internal/gssh/cmd.go new file mode 100644 index 0000000000..dc9f7320cd --- /dev/null +++ b/products/globalssh/internal/gssh/cmd.go @@ -0,0 +1,22 @@ +package gssh + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `gssh` root command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "gssh", + Short: "Create,list,update and delete globalssh instance", + Long: "Create,list,update and delete globalssh instance", + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newModify(ctx)) + cmd.AddCommand(newArea(ctx)) + return cmd +} diff --git a/products/globalssh/internal/gssh/completion.go b/products/globalssh/internal/gssh/completion.go new file mode 100644 index 0000000000..98ac74c2b4 --- /dev/null +++ b/products/globalssh/internal/gssh/completion.go @@ -0,0 +1,88 @@ +package gssh + +import ( + "fmt" + "strings" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllGssh(ctx *cli.Context, project string) ([]pathxsdk.GlobalSSHInfo, error) { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewDescribeGlobalSSHInstanceRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + resp, err := client.DescribeGlobalSSHInstance(req) + if err != nil { + return nil, err + } + return resp.InstanceSet, nil +} + +func getAllGsshIDNames(ctx *cli.Context, project string) []string { + gsshs, err := getAllGssh(ctx, project) + if err != nil { + return nil + } + list := []string{} + for _, gssh := range gsshs { + list = append(list, fmt.Sprintf("%s/%s", gssh.InstanceId, gssh.TargetIP)) + } + return list +} + +func getAllEip(ctx *cli.Context, projectID, region string, states, paymodes []string) []string { + list, err := fetchAllEip(ctx, projectID, region) + if err != nil { + return nil + } + strs := []string{} + for _, item := range list { + rightState := states == nil + for _, s := range states { + if item.Status == s { + rightState = true + } + } + rightPayMode := paymodes == nil + for _, m := range paymodes { + if item.PayMode == m { + rightPayMode = true + } + } + if !rightPayMode || !rightState { + continue + } + + ips := []string{} + for _, ip := range item.EIPAddr { + ips = append(ips, ip.IP) + } + strs = append(strs, item.EIPId+"/"+strings.Join(ips, ",")) + } + return strs +} + +func fetchAllEip(ctx *cli.Context, projectID, region string) ([]unet.UnetEIPSet, error) { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeEIPRequest() + list := []unet.UnetEIPSet{} + req.ProjectId = sdk.String(cli.PickResourceID(projectID)) + req.Region = sdk.String(region) + for offset, step := 0, 100; ; offset += step { + req.Offset = sdk.Int(offset) + req.Limit = sdk.Int(step) + resp, err := client.DescribeEIP(req) + if err != nil { + return nil, err + } + list = append(list, resp.EIPSet...) + if resp.TotalCount <= offset+step { + break + } + } + return list, nil +} diff --git a/products/globalssh/internal/gssh/create.go b/products/globalssh/internal/gssh/create.go new file mode 100644 index 0000000000..532372d8a2 --- /dev/null +++ b/products/globalssh/internal/gssh/create.go @@ -0,0 +1,77 @@ +package gssh + +import ( + "fmt" + "net" + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud gssh create +func newCreate(ctx *cli.Context) *cobra.Command { + var targetIP *net.IP + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewCreateGlobalSSHInstanceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create GlobalSSH instance", + Long: "Create GlobalSSH instance", + Example: "ucloud gssh create --location Washington --target-ip 8.8.8.8", + Run: func(cmd *cobra.Command, args []string) { + port := *req.Port + for code, area := range areaCodeMap { + if area == *req.AreaCode { + *req.AreaCode = code + } + } + if port < 1 || port > 65535 || port == 80 || port == 443 || port == 65123 { + fmt.Fprintln(ctx.ProgressWriter(), "The port number should be between 1 and 65535, and cannot be 80, 443 or 65123") + return + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.TargetIP = sdk.String(targetIP.String()) + resp, err := client.CreateGlobalSSHInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "gssh[%s] created\n", resp.InstanceId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.InstanceId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.AreaCode = flags.String("location", "", "Required. Location of the source server. See 'ucloud gssh location'") + targetIP = flags.IP("target-ip", nil, "Required. IP of the source server. Required") + ctx.BindProjectID(cmd, req) + req.Port = flags.Int("port", 22, "Optional. Port of The SSH service between 1 and 65535. Do not use ports such as 80, 443 or 65123.") + req.Remark = flags.String("remark", "", "Optional. Remark of your GlobalSSH.") + req.ChargeType = flags.String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly(requires access)") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") + req.InstanceType = flags.String("instance-type", "", "Optional. Possible values: 'Ultimate','Enterprise', 'Basic', 'Free'(Default value)") + req.ForwardRegion = flags.String("forward-region", "", "Optional. You can select one of 'cn-bj2','cn-sh2','cn-gd' When instance-type is 'Basic'") + req.BandwidthPackage = flags.Int("bandwidth-package", 0, "Optional. You can set one of 0, 20, 40 When instance-type is 'Ultimate'") + cmd.MarkFlagRequired("location") + cmd.MarkFlagRequired("target-ip") + command.SetFlagValues(cmd, "location", "LosAngeles", "Singapore", "Lagos", "HongKong", "Tokyo", "Washington", "Frankfurt") + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic", "Trial") + command.SetFlagValues(cmd, "bandwidth-package", "0", "20", "40") + command.SetFlagValues(cmd, "forward-region", "cn-bj2", "cn-sh2", "cn-gd") + command.SetFlagValues(cmd, "instance-type", "Free", "Basic", "Enterprise", "Ultimate") + ctx.SetCompletion(cmd, "target-ip", func() []string { + eips := getAllEip(ctx, *req.ProjectId, ctx.DefaultRegion(), nil, nil) + for idx, eip := range eips { + eips[idx] = strings.SplitN(eip, "/", 2)[1] + } + return eips + }) + return cmd +} diff --git a/products/globalssh/internal/gssh/delete.go b/products/globalssh/internal/gssh/delete.go new file mode 100644 index 0000000000..5f7ac35a92 --- /dev/null +++ b/products/globalssh/internal/gssh/delete.go @@ -0,0 +1,50 @@ +package gssh + +import ( + "fmt" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud gssh delete +func newDelete(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewDeleteGlobalSSHInstanceRequest() + gsshIds := []string{} + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete GlobalSSH instance", + Long: "Delete GlobalSSH instance", + Example: "ucloud gssh delete --gssh-id uga-xx1 --id uga-xx2", + Run: func(cmd *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range gsshIds { + id := ctx.PickResourceID(idname) + req.InstanceId = sdk.String(id) + _, err := client.DeleteGlobalSSHInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "gssh[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + flags.StringSliceVar(&gsshIds, "gssh-id", make([]string, 0), "Required. ID of the GlobalSSH instances you want to delete. Multiple values specified by multiple commas") + ctx.BindProjectID(cmd, req) + cmd.MarkFlagRequired("gssh-id") + ctx.SetCompletion(cmd, "gssh-id", func() []string { + return getAllGsshIDNames(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/globalssh/internal/gssh/list.go b/products/globalssh/internal/gssh/list.go new file mode 100644 index 0000000000..e8f55961aa --- /dev/null +++ b/products/globalssh/internal/gssh/list.go @@ -0,0 +1,60 @@ +package gssh + +import ( + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList ucloud gssh list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewDescribeGlobalSSHInstanceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List all GlobalSSH instances", + Long: "List all GlobalSSH instances", + Example: "ucloud gssh list", + Run: func(cmd *cobra.Command, args []string) { + areaMap := map[string]string{ + "洛杉矶": "LosAngeles", + "新加坡": "Singapore", + "香港": "HongKong", + "东京": "Tokyo", + "华盛顿": "Washington", + "法兰克福": "Frankfurt", + "拉各斯": "Lagos", + } + resp, err := client.DescribeGlobalSSHInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]GSSHRow, 0) + for _, gssh := range resp.InstanceSet { + row := GSSHRow{ + ResourceID: gssh.InstanceId, + SSHServerIP: gssh.TargetIP, + AcceleratingDomain: gssh.AcceleratingDomain, + SSHPort: gssh.Port, + GlobalSSHPort: gssh.GlobalSSHPort, + Remark: gssh.Remark, + InstanceType: gssh.InstanceType, + } + if val, ok := areaMap[gssh.Area]; ok { + row.SSHServerLocation = val + } else { + row.SSHServerLocation = gssh.Area + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + cmd.Flags().SortFlags = false + req.Region = cmd.Flags().String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.ProjectId = cmd.Flags().String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + return cmd +} diff --git a/products/globalssh/internal/gssh/modify.go b/products/globalssh/internal/gssh/modify.go new file mode 100644 index 0000000000..8a075a3eb2 --- /dev/null +++ b/products/globalssh/internal/gssh/modify.go @@ -0,0 +1,80 @@ +package gssh + +import ( + "fmt" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newModify ucloud gssh update +func newModify(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + gsshModifyPortReq := client.NewModifyGlobalSSHPortRequest() + gsshModifyRemarkReq := client.NewModifyGlobalSSHRemarkRequest() + project := ctx.DefaultProjectID() + gsshIDs := []string{} + cmd := &cobra.Command{ + Use: "update", + Short: "Update GlobalSSH instance", + Long: "Update GlobalSSH instance, including port and remark attribute", + Example: "ucloud gssh update --gssh-id uga-xxx --port 22", + Run: func(cmd *cobra.Command, args []string) { + gsshModifyPortReq.ProjectId = sdk.String(ctx.PickResourceID(project)) + gsshModifyRemarkReq.ProjectId = sdk.String(ctx.PickResourceID(project)) + if *gsshModifyPortReq.Port == 0 && *gsshModifyRemarkReq.Remark == "" { + fmt.Fprintln(ctx.ProgressWriter(), "Error, port or remark required") + } + results := []cli.OpResultRow{} + if *gsshModifyPortReq.Port != 0 { + port := *gsshModifyPortReq.Port + if port <= 1 || port >= 65535 || port == 80 || port == 443 || port == 65123 { + fmt.Fprintln(ctx.ProgressWriter(), "The port number should be between 1 and 65535, and cannot be equal to 80, 443 or 65123") + return + } + for _, idname := range gsshIDs { + id := ctx.PickResourceID(idname) + gsshModifyPortReq.InstanceId = sdk.String(id) + _, err := client.ModifyGlobalSSHPort(gsshModifyPortReq) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "gssh[%s]'s port updated\n", id) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "update-port", Status: "Updated"}) + } + } + if *gsshModifyRemarkReq.Remark != "" { + for _, idname := range gsshIDs { + id := ctx.PickResourceID(idname) + gsshModifyRemarkReq.InstanceId = sdk.String(id) + _, err := client.ModifyGlobalSSHRemark(gsshModifyRemarkReq) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "gssh[%s]'s remark updated\n", id) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "update-remark", Status: "Updated"}) + } + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&gsshIDs, "gssh-id", nil, "Required. ResourceID of your GlobalSSH instances") + flags.StringVar(&project, "project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + gsshModifyPortReq.Port = flags.Int("port", 0, "Optional. Port of SSH service.") + gsshModifyRemarkReq.Remark = flags.String("remark", "", "Optional. Remark of your GlobalSSH.") + cmd.MarkFlagRequired("gssh-id") + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + ctx.SetCompletion(cmd, "gssh-id", func() []string { + return getAllGsshIDNames(ctx, project) + }) + return cmd +} diff --git a/products/globalssh/internal/gssh/rows.go b/products/globalssh/internal/gssh/rows.go new file mode 100644 index 0000000000..94091ff560 --- /dev/null +++ b/products/globalssh/internal/gssh/rows.go @@ -0,0 +1,18 @@ +package gssh + +type GSSHRow struct { + ResourceID string + SSHServerIP string + AcceleratingDomain string + SSHServerLocation string + SSHPort int + GlobalSSHPort int + Remark string + InstanceType string +} + +type GsshLocation struct { + AirportCode string + SSHServerLocation string + CoveredArea string +} diff --git a/products/globalssh/product.go b/products/globalssh/product.go new file mode 100644 index 0000000000..2391c060fe --- /dev/null +++ b/products/globalssh/product.go @@ -0,0 +1,20 @@ +package globalssh + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalgssh "github.com/ucloud/ucloud-cli/products/globalssh/internal/gssh" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "globalssh", Commands: []string{"gssh"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalgssh.NewCommand(ctx)} +} diff --git a/products/globalssh/product.yaml b/products/globalssh/product.yaml new file mode 100644 index 0000000000..d40338599a --- /dev/null +++ b/products/globalssh/product.yaml @@ -0,0 +1,6 @@ +name: globalssh +owners: + - Episkey-G +commands: + - gssh +enabled: true diff --git a/products/globalssh/testdata/cmdtree.golden b/products/globalssh/testdata/cmdtree.golden new file mode 100644 index 0000000000..5c0ac287d8 --- /dev/null +++ b/products/globalssh/testdata/cmdtree.golden @@ -0,0 +1,24 @@ +ucloud gssh use=gssh short=Create,list,update and delete globalssh instance +ucloud gssh create use=create short=Create GlobalSSH instance + flag=bandwidth-package short= default=0 required= + flag=charge-type short= default=Month required= + flag=forward-region short= default= required= + flag=instance-type short= default= required= + flag=location short= default= required=true + flag=port short= default=22 required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=remark short= default= required= + flag=target-ip short= default= required=true +ucloud gssh delete use=delete short=Delete GlobalSSH instance + flag=gssh-id short= default=[] required=true + flag=project-id short= default= required= +ucloud gssh list use=list short=List all GlobalSSH instances + flag=project-id short= default= required= + flag=region short= default= required= +ucloud gssh location use=location short=List SSH server locations and covered areas +ucloud gssh update use=update short=Update GlobalSSH instance + flag=gssh-id short= default=[] required=true + flag=port short= default=0 required= + flag=project-id short= default= required= + flag=remark short= default= required= diff --git a/products/globalssh/testdata/completion.golden b/products/globalssh/testdata/completion.golden new file mode 100644 index 0000000000..dbb2df71e3 --- /dev/null +++ b/products/globalssh/testdata/completion.golden @@ -0,0 +1,11 @@ +ucloud gssh create bandwidth-package static 0,20,40 +ucloud gssh create charge-type static Dynamic,Month,Trial,Year +ucloud gssh create forward-region static cn-bj2,cn-gd,cn-sh2 +ucloud gssh create instance-type static Basic,Enterprise,Free,Ultimate +ucloud gssh create location static Frankfurt,HongKong,Lagos,LosAngeles,Singapore,Tokyo,Washington +ucloud gssh create project-id dynamic +ucloud gssh create target-ip dynamic +ucloud gssh delete gssh-id dynamic +ucloud gssh delete project-id dynamic +ucloud gssh update gssh-id dynamic +ucloud gssh update project-id dynamic diff --git a/products/uhost/internal/image/cmd.go b/products/image/internal/image/cmd.go similarity index 100% rename from products/uhost/internal/image/cmd.go rename to products/image/internal/image/cmd.go diff --git a/products/uhost/internal/image/completion.go b/products/image/internal/image/completion.go similarity index 100% rename from products/uhost/internal/image/completion.go rename to products/image/internal/image/completion.go diff --git a/products/uhost/internal/image/copy.go b/products/image/internal/image/copy.go similarity index 100% rename from products/uhost/internal/image/copy.go rename to products/image/internal/image/copy.go diff --git a/products/uhost/internal/image/create.go b/products/image/internal/image/create.go similarity index 100% rename from products/uhost/internal/image/create.go rename to products/image/internal/image/create.go diff --git a/products/uhost/internal/image/delete.go b/products/image/internal/image/delete.go similarity index 100% rename from products/uhost/internal/image/delete.go rename to products/image/internal/image/delete.go diff --git a/products/uhost/internal/image/describe.go b/products/image/internal/image/describe.go similarity index 100% rename from products/uhost/internal/image/describe.go rename to products/image/internal/image/describe.go diff --git a/products/uhost/internal/image/list.go b/products/image/internal/image/list.go similarity index 100% rename from products/uhost/internal/image/list.go rename to products/image/internal/image/list.go diff --git a/products/uhost/internal/image/rows.go b/products/image/internal/image/rows.go similarity index 100% rename from products/uhost/internal/image/rows.go rename to products/image/internal/image/rows.go diff --git a/products/uhost/internal/image/status.go b/products/image/internal/image/status.go similarity index 100% rename from products/uhost/internal/image/status.go rename to products/image/internal/image/status.go diff --git a/products/image/product.go b/products/image/product.go new file mode 100644 index 0000000000..4ce019731d --- /dev/null +++ b/products/image/product.go @@ -0,0 +1,20 @@ +package image + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalimage "github.com/ucloud/ucloud-cli/products/image/internal/image" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "image", Commands: []string{"image"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalimage.NewCommand(ctx)} +} diff --git a/products/image/product.yaml b/products/image/product.yaml new file mode 100644 index 0000000000..34e47ee80e --- /dev/null +++ b/products/image/product.yaml @@ -0,0 +1,6 @@ +name: image +owners: + - Episkey-G +commands: + - image +enabled: true diff --git a/products/image/testdata/cmdtree.golden b/products/image/testdata/cmdtree.golden new file mode 100644 index 0000000000..2cc9bf4c2c --- /dev/null +++ b/products/image/testdata/cmdtree.golden @@ -0,0 +1,33 @@ +ucloud image use=image short=List and manipulate images +ucloud image copy use=copy short=Copy custom images + flag=async short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=source-image-id short= default=[] required=true + flag=target-image-desc short= default= required= + flag=target-image-name short= default= required= + flag=target-project short= default= required= + flag=target-region short= default= required= + flag=zone short= default= required= +ucloud image create use=create short=Create image from an uhost instance + flag=async short=a default=false required= + flag=image-desc short= default= required= + flag=image-name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=uhost-id short= default= required=true + flag=zone short= default= required= +ucloud image delete use=delete short=Delete custom images + flag=image-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud image list use=list short=List image + flag=image-id short= default= required= + flag=image-type short= default=Base required= + flag=limit short= default=500 required= + flag=offset short= default=0 required= + flag=os-type short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= diff --git a/products/image/testdata/completion.golden b/products/image/testdata/completion.golden new file mode 100644 index 0000000000..0f1a6d1c21 --- /dev/null +++ b/products/image/testdata/completion.golden @@ -0,0 +1,9 @@ +ucloud image copy project-id dynamic +ucloud image copy region dynamic +ucloud image copy source-image-id dynamic +ucloud image copy target-project dynamic +ucloud image copy target-region dynamic +ucloud image copy zone dynamic +ucloud image create uhost-id dynamic +ucloud image delete image-id dynamic +ucloud image list image-type static Base,Business,Custom diff --git a/products/memcache/internal/memcache/cmd.go b/products/memcache/internal/memcache/cmd.go new file mode 100644 index 0000000000..dd4e53b1ac --- /dev/null +++ b/products/memcache/internal/memcache/cmd.go @@ -0,0 +1,21 @@ +package memcache + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud memcache command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "memcache", + Short: "List and manipulate memcache instances", + Long: "List and manipulate memcache instances", + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newRestart(ctx)) + return cmd +} diff --git a/products/memcache/internal/memcache/completion.go b/products/memcache/internal/memcache/completion.go new file mode 100644 index 0000000000..bbf2a67926 --- /dev/null +++ b/products/memcache/internal/memcache/completion.go @@ -0,0 +1,35 @@ +package memcache + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getIDList(ctx *cli.Context, project, region string) []string { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeUMemcacheGroupRequest() + req.ProjectId = &project + req.Region = ®ion + list := []string{} + + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeUMemcacheGroup(req) + if err != nil { + fmt.Fprintln(ctx.ProgressWriter(), err) + return nil + } + for _, ins := range resp.DataSet { + list = append(list, fmt.Sprintf("%s/%s", ins.GroupId, ins.Name)) + } + if offset+limit >= resp.TotalCount { + break + } + } + return list +} diff --git a/products/memcache/internal/memcache/create.go b/products/memcache/internal/memcache/create.go new file mode 100644 index 0000000000..c8f9de7b24 --- /dev/null +++ b/products/memcache/internal/memcache/create.go @@ -0,0 +1,75 @@ +package memcache + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate returns ucloud memcache create. +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewCreateUMemcacheGroupRequest() + var region, zone, projectID string + cmd := &cobra.Command{ + Use: "create", + Short: "Create memcache instance", + Long: "Create memcache instance", + Run: func(c *cobra.Command, args []string) { + if *req.Size > 32 || *req.Size < 1 { + fmt.Fprintln(ctx.ProgressWriter(), "size-gb should be between 1 and 32") + return + } + if err := fillDefaultVPCAndSubnet(ctx, req.VPCId, req.SubnetId, *req.ProjectId, *req.Region, *req.Zone); err != nil { + fmt.Fprintln(ctx.ProgressWriter(), err) + return + } + resp, err := client.CreateUMemcacheGroup(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "memcache[%s] created\n", resp.GroupId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.GroupId, Action: "create", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.Name = flags.String("name", "", "Required. Name of memcache instance to create") + req.Size = flags.Int("size-gb", 1, "Optional. Memory size of memcache instance. Unit GB. Accpet values:1,2,4,8,16,32") + req.VPCId = flags.String("vpc-id", "", "Optional. VPC ID. See 'ucloud vpc list'") + req.SubnetId = flags.String("subnet-id", "", "Optional. Subnet ID. See 'ucloud subnet list'") + flags.StringVar(®ion, "region", ctx.DefaultRegion(), "Optional. Override default region for this command invocation, see 'ucloud region'") + flags.StringVar(&zone, "zone", ctx.DefaultZone(), "Optional. Override default availability zone for this command invocation, see 'ucloud region'") + flags.StringVar(&projectID, "project-id", ctx.DefaultProjectID(), "Optional. Override default project-id for this command invocation, see 'ucloud project list'") + req.ChargeType = flags.String("charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") + req.Tag = flags.String("group", "", "Optional. Business group") + + req.Region = ®ion + req.Zone = &zone + req.ProjectId = &projectID + + command.SetCompletion(cmd, "region", ctx.RegionList) + command.SetCompletion(cmd, "zone", func() []string { return ctx.ZoneList(region) }) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + command.SetFlagValues(cmd, "size-gb", "1", "2", "4", "8", "16", "32") + command.SetFlagValues(cmd, "charge-type", "Month", "Dynamic", "Year") + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, projectID, region) + }) + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, *req.VPCId, projectID, region) + }) + + cmd.MarkFlagRequired("name") + + return cmd +} diff --git a/products/memcache/internal/memcache/delete.go b/products/memcache/internal/memcache/delete.go new file mode 100644 index 0000000000..45d2d7a30a --- /dev/null +++ b/products/memcache/internal/memcache/delete.go @@ -0,0 +1,56 @@ +package memcache + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete returns ucloud memcache delete. +func newDelete(ctx *cli.Context) *cobra.Command { + var idNames []string + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDeleteUMemcacheGroupRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete memcache instances", + Long: "Delete memcache instances", + Example: "ucloud memcache delete --umem-id umemcache-rl5xuxx/testcli1,umemcache-xsdfa/testcli2", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.GroupId = &id + _, err := client.DeleteUMemcacheGroup(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "memcache[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of memcache intances to delete") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZoneEmpty(cmd, req) + + cmd.MarkFlagRequired("umem-id") + + command.SetCompletion(cmd, "umem-id", func() []string { + return getIDList(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/memcache/internal/memcache/list.go b/products/memcache/internal/memcache/list.go new file mode 100644 index 0000000000..a43667c42b --- /dev/null +++ b/products/memcache/internal/memcache/list.go @@ -0,0 +1,57 @@ +package memcache + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList returns ucloud memcache list. +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeUMemcacheGroupRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List memcache instances", + Long: "List memcache instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.DescribeUMemcacheGroup(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []Row{} + for _, ins := range resp.DataSet { + row := Row{ + ResourceID: ins.GroupId, + Name: ins.Name, + Group: ins.Tag, + Size: fmt.Sprintf("%dGB", ins.Size), + UsedSize: fmt.Sprintf("%dMB", ins.UsedSize), + State: ins.State, + CreateTime: common.FormatDate(ins.CreateTime), + Address: fmt.Sprintf("%s:%d", ins.VirtualIP, ins.Port), + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.GroupId = flags.String("umem-id", "", "Optional. Resource ID of the redis to list") + ctx.BindRegion(cmd, req) + ctx.BindZoneEmpty(cmd, req) + ctx.BindProjectID(cmd, req) + ctx.BindOffset(cmd, req) + ctx.BindLimit(cmd, req) + + return cmd +} diff --git a/products/memcache/internal/memcache/poll.go b/products/memcache/internal/memcache/poll.go new file mode 100644 index 0000000000..a9b5550b30 --- /dev/null +++ b/products/memcache/internal/memcache/poll.go @@ -0,0 +1,32 @@ +package memcache + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func describeByID(ctx *cli.Context) func(string, *request.CommonBase) (interface{}, error) { + return func(memcacheID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeUMemRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.Protocol = sdk.String("memcache") + req.ResourceId = &memcacheID + + resp, err := client.DescribeUMem(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) < 1 { + return nil, fmt.Errorf("resource [%s] may not exist", memcacheID) + } + return &resp.DataSet[0], nil + } +} diff --git a/products/memcache/internal/memcache/restart.go b/products/memcache/internal/memcache/restart.go new file mode 100644 index 0000000000..9fb8b8edec --- /dev/null +++ b/products/memcache/internal/memcache/restart.go @@ -0,0 +1,79 @@ +package memcache + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newRestart returns ucloud memcache restart. +func newRestart(ctx *cli.Context) *cobra.Command { + idNames := make([]string, 0) + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewRestartUMemcacheGroupRequest() + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart memcache instances", + Long: "Restart memcache instances", + Run: func(c *cobra.Command, args []string) { + reqs := make([]request.Common, len(idNames)) + for idx, idname := range idNames { + id := ctx.PickResourceID(idname) + next := *req + next.GroupId = &id + reqs[idx] = &next + } + prog := ctx.NewProgress() + if len(reqs) > 5 { + prog.Disable() + } + ctx.ConcurrentAction(reqs, 10, restart(ctx, client, prog)) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of memcache to restart") + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetCompletion(cmd, "umem-id", func() []string { + return getIDList(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("umem-id") + return cmd +} + +func restart(ctx *cli.Context, client *umem.UMemClient, prog *cli.Progress) func(request.Common) (bool, []string) { + return func(creq request.Common) (bool, []string) { + req := creq.(*umem.RestartUMemcacheGroupRequest) + block := prog.NewBlock() + logs := []string{} + _, err := client.RestartUMemcacheGroup(req) + if err != nil { + msg := fmt.Sprintf("restart memcache[%s] failed: %s", *req.GroupId, cli.ParseError(err)) + block.Append(cli.ParseError(err)) + logs = append(logs, msg) + return false, logs + } + text := fmt.Sprintf("memcache[%s] is restarting", *req.GroupId) + ret := ctx.PollerTo(ctx.ProgressWriter(), describeByID(ctx)).Sspoll(*req.GroupId, text, []string{UMEM_RUNNING, UMEM_FAIL}, block, nil) + if ret.Err != nil { + block.Append(cli.ParseError(ret.Err)) + logs = append(logs, ret.Err.Error()) + } + if ret.Timeout { + logs = append(logs, fmt.Sprintf("poll memcache[%s] timeout", *req.GroupId)) + } + return ret.Done, logs + } +} diff --git a/products/memcache/internal/memcache/rows.go b/products/memcache/internal/memcache/rows.go new file mode 100644 index 0000000000..5e0e1b6435 --- /dev/null +++ b/products/memcache/internal/memcache/rows.go @@ -0,0 +1,12 @@ +package memcache + +type Row struct { + ResourceID string + Name string + Address string + Size string + UsedSize string + State string + Group string + CreateTime string +} diff --git a/products/memcache/internal/memcache/status.go b/products/memcache/internal/memcache/status.go new file mode 100644 index 0000000000..5b78cb4831 --- /dev/null +++ b/products/memcache/internal/memcache/status.go @@ -0,0 +1,6 @@ +package memcache + +const ( + UMEM_FAIL = "Fail" + UMEM_RUNNING = "Running" +) diff --git a/products/memcache/internal/memcache/vpc.go b/products/memcache/internal/memcache/vpc.go new file mode 100644 index 0000000000..60ef1dc02a --- /dev/null +++ b/products/memcache/internal/memcache/vpc.go @@ -0,0 +1,119 @@ +package memcache + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func fillDefaultVPCAndSubnet(ctx *cli.Context, vpcID, subnetID *string, projectID, region, zone string) error { + if *vpcID != "" && *subnetID != "" { + return nil + } + vpcs, err := getAllVPCIns(ctx, projectID, region) + if err != nil { + return fmt.Errorf("failed to get vpc list: %s", err) + } + if len(vpcs) == 0 { + return fmt.Errorf("no vpc found in region[%s], please specify --vpc-id and --subnet-id", region) + } + + var defaultVPC *vpc.VPCInfo + for i := range vpcs { + if vpcs[i].VPCType == "DefaultVPC" { + defaultVPC = &vpcs[i] + break + } + } + if defaultVPC == nil { + defaultVPC = &vpcs[0] + } + + if *vpcID == "" { + *vpcID = defaultVPC.VPCId + } + + if *subnetID == "" { + subnets, err := getAllSubnets(ctx, *vpcID, projectID, region) + if err != nil { + return fmt.Errorf("failed to get subnet list: %s", err) + } + if len(subnets) == 0 { + return fmt.Errorf("no subnet found in vpc[%s], please specify --subnet-id", *vpcID) + } + if zone != "" { + for _, sn := range subnets { + if sn.Zone == zone { + *subnetID = sn.SubnetId + return nil + } + } + } + *subnetID = subnets[0].SubnetId + } + + return nil +} + +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpc.VPCInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, vpc := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) + } + return list +} + +func getAllSubnets(ctx *cli.Context, vpcID, project, region string) ([]vpc.SubnetInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := []vpc.SubnetInfo{} + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeSubnet(req) + if err != nil { + return nil, err + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + return subnets, nil +} + +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + subnets, err := getAllSubnets(ctx, vpcID, project, region) + if err != nil { + return nil + } + list := []string{} + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} diff --git a/products/memcache/product.go b/products/memcache/product.go new file mode 100644 index 0000000000..267358904a --- /dev/null +++ b/products/memcache/product.go @@ -0,0 +1,20 @@ +package memcache + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalmemcache "github.com/ucloud/ucloud-cli/products/memcache/internal/memcache" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "memcache", Commands: []string{"memcache"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalmemcache.NewCommand(ctx)} +} diff --git a/products/memcache/product.yaml b/products/memcache/product.yaml new file mode 100644 index 0000000000..d8dc016041 --- /dev/null +++ b/products/memcache/product.yaml @@ -0,0 +1,6 @@ +name: memcache +owners: + - Episkey-G +commands: + - memcache +enabled: true diff --git a/products/memcache/testdata/cmdtree.golden b/products/memcache/testdata/cmdtree.golden new file mode 100644 index 0000000000..6c01f04634 --- /dev/null +++ b/products/memcache/testdata/cmdtree.golden @@ -0,0 +1,29 @@ +ucloud memcache use=memcache short=List and manipulate memcache instances +ucloud memcache create use=create short=Create memcache instance + flag=charge-type short= default=Month required= + flag=group short= default= required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=size-gb short= default=1 required= + flag=subnet-id short= default= required= + flag=vpc-id short= default= required= + flag=zone short= default= required= +ucloud memcache delete use=delete short=Delete memcache instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default=[] required=true + flag=zone short= default= required= +ucloud memcache list use=list short=List memcache instances + flag=limit short= default=100 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default= required= + flag=zone short= default= required= +ucloud memcache restart use=restart short=Restart memcache instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default=[] required=true + flag=zone short= default= required= diff --git a/products/memcache/testdata/completion.golden b/products/memcache/testdata/completion.golden new file mode 100644 index 0000000000..781cd1f7bf --- /dev/null +++ b/products/memcache/testdata/completion.golden @@ -0,0 +1,18 @@ +ucloud memcache create charge-type static Dynamic,Month,Year +ucloud memcache create project-id dynamic +ucloud memcache create region dynamic +ucloud memcache create size-gb static 1,16,2,32,4,8 +ucloud memcache create subnet-id dynamic +ucloud memcache create vpc-id dynamic +ucloud memcache create zone dynamic +ucloud memcache delete project-id dynamic +ucloud memcache delete region dynamic +ucloud memcache delete umem-id dynamic +ucloud memcache delete zone dynamic +ucloud memcache list project-id dynamic +ucloud memcache list region dynamic +ucloud memcache list zone dynamic +ucloud memcache restart project-id dynamic +ucloud memcache restart region dynamic +ucloud memcache restart umem-id dynamic +ucloud memcache restart zone dynamic diff --git a/products/udb/internal/mysql/backup.go b/products/mysql/internal/mysql/backup.go similarity index 100% rename from products/udb/internal/mysql/backup.go rename to products/mysql/internal/mysql/backup.go diff --git a/products/udb/internal/mysql/backup_create.go b/products/mysql/internal/mysql/backup_create.go similarity index 100% rename from products/udb/internal/mysql/backup_create.go rename to products/mysql/internal/mysql/backup_create.go diff --git a/products/udb/internal/mysql/backup_delete.go b/products/mysql/internal/mysql/backup_delete.go similarity index 100% rename from products/udb/internal/mysql/backup_delete.go rename to products/mysql/internal/mysql/backup_delete.go diff --git a/products/udb/internal/mysql/backup_download.go b/products/mysql/internal/mysql/backup_download.go similarity index 100% rename from products/udb/internal/mysql/backup_download.go rename to products/mysql/internal/mysql/backup_download.go diff --git a/products/udb/internal/mysql/backup_list.go b/products/mysql/internal/mysql/backup_list.go similarity index 100% rename from products/udb/internal/mysql/backup_list.go rename to products/mysql/internal/mysql/backup_list.go diff --git a/products/udb/internal/mysql/cmd.go b/products/mysql/internal/mysql/cmd.go similarity index 100% rename from products/udb/internal/mysql/cmd.go rename to products/mysql/internal/mysql/cmd.go diff --git a/products/udb/internal/mysql/completion.go b/products/mysql/internal/mysql/completion.go similarity index 100% rename from products/udb/internal/mysql/completion.go rename to products/mysql/internal/mysql/completion.go diff --git a/products/udb/internal/mysql/conf.go b/products/mysql/internal/mysql/conf.go similarity index 100% rename from products/udb/internal/mysql/conf.go rename to products/mysql/internal/mysql/conf.go diff --git a/products/udb/internal/mysql/conf_apply.go b/products/mysql/internal/mysql/conf_apply.go similarity index 100% rename from products/udb/internal/mysql/conf_apply.go rename to products/mysql/internal/mysql/conf_apply.go diff --git a/products/udb/internal/mysql/conf_clone.go b/products/mysql/internal/mysql/conf_clone.go similarity index 100% rename from products/udb/internal/mysql/conf_clone.go rename to products/mysql/internal/mysql/conf_clone.go diff --git a/products/udb/internal/mysql/conf_delete.go b/products/mysql/internal/mysql/conf_delete.go similarity index 100% rename from products/udb/internal/mysql/conf_delete.go rename to products/mysql/internal/mysql/conf_delete.go diff --git a/products/udb/internal/mysql/conf_describe.go b/products/mysql/internal/mysql/conf_describe.go similarity index 100% rename from products/udb/internal/mysql/conf_describe.go rename to products/mysql/internal/mysql/conf_describe.go diff --git a/products/udb/internal/mysql/conf_download.go b/products/mysql/internal/mysql/conf_download.go similarity index 100% rename from products/udb/internal/mysql/conf_download.go rename to products/mysql/internal/mysql/conf_download.go diff --git a/products/udb/internal/mysql/conf_list.go b/products/mysql/internal/mysql/conf_list.go similarity index 100% rename from products/udb/internal/mysql/conf_list.go rename to products/mysql/internal/mysql/conf_list.go diff --git a/products/udb/internal/mysql/conf_update.go b/products/mysql/internal/mysql/conf_update.go similarity index 100% rename from products/udb/internal/mysql/conf_update.go rename to products/mysql/internal/mysql/conf_update.go diff --git a/products/udb/internal/mysql/conf_upload.go b/products/mysql/internal/mysql/conf_upload.go similarity index 100% rename from products/udb/internal/mysql/conf_upload.go rename to products/mysql/internal/mysql/conf_upload.go diff --git a/products/udb/internal/mysql/create.go b/products/mysql/internal/mysql/create.go similarity index 100% rename from products/udb/internal/mysql/create.go rename to products/mysql/internal/mysql/create.go diff --git a/products/udb/internal/mysql/create_slave.go b/products/mysql/internal/mysql/create_slave.go similarity index 100% rename from products/udb/internal/mysql/create_slave.go rename to products/mysql/internal/mysql/create_slave.go diff --git a/products/udb/internal/mysql/db.go b/products/mysql/internal/mysql/db.go similarity index 100% rename from products/udb/internal/mysql/db.go rename to products/mysql/internal/mysql/db.go diff --git a/products/udb/internal/mysql/delete.go b/products/mysql/internal/mysql/delete.go similarity index 100% rename from products/udb/internal/mysql/delete.go rename to products/mysql/internal/mysql/delete.go diff --git a/products/udb/internal/mysql/list.go b/products/mysql/internal/mysql/list.go similarity index 100% rename from products/udb/internal/mysql/list.go rename to products/mysql/internal/mysql/list.go diff --git a/products/udb/internal/mysql/list_machine_type.go b/products/mysql/internal/mysql/list_machine_type.go similarity index 100% rename from products/udb/internal/mysql/list_machine_type.go rename to products/mysql/internal/mysql/list_machine_type.go diff --git a/products/udb/internal/mysql/logs.go b/products/mysql/internal/mysql/logs.go similarity index 100% rename from products/udb/internal/mysql/logs.go rename to products/mysql/internal/mysql/logs.go diff --git a/products/udb/internal/mysql/logs_archive.go b/products/mysql/internal/mysql/logs_archive.go similarity index 100% rename from products/udb/internal/mysql/logs_archive.go rename to products/mysql/internal/mysql/logs_archive.go diff --git a/products/udb/internal/mysql/logs_delete.go b/products/mysql/internal/mysql/logs_delete.go similarity index 100% rename from products/udb/internal/mysql/logs_delete.go rename to products/mysql/internal/mysql/logs_delete.go diff --git a/products/udb/internal/mysql/logs_download.go b/products/mysql/internal/mysql/logs_download.go similarity index 100% rename from products/udb/internal/mysql/logs_download.go rename to products/mysql/internal/mysql/logs_download.go diff --git a/products/udb/internal/mysql/logs_list.go b/products/mysql/internal/mysql/logs_list.go similarity index 100% rename from products/udb/internal/mysql/logs_list.go rename to products/mysql/internal/mysql/logs_list.go diff --git a/products/udb/internal/mysql/poll.go b/products/mysql/internal/mysql/poll.go similarity index 100% rename from products/udb/internal/mysql/poll.go rename to products/mysql/internal/mysql/poll.go diff --git a/products/udb/internal/mysql/promote_slave.go b/products/mysql/internal/mysql/promote_slave.go similarity index 100% rename from products/udb/internal/mysql/promote_slave.go rename to products/mysql/internal/mysql/promote_slave.go diff --git a/products/udb/internal/mysql/promote_to_ha.go b/products/mysql/internal/mysql/promote_to_ha.go similarity index 100% rename from products/udb/internal/mysql/promote_to_ha.go rename to products/mysql/internal/mysql/promote_to_ha.go diff --git a/products/udb/internal/mysql/reset_password.go b/products/mysql/internal/mysql/reset_password.go similarity index 100% rename from products/udb/internal/mysql/reset_password.go rename to products/mysql/internal/mysql/reset_password.go diff --git a/products/udb/internal/mysql/resize.go b/products/mysql/internal/mysql/resize.go similarity index 100% rename from products/udb/internal/mysql/resize.go rename to products/mysql/internal/mysql/resize.go diff --git a/products/udb/internal/mysql/restart.go b/products/mysql/internal/mysql/restart.go similarity index 100% rename from products/udb/internal/mysql/restart.go rename to products/mysql/internal/mysql/restart.go diff --git a/products/udb/internal/mysql/restore.go b/products/mysql/internal/mysql/restore.go similarity index 100% rename from products/udb/internal/mysql/restore.go rename to products/mysql/internal/mysql/restore.go diff --git a/products/udb/internal/mysql/rows.go b/products/mysql/internal/mysql/rows.go similarity index 100% rename from products/udb/internal/mysql/rows.go rename to products/mysql/internal/mysql/rows.go diff --git a/products/udb/internal/mysql/start.go b/products/mysql/internal/mysql/start.go similarity index 100% rename from products/udb/internal/mysql/start.go rename to products/mysql/internal/mysql/start.go diff --git a/products/udb/internal/mysql/status.go b/products/mysql/internal/mysql/status.go similarity index 100% rename from products/udb/internal/mysql/status.go rename to products/mysql/internal/mysql/status.go diff --git a/products/udb/internal/mysql/stop.go b/products/mysql/internal/mysql/stop.go similarity index 100% rename from products/udb/internal/mysql/stop.go rename to products/mysql/internal/mysql/stop.go diff --git a/products/mysql/product.go b/products/mysql/product.go new file mode 100644 index 0000000000..037ca2264a --- /dev/null +++ b/products/mysql/product.go @@ -0,0 +1,20 @@ +package mysql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalmysql "github.com/ucloud/ucloud-cli/products/mysql/internal/mysql" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "mysql", Commands: []string{"mysql"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalmysql.NewCommand(ctx)} +} diff --git a/products/mysql/product.yaml b/products/mysql/product.yaml new file mode 100644 index 0000000000..bb30480aee --- /dev/null +++ b/products/mysql/product.yaml @@ -0,0 +1,6 @@ +name: mysql +owners: + - Episkey-G +commands: + - mysql +enabled: true diff --git a/products/udb/testdata/cmdtree.golden b/products/mysql/testdata/cmdtree.golden similarity index 100% rename from products/udb/testdata/cmdtree.golden rename to products/mysql/testdata/cmdtree.golden diff --git a/products/udb/testdata/completion.golden b/products/mysql/testdata/completion.golden similarity index 100% rename from products/udb/testdata/completion.golden rename to products/mysql/testdata/completion.golden diff --git a/products/pathx/internal/pathx/area.go b/products/pathx/internal/pathx/area.go new file mode 100644 index 0000000000..5d2f5626b3 --- /dev/null +++ b/products/pathx/internal/pathx/area.go @@ -0,0 +1,18 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newArea builds `ucloud pathx area`. +func newArea(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "area", + Short: "List origin area or acceleration area information", + Long: "List origin area or acceleration area information", + } + cmd.AddCommand(newAreaList(ctx)) + return cmd +} diff --git a/products/pathx/internal/pathx/area_list.go b/products/pathx/internal/pathx/area_list.go new file mode 100644 index 0000000000..7826b995a9 --- /dev/null +++ b/products/pathx/internal/pathx/area_list.go @@ -0,0 +1,134 @@ +package pathx + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newAreaList ucloud pathx area list +func newAreaList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + areaGetReq := client.NewDescribeUGA3AreaRequest() + optimizationReq := client.NewDescribeUGA3OptimizationRequest() + var timeRange, accelerationArea, originDomain, originIp string + var noAccel bool + cmd := &cobra.Command{ + Use: "list", + Short: "List origin area or acceleration area information", + Long: "Provide optional flags to get the optional list of global access source stations", + Example: "ucloud pathx area list --origin-ip 0.0.0.0 --origin-domain test.com", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + if len(originDomain) == 0 && len(originIp) == 0 { + response, err := client.DescribeUGA3Area(areaGetReq) + if err != nil { + ctx.HandleError(err) + return + } + forwardAreas := response.AreaSet + if len(forwardAreas) == 0 { + ctx.HandleError(fmt.Errorf("Not found the origin area list")) + return + } + areasGroup := make(map[string][]PathxOptionalAreaRow) + for _, item := range forwardAreas { + areasGroup[item.ContinentCode] = append(areasGroup[item.ContinentCode], PathxOptionalAreaRow{ + AreaCode: item.AreaCode, + Area: item.Area, + CountryCode: item.CountryCode, + FlagUnicode: item.FlagUnicode, + FlagEmoji: item.FlagEmoji, + }) + } + fmt.Fprintln(w, "Origin areas :") + for area, rows := range areasGroup { + fmt.Fprintf(w, "ContinentCode: %s\n", area) + ctx.PrintList(rows) + fmt.Fprintln(w) + } + return + } + areaGetReq.Domain = &originDomain + areaGetReq.IPList = &originIp + response, err := client.DescribeUGA3Area(areaGetReq) + if err != nil { + ctx.HandleError(err) + return + } + forwardAreas := response.AreaSet + if len(forwardAreas) == 0 { + ctx.HandleError(fmt.Errorf("Not found the origin area list")) + return + } + forwardArea := forwardAreas[0] + fmt.Fprintf(w, "Recommend origin area:(%s)\n", forwardArea.ContinentCode) + ctx.PrintList([]PathxOptionalAreaRow{{ + AreaCode: forwardArea.AreaCode, + Area: forwardArea.Area, + CountryCode: forwardArea.CountryCode, + FlagUnicode: forwardArea.FlagUnicode, + FlagEmoji: forwardArea.FlagEmoji, + }}) + fmt.Fprintln(w) + if !noAccel { + areaCode := forwardAreas[0].AreaCode + optimizationReq.AreaCode = &areaCode + optimizationReq.AccelerationArea = &accelerationArea + optimizationReq.TimeRange = &timeRange + optimizationReq.SetProjectIdRef(areaGetReq.GetProjectIdRef()) + optimizationReq.SetRegionRef(areaGetReq.GetRegionRef()) + optimizationReq.SetZoneRef(areaGetReq.GetZoneRef()) + optimizationResponse, err := client.DescribeUGA3Optimization(optimizationReq) + if err != nil { + ctx.HandleError(err) + return + } + accelerationInfos := optimizationResponse.AccelerationInfos + if len(accelerationInfos) == 0 { + ctx.HandleError(fmt.Errorf("Not found the acceleration area information.")) + return + } + fmt.Fprintln(w, "Acceleration areas :") + for _, item := range accelerationInfos { + if len(accelerationArea) == 0 { + fmt.Fprintf(w, "%s(%s):\n", item.AccelerationName, item.AccelerationArea) + } + list := make([]PathxOptimizationRow, 0) + for _, node := range item.NodeInfo { + list = append(list, PathxOptimizationRow{ + Area: node.Area, + AreaCode: node.AreaCode, + CountryCode: node.CountryCode, + FlagUnicode: node.FlagUnicode, + FlagEmoji: node.FlagEmoji, + Latency: fmt.Sprintf("%s%s", strconv.FormatFloat(node.Latency, 'g', 12, 64), "ms"), + LatencyWAN: fmt.Sprintf("%s%s", strconv.FormatFloat(node.LatencyInternet, 'g', 12, 64), "ms"), + LatencyPathX: fmt.Sprintf("%s%s", strconv.FormatFloat(node.LatencyOptimization, 'g', 12, 64), "%"), + Loss: fmt.Sprintf("%s%s", strconv.FormatFloat(node.Loss, 'g', 12, 64), "%"), + LossWAN: fmt.Sprintf("%s%s", strconv.FormatFloat(node.LossInternet, 'g', 12, 64), "%"), + LossPathx: fmt.Sprintf("%s%s", strconv.FormatFloat(node.LossOptimization, 'g', 12, 64), "%"), + }) + } + ctx.PrintList(list) + } + } + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, areaGetReq) + ctx.BindRegion(cmd, areaGetReq) + ctx.BindZone(cmd, areaGetReq) + flags.StringVar(&timeRange, "time-range", "", "Optional. The default value is 1 day. Acceptable values:'Hour','Day','Week',and its value is not case sensitive") + flags.StringVar(&accelerationArea, "accel", "", "Optional. The acceleration area,acceptable values:'Global','AP','EU','ME','OA','AF','NA','SA'") + flags.StringVar(&originDomain, "origin-domain", "", "Optional. If you fill in the IP or domain name, a region will be recommended as the first in the return list") + flags.StringVar(&originIp, "origin-ip", "", "Optional. If you fill in the IP or domain name, a region will be recommended as the first IP collection of the source station in the return list, split by ',' example:110.10.10.1,111.100.0.10 ") + flags.BoolVar(&noAccel, "no-accel", false, "Optional. If it is specified,the print result will not be displayed acceleration areas") + return cmd +} diff --git a/products/pathx/internal/pathx/cmd.go b/products/pathx/internal/pathx/cmd.go new file mode 100644 index 0000000000..ed1f4b4802 --- /dev/null +++ b/products/pathx/internal/pathx/cmd.go @@ -0,0 +1,25 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `pathx` root command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "pathx", + Short: "Manipulate uga and upath instances", + Long: "Manipulate uga and upath instances", + } + cmd.AddCommand(newUGA(ctx)) + cmd.AddCommand(newUpath(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newModify(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newPrice(ctx)) + cmd.AddCommand(newArea(ctx)) + return cmd +} diff --git a/products/pathx/internal/pathx/completion.go b/products/pathx/internal/pathx/completion.go new file mode 100644 index 0000000000..978231fff1 --- /dev/null +++ b/products/pathx/internal/pathx/completion.go @@ -0,0 +1,78 @@ +package pathx + +import ( + "fmt" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getPathxList(ctx *cli.Context, project, region, zone string) []string { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + req := client.NewDescribeUGA3InstanceRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + req.Limit = sdk.Int(50) + resp, err := client.DescribeUGA3Instance(req) + if err != nil { + ctx.HandleError(err) + return nil + } + list := make([]string, 0) + for _, item := range resp.ForwardInstanceInfos { + list = append(list, item.InstanceId) + } + return list +} + +func getUGAList(ctx *cli.Context, project string) ([]ppathx.UGAAInfo, error) { + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDescribeUGAInstanceRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + resp, err := client.DescribeUGAInstance(req) + if err != nil { + return nil, err + } + return resp.UGAList, nil +} + +func getUGAIDList(ctx *cli.Context, project string) []string { + list, err := getUGAList(ctx, project) + if err != nil { + ctx.LogError(fmt.Sprintf("getUGAIDList failed:%v", err)) + return nil + } + strs := make([]string, 0) + for _, ins := range list { + strs = append(strs, fmt.Sprintf("%s/%s", ins.UGAId, ins.UGAName)) + } + return strs +} + +func getUpathList(ctx *cli.Context, project string) ([]ppathx.UPathInfo, error) { + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDescribeUPathRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + resp, err := client.DescribeUPath(req) + if err != nil { + return nil, err + } + return resp.UPathSet, nil +} + +func getUpathIDList(ctx *cli.Context, project string) []string { + list, err := getUpathList(ctx, project) + if err != nil { + ctx.LogError(fmt.Sprintf("getUpathIDList failed:%v", err)) + return nil + } + strs := make([]string, 0) + for _, ins := range list { + strs = append(strs, fmt.Sprintf("%s/%s", ins.UPathId, ins.Name)) + } + return strs +} diff --git a/products/pathx/internal/pathx/create.go b/products/pathx/internal/pathx/create.go new file mode 100644 index 0000000000..29f7dd93f1 --- /dev/null +++ b/products/pathx/internal/pathx/create.go @@ -0,0 +1,145 @@ +package pathx + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud pathx create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + createPathxReq := client.NewCreateUGA3InstanceRequest() + createPathxPortReq := client.NewCreateUGA3PortRequest() + var ports, originPorts []string + protocol := "tcp" + createCmd := &cobra.Command{ + Use: "create", + Short: "Create the pathx resource and port", + Long: "Create global unified access acceleration configuration item", + Example: "ucloud pathx create --bandwidth 10 --area-code DXB" + + "--charge-type Month --quantity 4 --accel Global --origin-ip 110.111.111.111" + + "--protocol TCP --port 30654 --origin-port 30564", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + fmt.Fprintln(w, "The pathx resource creating") + if *createPathxReq.OriginIPList == "" && *createPathxReq.OriginDomain == "" { + ctx.HandleError(fmt.Errorf("The origin-ip and origin-domain cannot be empty at the same time")) + return + } + portIntList := make([]int, 0) + originPortIntList := make([]int, 0) + if len(ports) > 0 || len(originPorts) > 0 { + if len(ports) == 0 { + ctx.HandleError(fmt.Errorf("The port cannot be empty.")) + return + } else if len(originPorts) == 0 { + ctx.HandleError(fmt.Errorf("The origin-port cannot be empty.")) + return + } + if strings.EqualFold(protocol, "UDP") { + ctx.HandleError(fmt.Errorf("The udp protocol is temporarily not supported for create")) + return + } else if !strings.EqualFold(protocol, "TCP") && !strings.EqualFold(protocol, "UDP") { + ctx.HandleError(fmt.Errorf("The value of protocol input error,please input 'TCP' or 'UDP',and the value entered is not case sensitive")) + return + } + tcpPortList, err := formatPortList(ports) + if err != nil { + ctx.HandleError(err) + return + } + for _, tcpPort := range tcpPortList { + port, _ := strconv.Atoi(tcpPort) + portIntList = append(portIntList, port) + } + rsTcpPortList, err := formatPortList(originPorts) + if err != nil { + ctx.HandleError(err) + return + } + for _, rsTcpPort := range rsTcpPortList { + rsPort, _ := strconv.Atoi(rsTcpPort) + originPortIntList = append(originPortIntList, rsPort) + } + if len(portIntList) != len(originPortIntList) { + ctx.HandleError(fmt.Errorf("The number of port must be consistent with the number of origin-port.")) + return + } else if len(portIntList) >= 10 { + ctx.HandleError(fmt.Errorf("The number of port cannot greater than or equals to 10")) + return + } + } + if strings.EqualFold(*createPathxReq.ChargeType, "Month") { + *createPathxReq.Quantity = 0 + } else if *createPathxReq.Quantity <= 0 { + ctx.HandleError(fmt.Errorf("If the value of charge-type is 'Year' or 'Hour',the value of quantity must be greater than 0")) + return + } + switch strings.ToLower(*createPathxReq.ChargeType) { + case "hour": + *createPathxReq.ChargeType = "Dynamic" + case "month": + *createPathxReq.ChargeType = "Month" + case "year": + *createPathxReq.ChargeType = "Year" + } + createUGA3InstanceResp, err := client.CreateUGA3Instance(createPathxReq) + if err != nil { + ctx.HandleError(err) + return + } + if createUGA3InstanceResp == nil || createUGA3InstanceResp.InstanceId == "" { + ctx.HandleError(fmt.Errorf("An unknown error occurred and could not be created successfully.")) + return + } + if len(portIntList) > 0 && len(originPortIntList) > 0 { + createPathxPortReq.InstanceId = &createUGA3InstanceResp.InstanceId + createPathxPortReq.SetRegionRef(createPathxReq.GetRegionRef()) + createPathxPortReq.SetProjectIdRef(createPathxReq.GetProjectIdRef()) + createPathxPortReq.SetZoneRef(createPathxReq.GetZoneRef()) + fmt.Fprintln(w, "The pathx port creating") + if strings.EqualFold(protocol, "TCP") { + createPathxPortReq.TCP = portIntList + createPathxPortReq.TCPRS = originPortIntList + } + _, err := client.CreateUGA3Port(createPathxPortReq) + if err != nil { + ctx.HandleError(err) + return + } + } + fmt.Fprintf(w, "The resource is created, and the resource ID is: %s\n", createUGA3InstanceResp.InstanceId) + ctx.EmitResult(cli.OpResultRow{ResourceID: createUGA3InstanceResp.InstanceId, Action: "create", Status: "Created"}) + }, + } + flags := createCmd.Flags() + flags.SortFlags = false + + ctx.BindProjectID(createCmd, createPathxReq) + ctx.BindRegion(createCmd, createPathxReq) + ctx.BindZone(createCmd, createPathxReq) + createPathxReq.Bandwidth = flags.String("bandwidth", "0", "Required. Shared bandwidth of the resource") + createPathxReq.AreaCode = flags.String("area-code", "", "Optional. When it is empty,the nearest zone will be selected based on the origin-domain and origin-ip. Acceptable values:'BKK'(曼谷),'DXB'(迪拜),'FRA'(法兰克福),'SGN'(胡志明市),'HKG'(香港),'CGK'(雅加达),'LOS'(拉各斯),'LHR'(伦敦),'LAX'(洛杉矶),'MNL'(马尼拉),'DME'(莫斯科),'BOM'(孟买),'MSP'(圣保罗),'ICN'(首尔),'PVG'(上海),'SIN'(新加坡),'NRT'(东京),'IAD'(华盛顿),'TPE'(台北)") + createPathxReq.ChargeType = flags.String("charge-type", "", "Optional. Payment method,its value is not case sensitive,acceptable values:'Year',pay yearly;'Month',pay monthly;'Hour', pay hourly") + createPathxReq.Quantity = flags.Int("quantity", 1, "Optional. The duration of the pathx resource, the value cannot be less than or equal to 0. N years/months") + createPathxReq.AccelerationArea = flags.String("accel", "", "Optional. The default value is 'Global'(全球). Other acceptable values:'AP'(亚太);'EU'(欧洲);'ME'(中东);'OA'(大洋洲);'AF'(非洲);'NA'(北美洲);'SA'(南美洲)") + createPathxReq.OriginIPList = flags.String("origin-ip", "", "Optional. But when the origin-domain is empty,it cannot be empty. If multiple values exist,please split by ','. For example '0.0.0.0,110.110.100.100'") + createPathxReq.OriginDomain = flags.String("origin-domain", "", "Optional. But when the origin-ip is empty,it cannot be empty") + flags.StringSliceVar(&ports, "port", nil, "Optional. Disable 65123 port,the port can be multiple,please split by ',' for example 80,3000-3010. The number of port must be consistent with the number of origin-port,and the number cannot greater than or equals to 10") + flags.StringSliceVar(&originPorts, "origin-port", nil, "Optional. The origin-port can be multiple,please split by ',' for example 80,3000-3010.The number of origin-port must be consistent with the number of port") + flags.StringVar(&protocol, "protocol", "TCP", "Its values can be TCP and UDP, but currently only supports TCP") + createCmd.MarkFlagRequired("bandwidth") + command.SetFlagValues(createCmd, "area-code", "BKK", "DXB", "FRA", "SGN", "HKG", "CGK", "LOS", "LHR", "LAX", "MNL", "DME", "BOM", "MSP", "ICN", "PVG", "SIN", "NRT", "IAD", "TPE") + command.SetFlagValues(createCmd, "charge-type", "Month", "Year", "Hour") + command.SetFlagValues(createCmd, "accel", "Global", "AP", "EU", "ME", "OA", "AF", "NA", "SA") + command.SetFlagValues(createCmd, "protocol", "TCP", "UDP") + return createCmd +} diff --git a/products/pathx/internal/pathx/delete.go b/products/pathx/internal/pathx/delete.go new file mode 100644 index 0000000000..97a6efec78 --- /dev/null +++ b/products/pathx/internal/pathx/delete.go @@ -0,0 +1,69 @@ +package pathx + +import ( + "fmt" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud pathx delete +func newDelete(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + deleteUga3Req := client.NewDeleteUGA3InstanceRequest() + deleteUga3PortReq := client.NewDeleteUGA3PortRequest() + yes := false + var instanceId string + removeCmd := &cobra.Command{ + Use: "delete", + Short: "Delete the pathx resource and port", + Long: "Delete the pathx resource and port", + Example: "ucloud pathx delete --id uga3-xxx", + Run: func(cmd *cobra.Command, args []string) { + if !yes { + ok, err := ctx.Confirm(false, "Are you sure you want to delete this resource ?") + if err != nil { + fmt.Fprintln(ctx.ProgressWriter(), err) + return + } + if !ok { + return + } + } + w := ctx.ProgressWriter() + fmt.Fprintf(w, "Starting delete the pathx[%s] resource port\n", instanceId) + deleteUga3PortReq.InstanceId = &instanceId + _, deletePortErr := client.DeleteUGA3Port(deleteUga3PortReq) + if deletePortErr != nil { + ctx.HandleError(deletePortErr) + return + } + fmt.Fprintf(w, "Starting delete the pathx[%s] resource\n", instanceId) + deleteUga3Req.InstanceId = &instanceId + deleteUga3Req.SetProjectIdRef(deleteUga3PortReq.GetProjectIdRef()) + deleteUga3Req.SetRegionRef(deleteUga3PortReq.GetRegionRef()) + deleteUga3Req.SetZoneRef(deleteUga3PortReq.GetZoneRef()) + _, err := client.DeleteUGA3Instance(deleteUga3Req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceId, Action: "delete", Status: "Deleted"}) + }, + } + flags := removeCmd.Flags() + flags.SortFlags = false + flags.StringVar(&instanceId, "id", "", "Required. It is the resource ID of pathx, and the deletion will be performed according to this") + ctx.BindProjectID(removeCmd, deleteUga3PortReq) + ctx.BindRegion(removeCmd, deleteUga3PortReq) + ctx.BindZone(removeCmd, deleteUga3PortReq) + removeCmd.MarkFlagRequired("id") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") + ctx.SetCompletion(removeCmd, "id", func() []string { + return getPathxList(ctx, *deleteUga3PortReq.ProjectId, *deleteUga3PortReq.Region, *deleteUga3PortReq.Zone) + }) + return removeCmd +} diff --git a/products/pathx/internal/pathx/format.go b/products/pathx/internal/pathx/format.go new file mode 100644 index 0000000000..e023d01f14 --- /dev/null +++ b/products/pathx/internal/pathx/format.go @@ -0,0 +1,152 @@ +package pathx + +import ( + "fmt" + "io" + "strconv" + "strings" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +var protocols = []string{"tcp", "udp"} + +var regionLabel = map[string]string{ + "cn-bj1": "Beijing1", + "cn-bj2": "Beijing2", + "cn-sh2": "Shanghai2", + "cn-gd": "Guangzhou", + "cn-qz": "Quanzhou", + "hk": "Hongkong", + "us-ca": "LosAngeles", + "us-ws": "Washington", + "ge-fra": "Frankfurt", + "th-bkk": "Bangkok", + "kr-seoul": "Seoul", + "sg": "Singapore", + "tw-kh": "Kaohsiung", + "rus-mosc": "Moscow", + "jpn-tky": "Tokyo", + "tw-tp": "TaiPei", + "uae-dubai": "Dubai", + "idn-jakarta": "Jakarta", + "ind-mumbai": "Bombay", + "bra-saopaulo": "SaoPaulo", + "uk-london": "London", + "afr-nigeria": "Lagos", +} + +func formatPortList(userPorts []string) ([]string, error) { + portList := make([]string, 0) + for _, port := range userPorts { + if strings.Contains(port, "-") { + portRange := strings.Split(port, "-") + if len(portRange) != 2 { + return nil, fmt.Errorf("port %s is invalid, it's pattern should be like 3000-3100", port) + } + min, err := strconv.Atoi(portRange[0]) + if err != nil { + return nil, fmt.Errorf("parse port failed: %v", err) + } + max, err := strconv.Atoi(portRange[1]) + if err != nil { + return nil, fmt.Errorf("parse port failed: %v", err) + } + for i := min; i <= max; i++ { + portList = append(portList, strconv.Itoa(i)) + } + } else { + portList = append(portList, port) + } + } + return portList, nil +} + +func getUpathStr(list []ppathx.UPathSet) string { + paths := make([]string, 0) + for _, p := range list { + paths = append(paths, fmt.Sprintf("%s->%s %dM", p.LineFromName, p.LineToName, p.Bandwidth)) + } + return strings.Join(paths, "\n") +} + +func getOutIPStr(list []ppathx.OutPublicIpInfo) string { + strs := make([]string, 0) + for _, p := range list { + strs = append(strs, fmt.Sprintf("%s %s", p.IP, regionLabel[p.Area])) + } + return strings.Join(strs, "\n") +} + +func getPortStr(list []ppathx.UGAATask) string { + strs := make([]string, 0) + for _, t := range list { + strs = append(strs, fmt.Sprintf("%s %d", t.Protocol, t.Port)) + } + return strings.Join(strs, "\n") +} + +func printPathxDetail(ctx *cli.Context, instanceInfo pathxsdk.ForwardInfo, out io.Writer) { + attrs := []describeRow{ + {Attribute: "ResourceID", Content: instanceInfo.InstanceId}, + {Attribute: "CName", Content: instanceInfo.CName}, + {Attribute: "Name", Content: instanceInfo.Name}, + {Attribute: "AccelerationArea", Content: instanceInfo.AccelerationArea}, + {Attribute: "AccelerationAreaName", Content: instanceInfo.AccelerationAreaName}, + {Attribute: "OriginAreaCode", Content: instanceInfo.OriginAreaCode}, + {Attribute: "OriginArea", Content: instanceInfo.OriginArea}, + {Attribute: "Bandwidth", Content: strconv.Itoa(instanceInfo.Bandwidth)}, + {Attribute: "ChargeType", Content: instanceInfo.ChargeType}, + {Attribute: "IPList", Content: strings.Join(instanceInfo.IPList, ",")}, + {Attribute: "Domain", Content: instanceInfo.Domain}, + {Attribute: "Remark", Content: instanceInfo.Remark}, + {Attribute: "CreateTime", Content: common.FormatDateTime(instanceInfo.CreateTime)}, + {Attribute: "ExpireTime", Content: common.FormatDateTime(instanceInfo.ExpireTime)}, + } + for _, attr := range attrs { + fmt.Fprintf(out, "%-22s: %s\n", attr.Attribute, attr.Content) + } + if len(instanceInfo.AccelerationAreaInfos) > 0 { + fmt.Fprintln(out) + fmt.Fprintln(out, "Acceleration area list:") + for _, area := range instanceInfo.AccelerationAreaInfos { + fmt.Fprintf(out, "%s:%5s\n", "Area", area.AccelerationArea) + areaList := make([]PathxOptionalAreaRow, 0) + for _, node := range area.AccelerationNodes { + areaList = append(areaList, PathxOptionalAreaRow{ + AreaCode: node.AreaCode, + Area: node.Area, + FlagUnicode: node.FlagUnicode, + FlagEmoji: node.FlagEmoji, + }) + } + ctx.PrintList(areaList) + } + } + if len(instanceInfo.EgressIpList) > 0 { + fmt.Fprintln(out) + fmt.Fprintln(out, "Egress ip list:") + egressIpList := make([]EgressIpInfoRow, 0) + for _, egressIp := range instanceInfo.EgressIpList { + egressIpList = append(egressIpList, EgressIpInfoRow{IP: egressIp.IP, Area: egressIp.Area}) + } + ctx.PrintList(egressIpList) + } + if len(instanceInfo.PortSets) > 0 { + fmt.Fprintln(out) + fmt.Fprintln(out, "Port list:") + portList := make([]Uga3PortRow, 0) + for _, portItem := range instanceInfo.PortSets { + portList = append(portList, Uga3PortRow{ + Protocol: portItem.Protocol, + Port: portItem.Port, + RSPort: portItem.RSPort, + }) + } + ctx.PrintList(portList) + } +} diff --git a/products/pathx/internal/pathx/list.go b/products/pathx/internal/pathx/list.go new file mode 100644 index 0000000000..19aab698dd --- /dev/null +++ b/products/pathx/internal/pathx/list.go @@ -0,0 +1,77 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList ucloud pathx list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + getPathxListReq := client.NewDescribeUGA3InstanceRequest() + var instanceId string + var detail bool + listCmd := &cobra.Command{ + Use: "list", + Short: "List all the pathx resource of project", + Long: "List all the pathx resource of project", + Example: "'ucloud pathx list or ucloud pathx list --id uga-xxx or ucloud pathx list --id uga-xxx --detail", + Run: func(cmd *cobra.Command, args []string) { + if len(instanceId) > 0 { + getPathxListReq.InstanceId = &instanceId + } + resp, err := client.DescribeUGA3Instance(getPathxListReq) + if err != nil { + ctx.HandleError(err) + return + } + forwardInfos := resp.ForwardInstanceInfos + if len(forwardInfos) == 0 { + ctx.HandleError(fmt.Errorf("No pathx resource found under the current project.")) + return + } + if detail && len(instanceId) > 0 { + printPathxDetail(ctx, forwardInfos[0], ctx.ProgressWriter()) + return + } + list := make([]Uga3DescribeRow, 0) + for _, item := range forwardInfos { + egressIps := []string{} + for _, egressIp := range item.EgressIpList { + egressIps = append(egressIps, fmt.Sprintf("%s:%s", egressIp.Area, egressIp.IP)) + } + list = append(list, Uga3DescribeRow{ + ResourceID: item.InstanceId, + CName: item.CName, + Name: item.Name, + AccelerationArea: item.AccelerationArea, + Bandwidth: item.Bandwidth, + OriginAreaCode: item.OriginAreaCode, + IPList: strings.Join(item.IPList, ","), + Domain: item.Domain, + CreateTime: common.FormatDate(item.CreateTime), + EgressIpList: strings.Join(egressIps, "|"), + }) + } + ctx.PrintList(list) + }, + } + flags := listCmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(listCmd, getPathxListReq) + ctx.BindRegion(listCmd, getPathxListReq) + ctx.BindZone(listCmd, getPathxListReq) + flags.StringVar(&instanceId, "id", "", "Required. It is the resource ID of pathx resource") + flags.BoolVar(&detail, "detail", false, "Optional. If it is specified,the details will be printed") + ctx.SetCompletion(listCmd, "id", func() []string { + return getPathxList(ctx, *getPathxListReq.ProjectId, *getPathxListReq.Region, *getPathxListReq.Zone) + }) + return listCmd +} diff --git a/products/pathx/internal/pathx/modify.go b/products/pathx/internal/pathx/modify.go new file mode 100644 index 0000000000..3c91e1d7d9 --- /dev/null +++ b/products/pathx/internal/pathx/modify.go @@ -0,0 +1,155 @@ +package pathx + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newModify ucloud pathx modify +func newModify(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + modifyBandwidthReq := client.NewModifyUGA3BandwidthRequest() + modifyOriginInfoReq := client.NewModifyUGA3OriginInfoRequest() + modifyInstanceReq := client.NewModifyUGA3InstanceRequest() + modifyPortReq := client.NewModifyUGA3PortRequest() + var tcpPorts, rsTcpPorts []string + var instanceId string + protocol := "TCP" + modifyCmd := &cobra.Command{ + Use: "modify", + Short: "Modify the pathx associated information. Example bandwidth or origin information or resource information", + Long: "Support modify bandwidth,origin information,resource information,port", + Example: "ucloud pathx modify --id uga3-xxx --bandwidth 1 --origin-ip 127.0.0.1 --name Pathx测试 --remark 加速资源 --protocol TCP --port 30010 --origin-port 39999", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + modifyBandwidthReq.InstanceId = &instanceId + modifyInstanceReq.InstanceId = &instanceId + modifyOriginInfoReq.InstanceId = &instanceId + modifyPortReq.InstanceId = &instanceId + results := []cli.OpResultRow{} + if *modifyBandwidthReq.Bandwidth != 0 { + fmt.Fprintf(w, "Starting modify the pathx[%s] bandwidth\n", instanceId) + if *modifyBandwidthReq.Bandwidth < 1 || *modifyBandwidthReq.Bandwidth > 100 { + ctx.HandleError(fmt.Errorf("The value of bandwidth size cannot be less than 1 and cannot be greater than 100")) + return + } + modifyBandwidthReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) + modifyBandwidthReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) + modifyBandwidthReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) + _, err := client.ModifyUGA3Bandwidth(modifyBandwidthReq) + if err != nil { + ctx.HandleError(err) + return + } + results = append(results, cli.OpResultRow{ResourceID: instanceId, Action: "modify-bandwidth", Status: "Modified"}) + } + if *modifyOriginInfoReq.OriginIPList != "" || *modifyOriginInfoReq.OriginDomain != "" { + fmt.Fprintf(w, "Starting modify the pathx[%s] origin information\n", instanceId) + modifyOriginInfoReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) + modifyOriginInfoReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) + modifyOriginInfoReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) + _, err := client.ModifyUGA3OriginInfo(modifyOriginInfoReq) + if err != nil { + ctx.HandleError(err) + return + } + results = append(results, cli.OpResultRow{ResourceID: instanceId, Action: "modify-origin", Status: "Modified"}) + } + if *modifyInstanceReq.Name != "" || *modifyInstanceReq.Remark != "" { + fmt.Fprintf(w, "Starting modify the pathx[%s] resource information\n", instanceId) + _, err := client.ModifyUGA3Instance(modifyInstanceReq) + if err != nil { + ctx.HandleError(err) + return + } + results = append(results, cli.OpResultRow{ResourceID: instanceId, Action: "modify", Status: "Modified"}) + } + tcpPortIntList := make([]int, 0) + rsTcpPortIntList := make([]int, 0) + if len(tcpPorts) > 0 || len(rsTcpPorts) > 0 { + fmt.Fprintf(w, "Starting modify the pathx[%s] port\n", instanceId) + if len(tcpPorts) == 0 { + ctx.HandleError(fmt.Errorf("The port cannot be empty.")) + return + } else if len(rsTcpPorts) == 0 { + ctx.HandleError(fmt.Errorf("The origin-port cannot be empty.")) + return + } + if strings.EqualFold(protocol, "UDP") { + ctx.HandleError(fmt.Errorf("The udp protocol is temporarily not supported for create")) + return + } else if !strings.EqualFold(protocol, "TCP") && !strings.EqualFold(protocol, "UDP") { + ctx.HandleError(fmt.Errorf("The value of protocol input error,please input 'TCP' or 'UDP',and the value entered is not case sensitive")) + return + } + tcpPortList, err := formatPortList(tcpPorts) + if err != nil { + ctx.HandleError(err) + return + } + for _, tcpPort := range tcpPortList { + port, _ := strconv.Atoi(tcpPort) + tcpPortIntList = append(tcpPortIntList, port) + } + rsTcpPortList, err := formatPortList(rsTcpPorts) + if err != nil { + ctx.HandleError(err) + return + } + for _, rsTcpPort := range rsTcpPortList { + rsPort, _ := strconv.Atoi(rsTcpPort) + rsTcpPortIntList = append(rsTcpPortIntList, rsPort) + } + if len(tcpPortIntList) != len(rsTcpPortIntList) { + ctx.HandleError(fmt.Errorf("The number of port must be consistent with the number of origin-port.")) + return + } else if len(tcpPortIntList) >= 10 { + ctx.HandleError(fmt.Errorf("The number of port cannot greater than or equals to 10")) + return + } + } + if len(tcpPortIntList) > 0 && len(rsTcpPortIntList) > 0 { + if strings.EqualFold(protocol, "TCP") { + modifyPortReq.TCP = tcpPortIntList + modifyPortReq.TCPRS = rsTcpPortIntList + } + modifyPortReq.SetProjectIdRef(modifyInstanceReq.GetProjectIdRef()) + modifyPortReq.SetRegionRef(modifyInstanceReq.GetRegionRef()) + modifyPortReq.SetZoneRef(modifyInstanceReq.GetZoneRef()) + _, err := client.ModifyUGA3Port(modifyPortReq) + if err != nil { + ctx.HandleError(err) + return + } + results = append(results, cli.OpResultRow{ResourceID: instanceId, Action: "modify-port", Status: "Modified"}) + } + ctx.EmitResult(results...) + }, + } + flags := modifyCmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(modifyCmd, modifyInstanceReq) + ctx.BindRegion(modifyCmd, modifyInstanceReq) + ctx.BindZone(modifyCmd, modifyInstanceReq) + flags.StringVar(&instanceId, "id", "", "Required. It is the resource ID of the pathx") + modifyBandwidthReq.Bandwidth = flags.Int("bandwidth", 0, "Optional. The bandwidth size. Its value range [1-100],no update if no value is specified") + modifyOriginInfoReq.OriginIPList = flags.String("origin-ip", "", "Optional. Acceleration source IP. If multiple values exist,please split by ','") + modifyOriginInfoReq.OriginDomain = flags.String("origin-domain", "", "Optional. Acceleration source domain name. Only 1 domain is supported") + modifyInstanceReq.Name = flags.String("name", "", "Optional. Accelerate configuration resource name. If its value is not filled in or an empty string is not updated") + modifyInstanceReq.Remark = flags.String("remark", "", "Optional. It will be modified if its value is not empty") + flags.StringSliceVar(&tcpPorts, "port", nil, "Optional. Disable 65123 port,the port can be multiple,please split by ',' for example 80,3000-3010. The number of port must be consistent with the number of origin-port,and the number cannot greater than or equals to 10") + flags.StringSliceVar(&rsTcpPorts, "origin-port", nil, "Optional. The origin-port can be multiple,please split by ',' for example 80,3000-3010.The number of origin-port must be consistent with the number of port") + flags.StringVar(&protocol, "protocol", "TCP", "Its values can be TCP and UDP, but currently only supports TCP") + modifyCmd.MarkFlagRequired("id") + ctx.SetCompletion(modifyCmd, "id", func() []string { + return getPathxList(ctx, *modifyInstanceReq.ProjectId, *modifyInstanceReq.Region, *modifyInstanceReq.Zone) + }) + return modifyCmd +} diff --git a/products/pathx/internal/pathx/price.go b/products/pathx/internal/pathx/price.go new file mode 100644 index 0000000000..0246d2eb7b --- /dev/null +++ b/products/pathx/internal/pathx/price.go @@ -0,0 +1,18 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPrice builds `ucloud pathx price`. +func newPrice(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "price", + Short: "List all the acceleration area price", + Long: "List all the acceleration area price", + } + cmd.AddCommand(newPriceList(ctx)) + return cmd +} diff --git a/products/pathx/internal/pathx/price_list.go b/products/pathx/internal/pathx/price_list.go new file mode 100644 index 0000000000..c4918880c6 --- /dev/null +++ b/products/pathx/internal/pathx/price_list.go @@ -0,0 +1,77 @@ +package pathx + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + pathxsdk "github.com/ucloud/ucloud-sdk-go/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPriceList ucloud pathx price list +func newPriceList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, pathxsdk.NewClient) + priceReq := client.NewGetUGA3PriceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List all the pathx acceleration area price", + Long: "List all the pathx acceleration area price", + Example: "ucloud pathx price list --bandwidth 10 --area-code BKK --charge-type Month", + Run: func(cmd *cobra.Command, args []string) { + if strings.EqualFold(*priceReq.ChargeType, "Month") { + *priceReq.Quantity = 0 + } else if *priceReq.Quantity <= 0 { + ctx.HandleError(fmt.Errorf("If the value of charge-type is 'Year' or 'Hour',its value must be greater than 0")) + return + } + switch strings.ToLower(*priceReq.ChargeType) { + case "hour": + *priceReq.ChargeType = "Dynamic" + case "month": + *priceReq.ChargeType = "Month" + case "year": + *priceReq.ChargeType = "Year" + } + response, err := client.GetUGA3Price(priceReq) + if err != nil { + ctx.HandleError(err) + return + } + priceList := response.UGA3Price + if len(priceList) == 0 { + ctx.HandleError(fmt.Errorf("Not found acceleration area price information.")) + return + } + list := make([]UGA3PriceRow, 0) + for _, info := range priceList { + list = append(list, UGA3PriceRow{ + AccelerationBandwidthPrice: fmt.Sprintf("%s%s", "¥", strconv.FormatFloat(info.AccelerationBandwidthPrice, 'g', 12, 64)), + AccelerationForwarderPrice: fmt.Sprintf("%s%s", "¥", strconv.FormatFloat(info.AccelerationForwarderPrice, 'g', 12, 64)), + AccelerationArea: info.AccelerationArea, + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, priceReq) + ctx.BindRegion(cmd, priceReq) + ctx.BindZone(cmd, priceReq) + priceReq.Bandwidth = flags.Int("bandwidth", 1, "Required. The bandwidth of acceleration area to get price") + priceReq.AreaCode = flags.String("area-code", "", "Required. The area-code of acceleration area to get price") + priceReq.Quantity = flags.Int("quantity", 1, "Optional. When the value of the charge-type is 'Month',its default value is 0,if the value of charge-type is 'Year' or 'Hour',its value must be greater than 0") + priceReq.ChargeType = flags.String("charge-type", "", "Optional. Its value is not case sensitive,acceptable values:'Year',pay yearly;'Month',pay monthly;'Hour',pay hourly") + priceReq.AccelerationArea = flags.String("accel", "", "Optional. The acceleration-area to get price") + _ = cmd.MarkFlagRequired("bandwidth") + _ = cmd.MarkFlagRequired("area-code") + command.SetFlagValues(cmd, "area-code", "BKK", "DXB", "FRA", "SGN", "HKG", "CGK", "LOS", "LHR", "LAX", "MNL", "DME", "BOM", "MSP", "ICN", "PVG", "SIN", "NRT", "IAD", "TPE") + command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Hour") + command.SetFlagValues(cmd, "accel", "Global", "AP", "EU", "ME", "OA", "AF", "NA", "SA") + return cmd +} diff --git a/products/pathx/internal/pathx/rows.go b/products/pathx/internal/pathx/rows.go new file mode 100644 index 0000000000..fad2c1d7aa --- /dev/null +++ b/products/pathx/internal/pathx/rows.go @@ -0,0 +1,88 @@ +package pathx + +type UGA3PriceRow struct { + AccelerationArea string + AccelerationAreaName string + AccelerationForwarderPrice string + AccelerationBandwidthPrice string +} + +type Uga3DescribeRow struct { + ResourceID string + CName string + Name string + AccelerationArea string + AccelerationAreaName string + EgressIpList string + Bandwidth int + Remark string + OriginArea string + OriginAreaCode string + CreateTime string + ExpireTime string + ChargeType string + IPList string + Domain string +} + +type Uga3PortRow struct { + Protocol string + RSPort int + Port int +} + +type PathxUpdatePriceRow struct { + InstanceId string + Bandwidth int + UpdatePrice float64 +} + +type PathxOptimizationRow struct { + AccelerationName string + AccelerationArea string + Area string + AreaCode string + CountryCode string + FlagUnicode string + FlagEmoji string + Latency string + LatencyWAN string + LatencyPathX string + Loss string + LossWAN string + LossPathx string +} + +type PathxOptionalAreaRow struct { + AreaCode string + Area string + CountryCode string + FlagUnicode string + FlagEmoji string + ContinentCode string +} + +type EgressIpInfoRow struct { + IP string + Area string +} + +type upathRow struct { + ResourceID string + UPathName string + AcceleratedPath string + BoundUGA string +} + +type UGARow struct { + ResourceID string + UGAName string + CName string + Origin string + AcceleratedPath string +} + +type describeRow struct { + Attribute string + Content string +} diff --git a/products/pathx/internal/pathx/uga.go b/products/pathx/internal/pathx/uga.go new file mode 100644 index 0000000000..ceb37e19fe --- /dev/null +++ b/products/pathx/internal/pathx/uga.go @@ -0,0 +1,23 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUGA builds `ucloud pathx uga`. +func newUGA(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "uga", + Short: "Create,list,update and delete pathx uga instances", + Long: "Create,list,update and delete pathx uga instances", + } + cmd.AddCommand(newUGAList(ctx)) + cmd.AddCommand(newUGADescribe(ctx)) + cmd.AddCommand(newUGACreate(ctx)) + cmd.AddCommand(newUGADelete(ctx)) + cmd.AddCommand(newUGAAddPort(ctx)) + cmd.AddCommand(newUGARemovePort(ctx)) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_add_port.go b/products/pathx/internal/pathx/uga_add_port.go new file mode 100644 index 0000000000..70060b17bd --- /dev/null +++ b/products/pathx/internal/pathx/uga_add_port.go @@ -0,0 +1,67 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUGAAddPort ucloud pathx uga add-port +func newUGAAddPort(ctx *cli.Context) *cobra.Command { + var ports []string + var protocol string + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewAddUGATaskRequest() + cmd := &cobra.Command{ + Use: "add-port", + Short: "Add port for uga instance", + Long: "Add port for uga instance", + Run: func(c *cobra.Command, args []string) { + portList, err := formatPortList(ports) + if err != nil { + ctx.HandleError(err) + return + } + switch strings.ToLower(protocol) { + case "tcp": + req.TCP = portList + case "udp": + req.UDP = portList + case "http": + req.HTTP = portList + case "https": + req.HTTPS = portList + default: + fmt.Fprintf(ctx.ProgressWriter(), "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) + } + *req.UGAId = ctx.PickResourceID(*req.UGAId) + _, err = client.AddUGATask(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "port %v added\n", ports) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.UGAId, Action: "add-port", Status: "Added"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, req) + req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance to add port") + flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) + flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") + cmd.MarkFlagRequired("protocol") + cmd.MarkFlagRequired("uga-id") + cmd.MarkFlagRequired("port") + command.SetFlagValues(cmd, "protocol", protocols...) + ctx.SetCompletion(cmd, "uga-id", func() []string { + return getUGAIDList(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_create.go b/products/pathx/internal/pathx/uga_create.go new file mode 100644 index 0000000000..b79717d901 --- /dev/null +++ b/products/pathx/internal/pathx/uga_create.go @@ -0,0 +1,98 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUGACreate ucloud pathx uga create +func newUGACreate(ctx *cli.Context) *cobra.Command { + var protocol string + var ports, lines []string + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewCreateUGAInstanceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create uga instance", + Long: "Create uga instance", + Example: "ucloud pathx uga create --name testcli1 --protocol tcp --origin-location 中国 --origin-domain lixiaojun.xyz --upath-id upath-auvfexxx/test_0 --port 80-90,100,110-115", + Run: func(c *cobra.Command, args []string) { + w := ctx.ProgressWriter() + if *req.IPList == "" && *req.Domain == "" { + fmt.Fprintln(w, "origin-ip and origin-domain can not be both empty") + return + } + portList, err := formatPortList(ports) + if err != nil { + ctx.HandleError(err) + return + } + switch strings.ToLower(protocol) { + case "tcp": + req.TCP = portList + case "udp": + req.UDP = portList + case "http": + req.HTTP = portList + case "https": + req.HTTPS = portList + default: + fmt.Fprintf(w, "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) + } + resp, err := client.CreateUGAInstance(req) + if err != nil { + if uErr, ok := err.(uerr.Error); ok && uErr.Code() == 33756 { + fmt.Fprintf(w, "The number of ports added exceeds the limit(50). We recommend that you could reduce the number of ports, then create an uga instance, \nand then add the remaining ports by executing 'ucloud pathx uga add-port --protocol %s --uga-id --port '\n", protocol) + } + return + } + fmt.Fprintf(w, "uga[%s] created\n", resp.UGAId) + results := []cli.OpResultRow{{ResourceID: resp.UGAId, Action: "create", Status: "Created"}} + for _, path := range lines { + p := ctx.PickResourceID(path) + bindReq := client.NewUGABindUPathRequest() + bindReq.ProjectId = req.ProjectId + bindReq.UGAId = sdk.String(resp.UGAId) + bindReq.UPathId = &p + _, err := client.UGABindUPath(bindReq) + if err != nil { + fmt.Fprintf(w, "bind uga[%s] and upath[%s] failed: %v\n", resp.UGAId, p, err) + } else { + fmt.Fprintf(w, "bound uga[%s] and upath[%s]\n", resp.UGAId, p) + results = append(results, cli.OpResultRow{ResourceID: p, Action: "bind-upath", Status: "Bound"}) + } + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, req) + req.Name = flags.String("name", "", "Required. Name of uga instance to create") + req.IPList = flags.String("origin-ip", "", "Required if origin-domain is empty. IP address of origin. multiple IP address separated by ','") + req.Domain = flags.String("origin-domain", "", "Required if origin-ip is empty.") + req.Location = flags.String("origin-location", "", "Required. Location of origin ip or domain. accpet valeus:'中国','洛杉矶','法兰克福','中国香港','雅加达','孟买','东京','莫斯科','新加坡','曼谷','中国台北','华盛顿','首尔'") + flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) + flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") + flags.StringSliceVar(&lines, "upath-id", nil, "Required. Accelerated path to bind with the uga instance to create. multiple upath-id separated by ','; see 'ucloud pathx upath list") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("origin-location") + cmd.MarkFlagRequired("protocol") + cmd.MarkFlagRequired("port") + cmd.MarkFlagRequired("upath-id") + command.SetFlagValues(cmd, "origin-location", "中国", "洛杉矶", "法兰克福", "中国香港", "雅加达", "孟买", "东京", "莫斯科", "新加坡", "曼谷", "中国台北", "华盛顿", "首尔") + command.SetFlagValues(cmd, "protocol", protocols...) + ctx.SetCompletion(cmd, "upath-id", func() []string { + return getUpathIDList(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_delete.go b/products/pathx/internal/pathx/uga_delete.go new file mode 100644 index 0000000000..aabc5291d0 --- /dev/null +++ b/products/pathx/internal/pathx/uga_delete.go @@ -0,0 +1,47 @@ +package pathx + +import ( + "fmt" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUGADelete ucloud pathx uga delete +func newUGADelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDeleteUGAInstanceRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete uga instances", + Long: "Delete uga instances", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.UGAId = &id + _, err := client.DeleteUGAInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "uga[%s] deleted\n", id) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, req) + flags.StringSliceVar(&idNames, "uga-id", nil, "Required. Resource ID of uga instances to delete. Multiple resource ids separated by comma") + cmd.MarkFlagRequired("uga-id") + ctx.SetCompletion(cmd, "uga-id", func() []string { + return getUGAIDList(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_describe.go b/products/pathx/internal/pathx/uga_describe.go new file mode 100644 index 0000000000..6956e9760f --- /dev/null +++ b/products/pathx/internal/pathx/uga_describe.go @@ -0,0 +1,55 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUGADescribe ucloud pathx uga describe +func newUGADescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDescribeUGAInstanceRequest() + cmd := &cobra.Command{ + Use: "describe", + Short: "Display detail informations about uga instances", + Long: "Display detail informations about uga instances", + Run: func(c *cobra.Command, args []string) { + *req.UGAId = ctx.PickResourceID(*req.UGAId) + resp, err := client.DescribeUGAInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + if len(resp.UGAList) != 1 { + ctx.HandleError(fmt.Errorf("uga[%s] may not exist", *req.UGAId)) + return + } + ins := resp.UGAList[0] + list := []describeRow{ + {Attribute: "ResourceID", Content: ins.UGAId}, + {Attribute: "UGAName", Content: ins.UGAName}, + {Attribute: "Origin", Content: fmt.Sprintf("%s%s", ins.Domain, strings.Join(ins.IPList, ","))}, + {Attribute: "CName", Content: ins.CName}, + {Attribute: "AcceleratedPath", Content: getUpathStr(ins.UPathSet)}, + {Attribute: "OutIP", Content: getOutIPStr(ins.OutPublicIpList)}, + {Attribute: "Port", Content: getPortStr(ins.TaskSet)}, + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance") + ctx.BindProjectID(cmd, req) + cmd.MarkFlagRequired("uga-id") + ctx.SetCompletion(cmd, "uga-id", func() []string { + return getUGAIDList(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_list.go b/products/pathx/internal/pathx/uga_list.go new file mode 100644 index 0000000000..8d2a8b36ba --- /dev/null +++ b/products/pathx/internal/pathx/uga_list.go @@ -0,0 +1,47 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUGAList ucloud pathx uga list +func newUGAList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDescribeUGAInstanceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "list uga instances", + Long: "list uga instances", + Run: func(c *cobra.Command, args []string) { + *req.UGAId = ctx.PickResourceID(*req.UGAId) + resp, err := client.DescribeUGAInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]UGARow, 0) + for _, ins := range resp.UGAList { + list = append(list, UGARow{ + ResourceID: ins.UGAId, + UGAName: ins.UGAName, + CName: ins.CName, + Origin: fmt.Sprintf("%s%s", strings.Join(ins.IPList, ","), ins.Domain), + AcceleratedPath: getUpathStr(ins.UPathSet), + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.UGAId = flags.String("uga-id", "", "Optional. Resource ID of uga instance") + ctx.BindProjectID(cmd, req) + return cmd +} diff --git a/products/pathx/internal/pathx/uga_remove_port.go b/products/pathx/internal/pathx/uga_remove_port.go new file mode 100644 index 0000000000..79c3704be5 --- /dev/null +++ b/products/pathx/internal/pathx/uga_remove_port.go @@ -0,0 +1,67 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUGARemovePort ucloud pathx uga delete-port +func newUGARemovePort(ctx *cli.Context) *cobra.Command { + var ports []string + var protocol string + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDeleteUGATaskRequest() + cmd := &cobra.Command{ + Use: "delete-port", + Short: "Delete port for uga instance", + Long: "Delete port for uga instance", + Run: func(c *cobra.Command, args []string) { + portList, err := formatPortList(ports) + if err != nil { + ctx.HandleError(err) + return + } + switch strings.ToLower(protocol) { + case "tcp": + req.TCP = portList + case "udp": + req.UDP = portList + case "http": + req.HTTP = portList + case "https": + req.HTTPS = portList + default: + fmt.Fprintf(ctx.ProgressWriter(), "protocol should be one of %s, received:%s\n", strings.Join(protocols, ","), protocol) + } + *req.UGAId = ctx.PickResourceID(*req.UGAId) + _, err = client.DeleteUGATask(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "port %v deleted\n", ports) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.UGAId, Action: "delete-port", Status: "Deleted"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, req) + req.UGAId = flags.String("uga-id", "", "Required. Resource ID of uga instance to delete port") + flags.StringVar(&protocol, "protocol", "", fmt.Sprintf("Required. accept values: %s", strings.Join(protocols, ","))) + flags.StringSliceVar(&ports, "port", nil, "Required. Single port or port range, separated by ',', for example 80,3000-3010") + cmd.MarkFlagRequired("protocol") + cmd.MarkFlagRequired("uga-id") + cmd.MarkFlagRequired("port") + command.SetFlagValues(cmd, "protocol", protocols...) + ctx.SetCompletion(cmd, "uga-id", func() []string { + return getUGAIDList(ctx, *req.ProjectId) + }) + return cmd +} diff --git a/products/pathx/internal/pathx/upath.go b/products/pathx/internal/pathx/upath.go new file mode 100644 index 0000000000..b4cdeaa660 --- /dev/null +++ b/products/pathx/internal/pathx/upath.go @@ -0,0 +1,18 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUpath builds `ucloud pathx upath`. +func newUpath(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "upath", + Short: "List pathx upath instances", + Long: "List pathx upath instances", + } + cmd.AddCommand(newUpathList(ctx)) + return cmd +} diff --git a/products/pathx/internal/pathx/upath_list.go b/products/pathx/internal/pathx/upath_list.go new file mode 100644 index 0000000000..b69f97a69a --- /dev/null +++ b/products/pathx/internal/pathx/upath_list.go @@ -0,0 +1,49 @@ +package pathx + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ppathx "github.com/ucloud/ucloud-sdk-go/private/services/pathx" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUpathList ucloud pathx upath list +func newUpathList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ppathx.NewClient) + req := client.NewDescribeUPathRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "list upath instances", + Long: "list upath instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.DescribeUPath(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]upathRow, 0) + for _, ins := range resp.UPathSet { + ids := []string{} + for _, ga := range ins.UGAList { + ids = append(ids, ga.UGAId) + } + list = append(list, upathRow{ + ResourceID: ins.UPathId, + UPathName: ins.Name, + AcceleratedPath: fmt.Sprintf("%s->%s %dM", ins.LineFromName, ins.LineToName, ins.Bandwidth), + BoundUGA: strings.Join(ids, ","), + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindProjectID(cmd, req) + req.UPathId = flags.String("upath-id", "", "Optional. Resource ID of upath instance to list") + return cmd +} diff --git a/products/pathx/product.go b/products/pathx/product.go new file mode 100644 index 0000000000..4279ee29f1 --- /dev/null +++ b/products/pathx/product.go @@ -0,0 +1,20 @@ +package pathx + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalpathx "github.com/ucloud/ucloud-cli/products/pathx/internal/pathx" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "pathx", Commands: []string{"pathx"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalpathx.NewCommand(ctx)} +} diff --git a/products/pathx/product.yaml b/products/pathx/product.yaml new file mode 100644 index 0000000000..95ee543037 --- /dev/null +++ b/products/pathx/product.yaml @@ -0,0 +1,6 @@ +name: pathx +owners: + - Episkey-G +commands: + - pathx +enabled: true diff --git a/products/pathx/testdata/cmdtree.golden b/products/pathx/testdata/cmdtree.golden new file mode 100644 index 0000000000..d2ea9690b9 --- /dev/null +++ b/products/pathx/testdata/cmdtree.golden @@ -0,0 +1,93 @@ +ucloud pathx use=pathx short=Manipulate uga and upath instances +ucloud pathx area use=area short=List origin area or acceleration area information +ucloud pathx area list use=list short=List origin area or acceleration area information + flag=accel short= default= required= + flag=no-accel short= default=false required= + flag=origin-domain short= default= required= + flag=origin-ip short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=time-range short= default= required= + flag=zone short= default= required= +ucloud pathx create use=create short=Create the pathx resource and port + flag=accel short= default= required= + flag=area-code short= default= required= + flag=bandwidth short= default=0 required=true + flag=charge-type short= default= required= + flag=origin-domain short= default= required= + flag=origin-ip short= default= required= + flag=origin-port short= default=[] required= + flag=port short= default=[] required= + flag=project-id short= default= required= + flag=protocol short= default=TCP required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pathx delete use=delete short=Delete the pathx resource and port + flag=id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud pathx list use=list short=List all the pathx resource of project + flag=detail short= default=false required= + flag=id short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pathx modify use=modify short=Modify the pathx associated information. Example bandwidth or origin information or resource information + flag=bandwidth short= default=0 required= + flag=id short= default= required=true + flag=name short= default= required= + flag=origin-domain short= default= required= + flag=origin-ip short= default= required= + flag=origin-port short= default=[] required= + flag=port short= default=[] required= + flag=project-id short= default= required= + flag=protocol short= default=TCP required= + flag=region short= default= required= + flag=remark short= default= required= + flag=zone short= default= required= +ucloud pathx price use=price short=List all the acceleration area price +ucloud pathx price list use=list short=List all the pathx acceleration area price + flag=accel short= default= required= + flag=area-code short= default= required=true + flag=bandwidth short= default=1 required=true + flag=charge-type short= default= required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pathx uga use=uga short=Create,list,update and delete pathx uga instances +ucloud pathx uga add-port use=add-port short=Add port for uga instance + flag=port short= default=[] required=true + flag=project-id short= default= required= + flag=protocol short= default= required=true + flag=uga-id short= default= required=true +ucloud pathx uga create use=create short=Create uga instance + flag=name short= default= required=true + flag=origin-domain short= default= required= + flag=origin-ip short= default= required= + flag=origin-location short= default= required=true + flag=port short= default=[] required=true + flag=project-id short= default= required= + flag=protocol short= default= required=true + flag=upath-id short= default=[] required=true +ucloud pathx uga delete use=delete short=Delete uga instances + flag=project-id short= default= required= + flag=uga-id short= default=[] required=true +ucloud pathx uga delete-port use=delete-port short=Delete port for uga instance + flag=port short= default=[] required=true + flag=project-id short= default= required= + flag=protocol short= default= required=true + flag=uga-id short= default= required=true +ucloud pathx uga describe use=describe short=Display detail informations about uga instances + flag=project-id short= default= required= + flag=uga-id short= default= required=true +ucloud pathx uga list use=list short=list uga instances + flag=project-id short= default= required= + flag=uga-id short= default= required= +ucloud pathx upath use=upath short=List pathx upath instances +ucloud pathx upath list use=list short=list upath instances + flag=project-id short= default= required= + flag=upath-id short= default= required= diff --git a/products/pathx/testdata/completion.golden b/products/pathx/testdata/completion.golden new file mode 100644 index 0000000000..bb3e28fed4 --- /dev/null +++ b/products/pathx/testdata/completion.golden @@ -0,0 +1,44 @@ +ucloud pathx area list project-id dynamic +ucloud pathx area list region dynamic +ucloud pathx area list zone dynamic +ucloud pathx create accel static AF,AP,EU,Global,ME,NA,OA,SA +ucloud pathx create area-code static BKK,BOM,CGK,DME,DXB,FRA,HKG,IAD,ICN,LAX,LHR,LOS,MNL,MSP,NRT,PVG,SGN,SIN,TPE +ucloud pathx create charge-type static Hour,Month,Year +ucloud pathx create project-id dynamic +ucloud pathx create protocol static TCP,UDP +ucloud pathx create region dynamic +ucloud pathx create zone dynamic +ucloud pathx delete id dynamic +ucloud pathx delete project-id dynamic +ucloud pathx delete region dynamic +ucloud pathx delete zone dynamic +ucloud pathx list id dynamic +ucloud pathx list project-id dynamic +ucloud pathx list region dynamic +ucloud pathx list zone dynamic +ucloud pathx modify id dynamic +ucloud pathx modify project-id dynamic +ucloud pathx modify region dynamic +ucloud pathx modify zone dynamic +ucloud pathx price list accel static AF,AP,EU,Global,ME,NA,OA,SA +ucloud pathx price list area-code static BKK,BOM,CGK,DME,DXB,FRA,HKG,IAD,ICN,LAX,LHR,LOS,MNL,MSP,NRT,PVG,SGN,SIN,TPE +ucloud pathx price list charge-type static Hour,Month,Year +ucloud pathx price list project-id dynamic +ucloud pathx price list region dynamic +ucloud pathx price list zone dynamic +ucloud pathx uga add-port project-id dynamic +ucloud pathx uga add-port protocol static tcp,udp +ucloud pathx uga add-port uga-id dynamic +ucloud pathx uga create origin-location static 东京,中国,中国台北,中国香港,华盛顿,孟买,新加坡,曼谷,法兰克福,洛杉矶,莫斯科,雅加达,首尔 +ucloud pathx uga create project-id dynamic +ucloud pathx uga create protocol static tcp,udp +ucloud pathx uga create upath-id dynamic +ucloud pathx uga delete project-id dynamic +ucloud pathx uga delete uga-id dynamic +ucloud pathx uga delete-port project-id dynamic +ucloud pathx uga delete-port protocol static tcp,udp +ucloud pathx uga delete-port uga-id dynamic +ucloud pathx uga describe project-id dynamic +ucloud pathx uga describe uga-id dynamic +ucloud pathx uga list project-id dynamic +ucloud pathx upath list project-id dynamic diff --git a/products/redis/internal/redis/cmd.go b/products/redis/internal/redis/cmd.go new file mode 100644 index 0000000000..a9686a09f6 --- /dev/null +++ b/products/redis/internal/redis/cmd.go @@ -0,0 +1,21 @@ +package redis + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud redis command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "redis", + Short: "List and manipulate redis instances", + Long: "List and manipulate redis instances", + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newRestart(ctx)) + return cmd +} diff --git a/products/redis/internal/redis/completion.go b/products/redis/internal/redis/completion.go new file mode 100644 index 0000000000..2acbc264ed --- /dev/null +++ b/products/redis/internal/redis/completion.go @@ -0,0 +1,34 @@ +package redis + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getIDList(ctx *cli.Context, project, region string) []string { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeURedisGroupRequest() + req.ProjectId = &project + req.Region = ®ion + list := []string{} + + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeURedisGroup(req) + if err != nil { + return nil + } + for _, ins := range resp.DataSet { + list = append(list, fmt.Sprintf("%s/%s", ins.GroupId, ins.Name)) + } + if offset+limit >= resp.TotalCount { + break + } + } + return list +} diff --git a/products/redis/internal/redis/create.go b/products/redis/internal/redis/create.go new file mode 100644 index 0000000000..5f3ef2efe9 --- /dev/null +++ b/products/redis/internal/redis/create.go @@ -0,0 +1,177 @@ +package redis + +import ( + "fmt" + "unicode/utf8" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +type createParams struct { + name string + password string + size int + region string + zone string + projectID string + chargeType string + quantity int + group string + vpcID string + subnetID string + version string + blockCnt int + proxySize int +} + +// newCreate returns ucloud redis create. +func newCreate(ctx *cli.Context) *cobra.Command { + var redisType string + var p createParams + cmd := &cobra.Command{ + Use: "create", + Short: "Create redis instance", + Long: "Create redis instance", + Run: func(c *cobra.Command, args []string) { + if l := utf8.RuneCountInString(p.name); l < 6 || l > 63 { + fmt.Fprintln(ctx.ProgressWriter(), "length of name should be between 6 and 63") + return + } + if p.password != "" { + if l := len(p.password); l < 6 || l > 36 { + fmt.Fprintln(ctx.ProgressWriter(), "length of password should be between 6 and 36") + return + } + } + if err := fillDefaultVPCAndSubnet(ctx, &p.vpcID, &p.subnetID, p.projectID, p.region, p.zone); err != nil { + fmt.Fprintln(ctx.ProgressWriter(), err) + return + } + switch redisType { + case "master-replica": + createMasterReplica(ctx, &p) + case "distributed": + createDistributed(ctx, &p) + default: + fmt.Fprintf(ctx.ProgressWriter(), "unknow redis type[%s], it's should be 'master-replica' or 'distributed'\n", redisType) + } + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&p.name, "name", "", "Required. Name of the redis to create. Range of the name length is [6,63]") + flags.StringVar(&redisType, "type", "", "Required. Type of the redis. Accept values:'master-replica','distributed'") + flags.IntVar(&p.size, "size-gb", 2, "Optional. Memory size. Default value 2GB. Unit GB") + flags.StringVar(&p.version, "version", "6.0", "Optional. Version of redis. Accept values: '4.0', '5.0', '6.0', '7.0'") + flags.StringVar(&p.vpcID, "vpc-id", "", "Optional. VPC ID. This field is required under VPC2.0. See 'ucloud vpc list'") + flags.StringVar(&p.subnetID, "subnet-id", "", "Optional. Subnet ID. This field is required under VPC2.0. See 'ucloud subnet list'") + flags.StringVar(&p.password, "password", "", "Optional. Password of redis to create. Range of the password length is [6,36] and the password can only contain letters and numbers") + + flags.IntVar(&p.blockCnt, "block-cnt", 2, "Optional. Block count. Default value 2(for distributed redis type).") + flags.IntVar(&p.proxySize, "proxy-size", 2, "Optional. Proxy size. Default value 2(for distributed redis type) Unit Core") + + flags.StringVar(&p.region, "region", ctx.DefaultRegion(), "Optional. Override default region for this command invocation, see 'ucloud region'") + flags.StringVar(&p.zone, "zone", ctx.DefaultZone(), "Optional. Override default availability zone for this command invocation, see 'ucloud region'") + flags.StringVar(&p.projectID, "project-id", ctx.DefaultProjectID(), "Optional. Override default project-id for this command invocation, see 'ucloud project list'") + flags.StringVar(&p.chargeType, "charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") + flags.IntVar(&p.quantity, "quantity", 1, "Optional. The duration of the instance. N years/months.") + flags.StringVar(&p.group, "group", "", "Optional. Business group") + + command.SetCompletion(cmd, "region", ctx.RegionList) + command.SetCompletion(cmd, "zone", func() []string { return ctx.ZoneList(p.region) }) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + command.SetFlagValues(cmd, "version", "4.0", "5.0", "6.0", "7.0") + command.SetFlagValues(cmd, "type", "master-replica", "distributed") + command.SetFlagValues(cmd, "charge-type", "Month", "Dynamic", "Year") + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, p.projectID, p.region) + }) + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, p.vpcID, p.projectID, p.region) + }) + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("type") + + return cmd +} + +func createMasterReplica(ctx *cli.Context, p *createParams) { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewCreateURedisGroupRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.Name = &p.name + req.HighAvailability = sdk.String("enable") + req.Size = &p.size + req.Version = &p.version + req.VPCId = &p.vpcID + req.SubnetId = &p.subnetID + req.ChargeType = &p.chargeType + req.Quantity = &p.quantity + req.Tag = &p.group + if p.password != "" { + req.Password = &p.password + } + + resp, err := client.CreateURedisGroup(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "redis[%s] created\n", resp.GroupId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.GroupId, Action: "create", Status: "Created"}) +} + +func createDistributed(ctx *cli.Context, p *createParams) { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewCreateUMemSpaceRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.Name = &p.name + req.Protocol = sdk.String("redis") + + if p.blockCnt <= 0 { + fmt.Fprintln(ctx.ProgressWriter(), "block-cnt should be greater than 0") + return + } + if p.size%p.blockCnt != 0 { + fmt.Fprintf(ctx.ProgressWriter(), "size-gb(%d) should be divisible by block-cnt(%d)\n", p.size, p.blockCnt) + return + } + if p.proxySize%2 != 0 { + fmt.Fprintf(ctx.ProgressWriter(), "proxy-size(%d) should be a multiple of 2\n", p.proxySize) + return + } + + req.BlockCnt = &p.blockCnt + req.ProxySize = &p.proxySize + req.Size = &p.size + req.Version = &p.version + req.VPCId = &p.vpcID + req.SubnetId = &p.subnetID + req.ChargeType = &p.chargeType + req.Quantity = &p.quantity + req.Tag = &p.group + if p.password != "" { + req.Password = &p.password + } + + resp, err := client.CreateUMemSpace(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "redis[%s] created\n", resp.SpaceId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.SpaceId, Action: "create", Status: "Created"}) +} diff --git a/products/redis/internal/redis/delete.go b/products/redis/internal/redis/delete.go new file mode 100644 index 0000000000..4e9372a695 --- /dev/null +++ b/products/redis/internal/redis/delete.go @@ -0,0 +1,99 @@ +package redis + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +type deleteParams struct { + region string + zone string + projectID string +} + +// newDelete returns ucloud redis delete. +func newDelete(ctx *cli.Context) *cobra.Command { + var idNames []string + var p deleteParams + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete redis instances", + Long: "Delete redis instances", + Example: "ucloud redis delete --umem-id uredis-rl5xuxx/testcli1,uredis-xsdfa/testcli2", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + if strings.HasPrefix(id, "uredis") || strings.HasPrefix(id, "uhredis") || strings.HasPrefix(id, "uregionredis") { + if deleteMasterReplica(ctx, &p, id) { + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + } else if strings.HasPrefix(id, "udredis") { + if deleteDistributed(ctx, &p, id) { + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + } else { + fmt.Fprintf(ctx.ProgressWriter(), "redis[%s] unknown id prefix, skip\n", idname) + } + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis instances to delete") + flags.StringVar(&p.region, "region", ctx.DefaultRegion(), "Optional. Override default region for this command invocation, see 'ucloud region'") + flags.StringVar(&p.zone, "zone", ctx.DefaultZone(), "Optional. Override default availability zone for this command invocation, see 'ucloud region'") + flags.StringVar(&p.projectID, "project-id", ctx.DefaultProjectID(), "Optional. Override default project-id for this command invocation, see 'ucloud project list'") + command.SetCompletion(cmd, "region", ctx.RegionList) + command.SetCompletion(cmd, "zone", func() []string { return ctx.ZoneList(p.region) }) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + + cmd.MarkFlagRequired("umem-id") + + command.SetCompletion(cmd, "umem-id", func() []string { + return getIDList(ctx, p.projectID, p.region) + }) + + return cmd +} + +func deleteMasterReplica(ctx *cli.Context, p *deleteParams, id string) bool { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDeleteURedisGroupRequest() + req.Region = &p.region + req.ProjectId = &p.projectID + req.GroupId = &id + _, err := client.DeleteURedisGroup(req) + if err != nil { + ctx.HandleError(err) + return false + } + fmt.Fprintf(ctx.ProgressWriter(), "redis[%s] deleted\n", id) + return true +} + +func deleteDistributed(ctx *cli.Context, p *deleteParams, id string) bool { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDeleteUMemSpaceRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.SpaceId = &id + _, err := client.DeleteUMemSpace(req) + if err != nil { + ctx.HandleError(err) + return false + } + fmt.Fprintf(ctx.ProgressWriter(), "redis[%s] deleted\n", id) + return true +} diff --git a/products/redis/internal/redis/list.go b/products/redis/internal/redis/list.go new file mode 100644 index 0000000000..e7b9112798 --- /dev/null +++ b/products/redis/internal/redis/list.go @@ -0,0 +1,88 @@ +package redis + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newList returns ucloud redis list. +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeUMemRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List redis instances", + Long: "List redis instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.DescribeUMem(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []Row{} + for _, ins := range resp.DataSet { + row := Row{ + ResourceID: ins.ResourceId, + Name: ins.Name, + Role: ins.Role, + Type: redisTypeMap[ins.ResourceType], + Group: ins.Tag, + Size: fmt.Sprintf("%dGB", ins.Size), + UsedSize: fmt.Sprintf("%dMB", ins.UsedSize), + State: ins.State, + Zone: ins.Zone, + CreateTime: common.FormatDate(ins.CreateTime), + } + addrs := []string{} + for _, addr := range ins.Address { + addrs = append(addrs, fmt.Sprintf("%s:%d", addr.IP, addr.Port)) + } + row.Address = strings.Join(addrs, "|") + list = append(list, row) + for _, slave := range ins.DataSet { + srow := Row{ + ResourceID: slave.GroupId, + Name: slave.Name, + Role: fmt.Sprintf("⮑ %s", slave.Role), + Type: redisTypeMap[slave.ResourceType], + Group: slave.Tag, + Size: fmt.Sprintf("%dGB", slave.Size), + UsedSize: fmt.Sprintf("%dMB", slave.UsedSize), + State: slave.State, + Zone: slave.Zone, + Address: fmt.Sprintf("%s:%d", slave.VirtualIP, slave.Port), + CreateTime: common.FormatDate(slave.CreateTime), + } + list = append(list, srow) + } + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ResourceId = flags.String("umem-id", "", "Optional. Resource ID of the redis to list") + ctx.BindRegion(cmd, req) + ctx.BindZoneEmpty(cmd, req) + ctx.BindProjectID(cmd, req) + ctx.BindOffset(cmd, req) + ctx.BindLimit(cmd, req) + req.Protocol = sdk.String("redis") + + command.SetCompletion(cmd, "umem-id", func() []string { + return getIDList(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/redis/internal/redis/poll.go b/products/redis/internal/redis/poll.go new file mode 100644 index 0000000000..8a81f12322 --- /dev/null +++ b/products/redis/internal/redis/poll.go @@ -0,0 +1,32 @@ +package redis + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func describeByID(ctx *cli.Context) func(string, *request.CommonBase) (interface{}, error) { + return func(redisID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewDescribeUMemRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.Protocol = sdk.String("redis") + req.ResourceId = &redisID + + resp, err := client.DescribeUMem(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) < 1 { + return nil, fmt.Errorf("resource [%s] may not exist", redisID) + } + return &resp.DataSet[0], nil + } +} diff --git a/products/redis/internal/redis/restart.go b/products/redis/internal/redis/restart.go new file mode 100644 index 0000000000..a598edc7d1 --- /dev/null +++ b/products/redis/internal/redis/restart.go @@ -0,0 +1,79 @@ +package redis + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/umem" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newRestart returns ucloud redis restart. +func newRestart(ctx *cli.Context) *cobra.Command { + idNames := make([]string, 0) + client := cli.NewServiceClient(ctx, umem.NewClient) + req := client.NewRestartURedisGroupRequest() + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart redis instances of master-replica type", + Long: "Restart redis instances of master-replica type", + Run: func(c *cobra.Command, args []string) { + reqs := make([]request.Common, len(idNames)) + for idx, idname := range idNames { + id := ctx.PickResourceID(idname) + next := *req + next.GroupId = &id + reqs[idx] = &next + } + prog := ctx.NewProgress() + if len(reqs) > 5 { + prog.Disable() + } + ctx.ConcurrentAction(reqs, 10, restart(ctx, client, prog)) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis instances to restart") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("umem-id") + command.SetCompletion(cmd, "umem-id", func() []string { + return getIDList(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} + +func restart(ctx *cli.Context, client *umem.UMemClient, prog *cli.Progress) func(request.Common) (bool, []string) { + return func(creq request.Common) (bool, []string) { + req := creq.(*umem.RestartURedisGroupRequest) + block := prog.NewBlock() + logs := []string{} + _, err := client.RestartURedisGroup(req) + if err != nil { + msg := fmt.Sprintf("restart redis[%s] failed: %s", *req.GroupId, cli.ParseError(err)) + block.Append(cli.ParseError(err)) + logs = append(logs, msg) + return false, logs + } + text := fmt.Sprintf("redis[%s] is restarting", *req.GroupId) + ret := ctx.PollerTo(ctx.ProgressWriter(), describeByID(ctx)).Sspoll(*req.GroupId, text, []string{UMEM_RUNNING, UMEM_FAIL}, block, nil) + if ret.Err != nil { + block.Append(cli.ParseError(ret.Err)) + logs = append(logs, ret.Err.Error()) + } + if ret.Timeout { + logs = append(logs, fmt.Sprintf("poll redis[%s] timeout", *req.GroupId)) + } + return ret.Done, logs + } +} diff --git a/products/redis/internal/redis/rows.go b/products/redis/internal/redis/rows.go new file mode 100644 index 0000000000..a79c0da41b --- /dev/null +++ b/products/redis/internal/redis/rows.go @@ -0,0 +1,20 @@ +package redis + +type Row struct { + ResourceID string + Name string + Role string + Type string + Address string + Size string + UsedSize string + State string + Group string + Zone string + CreateTime string +} + +var redisTypeMap = map[string]string{ + "single": "master-replica", + "distributed": "distributed", +} diff --git a/products/redis/internal/redis/status.go b/products/redis/internal/redis/status.go new file mode 100644 index 0000000000..737434fdd5 --- /dev/null +++ b/products/redis/internal/redis/status.go @@ -0,0 +1,6 @@ +package redis + +const ( + UMEM_FAIL = "Fail" + UMEM_RUNNING = "Running" +) diff --git a/products/redis/internal/redis/vpc.go b/products/redis/internal/redis/vpc.go new file mode 100644 index 0000000000..027c621733 --- /dev/null +++ b/products/redis/internal/redis/vpc.go @@ -0,0 +1,119 @@ +package redis + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func fillDefaultVPCAndSubnet(ctx *cli.Context, vpcID, subnetID *string, projectID, region, zone string) error { + if *vpcID != "" && *subnetID != "" { + return nil + } + vpcs, err := getAllVPCIns(ctx, projectID, region) + if err != nil { + return fmt.Errorf("failed to get vpc list: %s", err) + } + if len(vpcs) == 0 { + return fmt.Errorf("no vpc found in region[%s], please specify --vpc-id and --subnet-id", region) + } + + var defaultVPC *vpc.VPCInfo + for i := range vpcs { + if vpcs[i].VPCType == "DefaultVPC" { + defaultVPC = &vpcs[i] + break + } + } + if defaultVPC == nil { + defaultVPC = &vpcs[0] + } + + if *vpcID == "" { + *vpcID = defaultVPC.VPCId + } + + if *subnetID == "" { + subnets, err := getAllSubnets(ctx, *vpcID, projectID, region) + if err != nil { + return fmt.Errorf("failed to get subnet list: %s", err) + } + if len(subnets) == 0 { + return fmt.Errorf("no subnet found in vpc[%s], please specify --subnet-id", *vpcID) + } + if zone != "" { + for _, sn := range subnets { + if sn.Zone == zone { + *subnetID = sn.SubnetId + return nil + } + } + } + *subnetID = subnets[0].SubnetId + } + + return nil +} + +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpc.VPCInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, vpc := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) + } + return list +} + +func getAllSubnets(ctx *cli.Context, vpcID, project, region string) ([]vpc.SubnetInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := []vpc.SubnetInfo{} + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeSubnet(req) + if err != nil { + return nil, err + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + return subnets, nil +} + +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + subnets, err := getAllSubnets(ctx, vpcID, project, region) + if err != nil { + return nil + } + list := []string{} + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} diff --git a/products/redis/product.go b/products/redis/product.go new file mode 100644 index 0000000000..3af6d63a00 --- /dev/null +++ b/products/redis/product.go @@ -0,0 +1,20 @@ +package redis + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalredis "github.com/ucloud/ucloud-cli/products/redis/internal/redis" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "redis", Commands: []string{"redis"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalredis.NewCommand(ctx)} +} diff --git a/products/redis/product.yaml b/products/redis/product.yaml new file mode 100644 index 0000000000..34204c7139 --- /dev/null +++ b/products/redis/product.yaml @@ -0,0 +1,6 @@ +name: redis +owners: + - Episkey-G +commands: + - redis +enabled: true diff --git a/products/redis/testdata/cmdtree.golden b/products/redis/testdata/cmdtree.golden new file mode 100644 index 0000000000..f061c49fa3 --- /dev/null +++ b/products/redis/testdata/cmdtree.golden @@ -0,0 +1,34 @@ +ucloud redis use=redis short=List and manipulate redis instances +ucloud redis create use=create short=Create redis instance + flag=block-cnt short= default=2 required= + flag=charge-type short= default=Month required= + flag=group short= default= required= + flag=name short= default= required=true + flag=password short= default= required= + flag=project-id short= default= required= + flag=proxy-size short= default=2 required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=size-gb short= default=2 required= + flag=subnet-id short= default= required= + flag=type short= default= required=true + flag=version short= default=6.0 required= + flag=vpc-id short= default= required= + flag=zone short= default= required= +ucloud redis delete use=delete short=Delete redis instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default=[] required=true + flag=zone short= default= required= +ucloud redis list use=list short=List redis instances + flag=limit short= default=100 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default= required= + flag=zone short= default= required= +ucloud redis restart use=restart short=Restart redis instances of master-replica type + flag=project-id short= default= required= + flag=region short= default= required= + flag=umem-id short= default=[] required=true + flag=zone short= default= required= diff --git a/products/redis/testdata/completion.golden b/products/redis/testdata/completion.golden new file mode 100644 index 0000000000..1efe46792c --- /dev/null +++ b/products/redis/testdata/completion.golden @@ -0,0 +1,20 @@ +ucloud redis create charge-type static Dynamic,Month,Year +ucloud redis create project-id dynamic +ucloud redis create region dynamic +ucloud redis create subnet-id dynamic +ucloud redis create type static distributed,master-replica +ucloud redis create version static 4.0,5.0,6.0,7.0 +ucloud redis create vpc-id dynamic +ucloud redis create zone dynamic +ucloud redis delete project-id dynamic +ucloud redis delete region dynamic +ucloud redis delete umem-id dynamic +ucloud redis delete zone dynamic +ucloud redis list project-id dynamic +ucloud redis list region dynamic +ucloud redis list umem-id dynamic +ucloud redis list zone dynamic +ucloud redis restart project-id dynamic +ucloud redis restart region dynamic +ucloud redis restart umem-id dynamic +ucloud redis restart zone dynamic diff --git a/products/sharedbw/internal/bw/cmd.go b/products/sharedbw/internal/bw/cmd.go new file mode 100644 index 0000000000..146025582e --- /dev/null +++ b/products/sharedbw/internal/bw/cmd.go @@ -0,0 +1,19 @@ +package bw + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud bw command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "bw", + Short: "Manipulate bandwidth package and shared bandwidth", + Long: "Manipulate bandwidth package and shared bandwidth", + } + cmd.AddCommand(newPkg(ctx)) + cmd.AddCommand(newShared(ctx)) + return cmd +} diff --git a/products/sharedbw/internal/bw/completion.go b/products/sharedbw/internal/bw/completion.go new file mode 100644 index 0000000000..7c8c07d06e --- /dev/null +++ b/products/sharedbw/internal/bw/completion.go @@ -0,0 +1,90 @@ +package bw + +import ( + "strings" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllSharedBW(ctx *cli.Context, project, region string) ([]string, error) { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeShareBandwidthRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeShareBandwidth(req) + if err != nil { + return nil, err + } + list := []string{} + for _, item := range resp.DataSet { + list = append(list, item.ShareBandwidthId+"/"+item.Name) + } + return list, nil +} + +func getAllEip(ctx *cli.Context, projectID, region string, states, paymodes []string) []string { + list, err := fetchAllEip(ctx, projectID, region) + if err != nil { + return nil + } + strs := []string{} + for _, item := range list { + rightState := false + if states == nil { + rightState = true + } else { + for _, s := range states { + if item.Status == s { + rightState = true + } + } + } + + rightPayMode := false + if paymodes == nil { + rightPayMode = true + } else { + for _, m := range paymodes { + if item.PayMode == m { + rightPayMode = true + } + } + } + if !rightPayMode || !rightState { + continue + } + + ips := []string{} + for _, ip := range item.EIPAddr { + ips = append(ips, ip.IP) + } + strs = append(strs, item.EIPId+"/"+strings.Join(ips, ",")) + } + return strs +} + +func fetchAllEip(ctx *cli.Context, projectID, region string) ([]unet.UnetEIPSet, error) { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeEIPRequest() + list := []unet.UnetEIPSet{} + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + for offset, step := 0, 100; ; offset += step { + req.Offset = &offset + req.Limit = &step + resp, err := client.DescribeEIP(req) + if err != nil { + return nil, err + } + for i, size := 0, len(resp.EIPSet); i < size; i++ { + list = append(list, resp.EIPSet[i]) + } + if resp.TotalCount <= offset+step { + break + } + } + return list, nil +} diff --git a/products/sharedbw/internal/bw/pkg.go b/products/sharedbw/internal/bw/pkg.go new file mode 100644 index 0000000000..86e52538f0 --- /dev/null +++ b/products/sharedbw/internal/bw/pkg.go @@ -0,0 +1,20 @@ +package bw + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPkg returns ucloud bw pkg. +func newPkg(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "pkg", + Short: "List, create and delete bandwidth package instances", + Long: "List, create and delete bandwidth package instances", + } + cmd.AddCommand(newPkgCreate(ctx)) + cmd.AddCommand(newPkgList(ctx)) + cmd.AddCommand(newPkgDelete(ctx)) + return cmd +} diff --git a/products/sharedbw/internal/bw/pkg_create.go b/products/sharedbw/internal/bw/pkg_create.go new file mode 100644 index 0000000000..1cab191cc9 --- /dev/null +++ b/products/sharedbw/internal/bw/pkg_create.go @@ -0,0 +1,85 @@ +package bw + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPkgCreate returns ucloud bw pkg create. +func newPkgCreate(ctx *cli.Context) *cobra.Command { + var start, end *string + timeLayout := "2006-01-02/15:04:05" + ids := []string{} + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewCreateBandwidthPackageRequest() + loc, _ := time.LoadLocation("Local") + cmd := &cobra.Command{ + Use: "create", + Short: "Create bandwidth package", + Long: "Create bandwidth package", + Example: "ucloud bw pkg create --eip-id eip-xxx --bandwidth-mb 20 --start-time 2018-12-15/09:20:00 --end-time 2018-12-16/09:20:00", + Run: func(c *cobra.Command, args []string) { + st, err := time.ParseInLocation(timeLayout, *start, loc) + if err != nil { + ctx.HandleError(err) + return + } + et, err := time.ParseInLocation(timeLayout, *end, loc) + if err != nil { + ctx.HandleError(err) + return + } + if st.Sub(time.Now()) < 0 { + fmt.Fprintln(ctx.ProgressWriter(), "start-time must be after the current time") + return + } + du := et.Unix() - st.Unix() + if du <= 0 { + fmt.Fprintln(ctx.ProgressWriter(), "end-time must be after the start-time") + return + } + req.EnableTime = sdk.Int(int(st.Unix())) + req.TimeRange = sdk.Int(int(du)) + + results := []cli.OpResultRow{} + for _, id := range ids { + id = ctx.PickResourceID(id) + req.EIPId = &id + resp, err := client.CreateBandwidthPackage(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "bandwidth package[%s] created for eip[%s]\n", resp.BandwidthPackageId, id) + results = append(results, cli.OpResultRow{ResourceID: resp.BandwidthPackageId, Action: "create", Status: "Created"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + flags.StringSliceVar(&ids, "eip-id", nil, "Required. Resource ID of eip to be bound with created bandwidth package") + start = flags.String("start-time", "", "Required. The time to enable bandwidth package. Local time, for example '2018-12-25/08:30:00'") + end = flags.String("end-time", "", "Required. The time to disable bandwidth package. Local time, for example '2018-12-26/08:30:00'") + req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. bandwidth of the bandwidth package to create.Range [1,800]. Unit:'Mb'.") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetCompletion(cmd, "eip-id", func() []string { + return getAllEip(ctx, *req.ProjectId, *req.Region, []string{EIP_USED}, []string{EIP_CHARGE_BANDWIDTH}) + }) + + cmd.MarkFlagRequired("eip-id") + cmd.MarkFlagRequired("start-time") + cmd.MarkFlagRequired("end-time") + cmd.MarkFlagRequired("bandwidth-mb") + return cmd +} diff --git a/products/sharedbw/internal/bw/pkg_delete.go b/products/sharedbw/internal/bw/pkg_delete.go new file mode 100644 index 0000000000..8755eb69e1 --- /dev/null +++ b/products/sharedbw/internal/bw/pkg_delete.go @@ -0,0 +1,46 @@ +package bw + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPkgDelete returns ucloud bw pkg delete. +func newPkgDelete(ctx *cli.Context) *cobra.Command { + ids := []string{} + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDeleteBandwidthPackageRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete bandwidth packages", + Long: "Delete bandwidth packages", + Example: "ucloud bw pkg delete --resource-id bwpack-xxx", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range ids { + id := ctx.PickResourceID(idname) + req.BandwidthPackageId = &id + _, err := client.DeleteBandwidthPackage(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "bandwidth package[%s] deleted\n", id) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + flags.StringSliceVar(&ids, "resource-id", nil, "Required, Resource ID of bandwidth package to delete") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + return cmd +} diff --git a/products/sharedbw/internal/bw/pkg_list.go b/products/sharedbw/internal/bw/pkg_list.go new file mode 100644 index 0000000000..7f6cf16aaa --- /dev/null +++ b/products/sharedbw/internal/bw/pkg_list.go @@ -0,0 +1,54 @@ +package bw + +import ( + "strconv" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPkgList returns ucloud bw pkg list. +func newPkgList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeBandwidthPackageRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List bandwidth packages", + Long: "List bandwidth packages", + Run: func(c *cobra.Command, args []string) { + resp, err := client.DescribeBandwidthPackage(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []BandwidthPkgRow{} + for _, bp := range resp.DataSets { + row := BandwidthPkgRow{ + ResourceID: bp.BandwidthPackageId, + Bandwidth: strconv.Itoa(bp.Bandwidth) + "MB", + StartTime: common.FormatDateTime(bp.EnableTime), + EndTime: common.FormatDateTime(bp.DisableTime), + } + eip := bp.EIPId + for _, addr := range bp.EIPAddr { + eip += "/" + addr.IP + "/" + addr.OperatorName + } + row.EIP = eip + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.Offset = flags.Int("offset", 0, "Optional. Offset") + req.Limit = flags.Int("limit", 50, "Optional. Limit range [0,10000000]") + + return cmd +} diff --git a/products/sharedbw/internal/bw/rows.go b/products/sharedbw/internal/bw/rows.go new file mode 100644 index 0000000000..250d867357 --- /dev/null +++ b/products/sharedbw/internal/bw/rows.go @@ -0,0 +1,18 @@ +package bw + +type SharedBWRow struct { + Name string + ResourceID string + ChargeType string + Bandwidth string + EIP string + ExpirationTime string +} + +type BandwidthPkgRow struct { + ResourceID string + EIP string + Bandwidth string + StartTime string + EndTime string +} diff --git a/products/sharedbw/internal/bw/shared.go b/products/sharedbw/internal/bw/shared.go new file mode 100644 index 0000000000..7d4d06323d --- /dev/null +++ b/products/sharedbw/internal/bw/shared.go @@ -0,0 +1,21 @@ +package bw + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newShared returns ucloud bw shared. +func newShared(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "shared", + Short: "Create and manipulate shared bandwidth instances", + Long: "Create and manipulate shared bandwidth instances", + } + cmd.AddCommand(newSharedCreate(ctx)) + cmd.AddCommand(newSharedList(ctx)) + cmd.AddCommand(newSharedResize(ctx)) + cmd.AddCommand(newSharedDelete(ctx)) + return cmd +} diff --git a/products/sharedbw/internal/bw/shared_create.go b/products/sharedbw/internal/bw/shared_create.go new file mode 100644 index 0000000000..0e33c46bb1 --- /dev/null +++ b/products/sharedbw/internal/bw/shared_create.go @@ -0,0 +1,49 @@ +package bw + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSharedCreate returns ucloud bw shared create. +func newSharedCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewAllocateShareBandwidthRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create shared bandwidth instance", + Long: "Create shared bandwidth instance", + Run: func(c *cobra.Command, args []string) { + if *req.ShareBandwidth < 20 || *req.ShareBandwidth > 5000 { + fmt.Fprintf(ctx.ProgressWriter(), "bandwidth should be between 20 and 5000. received %d\n", *req.ShareBandwidth) + return + } + resp, err := client.AllocateShareBandwidth(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "shared bandwidth[%s] created\n", resp.ShareBandwidthId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.ShareBandwidthId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.Name = flags.String("name", "", "Required. Name of the shared bandwidth instance") + req.ShareBandwidth = flags.Int("bandwidth-mb", 20, "Optional. Unit:Mb. Bandwidth of the shared bandwidth. Range [20,5000]") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.ChargeType = flags.String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") + + cmd.MarkFlagRequired("name") + + return cmd +} diff --git a/products/sharedbw/internal/bw/shared_delete.go b/products/sharedbw/internal/bw/shared_delete.go new file mode 100644 index 0000000000..40a6f5bbf0 --- /dev/null +++ b/products/sharedbw/internal/bw/shared_delete.go @@ -0,0 +1,57 @@ +package bw + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSharedDelete returns ucloud bw shared delete. +func newSharedDelete(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewReleaseShareBandwidthRequest() + ids := []string{} + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete shared bandwidth instance", + Long: "Delete shared bandwidth instance", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range ids { + id := ctx.PickResourceID(idname) + req.ShareBandwidthId = sdk.String(id) + _, err := client.ReleaseShareBandwidth(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "shared bandwidth[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&ids, "shared-bw-id", nil, "Required. Resource ID of shared bandwidth instances to delete") + req.EIPBandwidth = flags.Int("eip-bandwidth-mb", 1, "Optional. Bandwidth of the joined EIPs,after deleting the shared bandwidth instance") + req.PayMode = flags.String("traffic-mode", "", "Optional. The charge mode of joined EIPs after deleting the shared bandwidth. Accept values:Bandwidth,Traffic") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + command.SetCompletion(cmd, "shared-bw-id", func() []string { + list, _ := getAllSharedBW(ctx, *req.ProjectId, *req.Region) + return list + }) + command.SetFlagValues(cmd, "traffic-mode", "Bandwidth", "Traffic") + + cmd.MarkFlagRequired("shared-bw-id") + + return cmd +} diff --git a/products/sharedbw/internal/bw/shared_list.go b/products/sharedbw/internal/bw/shared_list.go new file mode 100644 index 0000000000..1aa7c51510 --- /dev/null +++ b/products/sharedbw/internal/bw/shared_list.go @@ -0,0 +1,61 @@ +package bw + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSharedList returns ucloud bw shared list. +func newSharedList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeShareBandwidthRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List shared bandwidth instances", + Long: "List shared bandwidth instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.DescribeShareBandwidth(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []SharedBWRow{} + for _, sb := range resp.DataSet { + row := SharedBWRow{} + row.Name = sb.Name + row.ResourceID = sb.ShareBandwidthId + row.ChargeType = sb.ChargeType + row.Bandwidth = strconv.Itoa(sb.ShareBandwidth) + "Mb" + row.ExpirationTime = common.FormatDate(sb.ExpireTime) + eipList := []string{} + for _, eip := range sb.EIPSet { + eipText := "" + eipText += eip.EIPId + for _, ip := range eip.EIPAddr { + eipText += fmt.Sprintf("/%s/%s", ip.IP, ip.OperatorName) + } + eipList = append(eipList, eipText) + } + row.EIP = strings.Join(eipList, "\n") + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + flags.StringSliceVar(&req.ShareBandwidthIds, "shared-bw-id", nil, "Resource ID of shared bandwidth instances to list") + + return cmd +} diff --git a/products/sharedbw/internal/bw/shared_resize.go b/products/sharedbw/internal/bw/shared_resize.go new file mode 100644 index 0000000000..a744fa8d9e --- /dev/null +++ b/products/sharedbw/internal/bw/shared_resize.go @@ -0,0 +1,56 @@ +package bw + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSharedResize returns ucloud bw shared resize. +func newSharedResize(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewResizeShareBandwidthRequest() + cmd := &cobra.Command{ + Use: "resize", + Short: "Resize shared bandwidth instance's bandwidth", + Long: "Resize shared bandwidth instance's bandwidth", + Run: func(c *cobra.Command, args []string) { + if *req.ShareBandwidth < 20 || *req.ShareBandwidth > 5000 { + fmt.Fprintf(ctx.ProgressWriter(), "bandwidth should be between 20 and 5000. received %d\n", *req.ShareBandwidth) + return + } + req.ShareBandwidthId = sdk.String(ctx.PickResourceID(*req.ShareBandwidthId)) + _, err := client.ResizeShareBandwidth(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "shared bandwidth[%s] resized to %dMb\n", *req.ShareBandwidthId, *req.ShareBandwidth) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.ShareBandwidthId, Action: "resize", Status: "Resized"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ShareBandwidthId = flags.String("shared-bw-id", "", "Required. Resource ID of shared bandwidth instance to resize") + req.ShareBandwidth = flags.Int("bandwidth-mb", 0, "Required. Unit:Mb. resize to bandwidth value") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetCompletion(cmd, "shared-bw-id", func() []string { + list, _ := getAllSharedBW(ctx, *req.ProjectId, *req.Region) + return list + }) + + cmd.MarkFlagRequired("shared-bw-id") + cmd.MarkFlagRequired("bandwidth-mb") + + return cmd +} diff --git a/products/sharedbw/internal/bw/status.go b/products/sharedbw/internal/bw/status.go new file mode 100644 index 0000000000..f9c852f0d0 --- /dev/null +++ b/products/sharedbw/internal/bw/status.go @@ -0,0 +1,7 @@ +package bw + +const ( + EIP_USED = "used" + + EIP_CHARGE_BANDWIDTH = "Bandwidth" +) diff --git a/products/sharedbw/product.go b/products/sharedbw/product.go new file mode 100644 index 0000000000..f4a0e24c1f --- /dev/null +++ b/products/sharedbw/product.go @@ -0,0 +1,20 @@ +package sharedbw + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalbw "github.com/ucloud/ucloud-cli/products/sharedbw/internal/bw" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "sharedbw", Commands: []string{"bw"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalbw.NewCommand(ctx)} +} diff --git a/products/sharedbw/product.yaml b/products/sharedbw/product.yaml new file mode 100644 index 0000000000..70369773ce --- /dev/null +++ b/products/sharedbw/product.yaml @@ -0,0 +1,6 @@ +name: sharedbw +owners: + - Episkey-G +commands: + - bw +enabled: true diff --git a/products/sharedbw/testdata/cmdtree.golden b/products/sharedbw/testdata/cmdtree.golden new file mode 100644 index 0000000000..f8b071e5d5 --- /dev/null +++ b/products/sharedbw/testdata/cmdtree.golden @@ -0,0 +1,41 @@ +ucloud bw use=bw short=Manipulate bandwidth package and shared bandwidth +ucloud bw pkg use=pkg short=List, create and delete bandwidth package instances +ucloud bw pkg create use=create short=Create bandwidth package + flag=bandwidth-mb short= default=0 required=true + flag=eip-id short= default=[] required=true + flag=end-time short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=start-time short= default= required=true +ucloud bw pkg delete use=delete short=Delete bandwidth packages + flag=project-id short= default= required= + flag=region short= default= required= + flag=resource-id short= default=[] required= +ucloud bw pkg list use=list short=List bandwidth packages + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= +ucloud bw shared use=shared short=Create and manipulate shared bandwidth instances +ucloud bw shared create use=create short=Create shared bandwidth instance + flag=bandwidth-mb short= default=20 required= + flag=charge-type short= default=Month required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= +ucloud bw shared delete use=delete short=Delete shared bandwidth instance + flag=eip-bandwidth-mb short= default=1 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=shared-bw-id short= default=[] required=true + flag=traffic-mode short= default= required= +ucloud bw shared list use=list short=List shared bandwidth instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=shared-bw-id short= default=[] required= +ucloud bw shared resize use=resize short=Resize shared bandwidth instance's bandwidth + flag=bandwidth-mb short= default=0 required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=shared-bw-id short= default= required=true diff --git a/products/sharedbw/testdata/completion.golden b/products/sharedbw/testdata/completion.golden new file mode 100644 index 0000000000..deb65db956 --- /dev/null +++ b/products/sharedbw/testdata/completion.golden @@ -0,0 +1,19 @@ +ucloud bw pkg create eip-id dynamic +ucloud bw pkg create project-id dynamic +ucloud bw pkg create region dynamic +ucloud bw pkg delete project-id dynamic +ucloud bw pkg delete region dynamic +ucloud bw pkg list project-id dynamic +ucloud bw pkg list region dynamic +ucloud bw shared create charge-type static Dynamic,Month,Year +ucloud bw shared create project-id dynamic +ucloud bw shared create region dynamic +ucloud bw shared delete project-id dynamic +ucloud bw shared delete region dynamic +ucloud bw shared delete shared-bw-id dynamic +ucloud bw shared delete traffic-mode static Bandwidth,Traffic +ucloud bw shared list project-id dynamic +ucloud bw shared list region dynamic +ucloud bw shared resize project-id dynamic +ucloud bw shared resize region dynamic +ucloud bw shared resize shared-bw-id dynamic diff --git a/products/subnet/internal/subnet/cmd.go b/products/subnet/internal/subnet/cmd.go new file mode 100644 index 0000000000..b143c66aeb --- /dev/null +++ b/products/subnet/internal/subnet/cmd.go @@ -0,0 +1,22 @@ +package subnet + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud subnet command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "subnet", + Short: "List, create and delete subnet", + Long: "List, create and delete subnet", + Args: cobra.NoArgs, + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newListResource(ctx)) + return cmd +} diff --git a/products/subnet/internal/subnet/completion.go b/products/subnet/internal/subnet/completion.go new file mode 100644 index 0000000000..5b255c1e91 --- /dev/null +++ b/products/subnet/internal/subnet/completion.go @@ -0,0 +1,71 @@ +package subnet + +import ( + "fmt" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpcsdk.VPCInfo, error) { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, vpc := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) + } + return list +} + +func getAllSubnets(ctx *cli.Context, vpcID, project, region string) ([]vpcsdk.SubnetInfo, error) { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := []vpcsdk.SubnetInfo{} + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeSubnet(req) + if err != nil { + ctx.HandleError(err) + return nil, err + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + return subnets, nil +} + +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + subnets, err := getAllSubnets(ctx, vpcID, project, region) + if err != nil { + return nil + } + list := []string{} + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} diff --git a/products/subnet/internal/subnet/create.go b/products/subnet/internal/subnet/create.go new file mode 100644 index 0000000000..c20e23d83a --- /dev/null +++ b/products/subnet/internal/subnet/create.go @@ -0,0 +1,67 @@ +package subnet + +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate returns ucloud subnet create. +func newCreate(ctx *cli.Context) *cobra.Command { + var segment *net.IPNet + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewCreateSubnetRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create subnet of vpc network", + Long: "Create subnet of vpc network", + Example: "ucloud subnet create --vpc-id uvnet-vpcxid --name testName --segment 192.168.2.0/24", + Run: func(cmd *cobra.Command, args []string) { + ipMaskStrs := strings.SplitN(segment.String(), "/", 2) + req.Subnet = sdk.String(ipMaskStrs[0]) + mask, err := strconv.Atoi(ipMaskStrs[1]) + if err != nil { + ctx.HandleError(err) + return + } + req.Netmask = sdk.Int(mask) + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.CreateSubnet(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "subnet[%s] created\n", resp.SubnetId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.SubnetId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.VPCId = flags.String("vpc-id", "", "Required. Assign the VPC network of the subnet") + segment = flags.IPNet("segment", net.IPNet{}, "Required. Segment of subnet. For example '192.168.0.0/24'") + req.SubnetName = flags.String("name", "Subnet", "Optional. Name of subnet to create") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.Tag = flags.String("group", "", "Optional. Business group") + req.Remark = flags.String("remark", "", "Optional. Remark of subnet to create") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("vpc-id") + cmd.MarkFlagRequired("segment") + + return cmd +} diff --git a/products/subnet/internal/subnet/delete.go b/products/subnet/internal/subnet/delete.go new file mode 100644 index 0000000000..bb904d61f1 --- /dev/null +++ b/products/subnet/internal/subnet/delete.go @@ -0,0 +1,54 @@ +package subnet + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete returns ucloud subnet delete. +func newDelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDeleteSubnetRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete subnet", + Long: "Delete subnet", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, id := range idNames { + resourceID := ctx.PickResourceID(id) + req.SubnetId = sdk.String(resourceID) + _, err := client.DeleteSubnet(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "subnet[%s] deleted\n", id) + results = append(results, cli.OpResultRow{ResourceID: resourceID, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "subnet-id", nil, "Required. Resource ID of subent") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + cmd.MarkFlagRequired("subnet-id") + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, "", *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/subnet/internal/subnet/list.go b/products/subnet/internal/subnet/list.go new file mode 100644 index 0000000000..8710e24cd5 --- /dev/null +++ b/products/subnet/internal/subnet/list.go @@ -0,0 +1,55 @@ +package subnet + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList returns ucloud subnet list. +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeSubnetRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List subnet", + Long: `List subnet`, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.DescribeSubnet(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]Row, 0) + for _, sn := range resp.DataSet { + row := Row{} + row.SubnetName = sn.SubnetName + row.ResourceID = sn.SubnetId + row.Group = sn.Tag + row.AffiliatedVPC = fmt.Sprintf("%s/%s", sn.VPCId, sn.VPCName) + row.NetworkSegment = fmt.Sprintf("%s/%s", sn.Subnet, sn.Netmask) + row.CreationTime = common.FormatDate(sn.CreateTime) + list = append(list, row) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + flags.StringSliceVar(&req.SubnetIds, "subnet-id", []string{}, "Optional. Multiple values separated by commas") + req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC") + req.Tag = flags.String("group", "", "Optional. Group") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + req.Limit = flags.Int("limit", 50, "Optional. Limit") + + return cmd +} diff --git a/products/subnet/internal/subnet/list_resource.go b/products/subnet/internal/subnet/list_resource.go new file mode 100644 index 0000000000..9392a0b100 --- /dev/null +++ b/products/subnet/internal/subnet/list_resource.go @@ -0,0 +1,56 @@ +package subnet + +import ( + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newListResource returns ucloud subnet list-resource. +func newListResource(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeSubnetResourceRequest() + cmd := &cobra.Command{ + Use: "list-resource", + Short: "List resources belong to subnet", + Long: "List resources belong to subnet", + Run: func(c *cobra.Command, args []string) { + req.SubnetId = sdk.String(ctx.PickResourceID(*req.SubnetId)) + resp, err := client.DescribeSubnetResource(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []ResourceRow{} + for _, r := range resp.DataSet { + row := ResourceRow{ + ResourceName: r.Name, + ResourceID: r.ResourceId, + ResourceType: r.ResourceType, + PrivateIP: r.IP, + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.SubnetId = flags.String("subnet-id", "", "Required. Resource ID of subnet which resources to list belong to") + req.ResourceType = flags.String("resource-type", "", "Optional. Resource type of resources to list. Accept values:'uhost','phost','ulb','uhadoophost','ufortresshost','unatgw','ukafka','umem','docker','udb','udw' and 'vip'") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + ctx.BindLimit(cmd, req) + ctx.BindOffset(cmd, req) + cmd.MarkFlagRequired("subnet-id") + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, "", *req.ProjectId, *req.Region) + }) + command.SetFlagValues(cmd, "resource-type", "uhost", "phost", "ulb", "uhadoophost", "ufortresshost", "unatgw", "ukafka", "umem", "docker", "udb", "udw", "vip") + + return cmd +} diff --git a/products/subnet/internal/subnet/rows.go b/products/subnet/internal/subnet/rows.go new file mode 100644 index 0000000000..e4bf309bc2 --- /dev/null +++ b/products/subnet/internal/subnet/rows.go @@ -0,0 +1,17 @@ +package subnet + +type Row struct { + SubnetName string + ResourceID string + Group string + AffiliatedVPC string + NetworkSegment string + CreationTime string +} + +type ResourceRow struct { + ResourceName string + ResourceID string + ResourceType string + PrivateIP string +} diff --git a/products/subnet/product.go b/products/subnet/product.go new file mode 100644 index 0000000000..44606e99ee --- /dev/null +++ b/products/subnet/product.go @@ -0,0 +1,20 @@ +package subnet + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalsubnet "github.com/ucloud/ucloud-cli/products/subnet/internal/subnet" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "subnet", Commands: []string{"subnet"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalsubnet.NewCommand(ctx)} +} diff --git a/products/subnet/product.yaml b/products/subnet/product.yaml new file mode 100644 index 0000000000..fc1da014cc --- /dev/null +++ b/products/subnet/product.yaml @@ -0,0 +1,6 @@ +name: subnet +owners: + - Episkey-G +commands: + - subnet +enabled: true diff --git a/products/subnet/testdata/cmdtree.golden b/products/subnet/testdata/cmdtree.golden new file mode 100644 index 0000000000..c16587d310 --- /dev/null +++ b/products/subnet/testdata/cmdtree.golden @@ -0,0 +1,28 @@ +ucloud subnet use=subnet short=List, create and delete subnet +ucloud subnet create use=create short=Create subnet of vpc network + flag=group short= default= required= + flag=name short= default=Subnet required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=segment short= default= required=true + flag=vpc-id short= default= required=true +ucloud subnet delete use=delete short=Delete subnet + flag=project-id short= default= required= + flag=region short= default= required= + flag=subnet-id short= default=[] required=true +ucloud subnet list use=list short=List subnet + flag=group short= default= required= + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=subnet-id short= default=[] required= + flag=vpc-id short= default= required= +ucloud subnet list-resource use=list-resource short=List resources belong to subnet + flag=limit short= default=100 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=resource-type short= default= required= + flag=subnet-id short= default= required=true diff --git a/products/subnet/testdata/completion.golden b/products/subnet/testdata/completion.golden new file mode 100644 index 0000000000..7da48b41b2 --- /dev/null +++ b/products/subnet/testdata/completion.golden @@ -0,0 +1,12 @@ +ucloud subnet create project-id dynamic +ucloud subnet create region dynamic +ucloud subnet create vpc-id dynamic +ucloud subnet delete project-id dynamic +ucloud subnet delete region dynamic +ucloud subnet delete subnet-id dynamic +ucloud subnet list project-id dynamic +ucloud subnet list region dynamic +ucloud subnet list-resource project-id dynamic +ucloud subnet list-resource region dynamic +ucloud subnet list-resource resource-type static docker,phost,udb,udw,ufortresshost,uhadoophost,uhost,ukafka,ulb,umem,unatgw,vip +ucloud subnet list-resource subnet-id dynamic diff --git a/products/udb/product.yaml b/products/udb/product.yaml deleted file mode 100644 index 191ce6f1ed..0000000000 --- a/products/udb/product.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# products/udb/product.yaml — udb 产品元数据(归属真源,owner 自治维护) -name: udb -owners: - - Episkey-G -commands: - - mysql -enabled: true diff --git a/products/udpn/internal/udpn/cmd.go b/products/udpn/internal/udpn/cmd.go new file mode 100644 index 0000000000..58fd0126f2 --- /dev/null +++ b/products/udpn/internal/udpn/cmd.go @@ -0,0 +1,23 @@ +package udpn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `udpn` root command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "udpn", + Short: "List and manipulate udpn instances", + Long: "List and manipulate udpn instances", + } + + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newModifyBW(ctx)) + + return cmd +} diff --git a/products/udpn/internal/udpn/completion.go b/products/udpn/internal/udpn/completion.go new file mode 100644 index 0000000000..3423b9254e --- /dev/null +++ b/products/udpn/internal/udpn/completion.go @@ -0,0 +1,43 @@ +package udpn + +import ( + "fmt" + + udpnsdk "github.com/ucloud/ucloud-sdk-go/services/udpn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllUDPNIns(ctx *cli.Context, project, region string) ([]udpnsdk.UDPNData, error) { + client := cli.NewServiceClient(ctx, udpnsdk.NewClient) + req := client.NewDescribeUDPNRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + list := make([]udpnsdk.UDPNData, 0) + for offset, limit := 0, 50; ; offset += limit { + req.Offset = sdk.Int(offset) + req.Limit = sdk.Int(limit) + resp, err := client.DescribeUDPN(req) + if err != nil { + return nil, err + } + list = append(list, resp.DataSet...) + if offset+limit > resp.TotalCount { + break + } + } + return list, nil +} + +func getAllUDPNIdNames(ctx *cli.Context, project, region string) []string { + udpnInsList, err := getAllUDPNIns(ctx, project, region) + if err != nil { + return nil + } + idNameList := []string{} + for _, udpn := range udpnInsList { + idNameList = append(idNameList, fmt.Sprintf("%s/%s:%s", udpn.UDPNId, udpn.Peer1, udpn.Peer2)) + } + return idNameList +} diff --git a/products/udpn/internal/udpn/create.go b/products/udpn/internal/udpn/create.go new file mode 100644 index 0000000000..ff8fd496ab --- /dev/null +++ b/products/udpn/internal/udpn/create.go @@ -0,0 +1,68 @@ +package udpn + +import ( + "fmt" + + "github.com/spf13/cobra" + + udpnsdk "github.com/ucloud/ucloud-sdk-go/services/udpn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud udpn create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, udpnsdk.NewClient) + req := client.NewAllocateUDPNRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create UDPN tunnel", + Long: "Create UDPN tunnel", + Run: func(c *cobra.Command, args []string) { + if *req.Peer1 == *req.Peer2 { + fmt.Fprintln(ctx.ProgressWriter(), "Error, flags peer1 and peer2 can't be equal") + return + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.AllocateUDPN(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "udpn[%s] created\n", resp.UDPNId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.UDPNId, Action: "create", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.Peer1 = flags.String("peer1", ctx.DefaultRegion(), "Required. One end of the tunnel to create") + req.Peer2 = flags.String("peer2", "", "Required. The other end of the tunnel create") + req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. Bandwidth of the tunnel to create. Unit:Mb. Rnange [2,1000]") + req.ChargeType = flags.String("charge-type", "", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + ctx.SetCompletion(cmd, "peer1", ctx.RegionList) + ctx.SetCompletion(cmd, "peer2", func() []string { + regions := ctx.RegionList() + list := []string{} + for _, r := range regions { + if r != *req.Peer1 { + list = append(list, r) + } + } + return list + }) + + cmd.MarkFlagRequired("peer1") + cmd.MarkFlagRequired("peer2") + cmd.MarkFlagRequired("bandwidth-mb") + + return cmd +} diff --git a/products/udpn/internal/udpn/delete.go b/products/udpn/internal/udpn/delete.go new file mode 100644 index 0000000000..63a584af9a --- /dev/null +++ b/products/udpn/internal/udpn/delete.go @@ -0,0 +1,53 @@ +package udpn + +import ( + "fmt" + + "github.com/spf13/cobra" + + udpnsdk "github.com/ucloud/ucloud-sdk-go/services/udpn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud udpn delete +func newDelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, udpnsdk.NewClient) + req := client.NewReleaseUDPNRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "delete udpn instances", + Long: "delete udpn instances", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.UDPNId = sdk.String(id) + _, err := client.ReleaseUDPN(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "udpn[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "udpn-id", nil, "Required. Resource ID of udpn instances to delete") + ctx.BindProjectID(cmd, req) + + ctx.SetCompletion(cmd, "udpn-id", func() []string { + return getAllUDPNIdNames(ctx, *req.ProjectId, ctx.DefaultRegion()) + }) + + cmd.MarkFlagRequired("udpn-id") + + return cmd +} diff --git a/products/udpn/internal/udpn/list.go b/products/udpn/internal/udpn/list.go new file mode 100644 index 0000000000..d14671a4ef --- /dev/null +++ b/products/udpn/internal/udpn/list.go @@ -0,0 +1,58 @@ +package udpn + +import ( + "fmt" + + "github.com/spf13/cobra" + + udpnsdk "github.com/ucloud/ucloud-sdk-go/services/udpn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList ucloud udpn list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, udpnsdk.NewClient) + req := client.NewDescribeUDPNRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List udpn instances", + Long: "List udpn instances", + Run: func(c *cobra.Command, args []string) { + req.UDPNId = sdk.String(ctx.PickResourceID(*req.UDPNId)) + resp, err := client.DescribeUDPN(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []UDPNRow{} + for _, udpn := range resp.DataSet { + list = append(list, UDPNRow{ + ResourceID: udpn.UDPNId, + Peers: fmt.Sprintf("%s <--> %s", udpn.Peer1, udpn.Peer2), + Bandwidth: fmt.Sprintf("%dMb", udpn.Bandwidth), + ChargeType: udpn.ChargeType, + CreationTime: common.FormatDate(udpn.CreateTime), + }) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.UDPNId = flags.String("udpn-id", "", "Optional. Resource ID of udpn instances to list") + ctx.BindOffset(cmd, req) + req.Limit = flags.Int("limit", 50, "Optional. Limit") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + ctx.SetCompletion(cmd, "udpn-id", func() []string { + return getAllUDPNIdNames(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/udpn/internal/udpn/modify_bw.go b/products/udpn/internal/udpn/modify_bw.go new file mode 100644 index 0000000000..5eec0cdf65 --- /dev/null +++ b/products/udpn/internal/udpn/modify_bw.go @@ -0,0 +1,56 @@ +package udpn + +import ( + "fmt" + + "github.com/spf13/cobra" + + udpnsdk "github.com/ucloud/ucloud-sdk-go/services/udpn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newModifyBW ucloud udpn modify-bw +func newModifyBW(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, udpnsdk.NewClient) + req := client.NewModifyUDPNBandwidthRequest() + cmd := &cobra.Command{ + Use: "modify-bw", + Short: "Modify bandwidth of UDPN tunnel", + Long: "Modify bandwidth of UDPN tunnel", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.UDPNId = sdk.String(id) + _, err := client.ModifyUDPNBandwidth(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "udpn[%s]'s bandwidth modified\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "modify-bw", Status: "Modified"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "udpn-id", nil, "Required. Resource ID of UDPN to modify bandwidth") + req.Bandwidth = flags.Int("bandwidth-mb", 0, "Required. Bandwidth of UDPN tunnel. Unit:Mb. Range [2,1000]") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Region, see 'ucloud region'") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + ctx.SetCompletion(cmd, "udpn-id", func() []string { + return getAllUDPNIdNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("udpn-id") + cmd.MarkFlagRequired("bandwidth-mb") + + return cmd +} diff --git a/products/udpn/internal/udpn/rows.go b/products/udpn/internal/udpn/rows.go new file mode 100644 index 0000000000..1dd4af6d62 --- /dev/null +++ b/products/udpn/internal/udpn/rows.go @@ -0,0 +1,10 @@ +package udpn + +// UDPNRow is the table row for `ucloud udpn list`. +type UDPNRow struct { + ResourceID string + Peers string + Bandwidth string + ChargeType string + CreationTime string +} diff --git a/products/udpn/product.go b/products/udpn/product.go new file mode 100644 index 0000000000..f0504eb686 --- /dev/null +++ b/products/udpn/product.go @@ -0,0 +1,21 @@ +package udpn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internaludpn "github.com/ucloud/ucloud-cli/products/udpn/internal/udpn" +) + +type product struct{} + +// New returns the udpn product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "udpn", Commands: []string{"udpn"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internaludpn.NewCommand(ctx)} +} diff --git a/products/udpn/product.yaml b/products/udpn/product.yaml new file mode 100644 index 0000000000..06c927ec9d --- /dev/null +++ b/products/udpn/product.yaml @@ -0,0 +1,7 @@ +# products/udpn/product.yaml - UDPN product metadata. +name: udpn +owners: + - Episkey-G +commands: + - udpn +enabled: true diff --git a/products/udpn/testdata/cmdtree.golden b/products/udpn/testdata/cmdtree.golden new file mode 100644 index 0000000000..8d5488e22e --- /dev/null +++ b/products/udpn/testdata/cmdtree.golden @@ -0,0 +1,22 @@ +ucloud udpn use=udpn short=List and manipulate udpn instances +ucloud udpn create use=create short=Create UDPN tunnel + flag=bandwidth-mb short= default=0 required=true + flag=charge-type short= default= required= + flag=peer1 short= default= required=true + flag=peer2 short= default= required=true + flag=project-id short= default= required= + flag=quantity short= default=1 required= +ucloud udpn delete use=delete short=delete udpn instances + flag=project-id short= default= required= + flag=udpn-id short= default=[] required=true +ucloud udpn list use=list short=List udpn instances + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=udpn-id short= default= required= +ucloud udpn modify-bw use=modify-bw short=Modify bandwidth of UDPN tunnel + flag=bandwidth-mb short= default=0 required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=udpn-id short= default=[] required=true diff --git a/products/udpn/testdata/completion.golden b/products/udpn/testdata/completion.golden new file mode 100644 index 0000000000..9c8c280253 --- /dev/null +++ b/products/udpn/testdata/completion.golden @@ -0,0 +1,10 @@ +ucloud udpn create charge-type static Dynamic,Month,Year +ucloud udpn create peer1 dynamic +ucloud udpn create peer2 dynamic +ucloud udpn create project-id dynamic +ucloud udpn delete project-id dynamic +ucloud udpn delete udpn-id dynamic +ucloud udpn list project-id dynamic +ucloud udpn list region dynamic +ucloud udpn list udpn-id dynamic +ucloud udpn modify-bw udpn-id dynamic diff --git a/products/uhost/internal/uhost/create_image.go b/products/uhost/internal/uhost/create_image.go index 6eb59e1cce..71d2b9f14c 100644 --- a/products/uhost/internal/uhost/create_image.go +++ b/products/uhost/internal/uhost/create_image.go @@ -36,6 +36,7 @@ func newCreateImage(ctx *cli.Context) *cobra.Command { } else { ctx.PollerTo(w, describeImageByID(ctx, *req.ProjectId, *req.Region, *req.Zone)).Spoll(resp.ImageId, text, []string{IMAGE_AVAILABLE, IMAGE_UNAVAILABLE}) } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.ImageId, Action: "create", Status: "Making"}) }, } flags := cmd.Flags() diff --git a/products/uhost/product.go b/products/uhost/product.go index 065765c81b..2e074b41cd 100644 --- a/products/uhost/product.go +++ b/products/uhost/product.go @@ -4,22 +4,17 @@ import ( "github.com/spf13/cobra" "github.com/ucloud/ucloud-cli/pkg/cli" - internalimage "github.com/ucloud/ucloud-cli/products/uhost/internal/image" internaluhost "github.com/ucloud/ucloud-cli/products/uhost/internal/uhost" ) type product struct{} -// New returns the uhost product (registered via hack/gen-products). func New() cli.Product { return product{} } func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "uhost", Commands: []string{"uhost", "image"}} + return cli.Metadata{Name: "uhost", Commands: []string{"uhost"}} } func (product) NewCommand(ctx *cli.Context) []*cobra.Command { - return []*cobra.Command{ - internaluhost.NewCommand(ctx), - internalimage.NewCommand(ctx), - } + return []*cobra.Command{internaluhost.NewCommand(ctx)} } diff --git a/products/uhost/product.yaml b/products/uhost/product.yaml index 09eca5c583..2e066560ab 100644 --- a/products/uhost/product.yaml +++ b/products/uhost/product.yaml @@ -1,8 +1,6 @@ -# products/uhost/product.yaml — uhost 产品元数据(归属真源,owner 自治维护) name: uhost owners: - Episkey-G commands: - uhost - - image enabled: true diff --git a/products/uhost/testdata/cmdtree.golden b/products/uhost/testdata/cmdtree.golden index bef63ecb92..a329dba1bd 100644 --- a/products/uhost/testdata/cmdtree.golden +++ b/products/uhost/testdata/cmdtree.golden @@ -1,36 +1,3 @@ -ucloud image use=image short=List and manipulate images -ucloud image copy use=copy short=Copy custom images - flag=async short= default=false required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=source-image-id short= default=[] required=true - flag=target-image-desc short= default= required= - flag=target-image-name short= default= required= - flag=target-project short= default= required= - flag=target-region short= default= required= - flag=zone short= default= required= -ucloud image create use=create short=Create image from an uhost instance - flag=async short=a default=false required= - flag=image-desc short= default= required= - flag=image-name short= default= required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=uhost-id short= default= required=true - flag=zone short= default= required= -ucloud image delete use=delete short=Delete custom images - flag=image-id short= default=[] required=true - flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= -ucloud image list use=list short=List image - flag=image-id short= default= required= - flag=image-type short= default=Base required= - flag=limit short= default=500 required= - flag=offset short= default=0 required= - flag=os-type short= default= required= - flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= ucloud uhost use=uhost short=List,create,delete,stop,restart,poweroff or resize UHost instance ucloud uhost clone use=clone short=Create an uhost with the same configuration as another uhost, excluding bound eip and udisk flag=async short= default=false required= diff --git a/products/uhost/testdata/completion.golden b/products/uhost/testdata/completion.golden index 0314fbfe0f..e83163b58b 100644 --- a/products/uhost/testdata/completion.golden +++ b/products/uhost/testdata/completion.golden @@ -1,12 +1,3 @@ -ucloud image copy project-id dynamic -ucloud image copy region dynamic -ucloud image copy source-image-id dynamic -ucloud image copy target-project dynamic -ucloud image copy target-region dynamic -ucloud image copy zone dynamic -ucloud image create uhost-id dynamic -ucloud image delete image-id dynamic -ucloud image list image-type static Base,Business,Custom ucloud uhost clone uhost-id dynamic ucloud uhost create bind-eip dynamic ucloud uhost create charge-type static Dynamic,Month,Trial,Year diff --git a/products/ulb/internal/ulb/cmd.go b/products/ulb/internal/ulb/cmd.go new file mode 100644 index 0000000000..f165967f30 --- /dev/null +++ b/products/ulb/internal/ulb/cmd.go @@ -0,0 +1,23 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud ulb command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "ulb", + Short: "List and manipulate ULB instances", + Long: "List and manipulate ULB instances", + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newUpdate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newVServer(ctx)) + cmd.AddCommand(newSSL(ctx)) + return cmd +} diff --git a/products/ulb/internal/ulb/completion.go b/products/ulb/internal/ulb/completion.go new file mode 100644 index 0000000000..b2d391cf0a --- /dev/null +++ b/products/ulb/internal/ulb/completion.go @@ -0,0 +1,156 @@ +package ulb + +import ( + "fmt" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllULB(ctx *cli.Context, project, region string) ([]ulbsdk.ULBSet, error) { + list := []ulbsdk.ULBSet{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeULBRequest() + req.ProjectId = &project + req.Region = ®ion + + for offset, limit := 0, 50; ; offset += limit { + req.Offset = sdk.Int(offset) + req.Limit = sdk.Int(limit) + resp, err := client.DescribeULB(req) + if err != nil { + return nil, err + } + list = append(list, resp.DataSet...) + + if resp.TotalCount < offset+limit { + break + } + } + return list, nil +} + +func getAllULBIDNames(ctx *cli.Context, project, region string) []string { + list := []string{} + ulbList, err := getAllULB(ctx, project, region) + if err != nil { + return nil + } + for _, ulb := range ulbList { + list = append(list, fmt.Sprintf("%s/%s", ulb.ULBId, ulb.Name)) + } + return list +} + +func getAllVServers(ctx *cli.Context, ulbID, vserverID, project, region string) ([]ulbsdk.ULBVServerSet, error) { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeVServerRequest() + req.ULBId = sdk.String(cli.PickResourceID(ulbID)) + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = ®ion + if vserverID != "" { + req.VServerId = sdk.String(cli.PickResourceID(vserverID)) + } + resp, err := client.DescribeVServer(req) + if err != nil { + return nil, err + } + if vserverID != "" { + if len(resp.DataSet) < 1 { + return nil, fmt.Errorf("VServer[%s] may not exist", vserverID) + } else if len(resp.DataSet) > 1 { + return nil, fmt.Errorf("Internal Error, too many vserver:%#v", resp.DataSet) + } + } + return resp.DataSet, nil +} + +func getAllVServerIDNames(ctx *cli.Context, ulbID, project, region string) []string { + vservers, err := getAllVServers(ctx, ulbID, "", project, region) + if err != nil { + return nil + } + idNames := []string{} + for _, vs := range vservers { + idNames = append(idNames, fmt.Sprintf("%s/%s", vs.VServerId, vs.VServerName)) + } + return idNames +} + +func getAllBackendNodes(ctx *cli.Context, ulbID, vserverID, project, region string) ([]ulbsdk.ULBBackendSet, error) { + vsList, err := getAllVServers(ctx, ulbID, vserverID, project, region) + if err != nil { + return nil, err + } + nodeList := []ulbsdk.ULBBackendSet{} + for _, vs := range vsList { + nodeList = append(nodeList, vs.BackendSet...) + } + return nodeList, nil +} + +func getAllBackendNodeIDNames(ctx *cli.Context, ulbID, vserverID, project, region string) []string { + nodeList, err := getAllBackendNodes(ctx, ulbID, vserverID, project, region) + if err != nil { + return nil + } + idNames := []string{} + for _, node := range nodeList { + idNames = append(idNames, fmt.Sprintf("%s/%s", node.BackendId, node.ResourceName)) + } + return idNames +} + +func getAllSSLCertIDNames(ctx *cli.Context, project, region string) []string { + sslcs, err := getAllSSLCerts(ctx, project, region) + if err != nil { + return nil + } + idNames := []string{} + for _, ssl := range sslcs { + idNames = append(idNames, fmt.Sprintf("%s/%s", ssl.SSLId, ssl.SSLName)) + } + return idNames +} + +func getAllSSLCerts(ctx *cli.Context, project, region string) ([]ulbsdk.ULBSSLSet, error) { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeSSLRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + list := []ulbsdk.ULBSSLSet{} + for offset, limit := 0, 50; ; offset += limit { + req.Offset = sdk.Int(offset) + req.Limit = sdk.Int(limit) + resp, err := client.DescribeSSL(req) + if err != nil { + return nil, err + } + list = append(list, resp.DataSet...) + if resp.TotalCount <= offset+limit { + break + } + } + return list, nil +} + +func getSSLCertByID(ctx *cli.Context, sslID, project, region string) (*ulbsdk.ULBSSLSet, error) { + if sslID == "" { + return nil, fmt.Errorf("ssl certificate resource id can't be empty") + } + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeSSLRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + req.SSLId = sdk.String(cli.PickResourceID(sslID)) + resp, err := client.DescribeSSL(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) <= 0 { + return nil, fmt.Errorf("ssl certificate[%s] is not exists", sslID) + } + return &resp.DataSet[0], nil +} diff --git a/products/ulb/internal/ulb/create.go b/products/ulb/internal/ulb/create.go new file mode 100644 index 0000000000..2e0fae512a --- /dev/null +++ b/products/ulb/internal/ulb/create.go @@ -0,0 +1,122 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate returns ucloud ulb create. +func newCreate(ctx *cli.Context) *cobra.Command { + var bindEipID *string + mode := "outer" + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + unetClient := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewCreateULBRequest() + eipReq := unetClient.NewAllocateEIPRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create ULB instance", + Long: "Create ULB instance", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + if mode == "outer" { + if *bindEipID == "" && *eipReq.Bandwidth == 0 { + fmt.Fprintln(ctx.ProgressWriter(), "Outer mode ULB need a eip to bind, please assign eip by flag 'bind-eip' or create eip by 'create-eip-bandwidth-mb'") + return + } + if *eipReq.OperatorName == "" { + *eipReq.OperatorName = getEIPLine(*req.Region) + } + req.OuterMode = sdk.String("Yes") + } else if mode == "inner" { + req.InnerMode = sdk.String("Yes") + } else { + fmt.Fprintln(ctx.ProgressWriter(), "Error, flag mode should be 'outer' or 'inner'") + return + } + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + req.SubnetId = sdk.String(ctx.PickResourceID(*req.SubnetId)) + resp, err := client.CreateULB(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb[%s] created\n", resp.ULBId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.ULBId, Action: "create", Status: "Created"}) + if mode == "inner" { + return + } + bindEipID = sdk.String(ctx.PickResourceID(*bindEipID)) + if *bindEipID != "" { + _ = bindEIP(ctx, sdk.String(resp.ULBId), sdk.String("ulb"), bindEipID, req.ProjectId, req.Region) + return + } + if *eipReq.OperatorName != "" && *eipReq.Bandwidth != 0 { + eipReq.ChargeType = req.ChargeType + eipReq.Tag = req.Tag + eipReq.Region = req.Region + eipReq.ProjectId = req.ProjectId + eipResp, err := unetClient.AllocateEIP(eipReq) + + if err != nil { + ctx.HandleError(err) + return + } + + for _, eip := range eipResp.EIPSet { + fmt.Fprintf(ctx.ProgressWriter(), "allocate EIP[%s] ", eip.EIPId) + for _, ip := range eip.EIPAddr { + fmt.Fprintf(ctx.ProgressWriter(), "IP:%s Line:%s \n", ip.IP, ip.OperatorName) + } + _ = bindEIP(ctx, sdk.String(resp.ULBId), sdk.String("ulb"), sdk.String(eip.EIPId), req.ProjectId, req.Region) + } + } + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBName = flags.String("name", "", "Required. Name of ULB instance to create") + flags.StringVar(&mode, "mode", "outer", "Required. Network mode of ULB instance, outer or inner.") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC which the ULB to create belong to. See 'ucloud vpc list'") + req.SubnetId = flags.String("subnet-id", "", "Optional. Resource ID of subnet. This flag will be discarded when you are creating an outter mode ULB. See 'ucloud subnet list'") + req.ChargeType = flags.String("charge-type", "Month", "Optional.'Year',pay yearly;'Month',pay monthly;'Dynamic', pay hourly") + req.Tag = flags.String("group", "Default", "Optional. Business group") + req.Remark = flags.String("remark", "", "Optional. Remark of instance to create.") + bindEipID = flags.String("bind-eip", "", "Optional. Resource ID or IP Address of eip that will be bound to the new created outer mode ulb") + eipReq.Bandwidth = cmd.Flags().Int("create-eip-bandwidth-mb", 0, "Optional. Required if you want to create new EIP. Bandwidth(Unit:Mbps).The range of value related to network charge mode. By traffic [1, 300]; by bandwidth [1,800] (Unit: Mbps); it could be 0 if the eip belong to the shared bandwidth") + eipReq.OperatorName = flags.String("create-eip-line", "", "Optional. Line of created eip to bind with the new created outer mode ulb") + eipReq.PayMode = cmd.Flags().String("create-eip-traffic-mode", "Bandwidth", "Optional. 'Traffic','Bandwidth' or 'ShareBandwidth'") + eipReq.Name = flags.String("create-eip-name", "", "Optional. Name of created eip to bind with the new created outer mode ulb") + eipReq.Remark = cmd.Flags().String("create-eip-remark", "", "Optional. Remark of your EIP.") + + command.SetFlagValues(cmd, "mode", "outer", "inner") + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") + command.SetFlagValues(cmd, "create-eip-line", "BGP", "International") + command.SetFlagValues(cmd, "create-eip-traffic-mode", "Bandwidth", "Traffic", "ShareBandwidth") + command.SetCompletion(cmd, "bind-eip", func() []string { + return getAllEip(ctx, *req.ProjectId, *req.Region, []string{EIP_FREE}, nil) + }) + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, *req.VPCId, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("mode") + cmd.MarkFlagRequired("name") + + return cmd +} diff --git a/products/ulb/internal/ulb/delete.go b/products/ulb/internal/ulb/delete.go new file mode 100644 index 0000000000..3eb328efd0 --- /dev/null +++ b/products/ulb/internal/ulb/delete.go @@ -0,0 +1,56 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete returns ucloud ulb delete. +func newDelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDeleteULBRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete ULB instances by resource ID", + Long: "Delete ULB instances by resource ID", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.ULBId = sdk.String(id) + _, err := client.DeleteULB(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "ulb-id", nil, "Required. Resource ID of the ULB instances to delete") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/eip.go b/products/ulb/internal/ulb/eip.go new file mode 100644 index 0000000000..6de978a5cd --- /dev/null +++ b/products/ulb/internal/ulb/eip.go @@ -0,0 +1,126 @@ +package ulb + +import ( + "fmt" + "net" + "strings" + + "github.com/ucloud/ucloud-sdk-go/services/unet" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func bindEIP(ctx *cli.Context, resourceID, resourceType, eipID, projectID, region *string) error { + ip := net.ParseIP(*eipID) + if ip != nil { + id, err := getEIPIDbyIP(ctx, ip, *projectID, *region) + if err != nil { + ctx.HandleError(err) + } else { + *eipID = id + } + } + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewBindEIPRequest() + req.ResourceId = resourceID + req.ResourceType = resourceType + req.EIPId = sdk.String(ctx.PickResourceID(*eipID)) + req.ProjectId = sdk.String(ctx.PickResourceID(*projectID)) + req.Region = region + _, err := client.BindEIP(req) + if err != nil { + ctx.HandleError(err) + return err + } + fmt.Fprintf(ctx.ProgressWriter(), "bind EIP[%s] with %s[%s]\n", *req.EIPId, *req.ResourceType, *req.ResourceId) + return nil +} + +func getEIPIDbyIP(ctx *cli.Context, ip net.IP, projectID, region string) (string, error) { + eipList, err := fetchAllEip(ctx, projectID, region) + if err != nil { + return "", err + } + for _, eip := range eipList { + for _, addr := range eip.EIPAddr { + if addr.IP == ip.String() { + return eip.EIPId, nil + } + } + } + return "", fmt.Errorf("IP[%s] not exist", ip.String()) +} + +func fetchAllEip(ctx *cli.Context, projectID, region string) ([]unet.UnetEIPSet, error) { + client := cli.NewServiceClient(ctx, unet.NewClient) + req := client.NewDescribeEIPRequest() + list := []unet.UnetEIPSet{} + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + for offset, step := 0, 100; ; offset += step { + req.Offset = &offset + req.Limit = &step + resp, err := client.DescribeEIP(req) + if err != nil { + return nil, err + } + for i, size := 0, len(resp.EIPSet); i < size; i++ { + list = append(list, resp.EIPSet[i]) + } + if resp.TotalCount <= offset+step { + break + } + } + return list, nil +} + +func getAllEip(ctx *cli.Context, projectID, region string, states, paymodes []string) []string { + list, err := fetchAllEip(ctx, projectID, region) + if err != nil { + return nil + } + strs := []string{} + for _, item := range list { + rightState := false + if states == nil { + rightState = true + } else { + for _, s := range states { + if item.Status == s { + rightState = true + } + } + } + + rightPayMode := false + if paymodes == nil { + rightPayMode = true + } else { + for _, m := range paymodes { + if item.PayMode == m { + rightPayMode = true + } + } + } + if !rightPayMode || !rightState { + continue + } + + ips := []string{} + for _, ip := range item.EIPAddr { + ips = append(ips, ip.IP) + } + strs = append(strs, item.EIPId+"/"+strings.Join(ips, ",")) + } + return strs +} + +func getEIPLine(region string) (line string) { + if strings.HasPrefix(region, "cn") { + line = "BGP" + } else { + line = "International" + } + return +} diff --git a/products/ulb/internal/ulb/list.go b/products/ulb/internal/ulb/list.go new file mode 100644 index 0000000000..2e2047f676 --- /dev/null +++ b/products/ulb/internal/ulb/list.go @@ -0,0 +1,76 @@ +package ulb + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newList returns ucloud ulb list. +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeULBRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ULB instances", + Long: "List ULB instances", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + resp, err := client.DescribeULB(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []Row{} + for _, ulb := range resp.DataSet { + row := Row{} + row.ResourceID = ulb.ULBId + row.Name = ulb.Name + row.Group = ulb.BusinessId + row.VserverCount = len(ulb.VServerSet) + row.VPC = ulb.VPCId + row.CreationTime = common.FormatDate(ulb.CreateTime) + if ulb.ULBType == "OuterMode" { + ips := []string{} + for _, ip := range ulb.IPSet { + ips = append(ips, fmt.Sprintf("%s(%s)", ip.EIP, ip.EIPId)) + } + row.Network = strings.Join(ips, ",") + } else { + row.Network = ulb.PrivateIP + } + list = append(list, row) + } + + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + req.ULBId = flags.String("ulb-id", "", "Optional. Resource ID of ULB instance to list") + req.VPCId = flags.String("vpc-id", "", "Optional. Resource ID of VPC which the ULB instances to list belong to") + req.SubnetId = flags.String("subnet-id", "", "Optional. Resource ID of subnet which the ULB instances to list belong to") + req.BusinessId = flags.String("group", "", "Optional. Business group of ULB instances to list") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + req.Limit = flags.Int("limit", 50, "Optional. Limit") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/ulb/internal/ulb/read_file.go b/products/ulb/internal/ulb/read_file.go new file mode 100644 index 0000000000..35b7c4a0e3 --- /dev/null +++ b/products/ulb/internal/ulb/read_file.go @@ -0,0 +1,11 @@ +package ulb + +import "os" + +func readFile(file string) (string, error) { + byts, err := os.ReadFile(file) + if err != nil { + return "", err + } + return string(byts), nil +} diff --git a/products/ulb/internal/ulb/rows.go b/products/ulb/internal/ulb/rows.go new file mode 100644 index 0000000000..6809fd494b --- /dev/null +++ b/products/ulb/internal/ulb/rows.go @@ -0,0 +1,53 @@ +package ulb + +type Row struct { + Name string + ResourceID string + Group string + Network string + VserverCount int + VPC string + CreationTime string +} + +type VServerRow struct { + VServerName string + ResourceID string + ListenType string + Protocol string + Port int + LBMethod string + SessionMaintainMode string + SessionMaintainKey string + ClientTimeout string + HealthCheckMode string + HealthCheckDomain string + HealthCheckPath string +} + +type BackendRow struct { + Name string + ResourceID string + BackendID string + PrivateIP string + Port int + HealthCheck string + NodeMode string + Weight int +} + +type PolicyRow struct { + ForwardMethod string + Expression string + PolicyID string + PolicyType string + Backends string +} + +type SSLCertificate struct { + Name string + ResourceID string + MD5 string + BindResource string + UploadTime string +} diff --git a/products/ulb/internal/ulb/ssl.go b/products/ulb/internal/ulb/ssl.go new file mode 100644 index 0000000000..97178dd0ae --- /dev/null +++ b/products/ulb/internal/ulb/ssl.go @@ -0,0 +1,23 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSSL returns ucloud ulb ssl. +func newSSL(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "ssl", + Short: "List and manipulate SSL Certificates for ULB", + Long: "List and manipulate SSL Certificates for ULB", + } + cmd.AddCommand(newSSLList(ctx)) + cmd.AddCommand(newSSLDescribe(ctx)) + cmd.AddCommand(newSSLAdd(ctx)) + cmd.AddCommand(newSSLDelete(ctx)) + cmd.AddCommand(newSSLBind(ctx)) + cmd.AddCommand(newSSLUnbind(ctx)) + return cmd +} diff --git a/products/ulb/internal/ulb/ssl_add.go b/products/ulb/internal/ulb/ssl_add.go new file mode 100644 index 0000000000..39c34e166f --- /dev/null +++ b/products/ulb/internal/ulb/ssl_add.go @@ -0,0 +1,97 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSSLAdd returns ucloud ulb ssl add. +func newSSLAdd(ctx *cli.Context) *cobra.Command { + var allPath, sitePath, keyPath, caPath *string + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewCreateSSLRequest() + cmd := &cobra.Command{ + Use: "add", + Short: "Add SSL Certificate", + Long: "Add SSL Certificate", + Run: func(c *cobra.Command, args []string) { + if *allPath == "" && (*sitePath == "" || *keyPath == "") { + fmt.Fprintln(ctx.ProgressWriter(), "if all-in-one-file is omitted, site-certificate-file and private-key-file can't be empty") + return + } + if *allPath != "" { + content, err := readFile(*allPath) + if err != nil { + ctx.HandleError(err) + return + } + req.SSLContent = &content + } + if *sitePath != "" { + content, err := readFile(*sitePath) + if err != nil { + ctx.HandleError(err) + return + } + req.UserCert = &content + } + if *keyPath != "" { + content, err := readFile(*keyPath) + if err != nil { + ctx.HandleError(err) + return + } + req.PrivateKey = &content + } + if *caPath != "" { + content, err := readFile(*caPath) + if err != nil { + ctx.HandleError(err) + return + } + req.CaCert = &content + } + + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.CreateSSL(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] added\n", resp.SSLId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.SSLId, Action: "add-ssl", Status: "Added"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.SSLName = flags.String("name", "", "Required. Name of ssl certificate to add") + req.SSLType = flags.String("format", "Pem", "Optional. Format of ssl certificate") + allPath = flags.String("all-in-one-file", "", "Optional. Path of file which contain the complete content of the SSL certificate, including the content of site certificate, the private key which encrypted the site certificate, and the CA certificate. ") + sitePath = flags.String("site-certificate-file", "", "Optional. Path of user's certificate file, *.crt. Required if all-in-one-file is omitted") + keyPath = flags.String("private-key-file", "", "Optional. Path of private key file, *.key. Required if all-in-one-file is omitted") + caPath = flags.String("ca-certificate-file", "", "Optional. Path of CA certificate file, *.crt") + cmd.MarkFlagRequired("name") + ctx.SetCompletion(cmd, "all-in-one-file", func() []string { + return common.GetFileList("") + }) + ctx.SetCompletion(cmd, "private-key-file", func() []string { + return common.GetFileList(".key") + }) + ctx.SetCompletion(cmd, "ca-certificate-file", func() []string { + return common.GetFileList(".crt") + }) + ctx.SetCompletion(cmd, "site-certificate-file", func() []string { + return common.GetFileList(".crt") + }) + return cmd +} diff --git a/products/ulb/internal/ulb/ssl_bind.go b/products/ulb/internal/ulb/ssl_bind.go new file mode 100644 index 0000000000..6a6d526050 --- /dev/null +++ b/products/ulb/internal/ulb/ssl_bind.go @@ -0,0 +1,58 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSSLBind returns ucloud ulb ssl bind. +func newSSLBind(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewBindSSLRequest() + cmd := &cobra.Command{ + Use: "bind", + Short: "Bind SSL Certificate with VServer", + Long: "Bind SSL Certificate with VServer", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + req.SSLId = sdk.String(ctx.PickResourceID(*req.SSLId)) + _, err := client.BindSSL(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] bind with vserver[%s] of ulb[%s]\n", *req.SSLId, *req.VServerId, *req.ULBId) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.SSLId, Action: "bind-ssl", Status: "Bound"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.SSLId = flags.String("ssl-id", "", "Required. Resource ID of SSL Certificate to bind") + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") + command.SetCompletion(cmd, "ssl-id", func() []string { + return getAllSSLCertIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + return getAllVServerIDNames(ctx, *req.ULBId, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("ssl-id") + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + return cmd +} diff --git a/products/ulb/internal/ulb/ssl_delete.go b/products/ulb/internal/ulb/ssl_delete.go new file mode 100644 index 0000000000..deaa9cf960 --- /dev/null +++ b/products/ulb/internal/ulb/ssl_delete.go @@ -0,0 +1,51 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSSLDelete returns ucloud ulb ssl delete. +func newSSLDelete(ctx *cli.Context) *cobra.Command { + var idNames []string + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDeleteSSLRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete SSL Certificates by resource id(ssl id)", + Long: "Delete SSL Certificates by resource id(ssl id)", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.SSLId = sdk.String(id) + _, err := client.DeleteSSL(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete-ssl", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + flags.StringSliceVar(&idNames, "ssl-id", nil, "Required. Resource ID of SSL Certificates to delete") + command.SetCompletion(cmd, "ssl-id", func() []string { + return getAllSSLCertIDNames(ctx, *req.ProjectId, *req.Region) + }) + return cmd +} diff --git a/products/ulb/internal/ulb/ssl_describe.go b/products/ulb/internal/ulb/ssl_describe.go new file mode 100644 index 0000000000..fc17c6ae7c --- /dev/null +++ b/products/ulb/internal/ulb/ssl_describe.go @@ -0,0 +1,79 @@ +package ulb + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSSLDescribe returns ucloud ulb ssl describe. +func newSSLDescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeSSLRequest() + cmd := &cobra.Command{ + Use: "describe", + Short: "Display all data associated with SSL Certificate", + Long: "Display all data associated with SSL Certificate", + Run: func(c *cobra.Command, args []string) { + req.SSLId = sdk.String(ctx.PickResourceID(*req.SSLId)) + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.DescribeSSL(req) + if err != nil { + ctx.HandleError(err) + return + } + if len(resp.DataSet) <= 0 { + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] is not exists\n", *req.SSLId) + return + } + + sslcf := resp.DataSet[0] + targets := []string{} + for _, t := range sslcf.BindedTargetSet { + item := fmt.Sprintf("%s/%s-%s/%s", t.ULBId, t.ULBName, t.VServerId, t.VServerName) + targets = append(targets, item) + } + rows := []cli.DescribeRow{ + {Attribute: "ResourceID", Content: sslcf.SSLId}, + {Attribute: "Name", Content: sslcf.SSLName}, + {Attribute: "Type", Content: sslcf.SSLType}, + {Attribute: "UploadTime", Content: common.FormatDateTime(sslcf.CreateTime)}, + {Attribute: "BindResource", Content: strings.Join(targets, ",")}, + {Attribute: "MD5", Content: sslcf.HashValue}, + {Attribute: "Content", Content: sslcf.SSLContent}, + } + printDescribe(ctx, rows) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.SSLId = flags.String("ssl-id", "", "Required. ResouceID of ssl certificate to describe") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + command.SetCompletion(cmd, "ssl-id", func() []string { + return getAllSSLCertIDNames(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("ssl-id") + return cmd +} + +func printDescribe(ctx *cli.Context, rows []cli.DescribeRow) { + if ctx.Format() != cli.OutputTable { + ctx.PrintList(rows) + return + } + for _, row := range rows { + fmt.Fprintln(ctx.Out(), row.Attribute) + fmt.Fprintln(ctx.Out(), row.Content) + fmt.Fprintln(ctx.Out()) + } +} diff --git a/products/ulb/internal/ulb/ssl_list.go b/products/ulb/internal/ulb/ssl_list.go new file mode 100644 index 0000000000..2470736d71 --- /dev/null +++ b/products/ulb/internal/ulb/ssl_list.go @@ -0,0 +1,59 @@ +package ulb + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSSLList returns ucloud ulb ssl list. +func newSSLList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeSSLRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List SSL Certificates", + Long: "List SSL Certificates", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.DescribeSSL(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := []SSLCertificate{} + for _, ssl := range resp.DataSet { + row := SSLCertificate{} + row.Name = ssl.SSLName + row.ResourceID = ssl.SSLId + row.MD5 = ssl.HashValue + row.UploadTime = common.FormatDateTime(ssl.CreateTime) + targets := []string{} + for _, t := range ssl.BindedTargetSet { + item := fmt.Sprintf("%s/%s(%s/%s)", t.VServerId, t.VServerName, t.ULBId, t.ULBName) + targets = append(targets, item) + } + row.BindResource = strings.Join(targets, ",") + rows = append(rows, row) + } + ctx.PrintList(rows) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.SSLId = flags.String("ssl-id", "", "Optional. ResouceID of ssl certificate to list") + ctx.BindLimit(cmd, req) + ctx.BindOffset(cmd, req) + + return cmd +} diff --git a/products/ulb/internal/ulb/ssl_unbind.go b/products/ulb/internal/ulb/ssl_unbind.go new file mode 100644 index 0000000000..98d57bf5c8 --- /dev/null +++ b/products/ulb/internal/ulb/ssl_unbind.go @@ -0,0 +1,80 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newSSLUnbind returns ucloud ulb ssl unbind. +func newSSLUnbind(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewUnbindSSLRequest() + cmd := &cobra.Command{ + Use: "unbind", + Short: "Unbind SSL Certificate with VServer", + Long: "Unbind SSL Certificate with VServer", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + req.SSLId = sdk.String(ctx.PickResourceID(*req.SSLId)) + _, err := client.UnbindSSL(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] unbind with vserver[%s] of ulb[%s]\n", *req.SSLId, *req.VServerId, *req.ULBId) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.SSLId, Action: "unbind-ssl", Status: "Unbound"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.SSLId = flags.String("ssl-id", "", "Required. Resource ID of SSL Certificate to unbind") + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") + command.SetCompletion(cmd, "ssl-id", func() []string { + return getAllSSLCertIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "ulb-id", func() []string { + if *req.SSLId == "" { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + } + cert, err := getSSLCertByID(ctx, *req.SSLId, *req.ProjectId, *req.Region) + if err != nil { + return nil + } + ulbs := []string{} + for _, b := range cert.BindedTargetSet { + ulbs = append(ulbs, fmt.Sprintf("%s/%s", b.ULBId, b.ULBName)) + } + return ulbs + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + if *req.SSLId == "" { + return getAllVServerIDNames(ctx, *req.ULBId, *req.ProjectId, *req.Region) + } + cert, err := getSSLCertByID(ctx, *req.SSLId, *req.ProjectId, *req.Region) + if err != nil { + return nil + } + vservers := []string{} + for _, b := range cert.BindedTargetSet { + vservers = append(vservers, fmt.Sprintf("%s/%s", b.VServerId, b.VServerName)) + } + return vservers + }) + cmd.MarkFlagRequired("ssl-id") + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + return cmd +} diff --git a/products/ulb/internal/ulb/status.go b/products/ulb/internal/ulb/status.go new file mode 100644 index 0000000000..1d7d180812 --- /dev/null +++ b/products/ulb/internal/ulb/status.go @@ -0,0 +1,3 @@ +package ulb + +const EIP_FREE = "free" diff --git a/products/ulb/internal/ulb/update.go b/products/ulb/internal/ulb/update.go new file mode 100644 index 0000000000..d82084e753 --- /dev/null +++ b/products/ulb/internal/ulb/update.go @@ -0,0 +1,73 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpdate returns ucloud ulb update. +func newUpdate(ctx *cli.Context) *cobra.Command { + var name, group, remark string + idNames := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewUpdateULBAttributeRequest() + cmd := &cobra.Command{ + Use: "update", + Short: "Update ULB instance", + Long: "Update ULB instance", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.ULBId = sdk.String(id) + if name == "" && group == "" && remark == "" { + fmt.Fprintln(ctx.ProgressWriter(), "Error, name, remark and group can't be all empty") + return + } + if name != "" { + req.Name = &name + } + if group != "" { + req.Tag = &group + } + if remark != "" { + req.Remark = &remark + } + _, err := client.UpdateULBAttribute(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb[%s] updated\n", *req.ULBId) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "update", Status: "Updated"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + flags.StringSliceVar(&idNames, "ulb-id", nil, "Required. Resource ID of ULB instances to update") + flags.StringVar(&name, "name", "", "Optional, Name of ULB instance") + flags.StringVar(&remark, "remark", "", "Optional, Remark of ULB instance") + flags.StringVar(&group, "group", "", "Optional, Business group of ULB instance") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/vpc.go b/products/ulb/internal/ulb/vpc.go new file mode 100644 index 0000000000..953f91ffdd --- /dev/null +++ b/products/ulb/internal/ulb/vpc.go @@ -0,0 +1,70 @@ +package ulb + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpc.VPCInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, vpc := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) + } + return list +} + +func getAllSubnets(ctx *cli.Context, vpcID, project, region string) ([]vpc.SubnetInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := []vpc.SubnetInfo{} + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeSubnet(req) + if err != nil { + return nil, err + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + return subnets, nil +} + +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + subnets, err := getAllSubnets(ctx, vpcID, project, region) + if err != nil { + return nil + } + list := []string{} + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} diff --git a/products/ulb/internal/ulb/vserver.go b/products/ulb/internal/ulb/vserver.go new file mode 100644 index 0000000000..7ca07a1233 --- /dev/null +++ b/products/ulb/internal/ulb/vserver.go @@ -0,0 +1,23 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newVServer returns ucloud ulb vserver. +func newVServer(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "vserver", + Short: "List and manipulate ULB Vserver instances", + Long: "List and manipulate ULB Vserver instances", + } + cmd.AddCommand(newVServerList(ctx)) + cmd.AddCommand(newVServerCreate(ctx)) + cmd.AddCommand(newVServerUpdate(ctx)) + cmd.AddCommand(newVServerDelete(ctx)) + cmd.AddCommand(newBackend(ctx)) + cmd.AddCommand(newPolicy(ctx)) + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_create.go b/products/ulb/internal/ulb/vserver_create.go new file mode 100644 index 0000000000..08abcdb273 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_create.go @@ -0,0 +1,104 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newVServerCreate returns ucloud ulb vserver create. +func newVServerCreate(ctx *cli.Context) *cobra.Command { + sslID := "" + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewCreateVServerRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create ULB VServer instance", + Long: "Create ULB VServer instance", + Run: func(c *cobra.Command, args []string) { + if *req.ListenType == "RequestProxy" && (*req.ClientTimeout <= 0 || *req.ClientTimeout > 86400) { + fmt.Fprintln(ctx.ProgressWriter(), "Error, client-timeout-seconds in the range of (0,86400]") + return + } + if *req.ListenType == "PacketsTransmit" && (*req.ClientTimeout <= 0 || *req.ClientTimeout > 86400) { + fmt.Fprintln(ctx.ProgressWriter(), "Error, client-timeout-seconds in the range of [60,900]") + return + } + if *req.Protocol == "HTTPS" && sslID == "" { + fmt.Fprintln(ctx.ProgressWriter(), "Error, SSL Certificate is needed when you choose HTTPS") + return + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + resp, err := client.CreateVServer(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb-vserver[%s] created\n", resp.VServerId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.VServerId, Action: "create-vserver", Status: "Created"}) + if *req.Protocol == "HTTPS" && sslID != "" { + bindReq := client.NewBindSSLRequest() + bindReq.Region = req.Region + bindReq.ProjectId = req.ProjectId + bindReq.SSLId = sdk.String(ctx.PickResourceID(sslID)) + bindReq.VServerId = sdk.String(resp.VServerId) + bindReq.ULBId = req.ULBId + _, err := client.BindSSL(bindReq) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ssl certificate[%s] bind with vserver[%s] of ulb[%s]\n", sslID, *bindReq.VServerId, *bindReq.ULBId) + } + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.VServerName = flags.String("name", "", "Optional. Name of VServer to create") + req.ListenType = flags.String("listen-type", "RequestProxy", "Optional. Listen type, 'RequestProxy' or 'PacketsTransmit'") + req.Protocol = flags.String("protocol", "HTTP", "Optional. Protocol of VServer instance, 'HTTP','HTTPS','TCP' for listen type 'RequestProxy' and 'TCP','UDP' for listen type 'PacketsTransmit'") + req.FrontendPort = flags.Int("port", 80, "Optional. Port of VServer instance") + flags.StringVar(&sslID, "ssl-id", "", "Optional. Required if you choose HTTPS, Resource ID of SSL Certificate") + req.Method = flags.String("lb-method", "Roundrobin", "Optional. LB methods, accept values:Roundrobin,Source,ConsistentHash,SourcePort,ConsistentHashPort,WeightRoundrobin and Leastconn. \nConsistentHash,SourcePort and ConsistentHashPort are effective for listen type PacketsTransmit only;\nLeastconn is effective for listen type RequestProxy only;\nRoundrobin,Source and WeightRoundrobin are effective for both listen types") + req.PersistenceType = flags.String("session-maintain-mode", "None", "Optional. The method of maintaining user's session. Accept values: 'None','ServerInsert' and 'UserDefined'. 'None' meaning don't maintain user's session'; 'ServerInsert' meaning auto create session key; 'UserDefined' meaning specify session key which accpeted by flag seesion-maintain-key by yourself") + req.PersistenceInfo = flags.String("session-maintain-key", "", "Optional. Specify a key for maintaining session") + req.ClientTimeout = flags.Int("client-timeout-seconds", 60, "Optional.Unit seconds. For 'RequestProxy', it's lifetime for idle connections, range (0,86400]. For 'PacketsTransmit', it's the duration of the connection is maintained, range [60,900]") + req.MonitorType = flags.String("health-check-mode", "Port", "Optional. Method of checking real server's status of health. Accept values:'Port','Path'") + req.Domain = flags.String("health-check-domain", "", "Optional. Skip this flag if health-check-mode is assigned Port") + req.Path = flags.String("health-check-path", "", "Optional. Skip this flags if health-check-mode is assigned Port") + + command.SetFlagValues(cmd, "listen-type", "RequestProxy", "PacketsTransmit") + command.SetFlagValues(cmd, "protocol", "HTTP", "HTTPS", "TCP", "UDP") + command.SetCompletion(cmd, "lb-method", func() []string { + if *req.ListenType == "RequestProxy" { + return []string{"Roundrobin", "Source", "WeightRoundrobin", "Leastconn"} + } else if *req.ListenType == "PacketsTransmit" { + return []string{"Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort"} + } + return []string{"Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort", "Leastconn"} + }) + command.SetFlagValues(cmd, "session-maintain-mode", "None", "ServerInsert", "UserDefined") + command.SetFlagValues(cmd, "health-check-mode", "Port", "Path") + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "ssl-id", func() []string { + return getAllSSLCertIDNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_delete.go b/products/ulb/internal/ulb/vserver_delete.go new file mode 100644 index 0000000000..d22719b3b2 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_delete.go @@ -0,0 +1,63 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newVServerDelete returns ucloud ulb vserver delete. +func newVServerDelete(ctx *cli.Context) *cobra.Command { + vserverIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDeleteVServerRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete ULB VServer instances", + Long: "Delete ULB VServer instances", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + results := []cli.OpResultRow{} + for _, idname := range vserverIDs { + vsid := ctx.PickResourceID(idname) + req.VServerId = sdk.String(vsid) + _, err := client.DeleteVServer(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb-vserver[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: vsid, Action: "delete-vserver", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") + flags.StringSliceVar(&vserverIDs, "vserver-id", nil, "Required. Resource ID of Vserver to update") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + ulbID := ctx.PickResourceID(*req.ULBId) + return getAllVServerIDNames(ctx, ulbID, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_list.go b/products/ulb/internal/ulb/vserver_list.go new file mode 100644 index 0000000000..bc4361079f --- /dev/null +++ b/products/ulb/internal/ulb/vserver_list.go @@ -0,0 +1,67 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newVServerList returns ucloud ulb vserver list. +func newVServerList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeVServerRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ULB Vserver instances", + Long: "List ULB Vserver instances", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + resp, err := client.DescribeVServer(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []VServerRow{} + for _, vs := range resp.DataSet { + row := VServerRow{} + row.VServerName = vs.VServerName + row.ResourceID = vs.VServerId + row.ListenType = vs.ListenType + row.Protocol = vs.Protocol + row.Port = vs.FrontendPort + row.LBMethod = vs.Method + row.ClientTimeout = fmt.Sprintf("%ds", vs.ClientTimeout) + row.SessionMaintainMode = vs.PersistenceType + row.SessionMaintainKey = vs.PersistenceInfo + row.HealthCheckMode = vs.MonitorType + row.HealthCheckDomain = vs.Domain + row.HealthCheckPath = vs.Path + list = append(list, row) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Optional. Resource ID of vserver to list") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_node.go b/products/ulb/internal/ulb/vserver_node.go new file mode 100644 index 0000000000..cbe39ab451 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_node.go @@ -0,0 +1,21 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newBackend returns ucloud ulb vserver backend. +func newBackend(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "backend", + Short: "List and manipulate VServer backend nodes", + Long: "List and manipulate VServer backend nodes", + } + cmd.AddCommand(newBackendList(ctx)) + cmd.AddCommand(newBackendAdd(ctx)) + cmd.AddCommand(newBackendUpdate(ctx)) + cmd.AddCommand(newBackendDelete(ctx)) + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_node_add.go b/products/ulb/internal/ulb/vserver_node_add.go new file mode 100644 index 0000000000..5db2a56291 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_node_add.go @@ -0,0 +1,83 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackendAdd returns ucloud ulb vserver backend add. +func newBackendAdd(ctx *cli.Context) *cobra.Command { + var enable *string + var weight *int + var ids []string + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewAllocateBackendRequest() + cmd := &cobra.Command{ + Use: "add", + Short: "Add backend nodes for ULB Vserver instance", + Long: "Add backend nodes for ULB Vserver instance", + Run: func(c *cobra.Command, args []string) { + if *enable == "enable" { + req.Enabled = sdk.Int(1) + } else if *enable == "disable" { + req.Enabled = sdk.Int(0) + } else { + fmt.Fprintln(ctx.ProgressWriter(), "Error, backend-mode must be enable or disable") + return + } + if *weight < 0 || *weight > 100 { + fmt.Fprintln(ctx.ProgressWriter(), "Error, weight must be between 0 and 100") + return + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + results := []cli.OpResultRow{} + for _, id := range ids { + req.ResourceId = sdk.String(id) + resp, err := client.AllocateBackend(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "backend node[%s] added, backend-id:%s\n", *req.ResourceId, resp.BackendId) + results = append(results, cli.OpResultRow{ResourceID: resp.BackendId, Action: "add-backend", Status: "Added"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer which the backend nodes belong to") + flags.StringSliceVar(&ids, "resource-id", nil, "Required. Resource ID of the backend nodes to add") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.ResourceType = flags.String("resource-type", "UHost", "Optional. Resource type of the backend node to add. Accept values: UHost,UPM,UDHost,UDocker") + req.Port = flags.Int("port", 80, "Optional. The port of your real server on the backend node listening on") + enable = flags.String("backend-mode", "enable", "Optional. Enable backend node or not. Accept values: enable, disable") + weight = flags.Int("weight", 1, "Optional. effective for lb-method WeightRoundrobin. Rnage [0,100]") + + command.SetFlagValues(cmd, "resource-type", "Uhost", "UPM", "UDHost", "UDocker") + command.SetFlagValues(cmd, "backend-mode", "enable", "disable") + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + ulbID := ctx.PickResourceID(*req.ULBId) + return getAllVServerIDNames(ctx, ulbID, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + cmd.MarkFlagRequired("resource-id") + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_node_delete.go b/products/ulb/internal/ulb/vserver_node_delete.go new file mode 100644 index 0000000000..b103d44612 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_node_delete.go @@ -0,0 +1,59 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackendDelete returns ucloud ulb vserver backend delete. +func newBackendDelete(ctx *cli.Context) *cobra.Command { + backendIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewReleaseBackendRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete ULB VServer backend nodes", + Long: "Delete ULB VServer backend nodes", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + results := []cli.OpResultRow{} + for _, idname := range backendIDs { + id := ctx.PickResourceID(idname) + req.BackendId = sdk.String(id) + _, err := client.ReleaseBackend(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "backend node[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete-backend", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") + flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of backend nodes to update") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("backend-id") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, "", *req.ProjectId, *req.Region) + }) + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_node_list.go b/products/ulb/internal/ulb/vserver_node_list.go new file mode 100644 index 0000000000..1e8eb91120 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_node_list.go @@ -0,0 +1,81 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackendList returns ucloud ulb vserver backend list. +func newBackendList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeVServerRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ULB VServer backend nodes", + Long: "List ULB VServer backend nodes", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + resp, err := client.DescribeVServer(req) + if err != nil { + ctx.HandleError(err) + return + } + if len(resp.DataSet) != 1 { + fmt.Fprintf(ctx.ProgressWriter(), "ulb[%s] or vserver[%s] may not exist\n", *req.ULBId, *req.VServerId) + return + } + vs := resp.DataSet[0] + list := []BackendRow{} + for _, node := range vs.BackendSet { + row := BackendRow{} + row.Name = node.ResourceName + row.ResourceID = node.ResourceId + row.BackendID = node.BackendId + row.PrivateIP = node.PrivateIP + row.Weight = node.Weight + row.Port = node.Port + if node.Status == 0 { + row.HealthCheck = "Normal" + } else if node.Status == 1 { + row.HealthCheck = "Failed" + } + if node.Enabled == 1 { + row.NodeMode = "enable" + } else if node.Enabled == 0 { + row.NodeMode = "disable" + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer which the backend nodes belong to") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + ulbID := ctx.PickResourceID(*req.ULBId) + return getAllVServerIDNames(ctx, ulbID, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_node_update.go b/products/ulb/internal/ulb/vserver_node_update.go new file mode 100644 index 0000000000..6deb9f9770 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_node_update.go @@ -0,0 +1,89 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackendUpdate returns ucloud ulb vserver backend update. +func newBackendUpdate(ctx *cli.Context) *cobra.Command { + var mode *string + var weight *int + backendIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewUpdateBackendAttributeRequest() + cmd := &cobra.Command{ + Use: "update", + Short: "Update attributes of ULB backend nodes", + Long: "Update attributes of ULB backend nodes", + Run: func(c *cobra.Command, args []string) { + if *mode == "enable" { + req.Enabled = sdk.Int(1) + } else if *mode == "disable" { + req.Enabled = sdk.Int(0) + } else if *mode == "" { + req.Enabled = nil + } else { + fmt.Fprintln(ctx.ProgressWriter(), "Error, backend-mode must be enable or disable") + return + } + if *weight != -1 && (*weight < 0 || *weight > 100) { + fmt.Fprintln(ctx.ProgressWriter(), "Error, weight must be between 0 and 100") + return + } + if *weight != -1 { + req.Weight = weight + } + + if *req.Port == 0 { + req.Port = nil + } + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, bid := range backendIDs { + id := ctx.PickResourceID(bid) + req.BackendId = sdk.String(id) + _, err := client.UpdateBackendAttribute(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "backend node[%s] updated\n", bid) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "update-backend", Status: "Updated"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB which the backend nodes belong to") + flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of backend nodes to update") + req.Port = flags.Int("port", 0, "Optional. Port of your real server listening on backend nodes to update. Rnage [1,65535]") + mode = flags.String("backend-mode", "", "Optional. Enable backend node or not. Accept values: enable, disable") + weight = flags.Int("weight", -1, "Optional. effective for lb-method WeightRoundrobin. Rnage [0,100], -1 meaning no update") + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetFlagValues(cmd, "backend-mode", "enable", "disable") + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, "", *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("backend-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_policy.go b/products/ulb/internal/ulb/vserver_policy.go new file mode 100644 index 0000000000..8312cd6712 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_policy.go @@ -0,0 +1,21 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPolicy returns ucloud ulb vserver policy. +func newPolicy(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "policy", + Short: "List and manipulate forward policy for VServer", + Long: "List and manipulate forward policy for VServer", + } + cmd.AddCommand(newPolicyAdd(ctx)) + cmd.AddCommand(newPolicyList(ctx)) + cmd.AddCommand(newPolicyUpdate(ctx)) + cmd.AddCommand(newPolicyDelete(ctx)) + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_policy_create.go b/products/ulb/internal/ulb/vserver_policy_create.go new file mode 100644 index 0000000000..5d3dc86b38 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_policy_create.go @@ -0,0 +1,74 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPolicyAdd returns ucloud ulb vserver policy add. +func newPolicyAdd(ctx *cli.Context) *cobra.Command { + backendIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewCreatePolicyRequest() + cmd := &cobra.Command{ + Use: "add", + Short: "Add content forward policy for VServer", + Long: "Add content forward policy for VServer", + Run: func(c *cobra.Command, args []string) { + if *req.Type != "Domain" && *req.Type != "Path" { + fmt.Fprintln(ctx.ProgressWriter(), "Error, forward method must be Domain or Path") + return + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + for _, idname := range backendIDs { + req.BackendId = append(req.BackendId, ctx.PickResourceID(idname)) + } + resp, err := client.CreatePolicy(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "policy[%s] created\n", resp.PolicyId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.PolicyId, Action: "create-policy", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") + flags.StringSliceVar(&backendIDs, "backend-id", nil, "Required. BackendID of the VServer's backend nodes") + req.Type = flags.String("forward-method", "", "Required. Forward method, accept values:Domain and Path; Both forwarding methods can be described by using regular expressions or wildcards") + req.Match = flags.String("expression", "", "Required. Expression of domain or path, such as \"www.[123].demo.com\" or \"/path/img/*.jpg\"") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetFlagValues(cmd, "forward-method", "Domain", "Path") + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + return getAllVServerIDNames(ctx, *req.ULBId, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + cmd.MarkFlagRequired("backend-id") + cmd.MarkFlagRequired("forward-method") + cmd.MarkFlagRequired("expression") + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_policy_delete.go b/products/ulb/internal/ulb/vserver_policy_delete.go new file mode 100644 index 0000000000..7a1d0ef734 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_policy_delete.go @@ -0,0 +1,51 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPolicyDelete returns ucloud ulb vserver policy delete. +func newPolicyDelete(ctx *cli.Context) *cobra.Command { + policyIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDeletePolicyRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete content forward policies of ULB VServer", + Long: "Delete content forward policies of ULB VServer", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, p := range policyIDs { + req.PolicyId = sdk.String(p) + _, err := client.DeletePolicy(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "policy[%s] deleted\n", p) + results = append(results, cli.OpResultRow{ResourceID: p, Action: "delete-policy", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + flags.StringSliceVar(&policyIDs, "policy-id", nil, "Required. PolicyID of policies to delete") + req.VServerId = flags.String("vserver-id", "", "Optional. Resource ID of VServer") + + cmd.MarkFlagRequired("policy-id") + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_policy_list.go b/products/ulb/internal/ulb/vserver_policy_list.go new file mode 100644 index 0000000000..e3e62191e3 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_policy_list.go @@ -0,0 +1,69 @@ +package ulb + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPolicyList returns ucloud ulb vserver policy list. +func newPolicyList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewDescribeVServerRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List content forward policies of the VServer instance", + Long: "List content forward policies of the VServer instance", + Run: func(c *cobra.Command, args []string) { + ulbID := ctx.PickResourceID(*req.ULBId) + vserverID := ctx.PickResourceID(*req.VServerId) + vsList, err := getAllVServers(ctx, ulbID, vserverID, *req.ProjectId, *req.Region) + if err != nil { + ctx.HandleError(err) + return + } + if len(vsList) == 1 { + vs := vsList[0] + list := []PolicyRow{} + for _, p := range vs.PolicySet { + row := PolicyRow{} + row.ForwardMethod = p.Type + row.Expression = p.Match + row.PolicyID = p.PolicyId + row.PolicyType = p.PolicyType + nodes := []string{} + for _, b := range p.BackendSet { + nodes = append(nodes, fmt.Sprintf("%s|%s:%d|%s", b.BackendId, b.PrivateIP, b.Port, b.ResourceName)) + } + row.Backends = strings.Join(nodes, ",") + list = append(list, row) + } + ctx.PrintList(list) + } + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") + + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + ulb := ctx.PickResourceID(*req.ULBId) + return getAllVServerIDNames(ctx, ulb, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_policy_update.go b/products/ulb/internal/ulb/vserver_policy_update.go new file mode 100644 index 0000000000..bc94b713f3 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_policy_update.go @@ -0,0 +1,133 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPolicyUpdate returns ucloud ulb vserver policy update. +func newPolicyUpdate(ctx *cli.Context) *cobra.Command { + policyIDs := []string{} + backendIDs := []string{} + addBackendIDs := []string{} + removeBackendIDs := []string{} + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewUpdatePolicyRequest() + cmd := &cobra.Command{ + Use: "update", + Short: "Update content forward policies of ULB VServer", + Long: "Update content forward policies ULB VServer", + Run: func(c *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + req.VServerId = sdk.String(ctx.PickResourceID(*req.VServerId)) + + vsList, err := getAllVServers(ctx, *req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) + if err != nil { + ctx.HandleError(err) + return + } + vs := vsList[0] + + results := []cli.OpResultRow{} + for _, policyID := range policyIDs { + var policy *ulbsdk.ULBPolicySet + for _, p := range vs.PolicySet { + if p.PolicyId == policyID { + policy = &p + break + } + } + if policy == nil { + fmt.Fprintf(ctx.ProgressWriter(), "policy[%s] not found\n", policyID) + continue + } + req.PolicyId = sdk.String(policyID) + if *req.Type == "" { + req.Type = sdk.String(policy.Type) + } else if *req.Type != "Domain" && *req.Type != "Path" { + fmt.Fprintln(ctx.ProgressWriter(), "Error, forward-method must be Domain or Path") + continue + } + if *req.Match == "" { + req.Match = sdk.String(policy.Match) + } + backendIDMap := map[string]bool{} + if backendIDs == nil { + for _, b := range policy.BackendSet { + backendIDMap[b.BackendId] = true + } + } else { + for _, bid := range backendIDs { + backendIDMap[ctx.PickResourceID(bid)] = true + } + } + for _, bid := range addBackendIDs { + backendIDMap[ctx.PickResourceID(bid)] = true + } + for _, bid := range removeBackendIDs { + backendIDMap[ctx.PickResourceID(bid)] = false + } + req.BackendId = nil + for bid, ok := range backendIDMap { + if ok { + req.BackendId = append(req.BackendId, bid) + } + } + resp, err := client.UpdatePolicy(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "policy[%s] updated\n", resp.PolicyId) + results = append(results, cli.OpResultRow{ResourceID: resp.PolicyId, Action: "update-policy", Status: "Updated"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB") + req.VServerId = flags.String("vserver-id", "", "Required. Resource ID of VServer") + flags.StringSliceVar(&policyIDs, "policy-id", nil, "Required. PolicyID of policies to update") + flags.StringSliceVar(&backendIDs, "backend-id", nil, "Optional. BackendID of backend nodes. If assign this flag, it will rewrite all backend nodes of the policy") + flags.StringSliceVar(&addBackendIDs, "add-backend-id", nil, "Optional. BackendID of backend nodes. Add backend nodes to the policy") + flags.StringSliceVar(&removeBackendIDs, "remove-backend-id", nil, "Optional. BackendID of backend nodes. Remove those backend nodes from the policy") + req.Type = flags.String("forward-method", "", "Optional. Forward method of policy, accept values:Domain and Path") + req.Match = flags.String("expression", "", "Optional. Expression of domain or path, such as \"www.[123].demo.com\" or \"/path/img/*.jpg\"") + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + cmd.MarkFlagRequired("policy-id") + + command.SetFlagValues(cmd, "forward-method", "Domain", "Path") + command.SetCompletion(cmd, "ulb-id", func() []string { + project := ctx.PickResourceID(*req.ProjectId) + return getAllULBIDNames(ctx, project, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + return getAllVServerIDNames(ctx, *req.ULBId, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "add-backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "remove-backend-id", func() []string { + return getAllBackendNodeIDNames(ctx, *req.ULBId, *req.VServerId, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/ulb/internal/ulb/vserver_update.go b/products/ulb/internal/ulb/vserver_update.go new file mode 100644 index 0000000000..dae228c7a2 --- /dev/null +++ b/products/ulb/internal/ulb/vserver_update.go @@ -0,0 +1,97 @@ +package ulb + +import ( + "fmt" + + "github.com/spf13/cobra" + + ulbsdk "github.com/ucloud/ucloud-sdk-go/services/ulb" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newVServerUpdate returns ucloud ulb vserver update. +func newVServerUpdate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ulbsdk.NewClient) + req := client.NewUpdateVServerAttributeRequest() + vserverIDs := []string{} + cmd := &cobra.Command{ + Use: "update", + Short: "Update attributes of VServer instances", + Long: "Update attributes of VServer instances", + Run: func(c *cobra.Command, args []string) { + if *req.VServerName == "" { + req.VServerName = nil + } + if *req.Method == "" { + req.Method = nil + } + if *req.PersistenceType == "" { + req.PersistenceType = nil + } + if *req.PersistenceInfo == "" { + req.PersistenceInfo = nil + } + if *req.ClientTimeout == -1 { + req.ClientTimeout = nil + } + if *req.MonitorType == "" { + req.MonitorType = nil + } + if *req.Domain == "" { + req.Domain = nil + } + if *req.Path == "" { + req.Path = nil + } + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.ULBId = sdk.String(ctx.PickResourceID(*req.ULBId)) + results := []cli.OpResultRow{} + for _, idname := range vserverIDs { + id := ctx.PickResourceID(idname) + req.VServerId = sdk.String(id) + _, err := client.UpdateVServerAttribute(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ulb-vserver[%s] updated\n", *req.VServerId) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "update-vserver", Status: "Updated"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.ULBId = flags.String("ulb-id", "", "Required. Resource ID of ULB instance which the VServer to create belongs to") + flags.StringSliceVar(&vserverIDs, "vserver-id", nil, "Required. Resource ID of Vserver to update") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.VServerName = flags.String("name", "", "Optional. Name of VServer") + req.Method = flags.String("lb-method", "", "Optional. LB methods, accept values:Roundrobin,Source,ConsistentHash,SourcePort,ConsistentHashPort,WeightRoundrobin and Leastconn. \nConsistentHash,SourcePort and ConsistentHashPort are effective for listen type PacketsTransmit only;\nLeastconn is effective for listen type RequestProxy only;\nRoundrobin,Source and WeightRoundrobin are effective for both listen types") + req.PersistenceType = flags.String("session-maintain-mode", "", "Optional. The method of maintaining user's session. Accept values: 'None','ServerInsert' and 'UserDefined'. 'None' meaning don't maintain user's session'; 'ServerInsert' meaning auto create session key; 'UserDefined' meaning specify session key which accpeted by flag seesion-maintain-key by yourself") + req.PersistenceInfo = flags.String("session-maintain-key", "", "Optional. Specify a key for maintaining session") + req.ClientTimeout = flags.Int("client-timeout-seconds", -1, "Optional.Unit seconds. For 'RequestProxy', it's lifetime for idle connections, range (0,86400]. For 'PacketsTransmit', it's the duration of the connection is maintained, range [60,900]") + req.MonitorType = flags.String("health-check-mode", "", "Optional. Method of checking real server's status of health. Accept values:'Port','Path'") + req.Domain = flags.String("health-check-domain", "", "Optional. Skip this flag if health-check-mode is assigned Port") + req.Path = flags.String("health-check-path", "", "Optional. Skip this flags if health-check-mode is assigned Port") + + command.SetFlagValues(cmd, "lb-method", "Roundrobin", "Source", "WeightRoundrobin", "ConsistentHash", "SourcePort", "ConsistentHashPort", "Leastconn") + command.SetFlagValues(cmd, "session-maintain-mode", "None", "ServerInsert", "UserDefined") + command.SetFlagValues(cmd, "health-check-mode", "Port", "Path") + command.SetCompletion(cmd, "ulb-id", func() []string { + return getAllULBIDNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "vserver-id", func() []string { + ulbID := ctx.PickResourceID(*req.ULBId) + return getAllVServerIDNames(ctx, ulbID, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("ulb-id") + cmd.MarkFlagRequired("vserver-id") + + return cmd +} diff --git a/products/ulb/product.go b/products/ulb/product.go new file mode 100644 index 0000000000..9dbb868a06 --- /dev/null +++ b/products/ulb/product.go @@ -0,0 +1,21 @@ +package ulb + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalulb "github.com/ucloud/ucloud-cli/products/ulb/internal/ulb" +) + +type product struct{} + +// New returns the ulb product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "ulb", Commands: []string{"ulb"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalulb.NewCommand(ctx)} +} diff --git a/products/ulb/product.yaml b/products/ulb/product.yaml new file mode 100644 index 0000000000..f18008496e --- /dev/null +++ b/products/ulb/product.yaml @@ -0,0 +1,7 @@ +# products/ulb/product.yaml — ULB product metadata. +name: ulb +owners: + - Episkey-G +commands: + - ulb +enabled: true diff --git a/products/ulb/testdata/cmdtree.golden b/products/ulb/testdata/cmdtree.golden new file mode 100644 index 0000000000..456fc2c6b5 --- /dev/null +++ b/products/ulb/testdata/cmdtree.golden @@ -0,0 +1,172 @@ +ucloud ulb use=ulb short=List and manipulate ULB instances +ucloud ulb create use=create short=Create ULB instance + flag=bind-eip short= default= required= + flag=charge-type short= default=Month required= + flag=create-eip-bandwidth-mb short= default=0 required= + flag=create-eip-line short= default= required= + flag=create-eip-name short= default= required= + flag=create-eip-remark short= default= required= + flag=create-eip-traffic-mode short= default=Bandwidth required= + flag=group short= default=Default required= + flag=mode short= default=outer required=true + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=subnet-id short= default= required= + flag=vpc-id short= default= required= +ucloud ulb delete use=delete short=Delete ULB instances by resource ID + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default=[] required=true +ucloud ulb list use=list short=List ULB instances + flag=group short= default= required= + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=subnet-id short= default= required= + flag=ulb-id short= default= required= + flag=vpc-id short= default= required= +ucloud ulb ssl use=ssl short=List and manipulate SSL Certificates for ULB +ucloud ulb ssl add use=add short=Add SSL Certificate + flag=all-in-one-file short= default= required= + flag=ca-certificate-file short= default= required= + flag=format short= default=Pem required= + flag=name short= default= required=true + flag=private-key-file short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=site-certificate-file short= default= required= +ucloud ulb ssl bind use=bind short=Bind SSL Certificate with VServer + flag=project-id short= default= required= + flag=region short= default= required= + flag=ssl-id short= default= required=true + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb ssl delete use=delete short=Delete SSL Certificates by resource id(ssl id) + flag=project-id short= default= required= + flag=region short= default= required= + flag=ssl-id short= default=[] required= +ucloud ulb ssl describe use=describe short=Display all data associated with SSL Certificate + flag=project-id short= default= required= + flag=region short= default= required= + flag=ssl-id short= default= required=true +ucloud ulb ssl list use=list short=List SSL Certificates + flag=limit short= default=100 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=ssl-id short= default= required= +ucloud ulb ssl unbind use=unbind short=Unbind SSL Certificate with VServer + flag=project-id short= default= required= + flag=region short= default= required= + flag=ssl-id short= default= required=true + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb update use=update short=Update ULB instance + flag=group short= default= required= + flag=name short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=ulb-id short= default=[] required=true +ucloud ulb vserver use=vserver short=List and manipulate ULB Vserver instances +ucloud ulb vserver backend use=backend short=List and manipulate VServer backend nodes +ucloud ulb vserver backend add use=add short=Add backend nodes for ULB Vserver instance + flag=backend-mode short= default=enable required= + flag=port short= default=80 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=resource-id short= default=[] required=true + flag=resource-type short= default=UHost required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true + flag=weight short= default=1 required= +ucloud ulb vserver backend delete use=delete short=Delete ULB VServer backend nodes + flag=backend-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true +ucloud ulb vserver backend list use=list short=List ULB VServer backend nodes + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb vserver backend update use=update short=Update attributes of ULB backend nodes + flag=backend-id short= default=[] required=true + flag=backend-mode short= default= required= + flag=port short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=weight short= default=-1 required= +ucloud ulb vserver create use=create short=Create ULB VServer instance + flag=client-timeout-seconds short= default=60 required= + flag=health-check-domain short= default= required= + flag=health-check-mode short= default=Port required= + flag=health-check-path short= default= required= + flag=lb-method short= default=Roundrobin required= + flag=listen-type short= default=RequestProxy required= + flag=name short= default= required= + flag=port short= default=80 required= + flag=project-id short= default= required= + flag=protocol short= default=HTTP required= + flag=region short= default= required= + flag=session-maintain-key short= default= required= + flag=session-maintain-mode short= default=None required= + flag=ssl-id short= default= required= + flag=ulb-id short= default= required=true +ucloud ulb vserver delete use=delete short=Delete ULB VServer instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default=[] required=true +ucloud ulb vserver list use=list short=List ULB Vserver instances + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required= +ucloud ulb vserver policy use=policy short=List and manipulate forward policy for VServer +ucloud ulb vserver policy add use=add short=Add content forward policy for VServer + flag=backend-id short= default=[] required=true + flag=expression short= default= required=true + flag=forward-method short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb vserver policy delete use=delete short=Delete content forward policies of ULB VServer + flag=policy-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=vserver-id short= default= required= +ucloud ulb vserver policy list use=list short=List content forward policies of the VServer instance + flag=project-id short= default= required= + flag=region short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb vserver policy update use=update short=Update content forward policies of ULB VServer + flag=add-backend-id short= default=[] required= + flag=backend-id short= default=[] required= + flag=expression short= default= required= + flag=forward-method short= default= required= + flag=policy-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remove-backend-id short= default=[] required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default= required=true +ucloud ulb vserver update use=update short=Update attributes of VServer instances + flag=client-timeout-seconds short= default=-1 required= + flag=health-check-domain short= default= required= + flag=health-check-mode short= default= required= + flag=health-check-path short= default= required= + flag=lb-method short= default= required= + flag=name short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=session-maintain-key short= default= required= + flag=session-maintain-mode short= default= required= + flag=ulb-id short= default= required=true + flag=vserver-id short= default=[] required=true diff --git a/products/ulb/testdata/completion.golden b/products/ulb/testdata/completion.golden new file mode 100644 index 0000000000..4d9cd29cbe --- /dev/null +++ b/products/ulb/testdata/completion.golden @@ -0,0 +1,104 @@ +ucloud ulb create bind-eip dynamic +ucloud ulb create charge-type static Dynamic,Month,Year +ucloud ulb create create-eip-line static BGP,International +ucloud ulb create create-eip-traffic-mode static Bandwidth,ShareBandwidth,Traffic +ucloud ulb create mode static inner,outer +ucloud ulb create project-id dynamic +ucloud ulb create region dynamic +ucloud ulb create subnet-id dynamic +ucloud ulb create vpc-id dynamic +ucloud ulb delete project-id dynamic +ucloud ulb delete region dynamic +ucloud ulb delete ulb-id dynamic +ucloud ulb list project-id dynamic +ucloud ulb list region dynamic +ucloud ulb list vpc-id dynamic +ucloud ulb ssl add all-in-one-file static +ucloud ulb ssl add ca-certificate-file static +ucloud ulb ssl add private-key-file static +ucloud ulb ssl add project-id dynamic +ucloud ulb ssl add region dynamic +ucloud ulb ssl add site-certificate-file static +ucloud ulb ssl bind project-id dynamic +ucloud ulb ssl bind region dynamic +ucloud ulb ssl bind ssl-id dynamic +ucloud ulb ssl bind ulb-id dynamic +ucloud ulb ssl bind vserver-id dynamic +ucloud ulb ssl delete project-id dynamic +ucloud ulb ssl delete region dynamic +ucloud ulb ssl delete ssl-id dynamic +ucloud ulb ssl describe project-id dynamic +ucloud ulb ssl describe region dynamic +ucloud ulb ssl describe ssl-id dynamic +ucloud ulb ssl list project-id dynamic +ucloud ulb ssl list region dynamic +ucloud ulb ssl unbind project-id dynamic +ucloud ulb ssl unbind region dynamic +ucloud ulb ssl unbind ssl-id dynamic +ucloud ulb ssl unbind ulb-id dynamic +ucloud ulb ssl unbind vserver-id dynamic +ucloud ulb update project-id dynamic +ucloud ulb update region dynamic +ucloud ulb update ulb-id dynamic +ucloud ulb vserver backend add backend-mode static disable,enable +ucloud ulb vserver backend add project-id dynamic +ucloud ulb vserver backend add region dynamic +ucloud ulb vserver backend add resource-type static UDHost,UDocker,UPM,Uhost +ucloud ulb vserver backend add ulb-id dynamic +ucloud ulb vserver backend add vserver-id dynamic +ucloud ulb vserver backend delete backend-id dynamic +ucloud ulb vserver backend delete project-id dynamic +ucloud ulb vserver backend delete region dynamic +ucloud ulb vserver backend delete ulb-id dynamic +ucloud ulb vserver backend list project-id dynamic +ucloud ulb vserver backend list region dynamic +ucloud ulb vserver backend list ulb-id dynamic +ucloud ulb vserver backend list vserver-id dynamic +ucloud ulb vserver backend update backend-id dynamic +ucloud ulb vserver backend update backend-mode static disable,enable +ucloud ulb vserver backend update project-id dynamic +ucloud ulb vserver backend update region dynamic +ucloud ulb vserver backend update ulb-id dynamic +ucloud ulb vserver create health-check-mode static Path,Port +ucloud ulb vserver create lb-method static Leastconn,Roundrobin,Source,WeightRoundrobin +ucloud ulb vserver create listen-type static PacketsTransmit,RequestProxy +ucloud ulb vserver create project-id dynamic +ucloud ulb vserver create protocol static HTTP,HTTPS,TCP,UDP +ucloud ulb vserver create region dynamic +ucloud ulb vserver create session-maintain-mode static None,ServerInsert,UserDefined +ucloud ulb vserver create ssl-id dynamic +ucloud ulb vserver create ulb-id dynamic +ucloud ulb vserver delete project-id dynamic +ucloud ulb vserver delete region dynamic +ucloud ulb vserver delete ulb-id dynamic +ucloud ulb vserver delete vserver-id dynamic +ucloud ulb vserver list project-id dynamic +ucloud ulb vserver list region dynamic +ucloud ulb vserver list ulb-id dynamic +ucloud ulb vserver policy add backend-id dynamic +ucloud ulb vserver policy add forward-method static Domain,Path +ucloud ulb vserver policy add project-id dynamic +ucloud ulb vserver policy add region dynamic +ucloud ulb vserver policy add ulb-id dynamic +ucloud ulb vserver policy add vserver-id dynamic +ucloud ulb vserver policy delete project-id dynamic +ucloud ulb vserver policy delete region dynamic +ucloud ulb vserver policy list project-id dynamic +ucloud ulb vserver policy list region dynamic +ucloud ulb vserver policy list ulb-id dynamic +ucloud ulb vserver policy list vserver-id dynamic +ucloud ulb vserver policy update add-backend-id dynamic +ucloud ulb vserver policy update backend-id dynamic +ucloud ulb vserver policy update forward-method static Domain,Path +ucloud ulb vserver policy update project-id dynamic +ucloud ulb vserver policy update region dynamic +ucloud ulb vserver policy update remove-backend-id dynamic +ucloud ulb vserver policy update ulb-id dynamic +ucloud ulb vserver policy update vserver-id dynamic +ucloud ulb vserver update health-check-mode static Path,Port +ucloud ulb vserver update lb-method static ConsistentHash,ConsistentHashPort,Leastconn,Roundrobin,Source,SourcePort,WeightRoundrobin +ucloud ulb vserver update project-id dynamic +ucloud ulb vserver update region dynamic +ucloud ulb vserver update session-maintain-mode static None,ServerInsert,UserDefined +ucloud ulb vserver update ulb-id dynamic +ucloud ulb vserver update vserver-id dynamic diff --git a/products/unet/product.go b/products/unet/product.go deleted file mode 100644 index 8b07cf1aae..0000000000 --- a/products/unet/product.go +++ /dev/null @@ -1,25 +0,0 @@ -package unet - -import ( - "github.com/spf13/cobra" - - "github.com/ucloud/ucloud-cli/pkg/cli" - internaleip "github.com/ucloud/ucloud-cli/products/unet/internal/eip" - internalfirewall "github.com/ucloud/ucloud-cli/products/unet/internal/firewall" -) - -type product struct{} - -// New returns the unet product (registered via hack/gen-products). -func New() cli.Product { return product{} } - -func (product) Metadata() cli.Metadata { - return cli.Metadata{Name: "unet", Commands: []string{"eip", "firewall"}} -} - -func (product) NewCommand(ctx *cli.Context) []*cobra.Command { - return []*cobra.Command{ - internaleip.NewCommand(ctx), - internalfirewall.NewCommand(ctx), - } -} diff --git a/products/unet/product.yaml b/products/unet/product.yaml deleted file mode 100644 index 2398abcd51..0000000000 --- a/products/unet/product.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# products/unet/product.yaml — UNet 产品元数据(归属真源,owner 自治维护) -name: unet -owners: - - Episkey-G -commands: - - eip - - firewall -enabled: true diff --git a/products/vpc/internal/vpc/cmd.go b/products/vpc/internal/vpc/cmd.go new file mode 100644 index 0000000000..8ecb5722fd --- /dev/null +++ b/products/vpc/internal/vpc/cmd.go @@ -0,0 +1,24 @@ +package vpc + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand returns the ucloud vpc command tree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "vpc", + Short: "List and manipulate VPC instances", + Long: "List and manipulate VPC instances", + Args: cobra.NoArgs, + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newCreatePeer(ctx)) + cmd.AddCommand(newListPeer(ctx)) + cmd.AddCommand(newDeletePeer(ctx)) + return cmd +} diff --git a/products/vpc/internal/vpc/completion.go b/products/vpc/internal/vpc/completion.go new file mode 100644 index 0000000000..7fb6ddfc23 --- /dev/null +++ b/products/vpc/internal/vpc/completion.go @@ -0,0 +1,33 @@ +package vpc + +import ( + "fmt" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpcsdk.VPCInfo, error) { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, vpc := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", vpc.VPCId, vpc.Name)) + } + return list +} diff --git a/products/vpc/internal/vpc/create.go b/products/vpc/internal/vpc/create.go new file mode 100644 index 0000000000..cb14f0435c --- /dev/null +++ b/products/vpc/internal/vpc/create.go @@ -0,0 +1,51 @@ +package vpc + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newCreate returns ucloud vpc create. +func newCreate(ctx *cli.Context) *cobra.Command { + var segments *[]string + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewCreateVPCRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create vpc network", + Long: "Create vpc network", + Example: "ucloud vpc create --name xxx --segment 192.168.0.0/16", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + req.Network = *segments + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + resp, err := client.CreateVPC(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "vpc[%s] created\n", resp.VPCId) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.VPCId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + + req.Name = flags.String("name", "", "Required. Name of the vpc network.") + segments = flags.StringSlice("segment", nil, "Required. The segment for private network.") + req.Tag = flags.String("group", "", "Optional. Business group.") + req.Remark = flags.String("remark", "", "Optional. The description of the vpc.") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("segment") + + return cmd +} diff --git a/products/vpc/internal/vpc/delete.go b/products/vpc/internal/vpc/delete.go new file mode 100644 index 0000000000..0b77092b7e --- /dev/null +++ b/products/vpc/internal/vpc/delete.go @@ -0,0 +1,56 @@ +package vpc + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete returns ucloud vpc delete. +func newDelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDeleteVPCRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete vpc network", + Long: "Delete vpc network", + Example: "ucloud vpc delete --vpc-id uvnet-xxx", + Run: func(cmd *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.VPCId = sdk.String(id) + _, err := client.DeleteVPC(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "vpc[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + cmd.Flags().SortFlags = false + + cmd.Flags().StringSliceVar(&idNames, "vpc-id", nil, "Required. Resource ID of the vpc network to delete") + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + + cmd.MarkFlagRequired("vpc-id") + + return cmd +} diff --git a/products/vpc/internal/vpc/list.go b/products/vpc/internal/vpc/list.go new file mode 100644 index 0000000000..6c9795e19d --- /dev/null +++ b/products/vpc/internal/vpc/list.go @@ -0,0 +1,61 @@ +package vpc + +import ( + "strings" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newList returns ucloud vpc list. +func newList(ctx *cli.Context) *cobra.Command { + vpcIDs := []string{} + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeVPCRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List vpc", + Long: "List vpc", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + req.VPCIds = nil + for _, id := range vpcIDs { + req.VPCIds = append(req.VPCIds, ctx.PickResourceID(id)) + } + resp, err := client.DescribeVPC(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []Row{} + for _, vpc := range resp.DataSet { + row := Row{} + row.VPCName = vpc.Name + row.ResourceID = vpc.VPCId + row.Group = vpc.Tag + row.NetworkSegment = strings.Join(vpc.Network, ",") + row.SubnetCount = vpc.SubnetCount + row.CreationTime = common.FormatDate(vpc.CreateTime) + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + ctx.BindRegion(cmd, req) + ctx.BindProjectID(cmd, req) + req.Tag = flags.String("group", "", "Optional. Group") + flags.StringSliceVar(&vpcIDs, "vpc-id", []string{}, "Optional. Multiple values separated by commas") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + + return cmd +} diff --git a/products/vpc/internal/vpc/peer_create.go b/products/vpc/internal/vpc/peer_create.go new file mode 100644 index 0000000000..f27517dab2 --- /dev/null +++ b/products/vpc/internal/vpc/peer_create.go @@ -0,0 +1,63 @@ +package vpc + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreatePeer returns ucloud vpc create-intercome. +func newCreatePeer(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewCreateVPCIntercomRequest() + cmd := &cobra.Command{ + Use: "create-intercome", + Short: "Create intercome with other vpc", + Long: "Create intercome with other vpc", + Example: "ucloud vpc create-intercome --vpc-id xx --dst-vpc-id xx --dst-region xx", + Run: func(cmd *cobra.Command, args []string) { + req.ProjectId = sdk.String(ctx.PickResourceID(*req.ProjectId)) + req.DstProjectId = sdk.String(ctx.PickResourceID(*req.DstProjectId)) + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + req.DstVPCId = sdk.String(ctx.PickResourceID(*req.DstVPCId)) + _, err := client.CreateVPCIntercom(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "intercome [%s<-->%s] establish", *req.VPCId, *req.DstVPCId) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.VPCId, Action: "create-intercome", Status: "Created"}) + }, + } + + cmd.Flags().SortFlags = false + + req.VPCId = cmd.Flags().String("vpc-id", "", "Required. The source vpc you want to establish the intercome") + req.DstVPCId = cmd.Flags().String("dst-vpc-id", "", "Required. The target vpc you want to establish the intercome") + req.DstRegion = cmd.Flags().String("dst-region", ctx.DefaultRegion(), "Required. If the intercome established across different regions") + req.Region = cmd.Flags().String("region", ctx.DefaultRegion(), "Optioanl. The region of source vpc which will establish the intercome") + req.ProjectId = cmd.Flags().String("project-id", ctx.DefaultProjectID(), "Optional. The project id of the source vpc") + req.DstProjectId = cmd.Flags().String("dst-project-id", ctx.DefaultProjectID(), "Optional. The project id of the source vpc") + + cmd.MarkFlagRequired("vpc-id") + cmd.MarkFlagRequired("dst-vpc-id") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "dst-vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.DstProjectId, *req.DstRegion) + }) + command.SetCompletion(cmd, "region", ctx.RegionList) + command.SetCompletion(cmd, "dst-region", ctx.RegionList) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + command.SetCompletion(cmd, "dst-project-id", ctx.ProjectList) + + return cmd +} diff --git a/products/vpc/internal/vpc/peer_delete.go b/products/vpc/internal/vpc/peer_delete.go new file mode 100644 index 0000000000..13561d3a8a --- /dev/null +++ b/products/vpc/internal/vpc/peer_delete.go @@ -0,0 +1,55 @@ +package vpc + +import ( + "fmt" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDeletePeer returns ucloud vpc delete-intercome. +func newDeletePeer(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDeleteVPCIntercomRequest() + cmd := &cobra.Command{ + Use: "delete-intercome", + Short: "delete the vpc intercome", + Long: "delete the vpc intercome", + Example: "ucloud vpc delete-intercome --vpc-id xxx --dst-vpc-id xxx", + Run: func(cmd *cobra.Command, args []string) { + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + req.DstVPCId = sdk.String(ctx.PickResourceID(*req.DstVPCId)) + _, err := client.DeleteVPCIntercom(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "intercome [%s<-->%s] deleted\n", *req.VPCId, *req.DstVPCId) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.VPCId, Action: "delete-intercome", Status: "Deleted"}) + }, + } + + cmd.Flags().SortFlags = false + + req.VPCId = cmd.Flags().String("vpc-id", "", "Required. Resource ID of source VPC to disconnect with destination VPC") + req.DstVPCId = cmd.Flags().String("dst-vpc-id", "", "Required. Resource ID of destination VPC to disconnect with source VPC") + req.ProjectId = cmd.Flags().String("project-id", ctx.DefaultProjectID(), "Optional. The project id of source vpc") + req.Region = cmd.Flags().String("region", ctx.DefaultRegion(), "Optional. The region of source vpc to disconnect") + req.DstRegion = cmd.Flags().String("dst-region", "", "Optional. The region of dest vpc to disconnect") + + cmd.MarkFlagRequired("vpc-id") + cmd.MarkFlagRequired("dst-vpc-id") + cmd.MarkFlagRequired("dst-region") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "dst-region", ctx.RegionList) + + return cmd +} diff --git a/products/vpc/internal/vpc/peer_list.go b/products/vpc/internal/vpc/peer_list.go new file mode 100644 index 0000000000..9cdbeab1da --- /dev/null +++ b/products/vpc/internal/vpc/peer_list.go @@ -0,0 +1,58 @@ +package vpc + +import ( + "strings" + + "github.com/spf13/cobra" + + vpcsdk "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newListPeer returns ucloud vpc list-intercome. +func newListPeer(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, vpcsdk.NewClient) + req := client.NewDescribeVPCIntercomRequest() + cmd := &cobra.Command{ + Use: "list-intercome", + Short: "list intercome ", + Long: "list intercome", + Example: "ucloud vpc list-intercome --vpc-id xx", + Run: func(cmd *cobra.Command, args []string) { + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + resp, err := client.DescribeVPCIntercom(req) + if err != nil { + ctx.HandleError(err) + return + } + list := make([]IntercomRow, 0) + for _, vpcIntercom := range resp.DataSet { + row := IntercomRow{} + row.ProjectID = vpcIntercom.ProjectId + row.Segments = strings.Join(vpcIntercom.Network, ",") + row.DstRegion = vpcIntercom.DstRegion + row.VPCName = vpcIntercom.Name + row.ResourceID = vpcIntercom.VPCId + row.Group = vpcIntercom.Tag + list = append(list, row) + } + ctx.PrintList(list) + }, + } + req.VPCId = cmd.Flags().String("vpc-id", "", "Required. The vpc id which you wnat to describe the information") + req.ProjectId = cmd.Flags().String("project-id", ctx.DefaultProjectID(), "Optional. The project id of source vpc") + req.Region = cmd.Flags().String("region", ctx.DefaultRegion(), "Optional, The region of source vpc") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "region", ctx.RegionList) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + + cmd.MarkFlagRequired("vpc-id") + + return cmd +} diff --git a/products/vpc/internal/vpc/rows.go b/products/vpc/internal/vpc/rows.go new file mode 100644 index 0000000000..96973e8eb7 --- /dev/null +++ b/products/vpc/internal/vpc/rows.go @@ -0,0 +1,19 @@ +package vpc + +type Row struct { + VPCName string + ResourceID string + Group string + NetworkSegment string + SubnetCount int + CreationTime string +} + +type IntercomRow struct { + VPCName string + ResourceID string + Segments string + ProjectID string + DstRegion string + Group string +} diff --git a/products/vpc/product.go b/products/vpc/product.go new file mode 100644 index 0000000000..e7fda062fb --- /dev/null +++ b/products/vpc/product.go @@ -0,0 +1,20 @@ +package vpc + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalvpc "github.com/ucloud/ucloud-cli/products/vpc/internal/vpc" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "vpc", Commands: []string{"vpc"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalvpc.NewCommand(ctx)} +} diff --git a/products/vpc/product.yaml b/products/vpc/product.yaml new file mode 100644 index 0000000000..831c06f2d1 --- /dev/null +++ b/products/vpc/product.yaml @@ -0,0 +1,6 @@ +name: vpc +owners: + - Episkey-G +commands: + - vpc +enabled: true diff --git a/products/vpc/testdata/cmdtree.golden b/products/vpc/testdata/cmdtree.golden new file mode 100644 index 0000000000..bb4333254d --- /dev/null +++ b/products/vpc/testdata/cmdtree.golden @@ -0,0 +1,34 @@ +ucloud vpc use=vpc short=List and manipulate VPC instances +ucloud vpc create use=create short=Create vpc network + flag=group short= default= required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=segment short= default=[] required=true +ucloud vpc create-intercome use=create-intercome short=Create intercome with other vpc + flag=dst-project-id short= default= required= + flag=dst-region short= default= required= + flag=dst-vpc-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=vpc-id short= default= required=true +ucloud vpc delete use=delete short=Delete vpc network + flag=project-id short= default= required= + flag=region short= default= required= + flag=vpc-id short= default=[] required=true +ucloud vpc delete-intercome use=delete-intercome short=delete the vpc intercome + flag=dst-region short= default= required=true + flag=dst-vpc-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=vpc-id short= default= required=true +ucloud vpc list use=list short=List vpc + flag=group short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=vpc-id short= default=[] required= +ucloud vpc list-intercome use=list-intercome short=list intercome + flag=project-id short= default= required= + flag=region short= default= required= + flag=vpc-id short= default= required=true diff --git a/products/vpc/testdata/completion.golden b/products/vpc/testdata/completion.golden new file mode 100644 index 0000000000..b9d2341b8d --- /dev/null +++ b/products/vpc/testdata/completion.golden @@ -0,0 +1,19 @@ +ucloud vpc create project-id dynamic +ucloud vpc create region dynamic +ucloud vpc create-intercome dst-project-id dynamic +ucloud vpc create-intercome dst-region dynamic +ucloud vpc create-intercome dst-vpc-id dynamic +ucloud vpc create-intercome project-id dynamic +ucloud vpc create-intercome region dynamic +ucloud vpc create-intercome vpc-id dynamic +ucloud vpc delete project-id dynamic +ucloud vpc delete region dynamic +ucloud vpc delete vpc-id dynamic +ucloud vpc delete-intercome dst-region dynamic +ucloud vpc delete-intercome vpc-id dynamic +ucloud vpc list project-id dynamic +ucloud vpc list region dynamic +ucloud vpc list vpc-id dynamic +ucloud vpc list-intercome project-id dynamic +ucloud vpc list-intercome region dynamic +ucloud vpc list-intercome vpc-id dynamic From e8794610b87e3a461a3fa58cd2c7655c35badf72 Mon Sep 17 00:00:00 2001 From: Episkey Date: Wed, 8 Jul 2026 04:36:42 -0700 Subject: [PATCH 2/3] build(deps): bump ucloud sdk to 0.22.86 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index becaa0b830..2f7314cb07 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +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.25 + github.com/ucloud/ucloud-sdk-go v0.22.86 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 1dfa7c9e96..bbfaef96ac 100644 --- a/go.sum +++ b/go.sum @@ -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.25 h1:ceKeH7WFnpUt9nJSubn+mnxS1iKGrk/Q+HLwa0iYwmQ= -github.com/ucloud/ucloud-sdk-go v0.22.25/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= +github.com/ucloud/ucloud-sdk-go v0.22.86 h1:V8lCn0qhKyGgZYSobmKX5+kUJX5QFqh1SWw1yngqXC8= +github.com/ucloud/ucloud-sdk-go v0.22.86/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= From 4907772f2181d7e1875275bcb3d4c6a22bbd9e3b Mon Sep 17 00:00:00 2001 From: Episkey Date: Wed, 8 Jul 2026 20:05:25 -0700 Subject: [PATCH 3/3] chore: remove local tool version pin from product split --- .tool-versions | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 7d8a6ba830..0000000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -golang 1.25.11