├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README-CN.md ├── README.md ├── go.mod ├── go.sum ├── lib ├── allpart_size.go ├── allpart_size_test.go ├── append_file.go ├── append_file_test.go ├── bucket_access_monitor.go ├── bucket_access_monitor_test.go ├── bucket_cname.go ├── bucket_cname_test.go ├── bucket_cors.go ├── bucket_cors_test.go ├── bucket_encryption.go ├── bucket_encryption_test.go ├── bucket_inventory.go ├── bucket_inventory_test.go ├── bucket_lifecycle.go ├── bucket_lifecycle_test.go ├── bucket_logging.go ├── bucket_logging_test.go ├── bucket_policy.go ├── bucket_policy_test.go ├── bucket_qos.go ├── bucket_qos_test.go ├── bucket_referer.go ├── bucket_referer_test.go ├── bucket_replication.go ├── bucket_replication_test.go ├── bucket_resource_group.go ├── bucket_resource_group_test.go ├── bucket_style.go ├── bucket_style_test.go ├── bucket_tagging.go ├── bucket_tagging_test.go ├── bucket_versioning.go ├── bucket_versioning_test.go ├── bucket_website.go ├── bucket_website_test.go ├── bucket_worm.go ├── bucket_worm_test.go ├── cat.go ├── cat_test.go ├── command.go ├── command_manager.go ├── command_test.go ├── config.go ├── config_helper.go ├── config_test.go ├── const.go ├── cors_options.go ├── cors_options_test.go ├── cp.go ├── cp_test.go ├── create_symlink.go ├── du.go ├── du_test.go ├── ecs_role.go ├── ecs_role_test.go ├── error.go ├── hash.go ├── hash_test.go ├── help.go ├── help_test.go ├── lang.go ├── lang_test.go ├── lang_windows.go ├── lang_windows_test.go ├── lcb.go ├── listpart.go ├── listpart_test.go ├── lrb.go ├── lrb_test.go ├── ls.go ├── ls_test.go ├── mb.go ├── mb_test.go ├── mkdir.go ├── mkdir_test.go ├── monitor.go ├── monitor_test.go ├── object_tagging.go ├── object_tagging_test.go ├── option.go ├── ossutil_log.go ├── ossutil_log_test.go ├── probe.go ├── probe_test.go ├── read_symlink.go ├── report_helper.go ├── request_payment.go ├── request_payment_test.go ├── restore.go ├── restore_test.go ├── revert_versioning.go ├── revert_versioning_test.go ├── rm.go ├── rm_test.go ├── set_acl.go ├── set_acl_test.go ├── set_meta.go ├── set_meta_test.go ├── signurl.go ├── signurl_test.go ├── stat.go ├── stat_test.go ├── storage_url.go ├── symlink_test.go ├── sync.go ├── sync_test.go ├── update.go ├── update_test.go ├── user_qos.go ├── user_qos_test.go ├── util.go ├── util_sts.go └── util_sts_test.go ├── ossutil.go └── scripts ├── build_all.sh └── ossutil.bat /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.8 4 | install: 5 | - go get golang.org/x/tools/cmd/cover 6 | - go get github.com/mattn/goveralls 7 | - go get gopkg.in/check.v1 8 | - go get github.com/droundy/goopt 9 | - go get github.com/alyu/configparser 10 | - go get github.com/aliyun/aliyun-oss-go-sdk/oss 11 | - go get github.com/syndtr/goleveldb/leveldb 12 | - go get github.com/satori/go.uuid 13 | script: 14 | # - cd lib 15 | # - travis_wait 120 go test -v -timeout 120m -covermode=count -coverprofile=coverage.out 16 | # - "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci" 17 | env: 18 | global: 19 | - secure: YO0OXWMcm0O+VH5xrQ66kKN/346bYnlLuk6hSbsR1Uvkg2OUkAXQVcIxnNkzCjSu2iYW8mvnx20GenjCLphlLK4n7vRBmRMhDm6UbHfi4ivVG7rx9q2Rl6q5Apd+TItafK9mxMaVxY7mSgMEgU4Mq9BMPP3bI7qfIvo5jJlpu6Lzr2PZKqLkVRzf0H+XoOdvKKz/HeneOztcMixhhKdq7IE60YG7lA+US67B43Ztgrw7B/LnieZGYtVqHnVvqc8LCBEuBYlSM1k3uPQZ8e8Un9An6KwpRcj4Ei94guvhN3SKuglVG93EUF2VluS+kuvHKQFsRHDOwU9vzPKUhmJ27HUSC4QC9IduiZ1NhWuCH8+yuaqjmeVmWEZu9XSSYn6/VIxD/DXPDTvfeyni256RatxIQLzgRrcmxzfHpfb7qQRQt9h0sqLOSeVDbyCUaufWZFR7WzPHGMVNEH2Z01zbjb7RTjdPPMP25PPPaNyERe4Udp1ezBuEdtaY3/UYMBxKqQNqZF23zXlJrl2srPZr1tASxBvU+vy7IIJUBVWYrWoPfoAGMMjXb8FhFeT/Ci/pTRnzBEBxL05Sz8T1KYRPWxQUtuFuV5JGcaP3epBSVGyMvI+p8cU2JnJC6d97lM5SAG9cWgevgvL7N1yBojK6w4lfi4kypEuhPGAOSCLhcGc= 20 | - secure: R1DGbPdXHg6jMy9OfZBCpIU4+ksB/6GzTuVayGx5QGHUWIgZ7KJeaYQBNms+Gh615bGFKK4POl4F/TS/2eRjefhbF+vk7OGa101gZYt/H7eGWrDbmtL+kd/RU8f+jSXLV/qnakv8Owx02+ovSONLn++0jSWb4AnH+8uQV1z3gsGAIfaCmtkIRVWjnBbL6zS9drwRFW/jrldh3ivfK2/0CCJvJe2n0rv0pE0QzXI23IXHS5wlw8cQTU3Sxw4ozcPytu7+52vXRejKdqnWCUeDbcVvayVzuOX3UeHkm719aMncagw3M5V2lbGE9GhnSxN06fpwkxhsGXNAQhqz9qA8O8+3rK8Ibtq3vfGc6sNQPF+Q/13FwUStOof4lhV+wPKO9nBMp3tkMeoMw4t4zMw6vY3ZXsQqwQRpItLX7qSE93qy70Nmq9ZNFNYLD9YE4whV4sBbR1KFjXUOe4HacURN42wAaTJv3ZpSt7TTQULgRWYehQ+pVenmqzQQ9PON0VTvWmmV780+FdcqZPaTYfB3C/aFQZotuKiwFFzcK9gxrqtKj4g/6hFA8TtME46aeK5NskI1jyeCjLc/0PBL3Ff6r/IKHdZHGBDjHuep89ZXeh9NBC4hzijSVEywClk+sO97BQYXPe5rbGJysF1bp/4Kqwy/ZVeTWJAS2enxGdSfk/Q= 21 | - secure: kLAGI8kNWrWCEPJDCyMAmKr6Z9TkgRSdR+eh93g8gfUjsEpePpP0PWBx+onnt0Ale62Ic9BZlj0Li8VUOgM1Cr6CRVDBiNj0Dj3PkdmovNWJwZG+C9KlD2UmMbaovOEBjoA4qw6TxdoY9hx9GWX3fduz2yDnK/lGuF6jb6gh1AjYYYMeQIFZhJ1WOgYAvdUEoXtolXQgv+HMhN13TU7v6VbNpkS3v3qP0vFoNhkFgIGS6Sa+NnEIB+YQGrQ8CoSUOh4CbJqT0LpUE7YQPRJsegGgQfQAVIB//fDsICGMiPI/QmBiKk3QuZpAViyGRc3CJm+GxYBJ6mEPRGxgSuPWH60YT+OWNzyiw07dT1u3XrqxAeyFGh9pCtW7UP5L5bWvFiOtZZhhBSPfaZE7RaukS9MCm2j7m3iPhqbu3TDFxwbD4UOXcuNTJa6UZWuwyereZjkiIFXfzYTGazvHKbx+8T627sXg5luKvuhAL9M+iEJOlezVtkCIEdkIVEC2q19I2KoZzc7Bzl+4lbq9gomDnul//sKbVQqjWoYQNwJmSRqxIkZf9Y8MIlyEwK3W8zyvi73oasBJJuxfEtlbN2bc0Su7HFnIQoEoG/+F5l7dZAKWS0qDy/Br/jGovnM9HoWAV6CQ+wyY9MmSxGO/PbK2XGWKmOXOs6VCXdaRO0Nz0DY= 22 | - secure: NUcfrTX8QcD8k5qWe6DLkbVWkcTlm2GGzSzs8byp557ctmIqk/OWyzeWKHZZbzkKxKcwj3vdSPLldwE9XpfqB0zomZPus3NKcex3izvW4TCAevbqdDE1QbZiqQ9rUh9I3CrEmYkB7nCQ1LnNvXI0RRZkei6N9/AeUFE2NcxW+p2hp1BJvivT1EziEB1VjPpBHyKkikwdvqcpWCosB2ehZmt1EDuEZSVWDz3mxcjk8UBXbs4St7Pm6QWF9598DxY3EwCbvPtqjkPns92afrWshPpXhB+ioqUyyV6jn3JXV6tT8DinUW7u065YoSEURBb2XjTp+A2sYBnSAEvYNwsDkTqlcA0mwxZPolyPUaOXIxJmUt0GMVEB73rVZ16asZ0Zb2QSXN0lEg6nwOS4jRXOHkhCpYPhHDYdihkNb6D5Z6Mk1EbMhOJiq2Mql4FtRn/YcoKU1jFr0FSe4mrynS3yrDsMshn1VYTslp2UbQZGoZCe8VnTogJwC47vramO3/oDA00ZKSZW+kRznikhE/wD8Zh+J5jwIVqvYHFqxn0ZNAVSYz5miDO9Oa5hBl+FitQQgk67v9GMcJXxN3Z03IQrD3TK+djXsi8aX8HDv5SujXTmfgjTJMdPXR76gr/XsBVokmkXZYZDRhWQdZUqFPOQ1/87QyV86AQEdGAbabQ0eI0= 23 | - secure: t0DjGUs3JBZOK8LvMwsVRKL23EJGFPhUcBrFxBVgt0ZPUfe5vMh/QBYesMhAZFNajKWE8g2UJa0ecURXSsnm5xpa7XF+f/12DRCR2/RqflY12/i8D8rc3V7hEmuK3TyBcu4ki+d3vgGdTm6EKgrk0n5x4U29O0hYApctPFCIJXZbU+J85KH8VVNL0uo6tnihzDcEgGe5o4SrCGqpzT4upEhw7jXeoy07118EgGCRvT6GeS6lJi6TbuIsAop7K+uVmEG6HXOpo7jzWcdf7aUGIWoVYwVeTcm9twUv6isbde5mVkkUGHMwDrzi+wv5QL/uv5qkk4fz+zYWqbWNN5QuS4kSgsinsGDNVkx69KaqoqXaYaxLH/TWvlQsvigA32vpKQXdb3NcEeZFABAOEgTl08bygJIAHYOOy9XPl5bEmHboyr7oMSkkqgHGn+rITNRFLhvXUKe/U9SYq4Rth3jcI0OzbSIW1ErNalC6ZW4g8kBKFDvYapE5W3EB8EmW2GkRWd3WgB2USPra5DwsLyq+7kLM06Otg8ZIzcYHNqU5HWpScB/xLrPDBZXYzF2/vjSoweSRCHoTQkEj4PecxMn8YE9cyGSC83HzzIwtQ1kmWoZ6kUtcYTENhq0SnRIK0JrdqbtNYx68ODhZAkFSBACG44ssjBuBEeHV7hXv+DKMIlc= -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 版本号:v1.7.19日期:2023-11-19 2 | ### 变更内容 3 | - 增加: sign 命令支持 v4签名 4 | - 更新: golang.org/x/crypto v0.17.0 5 | - 更新: alyun-oss-sdk-go v3.0.2 6 | - 更新: ecs ram provider 支持返回错误 7 | 8 | ## 版本号:v1.7.18日期:2023-10-30 9 | ### 变更内容 10 | - 增加: 支持更多的probe 测试项 11 | - 增加: cp 命令支持 start-time 和 end-time 选项. 12 | 13 | ## 版本号:v1.7.17日期:2023-09-18 14 | ### 变更内容 15 | - 增加: 支持 path-style 访问方式 16 | 17 | ## 版本号:v1.7.16日期:2023-05-19 18 | ### 变更内容 19 | - 增加: 支持资源组接口 20 | - 增加: 支持图片样式接口 21 | - 增加: 支持更多的区域复制接口 22 | - 增加:支持更多的canme接口 23 | - 修复:arm环境下,update 命令更新不正确的问题 24 | - 增加:mb命令支持服务端加密参数 25 | 26 | ## 版本号:v1.7.15日期:2023-01-12 27 | ### 变更内容 28 | - 增加: 支持访问跟踪接口 29 | - 增加: 配置文件新增Default节,支持配置通用参数 30 | - 增加: sign命令支持query-param 选项,支持更多的参数设置 31 | - 修复:优化对symlink类型object下载, 对象大小获取不正确时的处理 32 | 33 | ## 版本号:v1.7.14日期:2022-09-14 34 | ### 变更内容 35 | - 增加: 分片上传支持X-Oss-Notification 参数 36 | - 增加: 过滤 mime type 接口返回的 charset= 信息 37 | - 增加: config 配置文件支持 user-agent 参数 38 | - 增加: sign 命令支持更多的凭证设置参数 39 | - 修复:修复sync命令在不同文件系统下重名失败的问题 40 | - 修复:修复 set meta 接口 不支持通用 header 参数设置 41 | - 修复:windows平台下, 不使用uname 方式获取平台信息 42 | 43 | ## 版本号:v1.7.13日期:2022-05-25 44 | ### 变更内容 45 | - 增加: cname命令支持创建、删除等操作 46 | - 增加: 清单命令使用xml的api 47 | - 修复:修复lcb命令显示不齐问题 48 | 49 | 50 | ## 版本号:v1.7.12日期:2022-05-13 51 | ### 变更内容 52 | - 增加: 支持cloud box 53 | - 增加: 支持v4签名 54 | 55 | ## 版本号:v1.7.11日期:2022-04-22 56 | ### 变更内容 57 | - 增加: restore和set-meta支持传入文件进行批量操作 58 | 59 | 60 | ## 版本号:v1.7.10日期:2022-03-24 61 | ### 变更内容 62 | - 增加: lifecycle命令使用新接口,直接返回服务端的xml内容 63 | 64 | 65 | ## 版本号:v1.7.9日期:2022-01-21 66 | ### 变更内容 67 | - 增加: 取消对于meta格式的限制,只要以x-oss开头即可 68 | 69 | ## 版本号:v1.7.8日期:2021-12-15 70 | ### 变更内容 71 | - 增加: cp下载支持传入meta信息,比如Accept-Encoding 72 | - 修复:set-acl修改bucket权限属性时候,如果bucket不存在行为从“自动创建”改成报错 73 | - 增加: 创建bucket支持传入配置文件,配置文件内容为bucket的创建属性 74 | - 增加: cp命令批量上传文件,如果扫描本地文件出错支持暂时忽略 75 | - 增加: 增加--ua选项,支持用户自定义user agent,会加在工具设置的user agent后面 76 | 77 | ## 版本号:v1.7.7日期:2021-08-26 78 | ### 变更内容 79 | - 增加: 支持cname查询 80 | - 增加:lifecycle配置支持prefix overlap 81 | 82 | ## 版本号:v1.7.6日期:2021-08-05 83 | ### 变更内容 84 | - 增加: 支持限速下载 85 | - 增加:支持同步边管理功能 86 | 87 | ## 版本号:v1.7.5 日期:2021-07-09 88 | ### 变更内容 89 | - 增加: 增加lrb命令 90 | - 增加:支持选择跳过服务端证书校验 91 | 92 | ## 版本号:v1.7.4 日期:2021-07-30 93 | ### 变更内容 94 | - 增加: 支持多种鉴权模式 95 | 96 | ## 版本号:v1.7.3 日期:2021-04-09 97 | ### 变更内容 98 | - 增加: du命令支持不同单位显示大小 99 | 100 | ## 版本号:v1.7.2 日期:2021-03-24 101 | ### 变更内容 102 | - 增加: 支持从键盘读取accessKeySecret 103 | 104 | ## 版本号:v1.7.1 日期:2021-01-13 105 | ### 变更内容 106 | - 增加: 增加worm命令 107 | 108 | ## 版本号:v1.7.0 日期:2020-11-19 109 | ### 变更内容 110 | - 增加: 增加sync命令 111 | - 增加:restore归档object配置文件支持传入天数参数 112 | 113 | ## 版本号:v1.6.19 日期:2020-09-03 114 | ### 变更内容 115 | - 修复: set-meta命令的更新模式会将object的acl重置为default 116 | 117 | ## 版本号:v1.6.18 日期:2020-07-24 118 | ### 变更内容 119 | - 修复: 使用oss go sdk v2.1.4重新编译,lifecycle支持输入NoncurrentVersionTransition数组 120 | 121 | ## 版本号:v1.6.17 日期:2020-07-11 122 | ### 变更内容 123 | - 修复: 使用oss go sdk v2.1.3重新编译,lifecycle支持冷归档(ColdArchive) 124 | 125 | ## 版本号:v1.6.16 日期:2020-07-06 126 | ### 变更内容 127 | - 新增: 优化对symlink类型object下载,object size取指向的目标object size 128 | 129 | ## 版本号:v1.6.15 日期:2020-06-24 130 | ### 变更内容 131 | - 新增: 增加revert-versioning命令 132 | 133 | ## 版本号:v1.6.14 日期:2020-06-04 134 | ### 变更内容 135 | - 新增: 支持国密(SM4)及其BYOK功能 136 | 137 | ## 版本号:v1.6.13 日期:2020-05-09 138 | ### 变更内容 139 | - 修复:windows vpn某些场景下获取home目录比较耗时 140 | - 修复:优化user agent获取频率, 仅启动时候获取一次 141 | - 修复: 下载时候对于目录已经存在情况不显示确认信息 142 | 143 | ## 版本号:v1.6.12 日期:2020-04-21 144 | ### 变更内容 145 | - 增加:支持清单、冷归档 146 | - 修复:cp命令进度条超过100%修复 147 | 148 | ## 版本号:v1.6.11 日期:2020-04-09 149 | ### 变更内容 150 | - 增加:du命令支持多版本 151 | - 增加:cp命令支持设置tagging 152 | - 增加: cp命令支持是否忽略错误选项 153 | 154 | ## 版本号:v1.6.10 日期:2020-01-03 155 | ### 变更内容 156 | - 增加:增加选项--disable-all-symlink, 上传时可以忽略所有的链接文件以及链接子目录 157 | - 增加:签名url支持访问者付费 158 | - 修复: 修复统计链接文件FileSize为0的bug 159 | 160 | 161 | ## 版本号:v1.6.9 日期:2019-11-13 162 | ### 变更内容 163 | - 增加:优化config命令的提示 164 | - 增加:支持创建3AZ的bucket 165 | 166 | ## 版本号:v1.6.8 日期:2019-10-23 167 | ### 变更内容 168 | - 增加:支持上传链接子目录 169 | - 增加:支持单级目录而非递归目录上传、下载以及copy 170 | - 增加:支持工具绑定指定ip地址(针对用户多网卡场景) 171 | - 增加:支持本地循环链接文件或者目录检测 172 | - 增加:支持并发带宽上传和下载测速 173 | - 增加:sign命令可以选择将path中的符号/不进行编码 174 | - 增加:cp命令上传时可以选择不为目录生成object 175 | - 修复: cp命令的失败重试会导致进度条超过100% 176 | 177 | ## 版本号:v1.6.7 日期:2019-09-17 178 | ### 变更内容 179 | - 修复:使用最新oss go sdk v2.0.3编译,修复无法分块上传归档object的bug 180 | 181 | ## 版本号:v1.6.6 日期:2019-08-06 182 | ### 变更内容 183 | - 增加:支持http/https/socks5代理 184 | - 增加:增加du、policy、request-payment、object-tagging命令 185 | - 增加:appendfromfile,cat,symlink,ls,restore,rm,stat支持访问者付费模式 186 | - 增加:url签名支持http连接限速参数x-oss-traffic-limit 187 | - 增加:read-symlink支持多版本 188 | - 增加:修复批量rm objects异常时,错误信息提示错误 189 | 190 | ## 版本号:v1.6.5 日期:2019-07-18 191 | ### 变更内容 192 | - 增加:cherry-pick之前的多版本特性 193 | 194 | ## 版本号:v1.6.4 日期:2019-07-12 195 | ### 变更内容 196 | - 增加:增加user-qos, bucket-qos命令 197 | - 增加: 支持利用ecs绑定的角色操作 198 | - 增加: ls & rm命令支持include、exclude选项 199 | - 修复: cp命令传输速度统计不准 200 | - 修复: windows平台配置项长度不能超过256字符 201 | - 修复: 无法删除key中带有特殊字符的object 202 | 203 | ## 版本号:v1.6.3 日期:2019-06-19 204 | ### 变更内容 205 | - 增加:增加lifecycle, website, cors-options命令 206 | 207 | ## 版本号:v1.6.2 日期:2019-06-06 208 | ### 变更内容 209 | - 增加:增加多版本versioning特性 210 | 211 | ## 版本号:v1.6.1 日期:2019-05-29 212 | ### 变更内容 213 | - 增加:增加bucket-tagging命令 214 | - 增加:增加bucket-encryption命令 215 | - 增加:为下载支持snapshot-path选项 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 aliyun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # Aliyun OSSUTIL 2 | 3 | [![GitHub version](https://badge.fury.io/gh/aliyun%2Fossutil.svg)](https://badge.fury.io/gh/aliyun%2Fossutil) 4 | [![Build Status](https://travis-ci.org/aliyun/ossutil.svg?branch=master)](https://travis-ci.org/aliyun/ossutil) 5 | [![Coverage Status](https://coveralls.io/repos/github/aliyun/ossutil/badge.svg?branch=master)](https://coveralls.io/github/aliyun/ossutil?branch=master) 6 | 7 | ### [README of English](https://github.com/aliyun/ossutil/blob/master/README.md) 8 | 9 | ## 关于 10 | 11 | - 此工具采用go语言,基于OSS[阿里云对象存储服务](http://www.aliyun.com/product/oss/)官方GO SDK 构建。 12 | - 阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量,安全,低成本,高可靠的云存储服务。 13 | - OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。 14 | - 该工具旨在为用户提供一个方便的,以命令行方式管理OSS数据的途径。 15 | - 当前版本提供了列举和删除Multipart Uploads功能。 16 | - 当前版本未提供Bucket管理功能功能,相关功能会在后续版本中开发。 17 | 18 | ## 版本 19 | 20 | - 当前版本:v1.7.19 21 | 22 | ## 运行环境 23 | 24 | - Linux 25 | - Windows 26 | - Mac 27 | 28 | ## 依赖的库 29 | 30 | - goopt (github.com/droundy/goopt) 31 | - configparser (github.com/alyu/configparser) 32 | - leveldb (github.com/syndtr/goleveldb/leveldb) 33 | - oss (github.com/aliyun/aliyun-oss-go-sdk/oss) 34 | - gopkg.in/check.v1 (gopkg.in/check.v1) 35 | 36 | ## 快速使用 37 | 38 | #### 获取命令列表 39 | 40 | ```go 41 | ./ossutil 42 | 或 ./ossutil help 43 | ``` 44 | 45 | #### 查看某命令的帮助文档 46 | 47 | ```go 48 | ./ossutil help cmd 49 | ``` 50 | 51 | #### 配置ossutil 52 | 53 | ```go 54 | ./ossutil config 55 | ``` 56 | 57 | #### 列举Buckets 58 | 59 | ```go 60 | ./ossutil ls 61 | 或 ./ossutil ls oss:// 62 | ``` 63 | 64 | #### 列举objects和Multipart Uploads 65 | 66 | ```go 67 | ./ossutil ls -a 68 | 或 ./ossutil ls oss:// -a 69 | ``` 70 | 71 | #### 上传文件 72 | 73 | ```go 74 | ./ossutil cp localfile oss://bucket 75 | ``` 76 | 77 | #### 其它 78 | 79 | 请使用./ossutil help cmd来查看想要使用的命令的帮助文档。 80 | 81 | ## 注意事项 82 | 83 | ### 运行 84 | 85 | - 首先配置您的go工程目录。 86 | - go get该工程依赖的库。 87 | - go get github.com/aliyun/ossutil。 88 | - 进入go工程目录下的src目录,build代码生成ossutil工具,例如:在linux下可以运行go build github.com/aliyun/ossutil/ossutil.go。 89 | - 参考上面示例运行ossutil工具。 90 | 91 | ### 测试 92 | 93 | - 进入go工程目录下的src目录,修改github.com/aliyun/ossutil/lib/command_test.go里的endpoint、AccessKeyId、AccessKeySecret、STSToken等配置。 94 | - 请在lib目录下执行`go test`。 95 | 96 | ## 联系我们 97 | 98 | - [阿里云OSS官方网站](http://oss.aliyun.com) 99 | - [阿里云OSS官方论坛](http://bbs.aliyun.com) 100 | - [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs) 101 | 102 | ## 作者 103 | 104 | - [Ting Zhang](https://github.com/dengwu12) 105 | 106 | ## License 107 | 108 | - [MIT](https://github.com/aliyun/ossutil/blob/master/LICENSE) 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alibaba Cloud OSSUTIL 2 | 3 | [![GitHub Version](https://badge.fury.io/gh/aliyun%2Fossutil.svg)](https://badge.fury.io/gh/aliyun%2Fossutil) 4 | [![Build Status](https://travis-ci.org/aliyun/ossutil.svg?branch=master)](https://travis-ci.org/aliyun/ossutil) 5 | [![Coverage Status](https://coveralls.io/repos/github/aliyun/ossutil/badge.svg?branch=master)](https://coveralls.io/github/aliyun/ossutil?branch=master) 6 | 7 | ### [README of Chinese](https://github.com/aliyun/ossutil/blob/master/README-CN.md) 8 | 9 | ## About 10 | 11 | - This tool is developed with Go and built on the official GO SDK of OSS [Alibaba Cloud Object Storage Service](http://www.aliyun.com/product/oss/). 12 | - OSS is a cloud storage service provided by Alibaba Cloud, featuring massive capacity, security, low cost, and high reliability. 13 | - OSS can store any type of files. It applies to various websites, development enterprises and developers. 14 | - This tool aims to provide a convenient-to-use command line for users to manage data in OSS. 15 | - The current version provides to list and delete multipart upload tasks. 16 | - The current version does not support bucket management. The feature will be available in future versions. 17 | 18 | ## Version 19 | 20 | - Current version: v1.7.19 21 | 22 | ## Run environment 23 | 24 | - Linux 25 | - Windows 26 | - Mac 27 | 28 | ## Dependent libraries 29 | 30 | - goopt (github.com/droundy/goopt) 31 | - configparser (github.com/alyu/configparser) 32 | - leveldb (github.com/syndtr/goleveldb/leveldb) 33 | - oss (github.com/aliyun/aliyun-oss-go-sdk/oss) 34 | - gopkg.in/check.v1 (gopkg.in/check.v1) 35 | 36 | ## Quick use 37 | 38 | #### Get the command list 39 | 40 | ```go 41 | ./ossutil 42 | or ./ossutil help 43 | ``` 44 | 45 | #### View the help documentation for a command 46 | 47 | ```go 48 | ./ossutil help cmd 49 | ``` 50 | 51 | #### Configure OSSUTIL 52 | 53 | ```go 54 | ./ossutil config 55 | ``` 56 | 57 | #### List buckets 58 | 59 | ```go 60 | ./ossutil ls 61 | or ./ossutil ls oss:// 62 | ``` 63 | 64 | #### List objects and multipart upload tasks 65 | 66 | ```go 67 | ./ossutil ls -a 68 | or ./ossutil ls oss:// -a 69 | ``` 70 | 71 | #### Upload a file 72 | 73 | ```go 74 | ./ossutil cp localfile oss://bucket 75 | ``` 76 | 77 | #### Others 78 | 79 | You can use `./ossutil help cmd` to view the help documentation for the command you want to use. 80 | 81 | ## Notes 82 | 83 | ### Run OSSUTIL 84 | 85 | - First, configure your Go project directory. 86 | - Use `go get` to get the library that ossutil depends on. 87 | - Run `go get github.com/aliyun/ossutil`. 88 | - Enter the *src* directory under the Go project directory and build to generate the OSSUTIL tool. For example, on Linux, you can run `go build github.com/aliyun/ossutil/ossutil.go`. 89 | - Refer to the example above to run the OSSUTIL tool. 90 | 91 | ### Test 92 | 93 | - Enter the *src* directory under the Go project directory and modify the endpoint, AccessKeyId, AccessKeySecret and STSToken configuration items in the *github.com/aliyun/ossutil/lib/command_test.go*. 94 | - Run `go test` under the *lib* directory. 95 | 96 | ## Contact us 97 | 98 | - [Alibaba Cloud OSS official website](http://oss.aliyun.com). 99 | - [Alibaba Cloud OSS official forum](http://bbs.aliyun.com). 100 | - [Alibaba Cloud OSS official documentation center](http://www.aliyun.com/product/oss#Docs). 101 | 102 | ## Author 103 | 104 | - [Ting Zhang](https://github.com/dengwu12) 105 | 106 | ## License 107 | 108 | - [MIT](https://github.com/aliyun/ossutil/blob/master/LICENSE) 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aliyun/ossutil 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible 7 | github.com/alyu/configparser v0.0.0-20191103060215-744e9a66e7bc 8 | github.com/droundy/goopt v0.0.0-20220217183150-48d6390ad4d1 9 | github.com/syndtr/goleveldb v1.0.0 10 | golang.org/x/crypto v0.17.0 11 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect 12 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= 2 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= 3 | github.com/alyu/configparser v0.0.0-20191103060215-744e9a66e7bc h1:eN2FUvn4J1A31pICABioDYukoh1Tmlei6L3ImZUin/I= 4 | github.com/alyu/configparser v0.0.0-20191103060215-744e9a66e7bc/go.mod h1:BYq/NZTroWuzkvsTPJgRBqSHGxKMHCz06gtlfY/W5RU= 5 | github.com/droundy/goopt v0.0.0-20220217183150-48d6390ad4d1 h1:6PKU05V7zJIJlTBq7AnEIrLVEUIYF4NjTU2a28Ho6ko= 6 | github.com/droundy/goopt v0.0.0-20220217183150-48d6390ad4d1/go.mod h1:ytRJ64WkuW4kf6/tuYqBATBCRFUP8X9+LDtgcvE+koI= 7 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 12 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 13 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 14 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 15 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 16 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 17 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 18 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 20 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 21 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 22 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 23 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 24 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 25 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 26 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 27 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 28 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 29 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 30 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 31 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 32 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 33 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 34 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 35 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 36 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 37 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 38 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 39 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 40 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 41 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 44 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 45 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 47 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 55 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 56 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 57 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 58 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 59 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 60 | golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= 61 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 62 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 63 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 64 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 65 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 66 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 67 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 68 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 69 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= 70 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 71 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 72 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 73 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 74 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 75 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 76 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 77 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 78 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 79 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 80 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 81 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 82 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 83 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 84 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 85 | -------------------------------------------------------------------------------- /lib/allpart_size.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 9 | ) 10 | 11 | var specChineseAllPartSize = SpecText{ 12 | synopsisText: "获取bucket所有未完成上传的multipart object的分块大小以及总和", 13 | 14 | paramText: "bucket_url [options]", 15 | 16 | syntaxText: ` 17 | ossutil getallpartsize oss://bucket [options] 18 | `, 19 | 20 | detailHelpText: ` 21 | 该命令会获取bucket所有未完成上传的multipart object的分块大小以及总和 22 | 23 | 24 | 用法: 25 | 26 | 该命令只有一种用法: 27 | 28 | 1) ossutil getallpartsize oss://bucket [options] 29 | 查询bucket的所有未完成上传的multipart object的块大小信息以及总和 30 | `, 31 | 32 | sampleText: ` 33 | 1) 根据bucket查询所有未完成上传的multipart object的块大小信息以及总和 34 | ossutil getallpartsize oss://bucket 35 | `, 36 | } 37 | 38 | var specEnglishAllPartSize = SpecText{ 39 | synopsisText: "Get bucket all uncompleted multipart objects's parts size and sum size", 40 | 41 | paramText: "bucket_url [options]", 42 | 43 | syntaxText: ` 44 | ossutil getallpartsize oss://bucket [options] 45 | `, 46 | 47 | detailHelpText: ` 48 | This command will list every uncompleted multipart objects's part size and sum size 49 | 50 | 51 | Usages: 52 | 53 | There is only one usage for this command: 54 | 55 | 1) ossutil getallpartsize oss://bucket [options] 56 | Get bucket all uncompleted mulitpart objects's parts size and sum size 57 | `, 58 | 59 | sampleText: ` 60 | 1) Get bucket all uncompleted multipart objects's parts size and sum size 61 | ossutil getallpartsize oss://bucket 62 | `, 63 | } 64 | 65 | type allPartSizeOptionType struct { 66 | bucketName string 67 | encodingType string 68 | headLineShowed bool 69 | statList []StatPartInfo 70 | } 71 | 72 | type AllPartSizeCommand struct { 73 | command Command 74 | apOption allPartSizeOptionType 75 | } 76 | 77 | var allPartSizeCommand = AllPartSizeCommand{ 78 | command: Command{ 79 | name: "getallpartsize", 80 | nameAlias: []string{"getallpartsize"}, 81 | minArgc: 1, 82 | maxArgc: 1, 83 | specChinese: specChineseAllPartSize, 84 | specEnglish: specEnglishAllPartSize, 85 | group: GroupTypeNormalCommand, 86 | validOptionNames: []string{ 87 | OptionConfigFile, 88 | OptionEndpoint, 89 | OptionAccessKeyID, 90 | OptionAccessKeySecret, 91 | OptionSTSToken, 92 | OptionProxyHost, 93 | OptionProxyUser, 94 | OptionProxyPwd, 95 | OptionEncodingType, 96 | OptionLogLevel, 97 | OptionPassword, 98 | OptionMode, 99 | OptionECSRoleName, 100 | OptionTokenTimeout, 101 | OptionRamRoleArn, 102 | OptionRoleSessionName, 103 | OptionReadTimeout, 104 | OptionConnectTimeout, 105 | OptionSTSRegion, 106 | OptionSkipVerifyCert, 107 | OptionUserAgent, 108 | OptionSignVersion, 109 | OptionRegion, 110 | OptionCloudBoxID, 111 | OptionForcePathStyle, 112 | }, 113 | }, 114 | } 115 | 116 | type StatPartInfo struct { 117 | objectName string 118 | uploadId string 119 | } 120 | 121 | // function for FormatHelper interface 122 | func (apc *AllPartSizeCommand) formatHelpForWhole() string { 123 | return apc.command.formatHelpForWhole() 124 | } 125 | 126 | func (apc *AllPartSizeCommand) formatIndependHelp() string { 127 | return apc.command.formatIndependHelp() 128 | } 129 | 130 | // Init simulate inheritance, and polymorphism 131 | func (apc *AllPartSizeCommand) Init(args []string, options OptionMapType) error { 132 | return apc.command.Init(args, options, apc) 133 | } 134 | 135 | // RunCommand simulate inheritance, and polymorphism 136 | func (apc *AllPartSizeCommand) RunCommand() error { 137 | srcBucketUrL, err := GetCloudUrl(apc.command.args[0], "") 138 | if err != nil { 139 | return err 140 | } 141 | apc.apOption.bucketName = srcBucketUrL.bucket 142 | apc.apOption.encodingType, _ = GetString(OptionEncodingType, apc.command.options) 143 | 144 | // first:get all object uploadid 145 | err = apc.GetAllStatInfo() 146 | if err != nil { 147 | return err 148 | } 149 | 150 | //second:stat every object parts 151 | client, err := apc.command.ossClient(apc.apOption.bucketName) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | bucket, err := client.Bucket(apc.apOption.bucketName) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | var totalPartCount int64 = 0 162 | var totalPartSize int64 = 0 163 | for _, v := range apc.apOption.statList { 164 | partCount, partSize, err := apc.GetObjectPartsSize(bucket, v) 165 | if err != nil { 166 | return err 167 | } 168 | totalPartCount += partCount 169 | totalPartSize += partSize 170 | } 171 | 172 | if totalPartSize > 0 { 173 | fmt.Printf("\ntotal part count:%d\ttotal part size(MB):%.2f\n\n", totalPartCount, float64(totalPartSize/1024)/1024) 174 | } 175 | 176 | return nil 177 | } 178 | 179 | func (apc *AllPartSizeCommand) GetAllStatInfo() error { 180 | client, err := apc.command.ossClient(apc.apOption.bucketName) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | bucket, err := client.Bucket(apc.apOption.bucketName) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | keyMarker := "" 191 | uploadIdMarker := "" 192 | for i := 0; ; i++ { 193 | lpOptions := []oss.Option{} 194 | lpOptions = append(lpOptions, oss.MaxParts(1000)) 195 | lpOptions = append(lpOptions, oss.KeyMarker(keyMarker)) 196 | lpOptions = append(lpOptions, oss.UploadIDMarker(uploadIdMarker)) 197 | 198 | lpRes, err := bucket.ListMultipartUploads(lpOptions...) 199 | if err != nil { 200 | return err 201 | } 202 | 203 | for _, v := range lpRes.Uploads { 204 | var statPartInfo StatPartInfo 205 | statPartInfo.objectName = v.Key 206 | statPartInfo.uploadId = v.UploadID 207 | apc.apOption.statList = append(apc.apOption.statList, statPartInfo) 208 | } 209 | 210 | if lpRes.IsTruncated { 211 | keyMarker = lpRes.NextKeyMarker 212 | uploadIdMarker = lpRes.NextUploadIDMarker 213 | } else { 214 | break 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | func (apc *AllPartSizeCommand) GetObjectPartsSize(bucket *oss.Bucket, statInfo StatPartInfo) (int64, int64, error) { 221 | var imur oss.InitiateMultipartUploadResult 222 | imur.Bucket = apc.apOption.bucketName 223 | imur.Key = statInfo.objectName 224 | imur.UploadID = statInfo.uploadId 225 | 226 | partNumberMarker := 0 227 | var totalPartCount int64 = 0 228 | var totalPartSize int64 = 0 229 | var cloudUrl CloudURL 230 | for i := 0; ; i++ { 231 | lpOptions := []oss.Option{} 232 | lpOptions = append(lpOptions, oss.MaxParts(1000)) 233 | lpOptions = append(lpOptions, oss.PartNumberMarker(partNumberMarker)) 234 | 235 | lpRes, err := bucket.ListUploadedParts(imur, lpOptions...) 236 | if err != nil { 237 | return 0, 0, err 238 | } else { 239 | totalPartCount += int64(len(lpRes.UploadedParts)) 240 | if !apc.apOption.headLineShowed && len(lpRes.UploadedParts) > 0 { 241 | fmt.Printf("%-10s\t%-32s\t%-10s\t%s\n", "PartNumber", "UploadId", "Size(Byte)", "Path") 242 | apc.apOption.headLineShowed = true 243 | } 244 | } 245 | 246 | for _, v := range lpRes.UploadedParts { 247 | cloudUrl.bucket = apc.apOption.bucketName 248 | if apc.apOption.encodingType == URLEncodingType { 249 | cloudUrl.object = url.QueryEscape(imur.Key) 250 | } else { 251 | cloudUrl.object = imur.Key 252 | } 253 | 254 | //PartNumber,uploadId,Size,Path 255 | fmt.Printf("%-10d\t%-32s\t%-10d\t%s\n", v.PartNumber, imur.UploadID, v.Size, cloudUrl.ToString()) 256 | totalPartSize += int64(v.Size) 257 | } 258 | 259 | if lpRes.IsTruncated { 260 | partNumberMarker, err = strconv.Atoi(lpRes.NextPartNumberMarker) 261 | if err != nil { 262 | return 0, 0, err 263 | } 264 | } else { 265 | break 266 | } 267 | } 268 | return totalPartCount, totalPartSize, nil 269 | 270 | } 271 | -------------------------------------------------------------------------------- /lib/allpart_size_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | func (s *OssutilCommandSuite) TestAllPartSize(c *C) { 13 | // create bucket 14 | bucketName := bucketNamePrefix + randLowStr(12) 15 | s.putBucket(bucketName, c) 16 | 17 | // object name 18 | objectName := "test-ossutil-object-" + randLowStr(10) 19 | 20 | // create file 21 | fileName := "test-ossutil-appendfile" + randLowStr(5) 22 | strText := randLowStr(1024 * 10) 23 | s.createFile(fileName, strText, c) 24 | 25 | // prepare chunks 26 | chunks, err := oss.SplitFileByPartNum(fileName, 10) 27 | c.Assert(err, IsNil) 28 | 29 | fd, err := os.Open(fileName) 30 | c.Assert(err, IsNil) 31 | defer fd.Close() 32 | 33 | // begin upload part 34 | client, err := oss.New(endpoint, accessKeyID, accessKeySecret) 35 | c.Assert(err, IsNil) 36 | 37 | bucket, err := client.Bucket(bucketName) 38 | c.Assert(err, IsNil) 39 | 40 | imur, err := bucket.InitiateMultipartUpload(objectName) 41 | c.Assert(err, IsNil) 42 | 43 | var parts []oss.UploadPart 44 | for _, chunk := range chunks { 45 | fd.Seek(chunk.Offset, os.SEEK_SET) 46 | part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number) 47 | c.Assert(err, IsNil) 48 | parts = append(parts, part) 49 | } 50 | 51 | // not CompleteMultipartUpload 52 | 53 | // begin getallpartsize 54 | var str string 55 | options := OptionMapType{ 56 | "endpoint": &str, 57 | "accessKeyID": &str, 58 | "accessKeySecret": &str, 59 | "stsToken": &str, 60 | "configFile": &configFile, 61 | } 62 | 63 | // output to file 64 | outputFile := "test-file-" + randLowStr(5) 65 | testResultFile, err = os.OpenFile(outputFile, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0664) 66 | c.Assert(err, IsNil) 67 | 68 | oldStdout := os.Stdout 69 | os.Stdout = testResultFile 70 | 71 | alArgs := []string{CloudURLToString(bucketName, "")} 72 | _, err = cm.RunCommand("getallpartsize", alArgs, options) 73 | if err != nil { 74 | fmt.Printf("error:%s\n", err.Error()) 75 | } 76 | c.Assert(err, IsNil) 77 | testResultFile.Close() 78 | 79 | os.Stdout = oldStdout 80 | 81 | // check file content 82 | outBody := s.readFile(outputFile, c) 83 | c.Assert(strings.Contains(outBody, "total part count:10"), Equals, true) 84 | 85 | os.Remove(outputFile) 86 | os.Remove(fileName) 87 | s.removeBucket(bucketName, true, c) 88 | } 89 | 90 | func (s *OssutilCommandSuite) TestAllPartSizeBucketError(c *C) { 91 | 92 | var str string 93 | options := OptionMapType{ 94 | "endpoint": &str, 95 | "accessKeyID": &str, 96 | "accessKeySecret": &str, 97 | "stsToken": &str, 98 | "configFile": &configFile, 99 | } 100 | 101 | alArgs := []string{"oss:////"} 102 | _, err := cm.RunCommand("getallpartsize", alArgs, options) 103 | c.Assert(err, NotNil) 104 | } 105 | 106 | func (s *OssutilCommandSuite) TestAllPartSizeEmptyEndpoint(c *C) { 107 | bucketName := bucketNamePrefix + randLowStr(12) 108 | s.putBucket(bucketName, c) 109 | 110 | cfile := randStr(10) 111 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 112 | s.createFile(cfile, data, c) 113 | 114 | var str string 115 | options := OptionMapType{ 116 | "endpoint": &str, 117 | "accessKeyID": &str, 118 | "accessKeySecret": &str, 119 | "stsToken": &str, 120 | "configFile": &cfile, 121 | } 122 | 123 | alArgs := []string{CloudURLToString(bucketName, "")} 124 | _, err := cm.RunCommand("getallpartsize", alArgs, options) 125 | c.Assert(err, NotNil) 126 | 127 | os.Remove(cfile) 128 | 129 | s.removeBucket(bucketName, true, c) 130 | } 131 | 132 | func (s *OssutilCommandSuite) TestAllPartSizeHelp(c *C) { 133 | // mkdir command test 134 | options := OptionMapType{} 135 | 136 | mkArgs := []string{"getallpartsize"} 137 | _, err := cm.RunCommand("help", mkArgs, options) 138 | c.Assert(err, IsNil) 139 | 140 | mkArgs = []string{} 141 | _, err = cm.RunCommand("help", mkArgs, options) 142 | c.Assert(err, IsNil) 143 | } 144 | -------------------------------------------------------------------------------- /lib/bucket_resource_group_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 6 | . "gopkg.in/check.v1" 7 | "os" 8 | ) 9 | 10 | func (s *OssutilCommandSuite) TestResourceGroupHelpInfo(c *C) { 11 | // mkdir command test 12 | options := OptionMapType{} 13 | 14 | mkArgs := []string{"resource-group"} 15 | _, err := cm.RunCommand("help", mkArgs, options) 16 | c.Assert(err, IsNil) 17 | 18 | mkArgs = []string{} 19 | _, err = cm.RunCommand("help", mkArgs, options) 20 | c.Assert(err, IsNil) 21 | } 22 | 23 | func (s *OssutilCommandSuite) TestPutBucketResourceGroupError(c *C) { 24 | bucketName := bucketNamePrefix + randLowStr(12) 25 | s.putBucket(bucketName, c) 26 | 27 | resourceFileName := "resource-group" + randLowStr(12) 28 | 29 | // resource group command test 30 | var str string 31 | strMethod := "" 32 | options := OptionMapType{ 33 | "endpoint": &str, 34 | "accessKeyID": &str, 35 | "accessKeySecret": &str, 36 | "stsToken": &str, 37 | "configFile": &configFile, 38 | "method": &strMethod, 39 | } 40 | 41 | // method is empty 42 | resourceArgs := []string{CloudURLToString(bucketName, ""), resourceFileName} 43 | _, err := cm.RunCommand("resource-group", resourceArgs, options) 44 | c.Assert(err, NotNil) 45 | 46 | //method is error 47 | strMethod = "puttt" 48 | options["method"] = &strMethod 49 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 50 | c.Assert(err, NotNil) 51 | 52 | // cloudurl is error 53 | strMethod = "put" 54 | options["method"] = &strMethod 55 | resourceArgs = []string{"http://mybucket", resourceFileName} 56 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 57 | c.Assert(err, NotNil) 58 | 59 | // local file is emtpy 60 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceFileName} 61 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 62 | c.Assert(err, NotNil) 63 | 64 | //local file is not exist 65 | os.Remove(resourceFileName) 66 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceFileName} 67 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 68 | c.Assert(err, NotNil) 69 | 70 | // local file is dir 71 | err = os.MkdirAll(resourceFileName, 0755) 72 | c.Assert(err, IsNil) 73 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceFileName} 74 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 75 | c.Assert(err, NotNil) 76 | os.Remove(resourceFileName) 77 | 78 | //local file is empty 79 | s.createFile(resourceFileName, "", c) 80 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 81 | c.Assert(err, NotNil) 82 | os.Remove(resourceFileName) 83 | 84 | //local file is not xml file 85 | s.createFile(resourceFileName, "aaa", c) 86 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 87 | c.Assert(err, NotNil) 88 | os.Remove(resourceFileName) 89 | 90 | // StorageURLFromString error 91 | resourceArgs = []string{"oss:///1.jpg"} 92 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 93 | c.Assert(err, NotNil) 94 | 95 | // bucketname is error 96 | resourceArgs = []string{"oss:///"} 97 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 98 | c.Assert(err, NotNil) 99 | 100 | //missing parameter 101 | resourceArgs = []string{CloudURLToString(bucketName, "")} 102 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 103 | c.Assert(err, NotNil) 104 | 105 | // bucketname not exist 106 | resourceArgs = []string{CloudURLToString("my-bucket", "")} 107 | _, err = cm.RunCommand("resource-group", resourceArgs, options) 108 | c.Assert(err, NotNil) 109 | 110 | os.Remove(resourceFileName) 111 | s.removeBucket(bucketName, true, c) 112 | } 113 | 114 | func (s *OssutilCommandSuite) TestPutBucketResourceGroup(c *C) { 115 | resourceXml := ` 116 | 117 | rg-acfmy7mo47b3adq 118 | ` 119 | 120 | accessConfigSrc := oss.PutBucketResourceGroup{} 121 | err := xml.Unmarshal([]byte(resourceXml), &accessConfigSrc) 122 | c.Assert(err, IsNil) 123 | 124 | resourceFileName := randLowStr(12) 125 | s.createFile(resourceFileName, resourceXml, c) 126 | 127 | bucketName := bucketNamePrefix + randLowStr(12) 128 | s.putBucket(bucketName, c) 129 | 130 | // resource group command test 131 | var str string 132 | strMethod := "put" 133 | options := OptionMapType{ 134 | "endpoint": &str, 135 | "accessKeyID": &str, 136 | "accessKeySecret": &str, 137 | "stsToken": &str, 138 | "configFile": &configFile, 139 | "method": &strMethod, 140 | } 141 | 142 | command := "resource-group" 143 | resourceArgs := []string{CloudURLToString(bucketName, ""), resourceFileName} 144 | _, err = cm.RunCommand(command, resourceArgs, options) 145 | c.Assert(err, IsNil) 146 | 147 | // check,get resource group 148 | resourceDownName := resourceFileName + "-down" 149 | strMethod = "get" 150 | options = OptionMapType{ 151 | "endpoint": &str, 152 | "accessKeyID": &str, 153 | "accessKeySecret": &str, 154 | "stsToken": &str, 155 | "configFile": &configFile, 156 | "method": &strMethod, 157 | } 158 | 159 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceDownName} 160 | _, err = cm.RunCommand(command, resourceArgs, options) 161 | c.Assert(err, IsNil) 162 | 163 | // check resource group DownName 164 | _, err = os.Stat(resourceDownName) 165 | c.Assert(err, IsNil) 166 | 167 | accessBody := s.readFile(resourceDownName, c) 168 | 169 | var out oss.GetBucketResourceGroupResult 170 | err = xml.Unmarshal([]byte(accessBody), &out) 171 | c.Assert(err, IsNil) 172 | 173 | c.Assert(accessConfigSrc.ResourceGroupId, Equals, out.ResourceGroupId) 174 | 175 | os.Remove(resourceFileName) 176 | os.Remove(resourceDownName) 177 | s.removeBucket(bucketName, true, c) 178 | } 179 | 180 | func (s *OssutilCommandSuite) TestGetBucketResourceGroupConfirm(c *C) { 181 | bucketName := bucketNamePrefix + randLowStr(12) 182 | s.putBucket(bucketName, c) 183 | 184 | resourceFileName := inputFileName + randLowStr(5) 185 | // get resource group 186 | resourceDownName := resourceFileName + "-down" 187 | var str string 188 | strMethod := "get" 189 | options := OptionMapType{ 190 | "endpoint": &str, 191 | "accessKeyID": &str, 192 | "accessKeySecret": &str, 193 | "configFile": &configFile, 194 | "method": &strMethod, 195 | } 196 | 197 | command := "resource-group" 198 | resourceArgs := []string{CloudURLToString(bucketName, ""), resourceDownName} 199 | _, err := cm.RunCommand(command, resourceArgs, options) 200 | c.Assert(err, IsNil) 201 | accessBody := s.readFile(resourceDownName, c) 202 | 203 | var out oss.GetBucketResourceGroupResult 204 | err = xml.Unmarshal([]byte(accessBody), &out) 205 | c.Assert(err, IsNil) 206 | id := out.ResourceGroupId 207 | 208 | os.Remove(resourceDownName) 209 | 210 | resourceXml := ` 211 | 212 | ` + id + ` 213 | ` 214 | 215 | s.createFile(resourceFileName, resourceXml, c) 216 | 217 | // resource group command test 218 | options[OptionMethod] = &strMethod 219 | 220 | command = "resource-group" 221 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceFileName} 222 | _, err = cm.RunCommand(command, resourceArgs, options) 223 | c.Assert(err, IsNil) 224 | 225 | // get resource group 226 | strMethod = "get" 227 | options[OptionMethod] = &strMethod 228 | 229 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceDownName} 230 | _, err = cm.RunCommand(command, resourceArgs, options) 231 | c.Assert(err, IsNil) 232 | 233 | resourceArgs = []string{CloudURLToString(bucketName, ""), resourceDownName} 234 | _, err = cm.RunCommand(command, resourceArgs, options) 235 | c.Assert(err, IsNil) 236 | 237 | resourceArgs = []string{CloudURLToString(bucketName, "")} 238 | _, err = cm.RunCommand(command, resourceArgs, options) 239 | c.Assert(err, IsNil) 240 | 241 | os.Remove(resourceFileName) 242 | os.Remove(resourceDownName) 243 | s.removeBucket(bucketName, true, c) 244 | } 245 | -------------------------------------------------------------------------------- /lib/bucket_tagging.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseBucketTag = SpecText{ 11 | synopsisText: "设置、查询或者删除bucket的tag配置", 12 | 13 | paramText: "bucket_url [tag_parameter] [options]", 14 | 15 | syntaxText: ` 16 | ossutil bucket-tagging --method put oss://bucket key#value 17 | ossutil bucket-tagging --method get oss://bucket 18 | ossutil bucket-tagging --method delete oss://bucket 19 | `, 20 | detailHelpText: ` 21 | bucket-tagging命令通过设置method选项值为put、get、delete,可以设置、查询或者删除bucket的tag配置 22 | 每个tag的key和value必须以字符'#'分隔,最多可以连续输入10个tag信息 23 | 24 | 用法: 25 | 该命令有三种用法: 26 | 27 | 1) ossutil bucket-tagging --method put oss://bucket tagkey#tagvalue 28 | 这个命令设置bucket的tag配置,key和value分别为tagkey、tagvalue 29 | 30 | 2) ossutil bucket-tagging --method get oss://bucket 31 | 这个命令查询bucket的tag配置 32 | 33 | 3) ossutil bucket-tagging --method delete oss://bucket 34 | 这个命令删除bucket的tag配置 35 | `, 36 | sampleText: ` 37 | 1) 设置bucket的tag配置 38 | ossutil bucket-tagging --method put oss://bucket tagkey#tagvalue 39 | 40 | 2) 设置bucket的多个tag配置 41 | ossutil bucket-tagging --method put oss://bucket tagkey1#tagvalue1 tagkey2#tagvalue2 42 | 43 | 3) 查询bucket的tag配置 44 | ossutil bucket-tagging --method get oss://bucket 45 | 46 | 4) 删除bucket的tag配置 47 | ossutil bucket-tagging --method delete oss://bucket 48 | `, 49 | } 50 | 51 | var specEnglishBucketTag = SpecText{ 52 | synopsisText: "Set, get or delete bucket tag configuration", 53 | 54 | paramText: "bucket_url [tag_parameter] [options]", 55 | 56 | syntaxText: ` 57 | ossutil bucket-tagging --method put oss://bucket key#value 58 | ossutil bucket-tagging --method get oss://bucket 59 | ossutil bucket-tagging --method delete oss://bucket 60 | `, 61 | detailHelpText: ` 62 | bucket-tagging command can set, get and delete the tag configuration of the oss bucket by set method option value to put, get, delete 63 | the key and value of each tag must be separated by the character '#', you can enter up to 10 tag parameters. 64 | Usage: 65 | There are three usages for this command: 66 | 67 | 1) ossutil bucket-tagging --method put oss://bucket tagkey#tagvalue 68 | The command sets the tag configuration of the bucket. The key and value are tagkey and tagvalue 69 | 70 | 2) ossutil bucket-tagging --method get oss://bucket 71 | The command gets the tag configuration of bucket 72 | 73 | 3) ossutil bucket-tagging --method delete oss://bucket 74 | The command deletes the tag configuration of bucket 75 | `, 76 | sampleText: ` 77 | 1) set bucket tag configuration with one tag 78 | ossutil bucket-tagging --method put oss://bucket tagkey#tagvalue 79 | 80 | 2) set bucket tag configuration with serveral tags 81 | ossutil bucket-tagging --method put oss://bucket tagkey1#tagvalue1 tagkey2#tagvalue2 82 | 83 | 3) get bucket tag configuration 84 | ossutil bucket-tagging --method get oss://bucket 85 | 86 | 4) delete bucket tag configuration 87 | ossutil bucket-tagging --method delete oss://bucket 88 | `, 89 | } 90 | 91 | type BucketTagCommand struct { 92 | command Command 93 | bucketName string 94 | tagResult oss.GetBucketTaggingResult 95 | } 96 | 97 | var bucketTagCommand = BucketTagCommand{ 98 | command: Command{ 99 | name: "bucket-tagging", 100 | nameAlias: []string{"bucket-tagging"}, 101 | minArgc: 1, 102 | maxArgc: 11, 103 | specChinese: specChineseBucketTag, 104 | specEnglish: specEnglishBucketTag, 105 | group: GroupTypeNormalCommand, 106 | validOptionNames: []string{ 107 | OptionConfigFile, 108 | OptionEndpoint, 109 | OptionAccessKeyID, 110 | OptionAccessKeySecret, 111 | OptionSTSToken, 112 | OptionProxyHost, 113 | OptionProxyUser, 114 | OptionProxyPwd, 115 | OptionMethod, 116 | OptionLogLevel, 117 | OptionPassword, 118 | OptionMode, 119 | OptionECSRoleName, 120 | OptionTokenTimeout, 121 | OptionRamRoleArn, 122 | OptionRoleSessionName, 123 | OptionReadTimeout, 124 | OptionConnectTimeout, 125 | OptionSTSRegion, 126 | OptionSkipVerifyCert, 127 | OptionUserAgent, 128 | OptionSignVersion, 129 | OptionRegion, 130 | OptionCloudBoxID, 131 | OptionForcePathStyle, 132 | }, 133 | }, 134 | } 135 | 136 | // function for FormatHelper interface 137 | func (btc *BucketTagCommand) formatHelpForWhole() string { 138 | return btc.command.formatHelpForWhole() 139 | } 140 | 141 | func (btc *BucketTagCommand) formatIndependHelp() string { 142 | return btc.command.formatIndependHelp() 143 | } 144 | 145 | // Init simulate inheritance, and polymorphism 146 | func (btc *BucketTagCommand) Init(args []string, options OptionMapType) error { 147 | return btc.command.Init(args, options, btc) 148 | } 149 | 150 | // RunCommand simulate inheritance, and polymorphism 151 | func (btc *BucketTagCommand) RunCommand() error { 152 | strMethod, _ := GetString(OptionMethod, btc.command.options) 153 | if strMethod == "" { 154 | return fmt.Errorf("--method value is empty") 155 | } 156 | 157 | strMethod = strings.ToLower(strMethod) 158 | if strMethod != "put" && strMethod != "get" && strMethod != "delete" { 159 | return fmt.Errorf("--method value is not in the optional value:put|get|delete") 160 | } 161 | 162 | srcBucketUrL, err := GetCloudUrl(btc.command.args[0], "") 163 | if err != nil { 164 | return err 165 | } 166 | 167 | btc.bucketName = srcBucketUrL.bucket 168 | 169 | if strMethod == "put" { 170 | err = btc.PutBucketTag() 171 | } else if strMethod == "get" { 172 | err = btc.GetBucketTag() 173 | } else if strMethod == "delete" { 174 | err = btc.DeleteBucketTag() 175 | } 176 | return err 177 | } 178 | 179 | func (btc *BucketTagCommand) PutBucketTag() error { 180 | if len(btc.command.args) < 2 { 181 | return fmt.Errorf("missing parameter,the tag value is empty") 182 | } 183 | 184 | var tagging oss.Tagging 185 | tagList := btc.command.args[1:len(btc.command.args)] 186 | for _, tag := range tagList { 187 | pSlice := strings.Split(tag, "#") 188 | if len(pSlice) != 2 { 189 | return fmt.Errorf("%s error,tag name and tag value must be separated by #", tag) 190 | } 191 | tagging.Tags = append(tagging.Tags, oss.Tag{Key: pSlice[0], Value: pSlice[1]}) 192 | } 193 | 194 | // put bucket tag 195 | client, err := btc.command.ossClient(btc.bucketName) 196 | if err != nil { 197 | return err 198 | } 199 | 200 | return client.SetBucketTagging(btc.bucketName, tagging) 201 | } 202 | 203 | func (btc *BucketTagCommand) GetBucketTag() error { 204 | client, err := btc.command.ossClient(btc.bucketName) 205 | if err != nil { 206 | return err 207 | } 208 | 209 | btc.tagResult, err = client.GetBucketTagging(btc.bucketName) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | if len(btc.tagResult.Tags) > 0 { 215 | fmt.Printf("%-10s%s\t%s\n", "index", "tag key", "tag value") 216 | fmt.Printf("---------------------------------------------------\n") 217 | } 218 | 219 | for index, tag := range btc.tagResult.Tags { 220 | fmt.Printf("%-10d\"%s\"\t\"%s\"\n", index, tag.Key, tag.Value) 221 | } 222 | 223 | fmt.Printf("\n\n") 224 | 225 | return nil 226 | } 227 | 228 | func (btc *BucketTagCommand) DeleteBucketTag() error { 229 | client, err := btc.command.ossClient(btc.bucketName) 230 | if err != nil { 231 | return err 232 | } 233 | return client.DeleteBucketTagging(btc.bucketName) 234 | } 235 | -------------------------------------------------------------------------------- /lib/bucket_tagging_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | 6 | . "gopkg.in/check.v1" 7 | ) 8 | 9 | func (s *OssutilCommandSuite) TestBucketTaggingPutSuccess(c *C) { 10 | // put tagging 11 | bucketName := bucketNamePrefix + randLowStr(12) 12 | s.putBucket(bucketName, c) 13 | 14 | // tagging command test 15 | var str string 16 | strMethod := "put" 17 | options := OptionMapType{ 18 | "endpoint": &str, 19 | "accessKeyID": &str, 20 | "accessKeySecret": &str, 21 | "stsToken": &str, 22 | "configFile": &configFile, 23 | "method": &strMethod, 24 | } 25 | 26 | tagInfo := "key1#value1" 27 | tagArgs := []string{CloudURLToString(bucketName, ""), tagInfo} 28 | _, err := cm.RunCommand("bucket-tagging", tagArgs, options) 29 | c.Assert(err, IsNil) 30 | 31 | // check,get tag 32 | strMethod = "get" 33 | tagArgs = []string{CloudURLToString(bucketName, "")} 34 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 35 | c.Assert(err, IsNil) 36 | c.Assert(len(bucketTagCommand.tagResult.Tags), Equals, 1) 37 | c.Assert(bucketTagCommand.tagResult.Tags[0].Key, Equals, "key1") 38 | c.Assert(bucketTagCommand.tagResult.Tags[0].Value, Equals, "value1") 39 | 40 | s.removeBucket(bucketName, true, c) 41 | } 42 | 43 | func (s *OssutilCommandSuite) TestBucketTaggingDeleteSuccess(c *C) { 44 | // put tagging 45 | bucketName := bucketNamePrefix + randLowStr(12) 46 | s.putBucket(bucketName, c) 47 | 48 | // tagging command test 49 | var str string 50 | strMethod := "put" 51 | options := OptionMapType{ 52 | "endpoint": &str, 53 | "accessKeyID": &str, 54 | "accessKeySecret": &str, 55 | "stsToken": &str, 56 | "configFile": &configFile, 57 | "method": &strMethod, 58 | } 59 | 60 | tagInfo := "key1#value1" 61 | tagArgs := []string{CloudURLToString(bucketName, ""), tagInfo} 62 | _, err := cm.RunCommand("bucket-tagging", tagArgs, options) 63 | c.Assert(err, IsNil) 64 | 65 | // check,get tag 66 | strMethod = "get" 67 | tagArgs = []string{CloudURLToString(bucketName, "")} 68 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 69 | c.Assert(err, IsNil) 70 | c.Assert(len(bucketTagCommand.tagResult.Tags), Equals, 1) 71 | c.Assert(bucketTagCommand.tagResult.Tags[0].Key, Equals, "key1") 72 | c.Assert(bucketTagCommand.tagResult.Tags[0].Value, Equals, "value1") 73 | 74 | // delete bucket tagging 75 | strMethod = "delete" 76 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 77 | c.Assert(err, IsNil) 78 | 79 | // get bucket tagging again:error 80 | strMethod = "get" 81 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 82 | c.Assert(err, IsNil) 83 | c.Assert(len(bucketTagCommand.tagResult.Tags), Equals, 0) 84 | 85 | s.removeBucket(bucketName, true, c) 86 | } 87 | 88 | func (s *OssutilCommandSuite) TestBucketTaggingError(c *C) { 89 | bucketName := bucketNamePrefix + randLowStr(12) 90 | s.putBucket(bucketName, c) 91 | 92 | // bucket-tagging command test 93 | var str string 94 | options := OptionMapType{ 95 | "endpoint": &str, 96 | "accessKeyID": &str, 97 | "accessKeySecret": &str, 98 | "stsToken": &str, 99 | "configFile": &configFile, 100 | } 101 | 102 | // method is empty 103 | tagInfo := "key1#value1" 104 | tagArgs := []string{CloudURLToString(bucketName, ""), tagInfo} 105 | _, err := cm.RunCommand("bucket-tagging", tagArgs, options) 106 | c.Assert(err, NotNil) 107 | 108 | // method is error 109 | strMethod := "puttt" 110 | options["method"] = &strMethod 111 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 112 | c.Assert(err, NotNil) 113 | 114 | // args is empty 115 | strMethod = "put" 116 | tagArgs = []string{CloudURLToString(bucketName, "")} 117 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 118 | c.Assert(err, NotNil) 119 | 120 | //value is error 121 | tagInfo = "key1:value1" 122 | tagArgs = []string{CloudURLToString(bucketName, ""), tagInfo} 123 | _, err = cm.RunCommand("bucket-tagging", tagArgs, options) 124 | c.Assert(err, NotNil) 125 | 126 | s.removeBucket(bucketName, true, c) 127 | 128 | } 129 | 130 | func (s *OssutilCommandSuite) TestBucketTaggingPutEmptyEndpoint(c *C) { 131 | bucketName := bucketNamePrefix + randLowStr(12) 132 | s.putBucket(bucketName, c) 133 | 134 | cfile := randStr(10) 135 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 136 | s.createFile(cfile, data, c) 137 | 138 | // bucket-tagging command test 139 | var str string 140 | strMethod := "put" 141 | options := OptionMapType{ 142 | "endpoint": &str, 143 | "accessKeyID": &str, 144 | "accessKeySecret": &str, 145 | "stsToken": &str, 146 | "configFile": &cfile, 147 | "method": &strMethod, 148 | } 149 | 150 | tagInfo := "key1#value1" 151 | tagArgs := []string{CloudURLToString(bucketName, ""), tagInfo} 152 | _, err := cm.RunCommand("bucket-tagging", tagArgs, options) 153 | c.Assert(err, NotNil) 154 | 155 | os.Remove(cfile) 156 | s.removeBucket(bucketName, true, c) 157 | } 158 | 159 | func (s *OssutilCommandSuite) TestBucketTaggingGetEmptyEndpoint(c *C) { 160 | bucketName := bucketNamePrefix + randLowStr(12) 161 | s.putBucket(bucketName, c) 162 | 163 | cfile := randStr(10) 164 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 165 | s.createFile(cfile, data, c) 166 | 167 | var str string 168 | strMethod := "get" 169 | options := OptionMapType{ 170 | "endpoint": &str, 171 | "accessKeyID": &str, 172 | "accessKeySecret": &str, 173 | "stsToken": &str, 174 | "configFile": &cfile, 175 | "method": &strMethod, 176 | } 177 | 178 | tagArgs := []string{CloudURLToString(bucketName, "")} 179 | _, err := cm.RunCommand("bucket-tagging", tagArgs, options) 180 | c.Assert(err, NotNil) 181 | 182 | os.Remove(cfile) 183 | s.removeBucket(bucketName, true, c) 184 | } 185 | 186 | func (s *OssutilCommandSuite) TestBucketTaggingHelpInfo(c *C) { 187 | options := OptionMapType{} 188 | 189 | mkArgs := []string{"bucket-tagging"} 190 | _, err := cm.RunCommand("help", mkArgs, options) 191 | c.Assert(err, IsNil) 192 | 193 | mkArgs = []string{} 194 | _, err = cm.RunCommand("help", mkArgs, options) 195 | c.Assert(err, IsNil) 196 | 197 | } 198 | -------------------------------------------------------------------------------- /lib/bucket_versioning.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseBucketVersioning = SpecText{ 11 | synopsisText: "设置、查询bucket的versioning配置", 12 | 13 | paramText: "bucket_url [versioning_parameter] [options]", 14 | 15 | syntaxText: ` 16 | ossutil bucket-versioning --method put oss://bucket versioning_parameter 17 | ossutil bucket-versioning --method get oss://bucket 18 | `, 19 | detailHelpText: ` 20 | bucket-versioning命令通过设置method选项值为put、get、可以设置、查询bucket的versioning配置 21 | 选项--method为put时,versioning状态参数只能为enabled、suspended 22 | 23 | 用法: 24 | 该命令有三种用法: 25 | 26 | 1) ossutil bucket-versioning --method put oss://bucket enabled 27 | 这个命令开通bucket的versioning功能 28 | 29 | 2) ossutil bucket-versioning --method put oss://bucket suspended 30 | 这个命令关闭bucket的versioning功能 31 | 32 | 3) ossutil bucket-versioning --method get oss://bucket 33 | 这个命令查询bucket的vesioning状态 34 | `, 35 | sampleText: ` 36 | 1) 开通bucket的versioning功能 37 | ossutil bucket-versioning --method put oss://bucket enabled 38 | 39 | 2) 关闭bucket的versioning功能 40 | ossutil bucket-versioning --method put oss://bucket suspended 41 | 42 | 3) 查询bucket的versioning状态 43 | ossutil bucket-versioning --method get oss://bucket 44 | `, 45 | } 46 | 47 | var specEnglishBucketVersioning = SpecText{ 48 | synopsisText: "Set, get bucket versioning configuration", 49 | 50 | paramText: "bucket_url [versioning_parameter] [options]", 51 | 52 | syntaxText: ` 53 | ossutil bucket-versioning --method put oss://bucket versioning_parameter 54 | ossutil bucket-versioning --method get oss://bucket 55 | `, 56 | detailHelpText: ` 57 | bucket-versioning command can set, get the versioning configuration of the oss bucket by set method option value to put, get 58 | If the --method option value is put,the versioning status value can only be enabled, suspended, 59 | Usage: 60 | There are three usages for this command: 61 | 62 | 1) ossutil bucket-versioning --method put oss://bucket enabled 63 | This command enables the bucket versioning 64 | 65 | 2) ossutil bucket-versioning --method put oss://bucket suspended 66 | This command disables the bucket versioning 67 | 68 | 3) ossutil bucket-versioning --method get oss://bucket 69 | This command query the bucket versioning status 70 | `, 71 | sampleText: ` 72 | 1) set bucket versioning enabled 73 | ossutil bucket-versioning --method put oss://bucket enabled 74 | 75 | 2) set bucket versioning disable 76 | ossutil bucket-versioning --method put oss://bucket suspended 77 | 78 | 3) get bucket versioning status 79 | ossutil bucket-versioning --method get oss://bucket 80 | `, 81 | } 82 | 83 | type BucketVersioningCommand struct { 84 | command Command 85 | bucketName string 86 | versioningResult oss.GetBucketVersioningResult 87 | } 88 | 89 | var bucketVersioningCommand = BucketVersioningCommand{ 90 | command: Command{ 91 | name: "bucket-versioning", 92 | nameAlias: []string{"bucket-versioning"}, 93 | minArgc: 1, 94 | maxArgc: 2, 95 | specChinese: specChineseBucketVersioning, 96 | specEnglish: specEnglishBucketVersioning, 97 | group: GroupTypeNormalCommand, 98 | validOptionNames: []string{ 99 | OptionConfigFile, 100 | OptionEndpoint, 101 | OptionAccessKeyID, 102 | OptionAccessKeySecret, 103 | OptionSTSToken, 104 | OptionProxyHost, 105 | OptionProxyUser, 106 | OptionProxyPwd, 107 | OptionMethod, 108 | OptionLogLevel, 109 | OptionPassword, 110 | OptionMode, 111 | OptionECSRoleName, 112 | OptionTokenTimeout, 113 | OptionRamRoleArn, 114 | OptionRoleSessionName, 115 | OptionReadTimeout, 116 | OptionConnectTimeout, 117 | OptionSTSRegion, 118 | OptionSkipVerifyCert, 119 | OptionUserAgent, 120 | OptionSignVersion, 121 | OptionRegion, 122 | OptionCloudBoxID, 123 | OptionForcePathStyle, 124 | }, 125 | }, 126 | } 127 | 128 | // function for FormatHelper interface 129 | func (bvc *BucketVersioningCommand) formatHelpForWhole() string { 130 | return bvc.command.formatHelpForWhole() 131 | } 132 | 133 | func (bvc *BucketVersioningCommand) formatIndependHelp() string { 134 | return bvc.command.formatIndependHelp() 135 | } 136 | 137 | // Init simulate inheritance, and polymorphism 138 | func (bvc *BucketVersioningCommand) Init(args []string, options OptionMapType) error { 139 | return bvc.command.Init(args, options, bvc) 140 | } 141 | 142 | // RunCommand simulate inheritance, and polymorphism 143 | func (bvc *BucketVersioningCommand) RunCommand() error { 144 | strMethod, _ := GetString(OptionMethod, bvc.command.options) 145 | if strMethod == "" { 146 | return fmt.Errorf("--method value is empty") 147 | } 148 | 149 | strMethod = strings.ToLower(strMethod) 150 | if strMethod != "put" && strMethod != "get" { 151 | return fmt.Errorf("--method value is not in the optional value:put|get") 152 | } 153 | 154 | srcBucketUrL, err := GetCloudUrl(bvc.command.args[0], "") 155 | if err != nil { 156 | return err 157 | } 158 | 159 | bvc.bucketName = srcBucketUrL.bucket 160 | 161 | if strMethod == "put" { 162 | err = bvc.PutBucketVersioning() 163 | } else if strMethod == "get" { 164 | err = bvc.GetBucketVersioning() 165 | } 166 | 167 | return err 168 | } 169 | 170 | func (bvc *BucketVersioningCommand) PutBucketVersioning() error { 171 | 172 | if len(bvc.command.args) < 2 { 173 | return fmt.Errorf("missing parameter,versioning status is empty") 174 | } 175 | 176 | strVersion := bvc.command.args[1] 177 | 178 | if strings.ToUpper(strVersion) != strings.ToUpper(string(oss.VersionEnabled)) && 179 | strings.ToUpper(strVersion) != strings.ToUpper(string(oss.VersionSuspended)) { 180 | return fmt.Errorf("version status must be %s or %s", string(oss.VersionEnabled), 181 | string(oss.VersionSuspended)) 182 | } 183 | 184 | // put bucket versioning 185 | client, err := bvc.command.ossClient(bvc.bucketName) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | var versioningConfig oss.VersioningConfig 191 | if strings.ToUpper(strVersion) == strings.ToUpper(string(oss.VersionEnabled)) { 192 | versioningConfig.Status = string(oss.VersionEnabled) 193 | } else if strings.ToUpper(strVersion) == strings.ToUpper(string(oss.VersionSuspended)) { 194 | versioningConfig.Status = string(oss.VersionSuspended) 195 | 196 | } 197 | return client.SetBucketVersioning(bvc.bucketName, versioningConfig) 198 | } 199 | 200 | func (bvc *BucketVersioningCommand) GetBucketVersioning() error { 201 | client, err := bvc.command.ossClient(bvc.bucketName) 202 | if err != nil { 203 | return err 204 | } 205 | 206 | bvc.versioningResult, err = client.GetBucketVersioning(bvc.bucketName) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | if bvc.versioningResult.Status == "" { 212 | bvc.versioningResult.Status = "null" 213 | } 214 | 215 | fmt.Printf("\nbucket versioning status:%s\n", bvc.versioningResult.Status) 216 | 217 | return nil 218 | } 219 | -------------------------------------------------------------------------------- /lib/bucket_versioning_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | . "gopkg.in/check.v1" 8 | ) 9 | 10 | func (s *OssutilCommandSuite) TestBucketVersioningPutSuccess(c *C) { 11 | bucketName := bucketNamePrefix + randLowStr(12) 12 | s.putBucket(bucketName, c) 13 | 14 | // versioning command test 15 | var str string 16 | strMethod := "get" 17 | options := OptionMapType{ 18 | "endpoint": &str, 19 | "accessKeyID": &str, 20 | "accessKeySecret": &str, 21 | "stsToken": &str, 22 | "configFile": &configFile, 23 | "method": &strMethod, 24 | } 25 | 26 | versioningArgs := []string{CloudURLToString(bucketName, "")} 27 | _, err := cm.RunCommand("bucket-versioning", versioningArgs, options) 28 | c.Assert(err, IsNil) 29 | c.Assert(bucketVersioningCommand.versioningResult.Status, Equals, "null") 30 | 31 | // set bucket versioning enabled 32 | strMethod = "put" 33 | versioningArgs = []string{CloudURLToString(bucketName, ""), "enabled"} 34 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 35 | c.Assert(err, IsNil) 36 | time.Sleep(time.Second * 3) 37 | 38 | // check 39 | strMethod = "get" 40 | versioningArgs = []string{CloudURLToString(bucketName, "")} 41 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 42 | c.Assert(err, IsNil) 43 | c.Assert(bucketVersioningCommand.versioningResult.Status, Equals, "Enabled") 44 | time.Sleep(time.Second * 3) 45 | 46 | // set bucket versioning suspend 47 | strMethod = "put" 48 | versioningArgs = []string{CloudURLToString(bucketName, ""), "suspended"} 49 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 50 | c.Assert(err, IsNil) 51 | time.Sleep(time.Second * 3) 52 | 53 | // check 54 | strMethod = "get" 55 | versioningArgs = []string{CloudURLToString(bucketName, "")} 56 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 57 | c.Assert(err, IsNil) 58 | c.Assert(bucketVersioningCommand.versioningResult.Status, Equals, "Suspended") 59 | 60 | s.removeBucket(bucketName, true, c) 61 | } 62 | 63 | func (s *OssutilCommandSuite) TestBucketVersioningError(c *C) { 64 | bucketName := bucketNamePrefix + randLowStr(12) 65 | s.putBucket(bucketName, c) 66 | 67 | // bucket-versioning command test 68 | var str string 69 | options := OptionMapType{ 70 | "endpoint": &str, 71 | "accessKeyID": &str, 72 | "accessKeySecret": &str, 73 | "stsToken": &str, 74 | "configFile": &configFile, 75 | } 76 | 77 | // method is empty 78 | versioningArgs := []string{CloudURLToString(bucketName, "")} 79 | _, err := cm.RunCommand("bucket-versioning", versioningArgs, options) 80 | c.Assert(err, NotNil) 81 | 82 | // method is error 83 | strMethod := "puttt" 84 | options["method"] = &strMethod 85 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 86 | c.Assert(err, NotNil) 87 | 88 | // args is empty 89 | strMethod = "put" 90 | versioningArgs = []string{CloudURLToString(bucketName, "")} 91 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 92 | c.Assert(err, NotNil) 93 | 94 | //value is error 95 | versioningArgs = []string{CloudURLToString(bucketName, ""), "disabled"} 96 | _, err = cm.RunCommand("bucket-versioning", versioningArgs, options) 97 | c.Assert(err, NotNil) 98 | 99 | s.removeBucket(bucketName, true, c) 100 | } 101 | 102 | func (s *OssutilCommandSuite) TestBucketVersioningPutEmptyEndpoint(c *C) { 103 | bucketName := bucketNamePrefix + randLowStr(12) 104 | s.putBucket(bucketName, c) 105 | 106 | cfile := randStr(10) 107 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 108 | s.createFile(cfile, data, c) 109 | 110 | // bucket-versioing command test 111 | var str string 112 | strMethod := "put" 113 | options := OptionMapType{ 114 | "endpoint": &str, 115 | "accessKeyID": &str, 116 | "accessKeySecret": &str, 117 | "stsToken": &str, 118 | "configFile": &cfile, 119 | "method": &strMethod, 120 | } 121 | 122 | versioningArgs := []string{CloudURLToString(bucketName, ""), "enabled"} 123 | _, err := cm.RunCommand("bucket-versioning", versioningArgs, options) 124 | c.Assert(err, NotNil) 125 | 126 | os.Remove(cfile) 127 | s.removeBucket(bucketName, true, c) 128 | } 129 | 130 | func (s *OssutilCommandSuite) TestBucketVersioningGetEmptyEndpoint(c *C) { 131 | bucketName := bucketNamePrefix + randLowStr(12) 132 | s.putBucket(bucketName, c) 133 | 134 | cfile := randStr(10) 135 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 136 | s.createFile(cfile, data, c) 137 | 138 | var str string 139 | strMethod := "get" 140 | options := OptionMapType{ 141 | "endpoint": &str, 142 | "accessKeyID": &str, 143 | "accessKeySecret": &str, 144 | "stsToken": &str, 145 | "configFile": &configFile, 146 | "method": &strMethod, 147 | } 148 | 149 | versioingArgs := []string{CloudURLToString(bucketName, "")} 150 | _, err := cm.RunCommand("bucket-versioing", versioingArgs, options) 151 | c.Assert(err, NotNil) 152 | 153 | os.Remove(cfile) 154 | s.removeBucket(bucketName, true, c) 155 | } 156 | 157 | func (s *OssutilCommandSuite) TestBucketVersioningHelpInfo(c *C) { 158 | options := OptionMapType{} 159 | 160 | mkArgs := []string{"bucket-versioning"} 161 | _, err := cm.RunCommand("help", mkArgs, options) 162 | c.Assert(err, IsNil) 163 | 164 | mkArgs = []string{} 165 | _, err = cm.RunCommand("help", mkArgs, options) 166 | c.Assert(err, IsNil) 167 | 168 | } 169 | -------------------------------------------------------------------------------- /lib/cat.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 10 | ) 11 | 12 | var specChineseCat = SpecText{ 13 | synopsisText: "将文件内容输出到标准输出", 14 | 15 | paramText: "object [options]", 16 | 17 | syntaxText: ` 18 | ossutil cat oss://bucket/object [--payer requester] [--version-id versionId] 19 | `, 20 | detailHelpText: ` 21 | cat命令可以将oss的object内容输出到标准输出,object内容最好是文本格式 22 | 23 | 用法: 24 | 该命令仅有一种用法: 25 | 26 | 1) ossutil cat oss://bucket/object [--version-id versionId] [--payer requester] 27 | 将object内容输出到标准输出 28 | `, 29 | sampleText: ` 30 | 1) 将object内容输出到标准输出 31 | ossutil cat oss://bucket/object 32 | 33 | 2) 将object指定版本内容输出到标准输出 34 | ossutil cat oss://bucket/object --version-id versionId 35 | 36 | 3) 访问者付费模式 37 | ossutil cat oss://bucket/object --payer requester 38 | `, 39 | } 40 | 41 | var specEnglishCat = SpecText{ 42 | synopsisText: "Output object content to standard output", 43 | 44 | paramText: "object [options]", 45 | 46 | syntaxText: ` 47 | ossutil cat oss://bucket/object [--payer requester] [--version-id versionId] 48 | `, 49 | detailHelpText: ` 50 | The cat command can output the object content of oss to standard output 51 | The object content is preferably text format 52 | 53 | Usage: 54 | There is only one usage for this command: 55 | 56 | 1) ossutil cat oss://bucket/object [--version-id versionId] [--payer requester] 57 | The command output object content to standard output 58 | `, 59 | sampleText: ` 60 | 1) output object content to standard output 61 | ossutil cat oss://bucket/object 62 | 63 | 2) output the object's specified version content to standard output 64 | ossutil cat oss://bucket/object --version-id versionId 65 | 66 | 3) output object content with requester payment 67 | ossutil cat oss://bucket/object --payer requester 68 | `, 69 | } 70 | 71 | type catOptionType struct { 72 | bucketName string 73 | objectName string 74 | encodingType string 75 | } 76 | 77 | type CatCommand struct { 78 | command Command 79 | catOption catOptionType 80 | commonOptions []oss.Option 81 | } 82 | 83 | var catCommand = CatCommand{ 84 | command: Command{ 85 | name: "cat", 86 | nameAlias: []string{"cat"}, 87 | minArgc: 1, 88 | maxArgc: 1, 89 | specChinese: specChineseCat, 90 | specEnglish: specEnglishCat, 91 | group: GroupTypeNormalCommand, 92 | validOptionNames: []string{ 93 | OptionConfigFile, 94 | OptionEndpoint, 95 | OptionAccessKeyID, 96 | OptionAccessKeySecret, 97 | OptionSTSToken, 98 | OptionProxyHost, 99 | OptionProxyUser, 100 | OptionProxyPwd, 101 | OptionEncodingType, 102 | OptionLogLevel, 103 | OptionVersionId, 104 | OptionRequestPayer, 105 | OptionPassword, 106 | OptionMode, 107 | OptionECSRoleName, 108 | OptionTokenTimeout, 109 | OptionRamRoleArn, 110 | OptionRoleSessionName, 111 | OptionReadTimeout, 112 | OptionConnectTimeout, 113 | OptionSTSRegion, 114 | OptionSkipVerifyCert, 115 | OptionUserAgent, 116 | OptionSignVersion, 117 | OptionRegion, 118 | OptionCloudBoxID, 119 | OptionForcePathStyle, 120 | }, 121 | }, 122 | } 123 | 124 | // function for FormatHelper interface 125 | func (catc *CatCommand) formatHelpForWhole() string { 126 | return catc.command.formatHelpForWhole() 127 | } 128 | 129 | func (catc *CatCommand) formatIndependHelp() string { 130 | return catc.command.formatIndependHelp() 131 | } 132 | 133 | // Init simulate inheritance, and polymorphism 134 | func (catc *CatCommand) Init(args []string, options OptionMapType) error { 135 | return catc.command.Init(args, options, catc) 136 | } 137 | 138 | // RunCommand simulate inheritance, and polymorphism 139 | func (catc *CatCommand) RunCommand() error { 140 | catc.catOption.encodingType, _ = GetString(OptionEncodingType, catc.command.options) 141 | srcBucketUrL, err := GetCloudUrl(catc.command.args[0], catc.catOption.encodingType) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | if srcBucketUrL.object == "" { 147 | return fmt.Errorf("object key is empty") 148 | } 149 | 150 | catc.catOption.bucketName = srcBucketUrL.bucket 151 | catc.catOption.objectName = srcBucketUrL.object 152 | 153 | // check object exist or not 154 | client, err := catc.command.ossClient(catc.catOption.bucketName) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | bucket, err := client.Bucket(catc.catOption.bucketName) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | payer, _ := GetString(OptionRequestPayer, catc.command.options) 165 | if payer != "" { 166 | if payer != strings.ToLower(string(oss.Requester)) { 167 | return fmt.Errorf("invalid request payer: %s, please check", payer) 168 | } 169 | catc.commonOptions = append(catc.commonOptions, oss.RequestPayer(oss.PayerType(payer))) 170 | } 171 | 172 | var options []oss.Option 173 | options = append(options, catc.commonOptions...) 174 | 175 | versionId, _ := GetString(OptionVersionId, catc.command.options) 176 | if len(versionId) > 0 { 177 | options = append(options, oss.VersionId(versionId)) 178 | } 179 | 180 | body, err := bucket.GetObject(catc.catOption.objectName, options...) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | defer body.Close() 186 | io.Copy(os.Stdout, body) 187 | fmt.Printf("\n") 188 | 189 | return err 190 | } 191 | -------------------------------------------------------------------------------- /lib/command_manager.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "runtime" 8 | "strings" 9 | "time" 10 | 11 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 12 | ) 13 | 14 | var commandLine string 15 | 16 | func LogEnd(startT time.Time) { 17 | LogInfo("ossutil run end,cost:%d(ms).\n", time.Now().UnixNano()/1000/1000-startT.UnixNano()/1000/1000) 18 | UnInitLogger() 19 | } 20 | 21 | // ParseAndRunCommand parse command line user input, get command and options, then run command 22 | func ParseAndRunCommand() error { 23 | ts := time.Now().UnixNano() 24 | 25 | commandLine = getCommandLine() 26 | 27 | clearEnv() 28 | 29 | args, options, err := ParseArgOptions() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var level = oss.LogOff 35 | strLevel, err := getLoglevelFromOptions(options) 36 | if strLevel == "info" { 37 | level = oss.Info 38 | } else if strLevel == "debug" { 39 | level = oss.Debug 40 | } else if len(strLevel) > 0 { 41 | return fmt.Errorf("loglevel must be:info|debug") 42 | } 43 | 44 | if level > oss.LogOff { 45 | InitLogger(level, logName) 46 | } 47 | 48 | startT := time.Now() 49 | LogInfo("ossutil run begin,cmd:%s\n", commandLine) 50 | LogInfo("ossutil version is %s\n", Version) 51 | LogInfo("oss go sdk version is %s\n", oss.Version) 52 | LogInfo("go version is %s\n", runtime.Version()) 53 | LogInfo("runtime.NumCPU is %d\n", runtime.NumCPU()) 54 | 55 | defer LogEnd(startT) 56 | 57 | showElapse, err := RunCommand(args, options) 58 | if err != nil { 59 | LogError("%s.\n", err.Error()) 60 | return err 61 | } 62 | if showElapse { 63 | te := time.Now().UnixNano() 64 | fmt.Printf("\n%.6f(s) elapsed\n", float64(te-ts)/1e9) 65 | return nil 66 | } 67 | return nil 68 | } 69 | 70 | func getCommandLine() string { 71 | return strings.Join(os.Args, " ") 72 | } 73 | 74 | func clearEnv() { 75 | if runtime.GOOS == "windows" { 76 | _, renameFilePath := getBinaryPath() 77 | os.Remove(renameFilePath) 78 | } 79 | } 80 | 81 | func RunCommand(args []string, options OptionMapType) (bool, error) { 82 | if len(args) == 0 { 83 | if val, _ := GetBool(OptionVersion, options); val { 84 | fmt.Printf("ossutil version: %s\n", Version) 85 | return false, nil 86 | } 87 | args = append(args, "help") 88 | } 89 | command := args[0] 90 | args = args[1:] 91 | 92 | cm := CommandManager{} 93 | cm.Init() 94 | showElapse, err := cm.RunCommand(command, args, options) 95 | return showElapse, err 96 | } 97 | 98 | // CommandManager is used to manager commands, such as build command map and run command 99 | type CommandManager struct { 100 | commandMap map[string]interface{} 101 | } 102 | 103 | // Init build command map 104 | func (cm *CommandManager) Init() { 105 | commandList := GetAllCommands() 106 | cm.commandMap = make(map[string]interface{}, len(commandList)) 107 | 108 | for _, cmd := range commandList { 109 | name := reflect.ValueOf(cmd).Elem().FieldByName("command").FieldByName("name").String() 110 | cm.commandMap[name] = cmd 111 | } 112 | } 113 | 114 | // RunCommand select command from command map, initialize command and run command 115 | func (cm *CommandManager) RunCommand(commandName string, args []string, options OptionMapType) (bool, error) { 116 | 117 | if cmd, ok := cm.commandMap[commandName]; ok { 118 | if err := cmd.(Commander).Init(args, options); err != nil { 119 | return false, err 120 | } 121 | if err := cmd.(Commander).RunCommand(); err != nil { 122 | return false, err 123 | } 124 | group := reflect.ValueOf(cmd).Elem().FieldByName("command").FieldByName("group").String() 125 | return group == GroupTypeNormalCommand, nil 126 | } 127 | return false, fmt.Errorf("no such command: \"%s\", please try \"help\" for more information", commandName) 128 | } 129 | 130 | func getLoglevelFromOptions(options OptionMapType) (string, error) { 131 | strLevel, err := GetString(OptionLogLevel, options) 132 | if err != nil { 133 | return "", err 134 | } 135 | if strLevel != "" { 136 | return strLevel, nil 137 | } 138 | 139 | configFile, _ := GetString(OptionConfigFile, options) 140 | 141 | strLevel, err = readLoglevelFromFile(configFile) 142 | if err != nil { 143 | return "", err 144 | } 145 | 146 | return strLevel, nil 147 | } 148 | -------------------------------------------------------------------------------- /lib/cors_options.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseOptions = SpecText{ 11 | synopsisText: "向oss发送http options请求,用于CORS检测", 12 | 13 | paramText: "oss_url [options]", 14 | 15 | syntaxText: ` 16 | ossutil cors-options --acr-method --origin --acr-headers oss://bucket/[object] [options] 17 | `, 18 | detailHelpText: ` 19 | cors-options命令向oss发送http options请求 20 | --acr-method、--origin、--acr-headers分别对应http header:Access-Control-Request-Method、Origin、Access-Control-Request-Headers 21 | --acr-headers如果有多个取值,各个header用逗号分隔,再加上双引号,比如 --acr-headers "header1,header2,header3" 22 | 23 | 用法: 24 | 该命令有一种用法: 25 | 26 | 1) ossutil cors-options --acr-method PUT --origin "www.aliyuncs.com" --acr-header x-oss-meta-author oss://bucket/ [options] 27 | 向oss发送options请求,Origin、Access-Control-Request-Method、Access-Control-Request-Headers分别为www.aliyuncs.com、PUT、x-oss-meta-author 28 | `, 29 | sampleText: ` 30 | 1) 发送options请求,Access-Control-Request-Method为PUT 31 | ossutil cors-options --acr-method PUT --origin "www.aliyuncs.com" --acr-header x-oss-meta-author oss://bucket/ 32 | 33 | 2) 发送options请求,有多个header参数,Access-Control-Request-Method为GET 34 | ossutil cors-options --acr-method GET --origin "www.aliyuncs.com" --acr-header "x-oss-meta-author1,x-oss-meta-author2" oss://bucket/ 35 | `, 36 | } 37 | 38 | var specEnglishOptions = SpecText{ 39 | synopsisText: "Send http options request to oss for CORS detection", 40 | 41 | paramText: "oss_url [options]", 42 | 43 | syntaxText: ` 44 | ossutil cors-options --acr-method --origin --acr-headers oss://bucket/[object] [options] 45 | `, 46 | detailHelpText: ` 47 | The cors-options command sends an http options request to oss 48 | --acr-method, --origin, --acr-headers correspond to http header:Access-Control-Request-Method, Origin, Access-Control-Request-Headers 49 | If --acr-headers have multiple values, each header is separated by a comma, followed by double quotes, for example: --acr-headers "header1,header2,header3" 50 | 51 | Usage: 52 | There are one usage for this command: 53 | 54 | 1) ossutil cors-options --acr-method PUT --origin "www.aliyuncs.com" --acr-header x-oss-meta-author oss://bucket/ [options] 55 | sends an http options request to oss,Origin、Access-Control-Request-Method、Access-Control-Request-Headers values are www.aliyuncs.com、PUT、x-oss-meta-author 56 | `, 57 | sampleText: ` 58 | 1) sends an http options request,Access-Control-Request-Method value is PUT 59 | ossutil cors-options --acr-method PUT --origin "www.aliyuncs.com" --acr-header x-oss-meta-author oss://bucket/ 60 | 61 | 2) sends an http options request,there are multipule values for --acr-header,Access-Control-Request-Method value is GET 62 | ossutil cors-options --acr-method GET --origin "www.aliyuncs.com" --acr-header "x-oss-meta-author1,x-oss-meta-author2" oss://bucket/ 63 | `, 64 | } 65 | 66 | type OptionsCommand struct { 67 | command Command 68 | } 69 | 70 | var corsOptionsCommand = OptionsCommand{ 71 | command: Command{ 72 | name: "cors-options", 73 | nameAlias: []string{"cors-options"}, 74 | minArgc: 1, 75 | maxArgc: 1, 76 | specChinese: specChineseOptions, 77 | specEnglish: specEnglishOptions, 78 | group: GroupTypeNormalCommand, 79 | validOptionNames: []string{ 80 | OptionConfigFile, 81 | OptionEndpoint, 82 | OptionAccessKeyID, 83 | OptionAccessKeySecret, 84 | OptionSTSToken, 85 | OptionProxyHost, 86 | OptionProxyUser, 87 | OptionProxyPwd, 88 | OptionLogLevel, 89 | OptionEncodingType, 90 | OptionOrigin, 91 | OptionAcrMethod, 92 | OptionAcrHeaders, 93 | OptionPassword, 94 | OptionMode, 95 | OptionECSRoleName, 96 | OptionTokenTimeout, 97 | OptionRamRoleArn, 98 | OptionRoleSessionName, 99 | OptionReadTimeout, 100 | OptionConnectTimeout, 101 | OptionSTSRegion, 102 | OptionSkipVerifyCert, 103 | OptionUserAgent, 104 | OptionSignVersion, 105 | OptionRegion, 106 | OptionCloudBoxID, 107 | OptionForcePathStyle, 108 | }, 109 | }, 110 | } 111 | 112 | // function for FormatHelper interface 113 | func (opsc *OptionsCommand) formatHelpForWhole() string { 114 | return opsc.command.formatHelpForWhole() 115 | } 116 | 117 | func (opsc *OptionsCommand) formatIndependHelp() string { 118 | return opsc.command.formatIndependHelp() 119 | } 120 | 121 | // Init simulate inheritance, and polymorphism 122 | func (opsc *OptionsCommand) Init(args []string, options OptionMapType) error { 123 | return opsc.command.Init(args, options, opsc) 124 | } 125 | 126 | // RunCommand simulate inheritance, and polymorphism 127 | func (opsc *OptionsCommand) RunCommand() error { 128 | strOrigin, _ := GetString(OptionOrigin, opsc.command.options) 129 | strMethod, _ := GetString(OptionAcrMethod, opsc.command.options) 130 | strAcrHeaders, _ := GetString(OptionAcrHeaders, opsc.command.options) 131 | 132 | strEncodingType, _ := GetString(OptionEncodingType, opsc.command.options) 133 | srcBucketUrL, err := GetCloudUrl(opsc.command.args[0], strEncodingType) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | options := []oss.Option{} 139 | 140 | if len(strOrigin) > 0 { 141 | options = append(options, oss.Origin(strOrigin)) 142 | } 143 | if len(strMethod) > 0 { 144 | options = append(options, oss.ACReqMethod(strings.ToUpper(strMethod))) 145 | } 146 | if len(strAcrHeaders) > 0 { 147 | options = append(options, oss.ACReqHeaders(strAcrHeaders)) 148 | } 149 | 150 | objectName := srcBucketUrL.object 151 | 152 | client, err := opsc.command.ossClient(srcBucketUrL.bucket) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | bucket, err := client.Bucket(srcBucketUrL.bucket) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | respHeader, err := bucket.OptionsMethod(objectName, options...) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | exclude := map[string]bool{} 168 | exclude["Connection"] = true 169 | exclude["ontent-Length"] = true 170 | exclude["Date"] = true 171 | exclude["Server"] = true 172 | exclude["X-Oss-Request-Id"] = true 173 | exclude["X-Oss-Server-Time"] = true 174 | exclude["Content-Length"] = true 175 | 176 | respHeader.WriteSubset(os.Stdout, exclude) 177 | 178 | return nil 179 | } 180 | -------------------------------------------------------------------------------- /lib/cors_options_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/xml" 5 | "os" 6 | 7 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | . "gopkg.in/check.v1" 9 | ) 10 | 11 | func (s *OssutilCommandSuite) TestCorsOptionsSuccess(c *C) { 12 | bucketName := bucketNamePrefix + randLowStr(12) 13 | s.putBucket(bucketName, c) 14 | 15 | corsXml := ` 16 | 17 | 18 | www.aliyun.com 19 | PUT 20 | x-oss-meta-author 21 | x-oss-meta-name 22 | 10001 23 | 24 | ` 25 | 26 | rulesConfigSrc := oss.CORSXML{} 27 | err := xml.Unmarshal([]byte(corsXml), &rulesConfigSrc) 28 | c.Assert(err, IsNil) 29 | 30 | corsFileName := "ossutil_test." + randLowStr(12) 31 | s.createFile(corsFileName, corsXml, c) 32 | 33 | // cors command test 34 | var str string 35 | strMethod := "put" 36 | options := OptionMapType{ 37 | "endpoint": &str, 38 | "accessKeyID": &str, 39 | "accessKeySecret": &str, 40 | "stsToken": &str, 41 | "configFile": &configFile, 42 | "method": &strMethod, 43 | } 44 | 45 | corsArgs := []string{CloudURLToString(bucketName, ""), corsFileName} 46 | _, err = cm.RunCommand("cors", corsArgs, options) 47 | c.Assert(err, IsNil) 48 | 49 | // cors-options success 50 | strOrigin := "www.aliyun.com" 51 | strAcrHeaders := "x-oss-meta-author" 52 | options["origin"] = &strOrigin 53 | options["acrHeaders"] = &strAcrHeaders 54 | options["acrMethod"] = &strMethod 55 | delete(options, "method") 56 | 57 | corsArgs = []string{CloudURLToString(bucketName, "")} 58 | _, err = cm.RunCommand("cors-options", corsArgs, options) 59 | c.Assert(err, IsNil) 60 | 61 | // cors-options error 62 | strOrigin = "www.test.com" 63 | _, err = cm.RunCommand("cors-options", corsArgs, options) 64 | c.Assert(err, NotNil) 65 | 66 | os.Remove(corsFileName) 67 | s.removeBucket(bucketName, true, c) 68 | } 69 | 70 | func (s *OssutilCommandSuite) TestCorsOptionsError(c *C) { 71 | bucketName := bucketNamePrefix + randLowStr(12) 72 | s.putBucket(bucketName, c) 73 | 74 | corsFileName := "ossutil_test_corsfile_" + randLowStr(12) 75 | 76 | // cors command test 77 | var str string 78 | strMethod := "" 79 | options := OptionMapType{ 80 | "endpoint": &str, 81 | "accessKeyID": &str, 82 | "accessKeySecret": &str, 83 | "stsToken": &str, 84 | "configFile": &configFile, 85 | "acrMethod": &strMethod, 86 | } 87 | 88 | // method is empty 89 | corsArgs := []string{CloudURLToString(bucketName, ""), corsFileName} 90 | _, err := cm.RunCommand("cors-options", corsArgs, options) 91 | c.Assert(err, NotNil) 92 | 93 | //method is error 94 | strMethod = "puttt" 95 | _, err = cm.RunCommand("cors-options", corsArgs, options) 96 | c.Assert(err, NotNil) 97 | 98 | // cloudurl is error 99 | strMethod = "put" 100 | corsArgs = []string{"http://mybucket", corsFileName} 101 | _, err = cm.RunCommand("cors-options", corsArgs, options) 102 | c.Assert(err, NotNil) 103 | 104 | // StorageURLFromString error 105 | corsArgs = []string{"oss:///1.jpg"} 106 | _, err = cm.RunCommand("cors-options", corsArgs, options) 107 | c.Assert(err, NotNil) 108 | 109 | os.Remove(corsFileName) 110 | s.removeBucket(bucketName, true, c) 111 | } 112 | 113 | func (s *OssutilCommandSuite) TestCorsOptionsEmptyEndpoint(c *C) { 114 | bucketName := bucketNamePrefix + randLowStr(12) 115 | s.putBucket(bucketName, c) 116 | 117 | cfile := randStr(10) 118 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 119 | s.createFile(cfile, data, c) 120 | 121 | var str string 122 | strMethod := "get" 123 | options := OptionMapType{ 124 | "endpoint": &str, 125 | "accessKeyID": &str, 126 | "accessKeySecret": &str, 127 | "stsToken": &str, 128 | "configFile": &cfile, 129 | "acrMethod": &strMethod, 130 | } 131 | 132 | versioingArgs := []string{CloudURLToString(bucketName, "")} 133 | _, err := cm.RunCommand("cors-options", versioingArgs, options) 134 | c.Assert(err, NotNil) 135 | 136 | os.Remove(cfile) 137 | s.removeBucket(bucketName, true, c) 138 | } 139 | 140 | func (s *OssutilCommandSuite) TestCorsOptionsHelpInfo(c *C) { 141 | // mkdir command test 142 | options := OptionMapType{} 143 | 144 | mkArgs := []string{"cors-options"} 145 | _, err := cm.RunCommand("help", mkArgs, options) 146 | c.Assert(err, IsNil) 147 | 148 | mkArgs = []string{} 149 | _, err = cm.RunCommand("help", mkArgs, options) 150 | c.Assert(err, IsNil) 151 | 152 | } 153 | -------------------------------------------------------------------------------- /lib/create_symlink.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseCreateSymlink = SpecText{ 11 | 12 | synopsisText: "创建符号链接", 13 | 14 | paramText: "cloud_url target_url [options]", 15 | 16 | syntaxText: ` 17 | ossutil create-symlink cloud_url target_object [--encoding-type url] [--payer requester] [-c file] 18 | `, 19 | 20 | detailHelpText: ` 21 | 该命令在oss上创建符号链接文件,链接的目标文件必须为相同bucket下的文件,且文件类型非符 22 | 号链接。即,cloud_url必须为形如oss://bucket/object的cloud_url,target_object为object名。 23 | 24 | 创建符号链接时: 25 | 不检查目标文件是否存在, 26 | 不检查目标文件类型是否合法, 27 | 不检查目标文件是否有权限访问, 28 | 以上检查,都推迟到GetObject等需要访问目标文件的API。 29 | 如果试图添加的文件已经存在,并且有访问权限。新添加的文件将覆盖原来的文件。 30 | 31 | 通过stat命令可以查看符号链接的目标文件。 32 | 33 | 更多信息见官网文档:https://help.aliyun.com/document_detail/45126.html?spm=5176.doc31979.6.870.x3Tqsh 34 | 35 | 用法: 36 | 37 | ossutil create-symlink oss://bucket/symlink-object target-object 38 | `, 39 | 40 | sampleText: ` 41 | ossutil create-symlink oss://bucket1/object1 object2 42 | 创建从指向object2的符号链接object1。 43 | 44 | ossutil create-symlink oss://bucket1/object1 object2 --payer requester 45 | 以访问者付费模式,创建从指向object2的符号链接object1 46 | `, 47 | } 48 | 49 | var specEnglishCreateSymlink = SpecText{ 50 | 51 | synopsisText: "Create symlink of object", 52 | 53 | paramText: "cloud_url target_url [options]", 54 | 55 | syntaxText: ` 56 | ossutil create-symlink cloud_url target_object [--encoding-type url] [--payer requester] [-c file] 57 | `, 58 | 59 | detailHelpText: ` 60 | The command create symlink of object in oss, the target object must be object in the 61 | same bucket of symlink object, and the file type of target object must not be symlink. 62 | So, cloud_url must be in format: oss://bucket/object, and target_object is the object 63 | name of target object. 64 | 65 | When create symlink: 66 | Will not check whether target object exists; 67 | Will not check whether target object type is valid; 68 | Will not check whether if have access permission of target object. 69 | The check will be done when visiting GetObject, etc. 70 | 71 | If the symlink object exist, and has access permission, the object newly created will 72 | cover the old object. 73 | 74 | We can use stat command to query the target object of symlink object. 75 | 76 | More information about symlink see: https://help.aliyun.com/document_detail/45126.html?spm=5176.doc31979.6.870.x3Tqsh 77 | 78 | Usage: 79 | 80 | ossutil create-symlink oss://bucket/symlink-object target-object 81 | `, 82 | 83 | sampleText: ` 84 | ossutil create-symlink oss://bucket1/object1 object2 85 | Create symlink object named object1, which point to object2. 86 | 87 | ossutil create-symlink oss://bucket1/object1 object2 --payer requester 88 | Create symlink object named object1, which point to object2 with requester payment mode 89 | `, 90 | } 91 | 92 | // CreateSymlinkCommand is the command list buckets or objects 93 | type CreateSymlinkCommand struct { 94 | command Command 95 | commonOptions []oss.Option 96 | } 97 | 98 | var createSymlinkCommand = CreateSymlinkCommand{ 99 | command: Command{ 100 | name: "create-symlink", 101 | nameAlias: []string{}, 102 | minArgc: 2, 103 | maxArgc: 2, 104 | specChinese: specChineseCreateSymlink, 105 | specEnglish: specEnglishCreateSymlink, 106 | group: GroupTypeNormalCommand, 107 | validOptionNames: []string{ 108 | OptionEncodingType, 109 | OptionConfigFile, 110 | OptionEndpoint, 111 | OptionAccessKeyID, 112 | OptionAccessKeySecret, 113 | OptionSTSToken, 114 | OptionProxyHost, 115 | OptionProxyUser, 116 | OptionProxyPwd, 117 | OptionRetryTimes, 118 | OptionLogLevel, 119 | OptionRequestPayer, 120 | OptionPassword, 121 | OptionMode, 122 | OptionECSRoleName, 123 | OptionTokenTimeout, 124 | OptionRamRoleArn, 125 | OptionRoleSessionName, 126 | OptionReadTimeout, 127 | OptionConnectTimeout, 128 | OptionSTSRegion, 129 | OptionSkipVerifyCert, 130 | OptionUserAgent, 131 | OptionSignVersion, 132 | OptionRegion, 133 | OptionCloudBoxID, 134 | OptionForcePathStyle, 135 | }, 136 | }, 137 | } 138 | 139 | // function for FormatHelper interface 140 | func (cc *CreateSymlinkCommand) formatHelpForWhole() string { 141 | return cc.command.formatHelpForWhole() 142 | } 143 | 144 | func (cc *CreateSymlinkCommand) formatIndependHelp() string { 145 | return cc.command.formatIndependHelp() 146 | } 147 | 148 | // Init simulate inheritance, and polymorphism 149 | func (cc *CreateSymlinkCommand) Init(args []string, options OptionMapType) error { 150 | return cc.command.Init(args, options, cc) 151 | } 152 | 153 | // RunCommand simulate inheritance, and polymorphism 154 | func (cc *CreateSymlinkCommand) RunCommand() error { 155 | encodingType, _ := GetString(OptionEncodingType, cc.command.options) 156 | cloudURL, err := CloudURLFromString(cc.command.args[0], encodingType) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | targetURL, err := StorageURLFromString(cc.command.args[1], encodingType) 162 | if err != nil { 163 | return err 164 | } 165 | 166 | if err := cc.checkArgs(cloudURL, targetURL); err != nil { 167 | return err 168 | } 169 | 170 | targetObject := targetURL.ToString() 171 | if targetURL.IsCloudURL() { 172 | targetObject = targetURL.(CloudURL).object 173 | } 174 | 175 | bucket, err := cc.command.ossBucket(cloudURL.bucket) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | payer, _ := GetString(OptionRequestPayer, cc.command.options) 181 | if payer != "" { 182 | if payer != strings.ToLower(string(oss.Requester)) { 183 | return fmt.Errorf("invalid request payer: %s, please check", payer) 184 | } 185 | cc.commonOptions = append(cc.commonOptions, oss.RequestPayer(oss.PayerType(payer))) 186 | } 187 | 188 | return cc.ossCreateSymlinkRetry(bucket, cloudURL.object, targetObject) 189 | } 190 | 191 | func (cc *CreateSymlinkCommand) checkArgs(symlinkURL CloudURL, targetURL StorageURLer) error { 192 | if symlinkURL.bucket == "" { 193 | return fmt.Errorf("invalid cloud url: %s, miss bucket", cc.command.args[0]) 194 | } 195 | if symlinkURL.object == "" { 196 | return fmt.Errorf("invalid cloud url: %s, miss object, symlink object can't be empty", cc.command.args[0]) 197 | } 198 | if targetURL.IsCloudURL() { 199 | if targetURL.(CloudURL).bucket == "" { 200 | return fmt.Errorf("invalid cloud url: %s, miss bucket", cc.command.args[1]) 201 | } 202 | if targetURL.(CloudURL).bucket != symlinkURL.bucket { 203 | return fmt.Errorf("the bucket of target object: %s must be the same with the bucket of symlink object: %s", targetURL.(CloudURL).bucket, symlinkURL.bucket) 204 | } 205 | if targetURL.(CloudURL).object == "" { 206 | return fmt.Errorf("invalid cloud url: %s, miss object, target object can't be empty", cc.command.args[1]) 207 | } 208 | } 209 | return nil 210 | } 211 | 212 | func (cc *CreateSymlinkCommand) ossCreateSymlinkRetry(bucket *oss.Bucket, symlinkObject, targetObject string) error { 213 | retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options) 214 | for i := 1; ; i++ { 215 | err := bucket.PutSymlink(symlinkObject, targetObject, cc.commonOptions...) 216 | if err == nil { 217 | return err 218 | } 219 | if int64(i) >= retryTimes { 220 | return ObjectError{err, bucket.BucketName, symlinkObject} 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /lib/ecs_role.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 13 | ) 14 | 15 | const ( 16 | AdvanceSeconds int64 = 60 17 | ) 18 | 19 | type STSAkJson struct { 20 | AccessKeyId string `json:"AccessKeyId,omitempty"` 21 | AccessKeySecret string `json:"AccessKeySecret,omitempty"` 22 | SecurityToken string `json:"SecurityToken,omitempty"` 23 | Expiration string `json:"Expiration,omitempty"` 24 | LastUpDated string `json:"LastUpDated,omitempty"` 25 | Code string `json:"Code,omitempty"` 26 | } 27 | 28 | func (stsJson *STSAkJson) String() string { 29 | return fmt.Sprintf("AccessKeyId:%s,AccessKeySecret:%s,SecurityToken:%s,Expiration:%s,LastUpDated:%s", 30 | stsJson.AccessKeyId, stsJson.AccessKeySecret, stsJson.SecurityToken, stsJson.Expiration, stsJson.LastUpDated) 31 | } 32 | 33 | type EcsRoleAK struct { 34 | AccessKeyId string 35 | AccessKeySecret string 36 | SecurityToken string 37 | } 38 | 39 | func (ecsRole *EcsRoleAK) GetAccessKeyID() string { 40 | return ecsRole.AccessKeyId 41 | } 42 | 43 | func (ecsRole *EcsRoleAK) GetAccessKeySecret() string { 44 | return ecsRole.AccessKeySecret 45 | } 46 | 47 | func (ecsRole *EcsRoleAK) GetSecurityToken() string { 48 | return ecsRole.SecurityToken 49 | } 50 | 51 | // for ecs bind ram and get ak by ossutil automaticly 52 | type EcsRoleAKBuild struct { 53 | lock sync.Mutex 54 | HasGet bool 55 | url string //url for get ak,such as http://100.100.100.200/latest/meta-data/Ram/security-credentials/RamRoleName 56 | AccessKeyId string 57 | AccessKeySecret string 58 | SecurityToken string 59 | Expiration string 60 | LastUpDated string 61 | } 62 | 63 | func (roleBuild *EcsRoleAKBuild) GetCredentials() oss.Credentials { 64 | cred, _ := roleBuild.GetCredentialsE() 65 | return cred 66 | } 67 | 68 | func (roleBuild *EcsRoleAKBuild) GetCredentialsE() (oss.Credentials, error) { 69 | roleBuild.lock.Lock() 70 | defer roleBuild.lock.Unlock() 71 | 72 | akJson := STSAkJson{} 73 | var err error = nil 74 | bTimeOut := false 75 | 76 | if !roleBuild.HasGet { 77 | bTimeOut = true 78 | } else { 79 | bTimeOut = roleBuild.IsTimeOut() 80 | } 81 | 82 | if bTimeOut { 83 | tStart := time.Now().UnixNano() / 1000 / 1000 84 | akJson, err = roleBuild.HttpReqAk() 85 | tEnd := time.Now().UnixNano() / 1000 / 1000 86 | 87 | if err != nil { 88 | return &EcsRoleAK{}, err 89 | } else { 90 | roleBuild.AccessKeyId = akJson.AccessKeyId 91 | roleBuild.AccessKeySecret = akJson.AccessKeySecret 92 | roleBuild.SecurityToken = akJson.SecurityToken 93 | roleBuild.Expiration = akJson.Expiration 94 | LogInfo("get sts ak success,%s,cost:%d(ms)\n", akJson.String(), tEnd-tStart) 95 | } 96 | } 97 | return &EcsRoleAK{ 98 | AccessKeyId: roleBuild.AccessKeyId, 99 | AccessKeySecret: roleBuild.AccessKeySecret, 100 | SecurityToken: roleBuild.SecurityToken, 101 | }, nil 102 | } 103 | 104 | func (roleBuild *EcsRoleAKBuild) IsTimeOut() bool { 105 | if roleBuild.Expiration == "" { 106 | return false 107 | } 108 | 109 | // attention: can't use time.ParseInLocation(),ecsRole.Expiration is UTC time 110 | utcExpirationTime, _ := time.Parse("2006-01-02T15:04:05Z", roleBuild.Expiration) 111 | 112 | // Now() returns the current local time 113 | nowLocalTime := time.Now() 114 | 115 | // Unix() returns the number of seconds elapsedsince January 1, 1970 UTC. 116 | if utcExpirationTime.Unix()-nowLocalTime.Unix()-AdvanceSeconds <= 0 { 117 | return true 118 | } 119 | return false 120 | } 121 | 122 | func (roleBuild *EcsRoleAKBuild) HttpReqAk() (STSAkJson, error) { 123 | akJson := STSAkJson{} 124 | 125 | //http time out 126 | c := &http.Client{ 127 | Timeout: 15 * time.Second, 128 | } 129 | 130 | resp, err := c.Get(roleBuild.url) 131 | if err != nil { 132 | LogError("insight getAK,http client get error,url is %s,%s\n", roleBuild.url, err.Error()) 133 | return akJson, err 134 | } 135 | defer resp.Body.Close() 136 | body, err := ioutil.ReadAll(resp.Body) 137 | if err != nil { 138 | return akJson, err 139 | } 140 | 141 | err = json.Unmarshal(body, &akJson) 142 | if err != nil { 143 | LogError("insight getAK,json.Unmarshal error,body is %s,%s\n", string(body), err.Error()) 144 | return akJson, err 145 | } 146 | 147 | // parsar json,such as 148 | //{ 149 | // "AccessKeyId" : "XXXXXXXXX", 150 | // "AccessKeySecret" : "XXXXXXXXX", 151 | // "Expiration" : "2017-11-01T05:20:01Z", 152 | // "SecurityToken" : "XXXXXXXXX", 153 | // "LastUpdated" : "2017-10-31T23:20:01Z", 154 | // "Code" : "Success" 155 | // } 156 | 157 | if akJson.Code != "" && strings.ToUpper(akJson.Code) != "SUCCESS" { 158 | LogError("insight getAK,get sts ak error,code:%s\n", akJson.Code) 159 | return akJson, fmt.Errorf("insight getAK,get sts ak error,code:%s", akJson.Code) 160 | } 161 | 162 | if akJson.AccessKeyId == "" || akJson.AccessKeySecret == "" { 163 | LogError("insight getAK,parsar http json body error:\n%s\n", string(body)) 164 | return akJson, fmt.Errorf("insight getAK,parsar http json body error:\n%s\n", string(body)) 165 | } 166 | 167 | if akJson.Expiration != "" { 168 | _, err := time.Parse("2006-01-02T15:04:05Z", akJson.Expiration) 169 | if err != nil { 170 | LogError("time.Parse error,Expiration is %s,%s\n", akJson.Expiration, err.Error()) 171 | return akJson, err 172 | } 173 | } 174 | 175 | roleBuild.HasGet = true 176 | return akJson, nil 177 | } 178 | -------------------------------------------------------------------------------- /lib/error.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // CommandError happens when use command in invalid way 8 | type CommandError struct { 9 | command string 10 | reason string 11 | } 12 | 13 | func (e CommandError) Error() string { 14 | return fmt.Sprintf("invalid usage of \"%s\" command, reason: %s, please try \"help %s\" for more information", e.command, e.reason, e.command) 15 | } 16 | 17 | // BucketError happens when access bucket error 18 | type BucketError struct { 19 | err error 20 | bucket string 21 | } 22 | 23 | func (e BucketError) Error() string { 24 | return fmt.Sprintf("%s, Bucket=%s", e.err.Error(), e.bucket) 25 | } 26 | 27 | // ObjectError happens when access object error 28 | type ObjectError struct { 29 | err error 30 | bucket string 31 | object string 32 | } 33 | 34 | func (e ObjectError) Error() string { 35 | return fmt.Sprintf("%s, Bucket=%s, Object=%s", e.err.Error(), e.bucket, e.object) 36 | } 37 | 38 | // FileError happens when access file error 39 | type FileError struct { 40 | err error 41 | file string 42 | } 43 | 44 | func (e FileError) Error() string { 45 | return fmt.Sprintf("%s, File=%s", e.err.Error(), e.file) 46 | } 47 | 48 | type CopyError struct { 49 | err error 50 | } 51 | 52 | func (e CopyError) Error() string { 53 | return e.err.Error() 54 | } 55 | -------------------------------------------------------------------------------- /lib/hash.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "fmt" 7 | "hash" 8 | "hash/crc64" 9 | "io" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | var specChineseHash = SpecText{ 15 | 16 | synopsisText: "计算本地文件的crc64或md5", 17 | 18 | paramText: "file_url [options]", 19 | 20 | syntaxText: ` 21 | ossutil hash file_url [--type=hashtype] 22 | `, 23 | 24 | detailHelpText: ` 25 | 该命令计算本地文件的crc64值或md5/content-md5值, 可以通过--type选项来控制计算的类型, 26 | 可选类型值为crc64或md5, 默认为` + DefaultHashType + `。 27 | 28 | 注意:oss文件的crc64和content-md5值一般可通过stat命令查看到,参考` + StatCRC64 + ` 29 | 字段和` + StatContentMD5 + `字段。若文件在oss支持crc64功能之前上传,则stat命令不支持查看crc64值; 30 | 对于append和multipart类型的文件,stat命令不支持查看content-md5值。 31 | 32 | crc64的计算标准参考ECMA-182标准(http://www.ecma-international.org/publications/standards/Ecma-182.htm)。 33 | 34 | 计算类型为md5时,会同时输出文件的md5以及content-md5值。content-md5值其实是先计算md5 35 | 值获得128比特位数字,然后对该数字进行base64编码得到的值。关于content-md5的更多信息, 36 | 请参考https://tools.ietf.org/html/rfc1864。 37 | 38 | 用法: 39 | 40 | ossutil hash file_url [--type=hashtype] 41 | `, 42 | 43 | sampleText: ` 44 | 1) 计算本地文件的crc64: 45 | ossutil hash test.txt 或 46 | ossutil hash test.txt --type=crc64 47 | 48 | 输出: 49 | CRC64-ECMA : 295992936743767023 50 | 51 | 2) 计算本地文件的md5: 52 | ossutil hash test.txt --type=md5 53 | 54 | 输出: 55 | MD5 : 01C3C45C03B2AF225EFAD9F911A33D73 56 | Content-MD5 : AcPEXAOyryJe+tn5EaM9cw== 57 | `, 58 | } 59 | 60 | var specEnglishHash = SpecText{ 61 | 62 | synopsisText: "Get crc64 or md5 of local file", 63 | 64 | paramText: "file_url [options]", 65 | 66 | syntaxText: ` 67 | ossutil hash file_url [--type=hashtype] 68 | `, 69 | 70 | detailHelpText: ` 71 | The command calculate crc64 or md5/content-md5 value of the specified local file, 72 | specify the hashtype by --type, default hashtype is ` + DefaultHashType + `. 73 | 74 | Warning: user can use stat command to check the crc64 or md5/content-md5 value of 75 | normal oss object, see the ` + StatCRC64 + ` and ` + StatContentMD5 + ` field. If the object 76 | was uploaded to oss before oss support crc64 feature, stat result will not show 77 | ` + StatCRC64 + `, if the object is append file type or multipart, stat result 78 | will not show ` + StatContentMD5 + `. 79 | 80 | Crc64 is calcuated according to ECMA-182(http://www.ecma-international.org/publications/standards/Ecma-182.htm). 81 | 82 | When hashtype is md5, it will output both md5 and content-md5 of local file. 83 | Content-md5 is base64 encoded string of md5. For more detial about content-md5, 84 | please refer to https://tools.ietf.org/html/rfc1864. 85 | 86 | Usage: 87 | 88 | ossutil hash file_url [--type=hashtype] 89 | `, 90 | 91 | sampleText: ` 92 | 1) Get crc64 of local file: 93 | ossutil hash test.txt or 94 | ossutil hash test.txt --type=crc64 95 | 96 | output: 97 | CRC64-ECMA : 295992936743767023 98 | 99 | 2) Get md5 of local file: 100 | ossutil hash test.txt --type=md5 101 | 102 | output: 103 | MD5 : 01C3C45C03B2AF225EFAD9F911A33D73 104 | Content-MD5 : AcPEXAOyryJe+tn5EaM9cw== 105 | `, 106 | } 107 | 108 | // HashCommand is the command to get crc64/md5 of local file 109 | type HashCommand struct { 110 | command Command 111 | } 112 | 113 | var hashCommand = HashCommand{ 114 | command: Command{ 115 | name: "hash", 116 | nameAlias: []string{""}, 117 | minArgc: 1, 118 | maxArgc: 1, 119 | specChinese: specChineseHash, 120 | specEnglish: specEnglishHash, 121 | group: GroupTypeAdditionalCommand, 122 | validOptionNames: []string{ 123 | OptionHashType, 124 | OptionLogLevel, 125 | }, 126 | }, 127 | } 128 | 129 | // function for RewriteLoadConfiger interface 130 | func (hc *HashCommand) rewriteLoadConfig(configFile string) error { 131 | // read config file, if error exist, do not print error 132 | var err error 133 | if hc.command.configOptions, err = LoadConfig(configFile); err != nil { 134 | hc.command.configOptions = OptionMapType{} 135 | } 136 | return nil 137 | } 138 | 139 | // function for FormatHelper interface 140 | func (hc *HashCommand) formatHelpForWhole() string { 141 | return hc.command.formatHelpForWhole() 142 | } 143 | 144 | func (hc *HashCommand) formatIndependHelp() string { 145 | return hc.command.formatIndependHelp() 146 | } 147 | 148 | // Init simulate inheritance, and polymorphism 149 | func (hc *HashCommand) Init(args []string, options OptionMapType) error { 150 | return hc.command.Init(args, options, hc) 151 | } 152 | 153 | // RunCommand simulate inheritance, and polymorphism 154 | func (hc *HashCommand) RunCommand() error { 155 | hashType, _ := GetString(OptionHashType, hc.command.options) 156 | path := hc.command.args[0] 157 | 158 | f, err := os.Open(path) 159 | if err != nil { 160 | return err 161 | } 162 | defer f.Close() 163 | f.Seek(0, os.SEEK_SET) 164 | 165 | switch strings.ToLower(hashType) { 166 | case MD5HashType: 167 | return hashMD5(f) 168 | default: 169 | return hashCRC64(f) 170 | } 171 | } 172 | 173 | func hashMD5(f io.Reader) error { 174 | md5Ins := md5.New() 175 | w, _ := md5Ins.(hash.Hash) 176 | if _, err := io.Copy(w, f); err != nil { 177 | return err 178 | } 179 | 180 | result := md5Ins.Sum(nil) 181 | fmt.Printf("%-28s: %X\n", HashMD5, result) 182 | 183 | encoded := base64.StdEncoding.EncodeToString(result) 184 | fmt.Printf("%-28s: %s\n", HashContentMD5, encoded) 185 | return nil 186 | } 187 | 188 | func hashCRC64(f io.Reader) error { 189 | crc64Ins := crc64.New(crc64.MakeTable(crc64.ECMA)) 190 | w, _ := crc64Ins.(hash.Hash) 191 | if _, err := io.Copy(w, f); err != nil { 192 | return err 193 | } 194 | 195 | result := crc64Ins.Sum64() 196 | fmt.Printf("%-28s: %d\n", HashCRC64, result) 197 | return nil 198 | } 199 | -------------------------------------------------------------------------------- /lib/hash_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | . "gopkg.in/check.v1" 5 | "os" 6 | ) 7 | 8 | func (s *OssutilCommandSuite) TestErrorInputFile(c *C) { 9 | command := "hash" 10 | fakeFileName := randStr(10) 11 | args := []string{fakeFileName} 12 | hashType := DefaultHashType 13 | options := OptionMapType{ 14 | "hash": &hashType, 15 | } 16 | showElapse, err := cm.RunCommand(command, args, options) 17 | c.Assert(err, NotNil) 18 | c.Assert(showElapse, Equals, false) 19 | } 20 | 21 | func (s *OssutilCommandSuite) TestErrorHashType(c *C) { 22 | command := "hash" 23 | hashType := "crc256" 24 | fakeFileName := randStr(10) 25 | args := []string{fakeFileName} 26 | options := OptionMapType{ 27 | "hashType": &hashType, 28 | } 29 | showElapse, err := cm.RunCommand(command, args, options) 30 | c.Assert(err, NotNil) 31 | c.Assert(showElapse, Equals, false) 32 | } 33 | 34 | func (s *OssutilCommandSuite) TestCrc64(c *C) { 35 | command := "hash" 36 | content = "this is content" 37 | s.createFile(inputFileName, content, c) 38 | 39 | args := []string{inputFileName} 40 | hashType := DefaultHashType 41 | options := OptionMapType{ 42 | "hashType": &hashType, 43 | } 44 | 45 | testResultFile, _ = os.OpenFile(resultPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0664) 46 | 47 | saveOut := os.Stdout 48 | os.Stdout = testResultFile 49 | 50 | showElapse, err := cm.RunCommand(command, args, options) 51 | c.Assert(err, IsNil) 52 | c.Assert(showElapse, Equals, false) 53 | 54 | hashStat := s.getHashResults(c) 55 | c.Assert(hashStat[HashCRC64], Equals, "2863152195715871371") 56 | 57 | os.Stdout = saveOut 58 | 59 | os.Remove(inputFileName) 60 | } 61 | 62 | func (s *OssutilCommandSuite) TestMd5(c *C) { 63 | command := "hash" 64 | content = "this is content" 65 | s.createFile(inputFileName, content, c) 66 | 67 | args := []string{inputFileName} 68 | hashType := MD5HashType 69 | options := OptionMapType{ 70 | "hashType": &hashType, 71 | } 72 | 73 | testResultFile, _ = os.OpenFile(resultPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0664) 74 | 75 | saveOut := os.Stdout 76 | os.Stdout = testResultFile 77 | 78 | showElapse, err := cm.RunCommand(command, args, options) 79 | c.Assert(err, IsNil) 80 | c.Assert(showElapse, Equals, false) 81 | 82 | hashStat := s.getHashResults(c) 83 | c.Assert(hashStat[HashMD5], Equals, "B7FCEF7FE745F2A95560FF5F550E3B8F") 84 | c.Assert(hashStat[HashContentMD5], Equals, "t/zvf+dF8qlVYP9fVQ47jw==") 85 | 86 | os.Stdout = saveOut 87 | 88 | os.Remove(inputFileName) 89 | } 90 | -------------------------------------------------------------------------------- /lib/help.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // global public variable for formating help text 10 | const ( 11 | FormatTAB = " " 12 | MaxCommandNameLen = 18 13 | UsageTextChinese = "用法: ossutil [command] [args...] [options...]\n请使用ossutil help command来显示command命令的帮助" 14 | UsageTextEnglish = "Usage: ossutil [command] [args...] [options...]\nPlease use 'ossutil help command' to show help of command" 15 | ) 16 | 17 | var specChineseHelp = SpecText{ 18 | 19 | synopsisText: "获取命令的帮助文档", 20 | 21 | paramText: "[command]", 22 | 23 | syntaxText: ` 24 | ossutil help [command] 25 | `, 26 | 27 | detailHelpText: ` 28 | 该命令提供ossutil所有命令的帮助文档,或者针对用户输入的某具体命令提供它的帮助文档。 29 | 30 | 用法: 31 | 32 | 该命令有两种用法: 33 | 34 | 1) ossutil help 35 | 该用法提供ossutil支持的所有命令的简介,对每个命令显示该命令的摘要和语法简介。 36 | 37 | 2) ossutil help command 38 | 该用法提供指定命令(command)的帮助文档,包括该命令的详细介绍、示例、可选参数。 39 | `, 40 | 41 | sampleText: ` 42 | ossutil help 43 | ossutil help help 44 | ossutil help ls 45 | `, 46 | } 47 | 48 | var specEnglishHelp = SpecText{ 49 | 50 | synopsisText: "Get help about commands", 51 | 52 | paramText: "[command]", 53 | 54 | syntaxText: ` 55 | ossutil help [command] 56 | `, 57 | 58 | detailHelpText: ` 59 | The command provide the usage of all commands on which help is available, 60 | or the usage of the specified command. 61 | 62 | Usage: 63 | 64 | There are two usages: 65 | 66 | 1) ossutil help 67 | The usage provides a summary of all commands, each command shows the 68 | synopsis and a simplified expression of the syntax. 69 | 70 | 2) ossutil help command 71 | The usage provides help about the specified command, which contains 72 | a detailed description of the command, include samples and optional options. 73 | `, 74 | 75 | sampleText: ` 76 | ossutil help 77 | ossutil help help 78 | ossutil help ls 79 | `, 80 | } 81 | 82 | // HelpCommand is the command format help text 83 | type HelpCommand struct { 84 | command Command 85 | } 86 | 87 | var helpCommand = HelpCommand{ 88 | command: Command{ 89 | name: "help", 90 | nameAlias: []string{}, 91 | minArgc: 0, 92 | maxArgc: 1, 93 | specChinese: specChineseHelp, 94 | specEnglish: specEnglishHelp, 95 | group: GroupTypeAdditionalCommand, 96 | validOptionNames: []string{ 97 | OptionLanguage, 98 | OptionLogLevel, 99 | }, 100 | }, 101 | } 102 | 103 | // function for RewriteLoadConfiger interface 104 | func (hc *HelpCommand) rewriteLoadConfig(configFile string) error { 105 | // read config file, if error exist, do not print error 106 | var err error 107 | if hc.command.configOptions, err = LoadConfig(configFile); err != nil { 108 | hc.command.configOptions = OptionMapType{} 109 | } 110 | return nil 111 | } 112 | 113 | // function for FormatHelper interface 114 | func (hc *HelpCommand) formatHelpForWhole() string { 115 | return hc.command.formatHelpForWhole() 116 | } 117 | 118 | func (hc *HelpCommand) formatIndependHelp() string { 119 | return hc.command.formatIndependHelp() 120 | } 121 | 122 | // Init simulate inheritance, and polymorphism 123 | func (hc *HelpCommand) Init(args []string, options OptionMapType) error { 124 | return hc.command.Init(args, options, hc) 125 | } 126 | 127 | // RunCommand simulate inheritance, and polymorphism 128 | func (hc *HelpCommand) RunCommand() error { 129 | groupCommandMap, subCommandMap := hc.getCommandMap() 130 | if len(hc.command.args) == 0 { 131 | // ossutil help 132 | text := hc.formatWholeHelp(groupCommandMap) 133 | Output(text) 134 | } else { 135 | //ossutil help command 136 | if text, err := hc.formatCommandHelp(subCommandMap); err == nil { 137 | Output(text) 138 | } else { 139 | return err 140 | } 141 | } 142 | return nil 143 | } 144 | 145 | func (hc *HelpCommand) getCommandMap() (map[string][]interface{}, map[string]interface{}) { 146 | commandList := GetAllCommands() 147 | groupCommandMap := map[string][]interface{}{} 148 | subCommandMap := map[string]interface{}{} 149 | for _, cmd := range commandList { 150 | group := reflect.ValueOf(cmd).Elem().FieldByName("command").FieldByName("group").String() 151 | groupCommandMap[group] = append(groupCommandMap[group], cmd) 152 | name := reflect.ValueOf(cmd).Elem().FieldByName("command").FieldByName("name").String() 153 | subCommandMap[name] = cmd 154 | } 155 | return groupCommandMap, subCommandMap 156 | } 157 | 158 | func (hc *HelpCommand) formatWholeHelp(groupCommandMap map[string][]interface{}) string { 159 | if len(groupCommandMap) == 0 { 160 | return "" 161 | } 162 | 163 | commandsText := "" 164 | for _, group := range CommandGroups { 165 | commandList := groupCommandMap[group] 166 | if len(commandList) == 0 { 167 | continue 168 | } 169 | 170 | commandsText += group 171 | for _, cmd := range commandList { 172 | commandsText += cmd.(FormatHelper).formatHelpForWhole() 173 | } 174 | } 175 | return fmt.Sprintf("%s\n%s", hc.getUsageText(), commandsText) 176 | } 177 | 178 | func (hc *HelpCommand) getUsageText() string { 179 | val, _ := GetString(OptionLanguage, helpCommand.command.options) 180 | switch strings.ToLower(val) { 181 | case LEnglishLanguage: 182 | return UsageTextEnglish 183 | default: 184 | return UsageTextChinese 185 | } 186 | 187 | } 188 | 189 | func (hc *HelpCommand) formatCommandHelp(subCommandMap map[string]interface{}) (string, error) { 190 | subCommandName := hc.command.args[0] 191 | if cmd, ok := subCommandMap[subCommandName]; ok { 192 | return cmd.(FormatHelper).formatIndependHelp(), nil 193 | } 194 | return "", fmt.Errorf("no such command: \"%s\", please try \"help\" for more information", subCommandName) 195 | } 196 | -------------------------------------------------------------------------------- /lib/help_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | . "gopkg.in/check.v1" 8 | ) 9 | 10 | type OssutilHelpSuite struct{} 11 | 12 | var _ = Suite(&OssutilHelpSuite{}) 13 | 14 | // Run once when the suite starts running 15 | func (s *OssutilHelpSuite) SetUpSuite(c *C) { 16 | fmt.Printf("set up suite OssutilHelpSuite\n") 17 | testLogger.Println("test help started") 18 | os.Stdout = testLogFile 19 | os.Stderr = testLogFile 20 | SetUpCredential() 21 | cm.Init() 22 | } 23 | 24 | // Run before each test or benchmark starts running 25 | func (s *OssutilHelpSuite) TearDownSuite(c *C) { 26 | fmt.Printf("tear down suite OssutilHelpSuite\n") 27 | testLogger.Println("test help completed") 28 | os.Remove(configFile) 29 | os.Stdout = out 30 | os.Stderr = errout 31 | } 32 | 33 | // Run after each test or benchmark runs 34 | func (s *OssutilHelpSuite) SetUpTest(c *C) { 35 | fmt.Printf("set up test:%s\n", c.TestName()) 36 | } 37 | 38 | // Run once after all tests or benchmarks have finished running 39 | func (s *OssutilHelpSuite) TearDownTest(c *C) { 40 | fmt.Printf("tear down test:%s\n", c.TestName()) 41 | } 42 | 43 | // test "help" 44 | func (s *OssutilHelpSuite) TestHelp(c *C) { 45 | command := "help" 46 | var args []string 47 | var options OptionMapType 48 | showElapse, err := cm.RunCommand(command, args, options) 49 | c.Assert(showElapse, Equals, false) 50 | c.Assert(err, IsNil) 51 | } 52 | 53 | // test "help" 54 | func (s *OssutilHelpSuite) TestHelpChinese(c *C) { 55 | command := "help" 56 | var args []string 57 | language := DefaultLanguage 58 | options := OptionMapType{ 59 | "language": &language, 60 | } 61 | showElapse, err := cm.RunCommand(command, args, options) 62 | c.Assert(showElapse, Equals, false) 63 | c.Assert(err, IsNil) 64 | } 65 | 66 | // test "help" 67 | func (s *OssutilHelpSuite) TestHelpEnglish(c *C) { 68 | command := "help" 69 | var args []string 70 | language := EnglishLanguage 71 | options := OptionMapType{ 72 | "language": &language, 73 | } 74 | showElapse, err := cm.RunCommand(command, args, options) 75 | c.Assert(showElapse, Equals, false) 76 | c.Assert(err, IsNil) 77 | } 78 | 79 | func (s *OssutilHelpSuite) TestHelpLEnglish(c *C) { 80 | command := "help" 81 | var args []string 82 | language := LEnglishLanguage 83 | options := OptionMapType{ 84 | "language": &language, 85 | } 86 | showElapse, err := cm.RunCommand(command, args, options) 87 | c.Assert(showElapse, Equals, false) 88 | c.Assert(err, IsNil) 89 | } 90 | 91 | // test "help options" 92 | func (s *OssutilHelpSuite) TestHelpOption(c *C) { 93 | command := "help" 94 | var args []string 95 | for _, option := range OptionMap { 96 | str := "abc" 97 | options := OptionMapType{option.name: &str} 98 | showElapse, err := cm.RunCommand(command, args, options) 99 | c.Assert(showElapse, Equals, false) 100 | c.Assert(err, Equals, CommandError{command: "help", reason: fmt.Sprintf("the command does not support option: \"%s\"", option.name)}) 101 | } 102 | } 103 | 104 | // test "help options" with not exist options 105 | var notExistOptions = []string{"notexist", "abc", "CONFIG_FILE", "ACCESSKEY_ID", "accesskey_id"} 106 | 107 | func (s *OssutilHelpSuite) TestHelpNotExistOption(c *C) { 108 | command := "help" 109 | var args []string 110 | for _, option := range notExistOptions { 111 | str := "abc" 112 | options := OptionMapType{option: &str} 113 | showElapse, err := cm.RunCommand(command, args, options) 114 | c.Assert(showElapse, Equals, false) 115 | c.Assert(err, Equals, CommandError{command: "help", reason: fmt.Sprintf("the command does not support option: \"%s\"", option)}) 116 | } 117 | } 118 | 119 | // test "help command" 120 | type helpCommandTestCase struct { 121 | subCommand string 122 | showElapse bool 123 | err error 124 | } 125 | 126 | var subCommands = []string{"help", "config", "hash", "update", "mb", "ls", "rm", "stat", "set-acl", "set-meta", "cp", "restore", "create-symlink", "read-symlink"} 127 | 128 | func (s *OssutilHelpSuite) TestHelpCommand(c *C) { 129 | command := "help" 130 | for _, language := range []string{DefaultLanguage, EnglishLanguage, LEnglishLanguage} { 131 | options := OptionMapType{ 132 | "language": &language, 133 | } 134 | for _, subCmd := range subCommands { 135 | args := []string{subCmd} 136 | showElapse, err := cm.RunCommand(command, args, options) 137 | c.Assert(showElapse, Equals, false) 138 | c.Assert(err, IsNil) 139 | } 140 | } 141 | } 142 | 143 | var notExistSubCommands = []string{"hp", "pb", "list", "remove", "delete", "info", "set_acl", "set_meta", "copy", "notexit"} 144 | 145 | func (s *OssutilHelpSuite) TestHelpNotExistCommand(c *C) { 146 | command := "help" 147 | var options OptionMapType 148 | for _, subCmd := range notExistSubCommands { 149 | args := []string{subCmd} 150 | showElapse, err := cm.RunCommand(command, args, options) 151 | c.Assert(showElapse, Equals, false) 152 | c.Assert(err, NotNil) 153 | } 154 | } 155 | 156 | // test "help command options" 157 | func (s *OssutilHelpSuite) TestHelpCommandOption(c *C) { 158 | command := "help" 159 | for _, subCmd := range subCommands { 160 | for _, option := range OptionMap { 161 | args := []string{subCmd} 162 | str := "abc" 163 | options := OptionMapType{option.name: &str} 164 | showElapse, err := cm.RunCommand(command, args, options) 165 | c.Assert(showElapse, Equals, false) 166 | c.Assert(err, NotNil) 167 | } 168 | } 169 | } 170 | 171 | func (s *OssutilHelpSuite) TestHelpLoadConfig(c *C) { 172 | fileName := "notexistconfigfile" 173 | err := helpCommand.rewriteLoadConfig(fileName) 174 | c.Assert(err, IsNil) 175 | 176 | err = helpCommand.rewriteLoadConfig(configFile) 177 | c.Assert(err, IsNil) 178 | } 179 | -------------------------------------------------------------------------------- /lib/lang.go: -------------------------------------------------------------------------------- 1 | // This is for Condition Compling, which means it will be built on all non-windows platform. 2 | 3 | //go:build !windows 4 | // +build !windows 5 | 6 | package lib 7 | 8 | import ( 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func getOsLang() string { 14 | lang := os.Getenv("LANG") 15 | langstr := strings.Split(lang, ".") 16 | 17 | if langstr[0] == "zh_CN" { 18 | return ChineseLanguage 19 | } 20 | return EnglishLanguage 21 | } 22 | -------------------------------------------------------------------------------- /lib/lang_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package lib 5 | 6 | import ( 7 | . "gopkg.in/check.v1" 8 | "os" 9 | ) 10 | 11 | func (s *OssutilCommandSuite) TestGetOsLang(c *C) { 12 | lang := os.Getenv("LANG") 13 | 14 | os.Setenv("LANG", "zh_CN.GB2312") 15 | c.Assert(getOsLang(), Equals, ChineseLanguage) 16 | os.Setenv("LANG", "zh_CN") 17 | c.Assert(getOsLang(), Equals, ChineseLanguage) 18 | os.Setenv("LANG", "en_US.UTF-8") 19 | c.Assert(getOsLang(), Equals, EnglishLanguage) 20 | os.Setenv("LANG", "es_ES.UTF-8") 21 | c.Assert(getOsLang(), Equals, EnglishLanguage) 22 | os.Setenv("LANG", "da_DK") 23 | c.Assert(getOsLang(), Equals, EnglishLanguage) 24 | os.Setenv("LANG", "zh_TW") 25 | c.Assert(getOsLang(), Equals, EnglishLanguage) 26 | 27 | os.Setenv("LANG", lang) 28 | } 29 | -------------------------------------------------------------------------------- /lib/lang_windows.go: -------------------------------------------------------------------------------- 1 | // This filename is for Condition Compling, which means it will be built only on windows platform. 2 | 3 | package lib 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func getOsLang() string { 10 | var mod = syscall.NewLazyDLL("kernel32.dll") 11 | var proc = mod.NewProc("GetUserDefaultUILanguage") 12 | ret, _, _ := proc.Call() 13 | 14 | /* Refer following link about LanggId values 15 | * https://msdn.microsoft.com/en-us/library/bb165625(v=vs.90).aspx 16 | */ 17 | if ret == 2052 { // 2052 is User Language ID, means Chinese (Simplified) 18 | return ChineseLanguage 19 | } 20 | return EnglishLanguage 21 | } 22 | -------------------------------------------------------------------------------- /lib/lang_windows_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package lib 5 | 6 | import ( 7 | "fmt" 8 | "syscall" 9 | 10 | . "gopkg.in/check.v1" 11 | ) 12 | 13 | func (s *OssutilCommandSuite) TestGetOsLang(c *C) { 14 | var mod = syscall.NewLazyDLL("kernel32.dll") 15 | 16 | var proc = mod.NewProc("GetUserDefaultUILanguage") 17 | err := proc.Find() 18 | if err != nil { 19 | fmt.Printf("%s", err.Error()) 20 | return 21 | } 22 | 23 | var sproc = mod.NewProc("SetUserDefaultUILanguage") 24 | err = sproc.Find() 25 | if err != nil { 26 | fmt.Printf("%s", err.Error()) 27 | return 28 | } 29 | 30 | oriLang, _, _ := proc.Call() 31 | 32 | sproc.Call(1033) 33 | c.Assert(getOsLang(), Equals, EnglishLanguage) 34 | 35 | sproc.Call(2052) 36 | c.Assert(getOsLang(), Equals, ChineseLanguage) 37 | 38 | sproc.Call(oriLang) 39 | } 40 | -------------------------------------------------------------------------------- /lib/lcb.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | 6 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 7 | ) 8 | 9 | var specChineseListCloudBox = SpecText{ 10 | 11 | synopsisText: "列举云盒信息", 12 | 13 | paramText: "[options]", 14 | 15 | syntaxText: ` 16 | ossutil lcb [-e endpoint] 17 | `, 18 | 19 | detailHelpText: ` 20 | 该命令列举云盒的详细信息 21 | `, 22 | 23 | sampleText: ` 24 | 1) ossutil lcb --sign-version v4 --region cn-hangzhou --cloudbox-id cb-abcdef 25 | `, 26 | } 27 | 28 | var specEnglishListCloudBox = SpecText{ 29 | 30 | synopsisText: "List cloud box information", 31 | 32 | paramText: "[options]", 33 | 34 | syntaxText: ` 35 | ossutil lcb [-e endpoint] 36 | `, 37 | 38 | detailHelpText: ` 39 | This command lists cloud box information 40 | `, 41 | 42 | sampleText: ` 43 | 1) ossutil lcb --sign-version v4 --region cn-hangzhou --cloudbox-id cb-abcdef 44 | `, 45 | } 46 | 47 | // LcbCommand is the command list region buckets or objects 48 | type LcbCommand struct { 49 | command Command 50 | } 51 | 52 | var lcbCommand = LcbCommand{ 53 | command: Command{ 54 | name: "lcb", 55 | nameAlias: []string{"lcb"}, 56 | minArgc: 0, 57 | maxArgc: 1, 58 | specChinese: specChineseListCloudBox, 59 | specEnglish: specEnglishListCloudBox, 60 | group: GroupTypeNormalCommand, 61 | validOptionNames: []string{ 62 | OptionConfigFile, 63 | OptionEndpoint, 64 | OptionAccessKeyID, 65 | OptionAccessKeySecret, 66 | OptionSTSToken, 67 | OptionProxyHost, 68 | OptionProxyUser, 69 | OptionProxyPwd, 70 | OptionRetryTimes, 71 | OptionLogLevel, 72 | OptionPassword, 73 | OptionMode, 74 | OptionECSRoleName, 75 | OptionTokenTimeout, 76 | OptionRamRoleArn, 77 | OptionRoleSessionName, 78 | OptionReadTimeout, 79 | OptionConnectTimeout, 80 | OptionSTSRegion, 81 | OptionSkipVerifyCert, 82 | OptionUserAgent, 83 | OptionRegion, 84 | OptionSignVersion, 85 | OptionLimitedNum, 86 | OptionMarker, 87 | OptionCloudBoxID, 88 | OptionForcePathStyle, 89 | }, 90 | }, 91 | } 92 | 93 | // function for FormatHelper interface 94 | func (lc *LcbCommand) formatHelpForWhole() string { 95 | return lc.command.formatHelpForWhole() 96 | } 97 | 98 | func (lc *LcbCommand) formatIndependHelp() string { 99 | return lc.command.formatIndependHelp() 100 | } 101 | 102 | // Init simulate inheritance, and polymorphism 103 | func (lc *LcbCommand) Init(args []string, options OptionMapType) error { 104 | return lc.command.Init(args, options, lc) 105 | } 106 | 107 | // RunCommand simulate inheritance, and polymorphism 108 | func (lc *LcbCommand) RunCommand() error { 109 | prefix := "" 110 | if len(lc.command.args) > 0 { 111 | cloudURL, err := CloudURLFromString(lc.command.args[0], "") 112 | if err != nil { 113 | return err 114 | } 115 | prefix = cloudURL.bucket 116 | } 117 | 118 | limitedNum, _ := GetInt(OptionLimitedNum, lc.command.options) 119 | vmarker, _ := GetString(OptionMarker, lc.command.options) 120 | if vmarker, err := lc.command.getRawMarker(vmarker); err != nil { 121 | return fmt.Errorf("invalid marker: %s, marker is not url encoded, %s", vmarker, err.Error()) 122 | } 123 | 124 | var num int64 125 | num = 0 126 | 127 | client, err := lc.command.ossClient("") 128 | if err != nil { 129 | return err 130 | } 131 | 132 | // list all cloudbox 133 | pre := oss.Prefix(prefix) 134 | marker := oss.Marker(vmarker) 135 | for limitedNum < 0 || num < limitedNum { 136 | lcr, err := lc.ossListCloudBoxesRetry(client, pre, marker) 137 | if err != nil { 138 | return err 139 | } 140 | pre = oss.Prefix(lcr.Prefix) 141 | marker = oss.Marker(lcr.NextMarker) 142 | for _, box := range lcr.CloudBoxes { 143 | if limitedNum >= 0 && num >= limitedNum { 144 | break 145 | } 146 | fmt.Printf("%-15s:%d\n", "No", num) 147 | fmt.Printf("%-15s:%s\n", "Id", box.ID) 148 | fmt.Printf("%-15s:%s\n", "Name", box.Name) 149 | fmt.Printf("%-15s:%s\n", "Owner", lcr.Owner) 150 | fmt.Printf("%-15s:%s\n", "Region", box.Region) 151 | fmt.Printf("%-15s:%s\n", "ControlEndpoint", box.ControlEndpoint) 152 | fmt.Printf("%-15s:%s\n", "DataEndpoint", box.DataEndpoint) 153 | fmt.Printf("----------------------------------------------------------------------\n") 154 | num++ 155 | } 156 | if !lcr.IsTruncated { 157 | break 158 | } 159 | } 160 | return nil 161 | } 162 | 163 | func (lc *LcbCommand) ossListCloudBoxesRetry(client *oss.Client, options ...oss.Option) (oss.ListCloudBoxResult, error) { 164 | retryTimes, _ := GetInt(OptionRetryTimes, lc.command.options) 165 | for i := 1; ; i++ { 166 | lbr, err := client.ListCloudBoxes(options...) 167 | if err == nil || int64(i) >= retryTimes { 168 | return lbr, err 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/listpart.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseListPart = SpecText{ 11 | synopsisText: "列出没有完成分块上传的object的分块信息", 12 | 13 | paramText: "oss_object uploadid [options]", 14 | 15 | syntaxText: ` 16 | ossutil listpart oss://bucket/object uploadid [options] 17 | `, 18 | 19 | detailHelpText: ` 20 | 可以通过ls命令查看bucket的object和uploadid信息,在用本命令查看详细信息 21 | 22 | 23 | 用法: 24 | 25 | 该命令只有一种用法: 26 | 27 | 1) ossutil listpart oss://bucket/object uploadid [options] 28 | 根据object和uploadid查询块信息 29 | `, 30 | 31 | sampleText: ` 32 | 1) 根据object和uploadid查询块信息 33 | ossutil listpart oss://bucket/object 8A1912289A705A5F0503FCA71DABFD5A 34 | `, 35 | } 36 | 37 | var specEnglishListPart = SpecText{ 38 | synopsisText: "List parts information of uncompleted multipart object", 39 | 40 | paramText: "oss_object uploadid [options]", 41 | 42 | syntaxText: ` 43 | ossutil listpart oss://bucket/object uploadid [options] 44 | `, 45 | 46 | detailHelpText: ` 47 | You can use the ls command to view the object and uploadid information of a bucket. 48 | Then Use this command to view detailed part information. 49 | 50 | Usages: 51 | 52 | There is only one usage for this command: 53 | 54 | 1) ossutil listpart oss://bucket/object uploadid [options] 55 | 56 | Query parts information according to object and uploadid 57 | `, 58 | 59 | sampleText: ` 60 | 1) Query parts information according to object and uploadid 61 | 62 | ossutil listpart oss://bucket/object 8A1912289A705A5F0503FCA71DABFD5A 63 | `, 64 | } 65 | 66 | type listPartOptionType struct { 67 | cloudUrl CloudURL 68 | uploadId string 69 | encodingType string 70 | } 71 | 72 | type ListPartCommand struct { 73 | command Command 74 | lpOption listPartOptionType 75 | } 76 | 77 | var listPartCommand = ListPartCommand{ 78 | command: Command{ 79 | name: "listpart", 80 | nameAlias: []string{"listpart"}, 81 | minArgc: 2, 82 | maxArgc: 2, 83 | specChinese: specChineseListPart, 84 | specEnglish: specEnglishListPart, 85 | group: GroupTypeNormalCommand, 86 | validOptionNames: []string{ 87 | OptionConfigFile, 88 | OptionEndpoint, 89 | OptionAccessKeyID, 90 | OptionAccessKeySecret, 91 | OptionSTSToken, 92 | OptionProxyHost, 93 | OptionProxyUser, 94 | OptionProxyPwd, 95 | OptionEncodingType, 96 | OptionLogLevel, 97 | OptionPassword, 98 | OptionMode, 99 | OptionECSRoleName, 100 | OptionTokenTimeout, 101 | OptionRamRoleArn, 102 | OptionRoleSessionName, 103 | OptionReadTimeout, 104 | OptionConnectTimeout, 105 | OptionSTSRegion, 106 | OptionSkipVerifyCert, 107 | OptionUserAgent, 108 | OptionSignVersion, 109 | OptionRegion, 110 | OptionCloudBoxID, 111 | OptionForcePathStyle, 112 | }, 113 | }, 114 | } 115 | 116 | // function for FormatHelper interface 117 | func (lpc *ListPartCommand) formatHelpForWhole() string { 118 | return lpc.command.formatHelpForWhole() 119 | } 120 | 121 | func (lpc *ListPartCommand) formatIndependHelp() string { 122 | return lpc.command.formatIndependHelp() 123 | } 124 | 125 | // Init simulate inheritance, and polymorphism 126 | func (lpc *ListPartCommand) Init(args []string, options OptionMapType) error { 127 | return lpc.command.Init(args, options, lpc) 128 | } 129 | 130 | // RunCommand simulate inheritance, and polymorphism 131 | func (lpc *ListPartCommand) RunCommand() error { 132 | lpc.lpOption.encodingType, _ = GetString(OptionEncodingType, lpc.command.options) 133 | srcBucketUrL, err := GetCloudUrl(lpc.command.args[0], lpc.lpOption.encodingType) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | if srcBucketUrL.object == "" { 139 | return fmt.Errorf("object name is empty") 140 | } 141 | 142 | lpc.lpOption.cloudUrl = *srcBucketUrL 143 | lpc.lpOption.uploadId = lpc.command.args[1] 144 | 145 | return lpc.ListPart() 146 | } 147 | 148 | func (lpc *ListPartCommand) ListPart() error { 149 | client, err := lpc.command.ossClient(lpc.lpOption.cloudUrl.bucket) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | bucket, err := client.Bucket(lpc.lpOption.cloudUrl.bucket) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | var imur oss.InitiateMultipartUploadResult 160 | imur.Bucket = lpc.lpOption.cloudUrl.bucket 161 | imur.Key = lpc.lpOption.cloudUrl.object 162 | imur.UploadID = lpc.lpOption.uploadId 163 | 164 | partNumberMarker := 0 165 | totalPartCount := 0 166 | var totalPartSize int64 = 0 167 | for i := 0; ; i++ { 168 | lpOptions := []oss.Option{} 169 | lpOptions = append(lpOptions, oss.MaxParts(1000)) 170 | lpOptions = append(lpOptions, oss.PartNumberMarker(partNumberMarker)) 171 | 172 | lpRes, err := bucket.ListUploadedParts(imur, lpOptions...) 173 | if err != nil { 174 | return err 175 | } else { 176 | totalPartCount += len(lpRes.UploadedParts) 177 | if i == 0 && len(lpRes.UploadedParts) > 0 { 178 | fmt.Printf("%-10s\t%-32s\t%-10s\t%s\n", "PartNumber", "Etag", "Size(Byte)", "LastModifyTime") 179 | } 180 | } 181 | 182 | for _, v := range lpRes.UploadedParts { 183 | //PartNumber,ETag,Size,LastModified 184 | fmt.Printf("%-10d\t%-32s\t%-10d\t%s\n", v.PartNumber, v.ETag, v.Size, v.LastModified.Format("2006-01-02 15:04:05")) 185 | totalPartSize += int64(v.Size) 186 | } 187 | 188 | if lpRes.IsTruncated { 189 | partNumberMarker, err = strconv.Atoi(lpRes.NextPartNumberMarker) 190 | if err != nil { 191 | return err 192 | } 193 | } else { 194 | if totalPartCount > 0 { 195 | fmt.Printf("\ntotal part count:%d\ttotal part size(MB):%.2f\n\n", totalPartCount, float64(totalPartSize/1024)/1024) 196 | } 197 | break 198 | } 199 | } 200 | return nil 201 | } 202 | -------------------------------------------------------------------------------- /lib/listpart_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | func (s *OssutilCommandSuite) TestListPartSuccess(c *C) { 13 | // create bucket 14 | bucketName := bucketNamePrefix + randLowStr(12) 15 | s.putBucket(bucketName, c) 16 | 17 | // object name 18 | objectName := "test-ossutil-object-" + randLowStr(10) 19 | 20 | // create file 21 | fileName := "test-ossutil-appendfile" + randLowStr(5) 22 | strText := randLowStr(1024 * 10) 23 | s.createFile(fileName, strText, c) 24 | 25 | // prepare chunks 26 | chunks, err := oss.SplitFileByPartNum(fileName, 10) 27 | c.Assert(err, IsNil) 28 | 29 | fd, err := os.Open(fileName) 30 | c.Assert(err, IsNil) 31 | defer fd.Close() 32 | 33 | // begin upload part 34 | client, err := oss.New(endpoint, accessKeyID, accessKeySecret) 35 | c.Assert(err, IsNil) 36 | 37 | bucket, err := client.Bucket(bucketName) 38 | c.Assert(err, IsNil) 39 | 40 | imur, err := bucket.InitiateMultipartUpload(objectName) 41 | c.Assert(err, IsNil) 42 | 43 | var parts []oss.UploadPart 44 | for _, chunk := range chunks { 45 | fd.Seek(chunk.Offset, os.SEEK_SET) 46 | part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number) 47 | c.Assert(err, IsNil) 48 | parts = append(parts, part) 49 | } 50 | 51 | // not CompleteMultipartUpload 52 | 53 | // begin listpart 54 | var str string 55 | options := OptionMapType{ 56 | "endpoint": &str, 57 | "accessKeyID": &str, 58 | "accessKeySecret": &str, 59 | "stsToken": &str, 60 | "configFile": &configFile, 61 | } 62 | 63 | // output to file 64 | outputFile := "test-file-" + randLowStr(5) 65 | testResultFile, err = os.OpenFile(outputFile, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0664) 66 | c.Assert(err, IsNil) 67 | 68 | oldStdout := os.Stdout 69 | os.Stdout = testResultFile 70 | 71 | listArgs := []string{CloudURLToString(bucketName, objectName), imur.UploadID} 72 | _, err = cm.RunCommand("listpart", listArgs, options) 73 | if err != nil { 74 | fmt.Printf("error:%s\n", err.Error()) 75 | } 76 | c.Assert(err, IsNil) 77 | testResultFile.Close() 78 | 79 | os.Stdout = oldStdout 80 | 81 | // check file content 82 | outBody := s.readFile(outputFile, c) 83 | c.Assert(strings.Contains(outBody, "total part count:10"), Equals, true) 84 | 85 | os.Remove(outputFile) 86 | os.Remove(fileName) 87 | s.removeBucket(bucketName, true, c) 88 | } 89 | 90 | func (s *OssutilCommandSuite) TestListPartError(c *C) { 91 | // create bucket 92 | bucketName := bucketNamePrefix + randLowStr(12) 93 | s.putBucket(bucketName, c) 94 | 95 | // create file 96 | fileName := "test-ossutil-appendfile" + randLowStr(5) 97 | strText := randLowStr(1024 * 10) 98 | s.createFile(fileName, strText, c) 99 | 100 | // object name 101 | objectName := "test-ossutil-object-" + randLowStr(10) 102 | s.PutObject(bucketName, objectName, strText, c) 103 | 104 | // begin listpart 105 | var str string 106 | options := OptionMapType{ 107 | "endpoint": &str, 108 | "accessKeyID": &str, 109 | "accessKeySecret": &str, 110 | "stsToken": &str, 111 | "configFile": &configFile, 112 | } 113 | 114 | // cloud url error 115 | listArgs := []string{"oss:///1.jpg", "aaaaaaaaaa"} 116 | _, err := cm.RunCommand("listpart", listArgs, options) 117 | c.Assert(err, NotNil) 118 | 119 | // object is empty 120 | listArgs = []string{CloudURLToString(bucketName, ""), "aaaaaaaaaa"} 121 | _, err = cm.RunCommand("listpart", listArgs, options) 122 | c.Assert(err, NotNil) 123 | 124 | // uploadid is not exist 125 | listArgs = []string{CloudURLToString(bucketName, objectName), "aaaaaaaaaa"} 126 | _, err = cm.RunCommand("listpart", listArgs, options) 127 | c.Assert(err, NotNil) 128 | 129 | os.Remove(fileName) 130 | s.removeBucket(bucketName, true, c) 131 | } 132 | 133 | func (s *OssutilCommandSuite) TestListPartClientError(c *C) { 134 | // create bucket 135 | bucketName := bucketNamePrefix + randLowStr(12) 136 | s.putBucket(bucketName, c) 137 | 138 | // create file 139 | fileName := "test-ossutil-listpart" + randLowStr(5) 140 | strText := randLowStr(1024) 141 | s.createFile(fileName, strText, c) 142 | 143 | // object name 144 | objectName := "test-ossutil-object-" + randLowStr(10) 145 | 146 | cfile := randStr(10) 147 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 148 | s.createFile(cfile, data, c) 149 | 150 | // begin listpart 151 | var str string 152 | options := OptionMapType{ 153 | "endpoint": &str, 154 | "accessKeyID": &str, 155 | "accessKeySecret": &str, 156 | "stsToken": &str, 157 | "configFile": &cfile, 158 | } 159 | 160 | listArgs := []string{CloudURLToString(bucketName, objectName), "aaaaaaaaaa"} 161 | _, err := cm.RunCommand("listpart", listArgs, options) 162 | c.Assert(err, NotNil) 163 | 164 | os.Remove(cfile) 165 | os.Remove(fileName) 166 | s.removeBucket(bucketName, true, c) 167 | } 168 | 169 | func (s *OssutilCommandSuite) TestListPartHelp(c *C) { 170 | // mkdir command test 171 | options := OptionMapType{} 172 | 173 | mkArgs := []string{"listpart"} 174 | _, err := cm.RunCommand("help", mkArgs, options) 175 | c.Assert(err, IsNil) 176 | 177 | mkArgs = []string{} 178 | _, err = cm.RunCommand("help", mkArgs, options) 179 | c.Assert(err, IsNil) 180 | } 181 | -------------------------------------------------------------------------------- /lib/lrb.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 11 | ) 12 | 13 | var specChineseListRegionBucket = SpecText{ 14 | 15 | synopsisText: "列举某个region下的Buckets", 16 | 17 | paramText: "[conf_file] [options]", 18 | 19 | syntaxText: ` 20 | ossutil lrb [conf_file] [-e endpoint] 21 | `, 22 | 23 | detailHelpText: ` 24 | 该命令列举单个region或者多个region下的bucket列表, 多个region的endpoint信息在配置文件里面配置 25 | `, 26 | 27 | sampleText: ` 28 | 1) ossutil lrb conf_file 29 | 30 | 2) ossutil lrb -e oss-cn-shenzhen.aliyuncs.com 31 | 32 | 3) ossutil lrb 33 | `, 34 | } 35 | 36 | var specEnglishListRegionBucket = SpecText{ 37 | 38 | synopsisText: "List region buckets", 39 | 40 | paramText: "[conf_file] [options]", 41 | 42 | syntaxText: ` 43 | ossutil lrb [conf_file] [-e endpoint] 44 | `, 45 | 46 | detailHelpText: ` 47 | This command lists buckets under a single region or multiple regions 48 | The multiple regions's endpoints are configured in the configuration file 49 | `, 50 | 51 | sampleText: ` 52 | 1) ossutil lrb conf_file 53 | 54 | 2) ossutil lrb -e oss-cn-shenzhen.aliyuncs.com 55 | 56 | 3) ossutil lrb 57 | `, 58 | } 59 | 60 | // LrbCommand is the command list region buckets or objects 61 | type LrbCommand struct { 62 | command Command 63 | listResult []oss.ListBucketsResult 64 | errorEndpoints []string 65 | err error 66 | } 67 | 68 | var lrbCommand = LrbCommand{ 69 | command: Command{ 70 | name: "lrb", 71 | nameAlias: []string{"lrb"}, 72 | minArgc: 0, 73 | maxArgc: 1, 74 | specChinese: specChineseListRegionBucket, 75 | specEnglish: specEnglishListRegionBucket, 76 | group: GroupTypeNormalCommand, 77 | validOptionNames: []string{ 78 | OptionConfigFile, 79 | OptionEndpoint, 80 | OptionAccessKeyID, 81 | OptionAccessKeySecret, 82 | OptionSTSToken, 83 | OptionProxyHost, 84 | OptionProxyUser, 85 | OptionProxyPwd, 86 | OptionLogLevel, 87 | OptionPassword, 88 | OptionMode, 89 | OptionECSRoleName, 90 | OptionTokenTimeout, 91 | OptionRamRoleArn, 92 | OptionRoleSessionName, 93 | OptionReadTimeout, 94 | OptionConnectTimeout, 95 | OptionSTSRegion, 96 | OptionSkipVerifyCert, 97 | OptionUserAgent, 98 | OptionSignVersion, 99 | OptionRegion, 100 | OptionCloudBoxID, 101 | OptionForcePathStyle, 102 | }, 103 | }, 104 | } 105 | 106 | // function for FormatHelper interface 107 | func (lc *LrbCommand) formatHelpForWhole() string { 108 | return lc.command.formatHelpForWhole() 109 | } 110 | 111 | func (lc *LrbCommand) formatIndependHelp() string { 112 | return lc.command.formatIndependHelp() 113 | } 114 | 115 | // Init simulate inheritance, and polymorphism 116 | func (lc *LrbCommand) Init(args []string, options OptionMapType) error { 117 | return lc.command.Init(args, options, lc) 118 | } 119 | 120 | // RunCommand simulate inheritance, and polymorphism 121 | func (lc *LrbCommand) RunCommand() error { 122 | var err error 123 | if len(lc.command.args) == 0 { 124 | lc.err = lc.listBuckets("") 125 | return lc.display() 126 | } 127 | // read all endpoints from conf file 128 | fileName := lc.command.args[0] 129 | rf, err := os.OpenFile(fileName, os.O_RDONLY, 0600) 130 | if err != nil { 131 | return err 132 | } 133 | defer rf.Close() 134 | 135 | rd := bufio.NewReader(rf) 136 | for { 137 | endpoint, err := rd.ReadString('\n') 138 | if endpoint == "" && io.EOF == err { 139 | break 140 | } 141 | endpoint = strings.TrimSpace(endpoint) 142 | endpoint = strings.Trim(endpoint, "\r") 143 | if strings.HasPrefix(endpoint, "#") { 144 | continue 145 | } 146 | err = lc.listBuckets(endpoint) 147 | if err != nil { 148 | lc.errorEndpoints = append(lc.errorEndpoints, endpoint) 149 | lc.err = err 150 | } 151 | } 152 | return lc.display() 153 | } 154 | 155 | func (lc *LrbCommand) listBuckets(endpoint string) error { 156 | if endpoint != "" { 157 | lc.command.options[OptionEndpoint] = &endpoint 158 | } 159 | 160 | var err error 161 | client, err := lc.command.ossClient("") 162 | if err != nil { 163 | return err 164 | } 165 | 166 | // list region bucket 167 | pre := oss.Prefix("") 168 | marker := oss.Marker("") 169 | 170 | for { 171 | lbr, err := client.ListBuckets(pre, marker, oss.AddParam("regionList", "")) 172 | if err != nil { 173 | return err 174 | } 175 | lc.listResult = append(lc.listResult, lbr) 176 | pre = oss.Prefix(lbr.Prefix) 177 | marker = oss.Marker(lbr.NextMarker) 178 | if !lbr.IsTruncated { 179 | break 180 | } 181 | } 182 | return nil 183 | } 184 | 185 | func (lc *LrbCommand) display() error { 186 | count := 0 187 | for _, result := range lc.listResult { 188 | for _, bucket := range result.Buckets { 189 | if count == 0 { 190 | fmt.Printf("%-30s %20s%s%12s%s%s\n", "CreationTime", "Region", FormatTAB, "StorageClass", FormatTAB, "BucketName") 191 | } 192 | fmt.Printf("%-30s %20s%s%12s%s%s\n", utcToLocalTime(bucket.CreationDate), bucket.Location, FormatTAB, bucket.StorageClass, FormatTAB, CloudURLToString(bucket.Name, "")) 193 | count = count + 1 194 | } 195 | } 196 | 197 | fmt.Printf("\nBucket Number is: %d\n\n", count) 198 | if len(lc.errorEndpoints) > 0 { 199 | fmt.Printf("list bucket failure from these endpoint:\n") 200 | for _, endpoint := range lc.errorEndpoints { 201 | fmt.Printf("%s\n", endpoint) 202 | } 203 | fmt.Printf("\n") 204 | } 205 | return lc.err 206 | } 207 | -------------------------------------------------------------------------------- /lib/mkdir.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var specChineseMkdir = SpecText{ 9 | synopsisText: "创建一个目录,在oss中目录名字有后缀字符'/'", 10 | 11 | paramText: "dir_name [options]", 12 | 13 | syntaxText: ` 14 | ossutil mkdir oss://bucket/dir_name 15 | `, 16 | 17 | detailHelpText: ` 18 | 1) 如果输入的参数没有以后缀字符'/'结尾,工具会自动添加 19 | 2) 如果目录已经存在,会提示报错 20 | 3) 如果输入的参数包含多级目录,只会创建最后的那一个目录 21 | `, 22 | 23 | sampleText: ` 24 | 1) 创建一个目录 25 | ossutil mkdir oss://bucket/dir 26 | 27 | 2) 创建一个多级目录 28 | ossutil mkdir oss://bucket/dir1/dir2 29 | `, 30 | } 31 | 32 | var specEnglishMkdir = SpecText{ 33 | synopsisText: "Create a oss directory whose object name has the suffix character '/'", 34 | 35 | paramText: "dir_name [options]", 36 | 37 | syntaxText: ` 38 | ossutil mkdir oss://bucket/dir_name 39 | `, 40 | detailHelpText: ` 41 | 1) If the input parameter does not end with the suffix character '/', the tool will automatically add 42 | 2) If the directory already exists, you will be prompted with an error. 43 | 3) If the input parameter contains multiple levels of directories, only the last directory will be created. 44 | `, 45 | sampleText: ` 46 | 1) create a diretory 47 | ossutil mkdir oss://bucket/dir 48 | 49 | 2) create a multi-level directory 50 | ossutil mkdir oss://bucket/dir1/dir2 51 | `, 52 | } 53 | 54 | type mkOptionType struct { 55 | encodingType string 56 | } 57 | 58 | type MkdirCommand struct { 59 | command Command 60 | mkOption mkOptionType 61 | } 62 | 63 | var mkdirCommand = MkdirCommand{ 64 | command: Command{ 65 | name: "mkdir", 66 | nameAlias: []string{"mkdir"}, 67 | minArgc: 1, 68 | maxArgc: 1, 69 | specChinese: specChineseMkdir, 70 | specEnglish: specEnglishMkdir, 71 | group: GroupTypeNormalCommand, 72 | validOptionNames: []string{ 73 | OptionConfigFile, 74 | OptionEndpoint, 75 | OptionAccessKeyID, 76 | OptionAccessKeySecret, 77 | OptionSTSToken, 78 | OptionProxyHost, 79 | OptionProxyUser, 80 | OptionProxyPwd, 81 | OptionLogLevel, 82 | OptionEncodingType, 83 | OptionPassword, 84 | OptionMode, 85 | OptionECSRoleName, 86 | OptionTokenTimeout, 87 | OptionRamRoleArn, 88 | OptionRoleSessionName, 89 | OptionReadTimeout, 90 | OptionConnectTimeout, 91 | OptionSTSRegion, 92 | OptionSkipVerifyCert, 93 | OptionUserAgent, 94 | OptionSignVersion, 95 | OptionRegion, 96 | OptionCloudBoxID, 97 | OptionForcePathStyle, 98 | }, 99 | }, 100 | } 101 | 102 | // function for FormatHelper interface 103 | func (mkc *MkdirCommand) formatHelpForWhole() string { 104 | return mkc.command.formatHelpForWhole() 105 | } 106 | 107 | func (mkc *MkdirCommand) formatIndependHelp() string { 108 | return mkc.command.formatIndependHelp() 109 | } 110 | 111 | // Init simulate inheritance, and polymorphism 112 | func (mkc *MkdirCommand) Init(args []string, options OptionMapType) error { 113 | return mkc.command.Init(args, options, mkc) 114 | } 115 | 116 | // RunCommand simulate inheritance, and polymorphism 117 | func (mkc *MkdirCommand) RunCommand() error { 118 | mkc.mkOption.encodingType, _ = GetString(OptionEncodingType, mkc.command.options) 119 | 120 | dirUrL, err := StorageURLFromString(mkc.command.args[0], mkc.mkOption.encodingType) 121 | if err != nil { 122 | return fmt.Errorf("StorageURLFromString error") 123 | } 124 | 125 | if !dirUrL.IsCloudURL() { 126 | return fmt.Errorf("parameter is not a cloud url,url is %s", dirUrL.ToString()) 127 | } 128 | 129 | cloudUrl := dirUrL.(CloudURL) 130 | 131 | if cloudUrl.bucket == "" { 132 | return fmt.Errorf("bucket name is empty,url is %s", cloudUrl.ToString()) 133 | } 134 | 135 | if cloudUrl.object == "" { 136 | return fmt.Errorf("object name is empty,url is %s", cloudUrl.ToString()) 137 | } 138 | 139 | if !strings.HasSuffix(cloudUrl.object, "/") { 140 | cloudUrl.object += "/" 141 | } 142 | 143 | return mkc.MkBucketDir(cloudUrl) 144 | } 145 | 146 | func (mkc *MkdirCommand) MkBucketDir(dirUrl CloudURL) error { 147 | bucket, err := mkc.command.ossBucket(dirUrl.bucket) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | bExist, err := bucket.IsObjectExist(dirUrl.object) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | if bExist { 158 | return fmt.Errorf("%s already exists", dirUrl.object) 159 | } 160 | 161 | return bucket.PutObject(dirUrl.object, strings.NewReader("")) 162 | } 163 | -------------------------------------------------------------------------------- /lib/mkdir_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | 7 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | . "gopkg.in/check.v1" 9 | ) 10 | 11 | func (s *OssutilCommandSuite) TestMkdirAll(c *C) { 12 | // create bucket 13 | client, err := oss.New(endpoint, accessKeyID, accessKeySecret) 14 | c.Assert(err, IsNil) 15 | 16 | bucketName := bucketNamePrefix + randLowStr(12) 17 | err = client.CreateBucket(bucketName) 18 | c.Assert(err, IsNil) 19 | 20 | bucket, err := client.Bucket(bucketName) 21 | c.Assert(err, IsNil) 22 | 23 | // mkdir command test 24 | var str string 25 | options := OptionMapType{ 26 | "endpoint": &str, 27 | "accessKeyID": &str, 28 | "accessKeySecret": &str, 29 | "stsToken": &str, 30 | "configFile": &configFile, 31 | } 32 | 33 | // error,not a cloud url 34 | mkArgs := []string{"http://www.nn.com/test.jpg"} 35 | _, err = cm.RunCommand("mkdir", mkArgs, options) 36 | c.Assert(err, NotNil) 37 | 38 | // error,bucket is empty 39 | mkArgs = []string{CloudURLToString("", "")} 40 | _, err = cm.RunCommand("mkdir", mkArgs, options) 41 | c.Assert(err, NotNil) 42 | 43 | // error,object is empty 44 | mkArgs = []string{CloudURLToString(bucketName, "")} 45 | _, err = cm.RunCommand("mkdir", mkArgs, options) 46 | c.Assert(err, NotNil) 47 | 48 | // success 49 | dirNameA := randLowStr(12) 50 | mkArgs = []string{CloudURLToString(bucketName, dirNameA)} 51 | _, err = cm.RunCommand("mkdir", mkArgs, options) 52 | c.Assert(err, IsNil) 53 | 54 | //mkdir again ,error 55 | mkArgs = []string{CloudURLToString(bucketName, dirNameA)} 56 | _, err = cm.RunCommand("mkdir", mkArgs, options) 57 | c.Assert(err, NotNil) 58 | 59 | // dirname/ 60 | dirNameB := randLowStr(12) + "/" 61 | mkArgs = []string{CloudURLToString(bucketName, dirNameB)} 62 | _, err = cm.RunCommand("mkdir", mkArgs, options) 63 | c.Assert(err, IsNil) 64 | 65 | // dirname/dirname 66 | dirNameC := randLowStr(12) + "/" + randLowStr(12) 67 | mkArgs = []string{CloudURLToString(bucketName, dirNameC)} 68 | _, err = cm.RunCommand("mkdir", mkArgs, options) 69 | c.Assert(err, IsNil) 70 | 71 | //check the exist dirname 72 | dirList := []string{dirNameA, dirNameB, dirNameC} 73 | for _, v := range dirList { 74 | if !strings.HasSuffix(v, "/") { 75 | v += "/" 76 | } 77 | body, err := bucket.GetObject(v) 78 | c.Assert(err, IsNil) 79 | 80 | data, err := ioutil.ReadAll(body) 81 | body.Close() 82 | c.Assert(err, IsNil) 83 | c.Assert(string(data), Equals, "") 84 | } 85 | s.removeBucket(bucketName, true, c) 86 | } 87 | 88 | func (s *OssutilCommandSuite) TestMkdirAllEncodingError(c *C) { 89 | // mkdir command test 90 | var str string 91 | strEncode := "url" 92 | options := OptionMapType{ 93 | "endpoint": &str, 94 | "accessKeyID": &str, 95 | "accessKeySecret": &str, 96 | "stsToken": &str, 97 | "configFile": &configFile, 98 | "encodingType": &strEncode, 99 | } 100 | 101 | // url encoding error 102 | mkArgs := []string{"http%3a%3a%2%2faaada%5ct"} 103 | _, err := cm.RunCommand("mkdir", mkArgs, options) 104 | c.Assert(err, NotNil) 105 | } 106 | 107 | func (s *OssutilCommandSuite) TestMkdirHelpInfo(c *C) { 108 | // mkdir command test 109 | options := OptionMapType{} 110 | 111 | mkArgs := []string{"mkdir"} 112 | _, err := cm.RunCommand("help", mkArgs, options) 113 | c.Assert(err, IsNil) 114 | 115 | mkArgs = []string{} 116 | _, err = cm.RunCommand("help", mkArgs, options) 117 | c.Assert(err, IsNil) 118 | 119 | } 120 | -------------------------------------------------------------------------------- /lib/ossutil_log.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | 10 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 11 | ) 12 | 13 | var logName = "ossutil.log" 14 | var logLevel = oss.LogOff 15 | var utilLogger *log.Logger 16 | var logFile *os.File 17 | 18 | func openLogFile() (*os.File, error) { 19 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 20 | if err != nil { 21 | dir = "." 22 | } 23 | absLogName := dir + string(os.PathSeparator) + logName 24 | f, err := os.OpenFile(absLogName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660) 25 | if err != nil { 26 | fmt.Printf("open %s error,info:%s.\n", absLogName, err.Error()) 27 | } else { 28 | fmt.Printf("log file is %s\n", absLogName) 29 | } 30 | return f, err 31 | } 32 | 33 | func InitLogger(level int, name string) { 34 | logLevel = level 35 | logName = name 36 | f, err := openLogFile() 37 | if err != nil { 38 | return 39 | } 40 | utilLogger = log.New(f, "", log.LstdFlags|log.Lmicroseconds) 41 | logFile = f 42 | } 43 | 44 | func UnInitLogger() { 45 | if logFile == nil { 46 | return 47 | } 48 | 49 | logFile.Close() 50 | logFile = nil 51 | utilLogger = nil 52 | } 53 | 54 | func writeLog(level int, format string, a ...interface{}) { 55 | if utilLogger == nil { 56 | return 57 | } 58 | 59 | var logBuffer bytes.Buffer 60 | logBuffer.WriteString(oss.LogTag[level-1]) 61 | logBuffer.WriteString(fmt.Sprintf(format, a...)) 62 | utilLogger.Printf("%s", logBuffer.String()) 63 | return 64 | } 65 | 66 | func LogError(format string, a ...interface{}) { 67 | if logLevel < oss.Error { 68 | return 69 | } 70 | writeLog(oss.Error, format, a...) 71 | } 72 | 73 | func LogWarn(format string, a ...interface{}) { 74 | if logLevel < oss.Warn { 75 | return 76 | } 77 | writeLog(oss.Warn, format, a...) 78 | } 79 | 80 | func LogInfo(format string, a ...interface{}) { 81 | 82 | if logLevel < oss.Info { 83 | return 84 | } 85 | writeLog(oss.Info, format, a...) 86 | } 87 | 88 | func LogDebug(format string, a ...interface{}) { 89 | if logLevel < oss.Debug { 90 | return 91 | } 92 | writeLog(oss.Debug, format, a...) 93 | } 94 | -------------------------------------------------------------------------------- /lib/ossutil_log_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | type OssUtilLogSuite struct { 15 | testLogName string 16 | testLogLevel int 17 | } 18 | 19 | var _ = Suite(&OssUtilLogSuite{}) 20 | 21 | // Run once when the suite starts running 22 | func (s *OssUtilLogSuite) SetUpSuite(c *C) { 23 | fmt.Printf("set up suite OssUtilLogSuite\n") 24 | } 25 | 26 | // Run before each test or benchmark starts running 27 | func (s *OssUtilLogSuite) TearDownSuite(c *C) { 28 | fmt.Printf("tear down OssUtilLogSuite\n") 29 | } 30 | 31 | // Run after each test or benchmark runs 32 | func (s *OssUtilLogSuite) SetUpTest(c *C) { 33 | fmt.Printf("set up test:%s\n", c.TestName()) 34 | s.testLogName = logName 35 | s.testLogLevel = logLevel 36 | 37 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 38 | if err != nil { 39 | dir = "" 40 | } 41 | absLogName := dir + string(os.PathSeparator) + logName 42 | os.Remove(absLogName) 43 | } 44 | 45 | // Run once after all tests or benchmarks have finished running 46 | func (s *OssUtilLogSuite) TearDownTest(c *C) { 47 | fmt.Printf("tear down test:%s\n", c.TestName()) 48 | logName = s.testLogName 49 | logLevel = s.testLogLevel 50 | 51 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 52 | if err != nil { 53 | dir = "" 54 | } 55 | absLogName := dir + string(os.PathSeparator) + logName 56 | os.Remove(absLogName) 57 | } 58 | 59 | // test "config" 60 | func (s *OssUtilLogSuite) TestLogLevel(c *C) { 61 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 62 | if err != nil { 63 | dir = "" 64 | } 65 | absLogName := dir + string(os.PathSeparator) + logName 66 | 67 | fmt.Printf("absLogName:%s.\n", absLogName) 68 | 69 | // nologLevel 70 | logLevel = oss.LogOff 71 | InitLogger(logLevel, logName) 72 | 73 | errorContext := "i am error log.\n" 74 | LogError(errorContext) 75 | LogWarn(errorContext) 76 | LogInfo(errorContext) 77 | LogDebug(errorContext) 78 | 79 | contents, err := ioutil.ReadFile(absLogName) 80 | LogContent := string(contents) 81 | c.Assert(strings.Contains(LogContent, "[error]"+errorContext), Equals, false) 82 | c.Assert(strings.Contains(LogContent, "[warn]"+errorContext), Equals, false) 83 | c.Assert(strings.Contains(LogContent, "[info]"+errorContext), Equals, false) 84 | c.Assert(strings.Contains(LogContent, "[debug]"+errorContext), Equals, false) 85 | UnInitLogger() 86 | os.Remove(absLogName) 87 | 88 | // errorLevel 89 | logLevel = oss.Error 90 | InitLogger(logLevel, logName) 91 | LogError(errorContext) 92 | LogWarn(errorContext) 93 | LogInfo(errorContext) 94 | LogDebug(errorContext) 95 | 96 | contents, err = ioutil.ReadFile(absLogName) 97 | LogContent = string(contents) 98 | c.Assert(strings.Contains(LogContent, "[error]"+errorContext), Equals, true) 99 | c.Assert(strings.Contains(LogContent, "[warn]"+errorContext), Equals, false) 100 | c.Assert(strings.Contains(LogContent, "[info]"+errorContext), Equals, false) 101 | c.Assert(strings.Contains(LogContent, "[debug]"+errorContext), Equals, false) 102 | UnInitLogger() 103 | os.Remove(absLogName) 104 | 105 | // normalLevel 106 | logLevel = oss.Warn 107 | InitLogger(logLevel, logName) 108 | normalContext := "i am normal log.\n" 109 | LogError(normalContext) 110 | LogWarn(normalContext) 111 | LogInfo(normalContext) 112 | LogDebug(normalContext) 113 | 114 | contents, err = ioutil.ReadFile(absLogName) 115 | LogContent = string(contents) 116 | c.Assert(strings.Contains(LogContent, "[error]"+normalContext), Equals, true) 117 | c.Assert(strings.Contains(LogContent, "[warn]"+normalContext), Equals, true) 118 | c.Assert(strings.Contains(LogContent, "[info]"+normalContext), Equals, false) 119 | c.Assert(strings.Contains(LogContent, "[debug]"+normalContext), Equals, false) 120 | UnInitLogger() 121 | os.Remove(absLogName) 122 | 123 | // infolevel 124 | logLevel = oss.Info 125 | InitLogger(logLevel, logName) 126 | infoContext := "i am info log.\n" 127 | LogError(infoContext) 128 | LogWarn(infoContext) 129 | LogInfo(infoContext) 130 | LogDebug(infoContext) 131 | 132 | contents, err = ioutil.ReadFile(absLogName) 133 | LogContent = string(contents) 134 | c.Assert(strings.Contains(LogContent, "[error]"+infoContext), Equals, true) 135 | c.Assert(strings.Contains(LogContent, "[warn]"+infoContext), Equals, true) 136 | c.Assert(strings.Contains(LogContent, "[info]"+infoContext), Equals, true) 137 | c.Assert(strings.Contains(LogContent, "[debug]"+infoContext), Equals, false) 138 | UnInitLogger() 139 | os.Remove(absLogName) 140 | 141 | // debuglevel 142 | logLevel = oss.Debug 143 | InitLogger(logLevel, logName) 144 | debugContext := "i am debug log.\n" 145 | LogError(debugContext) 146 | LogWarn(debugContext) 147 | LogInfo(debugContext) 148 | LogDebug(debugContext) 149 | 150 | contents, err = ioutil.ReadFile(absLogName) 151 | LogContent = string(contents) 152 | c.Assert(strings.Contains(LogContent, "[error]"+debugContext), Equals, true) 153 | c.Assert(strings.Contains(LogContent, "[warn]"+debugContext), Equals, true) 154 | c.Assert(strings.Contains(LogContent, "[info]"+debugContext), Equals, true) 155 | c.Assert(strings.Contains(LogContent, "[debug]"+debugContext), Equals, true) 156 | UnInitLogger() 157 | os.Remove(absLogName) 158 | } 159 | 160 | func (s *OssUtilLogSuite) TestOpenFileError(c *C) { 161 | oldLogName := logName 162 | logName = "" 163 | _, err := openLogFile() 164 | c.Assert(err, NotNil) 165 | logName = oldLogName 166 | } 167 | 168 | func (s *OssUtilLogSuite) TestInitLoggerError(c *C) { 169 | oldLogName := logName 170 | oldLogFile := logFile 171 | InitLogger(oss.Info, "") 172 | c.Assert(logFile, IsNil) 173 | 174 | logName = oldLogName 175 | logFile = oldLogFile 176 | } 177 | -------------------------------------------------------------------------------- /lib/read_symlink.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sort" 7 | "strings" 8 | "time" 9 | 10 | oss "github.com/aliyun/aliyun-oss-go-sdk/oss" 11 | ) 12 | 13 | var specChineseReadSymlink = SpecText{ 14 | 15 | synopsisText: "读取符号链接文件的描述信息", 16 | 17 | paramText: "cloud_url [options]", 18 | 19 | syntaxText: ` 20 | ossutil read-symlink oss://bucket/object [--encoding-type url] [--version-id versionId] [--payer requester] [-c file] 21 | `, 22 | 23 | detailHelpText: ` 24 | 该命令获取指定符号链接object的描述信息,此操作要求用户对该符号链接有读权限。 25 | 如果需要读取符合链接object的文件内容,请使用cp命令下载该object。 26 | 27 | 返回的项中X-Oss-Symlink-Target表示符号链接的目标文件。 28 | 29 | 如果object并非符号链接文件,该操作返回错误:NotSymlink。 30 | 31 | 更多信息见官网API文档:https://help.aliyun.com/document_detail/45146.html?spm=5176.doc31968.6.871.24y1VX 32 | 33 | 用法: 34 | 35 | ossutil read-symlink oss://bucket/symlink-object [--version-id versionId] [--payer requester] 36 | `, 37 | 38 | sampleText: ` 39 | ossutil read-symlink oss://bucket1/object1 40 | Etag : 455E20DBFFF1D588B67D092C46B16DB6 41 | Last-Modified : 2017-04-17 14:49:42 +0800 CST 42 | X-Oss-Symlink-Target : a 43 | 44 | ossutil read-symlink oss://bucket1/object --version-id versionId 45 | 46 | ossutil read-symlink oss://bucket1/object --payer requester 47 | `, 48 | } 49 | 50 | var specEnglishReadSymlink = SpecText{ 51 | 52 | synopsisText: "Display meta information of symlink object", 53 | 54 | paramText: "cloud_url [options]", 55 | 56 | syntaxText: ` 57 | ossutil read-symlink oss://bucket/object [--encoding-type url] [--payer requester] [-c file] 58 | `, 59 | 60 | detailHelpText: ` 61 | The command display the meta information of symlink object. The operation 62 | requires that the user have read permission of the symlink object. If you 63 | want to get the file data of symlink object, please use cp command to download 64 | the symlink object. 65 | 66 | The item X-Oss-Symlink-Target shows the target object of the symlink object. 67 | 68 | If the object is not symlink object, ossutil return error: NotSymlink. 69 | 70 | More information about symlink see: https://help.aliyun.com/document_detail/45146.html?spm=5176.doc31968.6.871.24y1VX 71 | 72 | Usage: 73 | 74 | ossutil read-symlink oss://bucket/symlink-object [--version-id versionId] [--payer requester] 75 | `, 76 | 77 | sampleText: ` 78 | ossutil read-symlink oss://bucket1/object1 79 | Etag : 455E20DBFFF1D588B67D092C46B16DB6 80 | Last-Modified : 2017-04-17 14:49:42 +0800 CST 81 | X-Oss-Symlink-Target : a 82 | 83 | ossutil read-symlink oss://bucket1/object --version-id versionId 84 | 85 | ossutil read-symlink oss://bucket1/object --payer requester 86 | `, 87 | } 88 | 89 | // ReadSymlinkCommand is the command list buckets or objects 90 | type ReadSymlinkCommand struct { 91 | command Command 92 | commonOptions []oss.Option 93 | } 94 | 95 | var readSymlinkCommand = ReadSymlinkCommand{ 96 | command: Command{ 97 | name: "read-symlink", 98 | nameAlias: []string{}, 99 | minArgc: 1, 100 | maxArgc: 1, 101 | specChinese: specChineseReadSymlink, 102 | specEnglish: specEnglishReadSymlink, 103 | group: GroupTypeNormalCommand, 104 | validOptionNames: []string{ 105 | OptionEncodingType, 106 | OptionConfigFile, 107 | OptionEndpoint, 108 | OptionAccessKeyID, 109 | OptionAccessKeySecret, 110 | OptionSTSToken, 111 | OptionProxyHost, 112 | OptionProxyUser, 113 | OptionProxyPwd, 114 | OptionRetryTimes, 115 | OptionLogLevel, 116 | OptionVersionId, 117 | OptionRequestPayer, 118 | OptionPassword, 119 | OptionMode, 120 | OptionECSRoleName, 121 | OptionTokenTimeout, 122 | OptionRamRoleArn, 123 | OptionRoleSessionName, 124 | OptionReadTimeout, 125 | OptionConnectTimeout, 126 | OptionSTSRegion, 127 | OptionSkipVerifyCert, 128 | OptionUserAgent, 129 | OptionSignVersion, 130 | OptionRegion, 131 | OptionCloudBoxID, 132 | OptionForcePathStyle, 133 | }, 134 | }, 135 | } 136 | 137 | // function for FormatHelper interface 138 | func (rc *ReadSymlinkCommand) formatHelpForWhole() string { 139 | return rc.command.formatHelpForWhole() 140 | } 141 | 142 | func (rc *ReadSymlinkCommand) formatIndependHelp() string { 143 | return rc.command.formatIndependHelp() 144 | } 145 | 146 | // Init simulate inheritance, and polymorphism 147 | func (rc *ReadSymlinkCommand) Init(args []string, options OptionMapType) error { 148 | return rc.command.Init(args, options, rc) 149 | } 150 | 151 | // RunCommand simulate inheritance, and polymorphism 152 | func (rc *ReadSymlinkCommand) RunCommand() error { 153 | encodingType, _ := GetString(OptionEncodingType, rc.command.options) 154 | cloudURL, err := ObjectURLFromString(rc.command.args[0], encodingType) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | versionId, _ := GetString(OptionVersionId, rc.command.options) 160 | symlinkOptions := []oss.Option{} 161 | if len(versionId) > 0 { 162 | symlinkOptions = append(symlinkOptions, oss.VersionId(versionId)) 163 | } 164 | 165 | payer, _ := GetString(OptionRequestPayer, rc.command.options) 166 | if payer != "" { 167 | if payer != strings.ToLower(string(oss.Requester)) { 168 | return fmt.Errorf("invalid request payer: %s, please check", payer) 169 | } 170 | rc.commonOptions = append(rc.commonOptions, oss.RequestPayer(oss.PayerType(payer))) 171 | } 172 | 173 | symlinkOptions = append(symlinkOptions, rc.commonOptions...) 174 | bucket, err := rc.command.ossBucket(cloudURL.bucket) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return rc.linkStat(bucket, cloudURL, symlinkOptions...) 180 | } 181 | 182 | func (rc *ReadSymlinkCommand) linkStat(bucket *oss.Bucket, cloudURL CloudURL, options ...oss.Option) error { 183 | // normal info 184 | props, err := rc.ossGetSymlinkRetry(bucket, cloudURL.object, options...) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | sortNames := []string{} 190 | attrMap := map[string]string{} 191 | for name := range props { 192 | ln := strings.ToLower(name) 193 | if ln != strings.ToLower(oss.HTTPHeaderDate) && 194 | ln != strings.ToLower(oss.HTTPHeaderOssRequestID) && 195 | ln != strings.ToLower(oss.HTTPHeaderServer) && 196 | ln != strings.ToLower(oss.HTTPHeaderContentLength) && 197 | ln != "x-oss-server-time" && 198 | ln != "connection" { 199 | sortNames = append(sortNames, name) 200 | attrMap[name] = props.Get(name) 201 | } 202 | } 203 | 204 | if lm, err := time.Parse(http.TimeFormat, attrMap[StatLastModified]); err == nil { 205 | attrMap[StatLastModified] = fmt.Sprintf("%s", utcToLocalTime(lm.UTC())) 206 | } 207 | 208 | sort.Strings(sortNames) 209 | 210 | for _, name := range sortNames { 211 | if strings.ToLower(name) != "etag" { 212 | fmt.Printf("%-24s: %s\n", name, attrMap[name]) 213 | } else { 214 | fmt.Printf("%-24s: %s\n", name, strings.Trim(attrMap[name], "\"")) 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | func (rc *ReadSymlinkCommand) ossGetSymlinkRetry(bucket *oss.Bucket, symlinkObject string, options ...oss.Option) (http.Header, error) { 221 | retryTimes, _ := GetInt(OptionRetryTimes, rc.command.options) 222 | for i := 1; ; i++ { 223 | props, err := bucket.GetSymlink(symlinkObject, options...) 224 | if err == nil { 225 | return props, err 226 | } 227 | if int64(i) >= retryTimes { 228 | return props, ObjectError{err, bucket.BucketName, symlinkObject} 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/report_helper.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | ) 9 | 10 | type Reporter struct { 11 | rlogger *log.Logger 12 | written bool 13 | prompted bool 14 | path string 15 | comment string 16 | outputDir string 17 | createDir bool 18 | fileHandle *os.File 19 | } 20 | 21 | func (re *Reporter) Init(outputDir, comment string) error { 22 | if outputDir == "" { 23 | outputDir = DefaultOutputDir 24 | } 25 | re.outputDir = outputDir 26 | re.createDir = false 27 | if _, err := os.Stat(outputDir); err != nil && os.IsNotExist(err) { 28 | re.createDir = true 29 | } 30 | if err := os.MkdirAll(outputDir, 0755); err != nil { 31 | return err 32 | } 33 | re.path = re.outputDir + string(os.PathSeparator) + ReportPrefix + time.Now().Format("20060102_150405") + ReportSuffix 34 | re.comment = comment 35 | re.written = false 36 | re.prompted = false 37 | f, err := os.OpenFile(re.path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0664) 38 | if err != nil { 39 | return fmt.Errorf("Create reporter file error: %s", err.Error()) 40 | } 41 | re.fileHandle = f 42 | re.rlogger = log.New(f, "", log.Ldate|log.Ltime) 43 | re.Comment() 44 | re.rlogger.SetFlags(log.Ldate | log.Ltime) 45 | return nil 46 | } 47 | 48 | func (re *Reporter) Clear() { 49 | if re != nil && re.fileHandle != nil { 50 | re.fileHandle.Close() 51 | } 52 | 53 | if re != nil && !re.written { 54 | os.Remove(re.path) 55 | if re.createDir { 56 | os.RemoveAll(re.outputDir) 57 | } 58 | } 59 | } 60 | 61 | func (re *Reporter) HasPrompt() bool { 62 | if re == nil { 63 | return false 64 | } 65 | return re.prompted == false 66 | } 67 | 68 | func (re *Reporter) Comment() { 69 | if re != nil && !re.written { 70 | re.rlogger.SetFlags(0) 71 | re.rlogger.SetPrefix("# ") 72 | re.rlogger.Println(re.comment) 73 | } 74 | } 75 | 76 | func (re *Reporter) ReportError(msg string) { 77 | if re != nil && re.rlogger != nil { 78 | re.written = true 79 | re.rlogger.SetPrefix("[Error] ") 80 | re.rlogger.Println(msg) 81 | } 82 | } 83 | 84 | func (re *Reporter) Prompt(err error) { 85 | if re != nil && re.written && re.HasPrompt() { 86 | re.prompted = true 87 | fmt.Printf("\r%s\rError occurs, message: %s. See more information in file: %s\n", clearStr, err.Error(), re.path) 88 | } 89 | } 90 | 91 | func GetReporter(need bool, outputDir, comment string) (*Reporter, error) { 92 | if need { 93 | var reporter Reporter 94 | if err := reporter.Init(outputDir, comment); err != nil { 95 | return nil, err 96 | } 97 | return &reporter, nil 98 | } 99 | return nil, nil 100 | } 101 | -------------------------------------------------------------------------------- /lib/request_payment.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | var specChineseRequestPayment = SpecText{ 11 | synopsisText: "设置、查询bucket的访问者付费配置", 12 | 13 | paramText: "bucket_url [payment_parameter] [options]", 14 | 15 | syntaxText: ` 16 | ossutil request-payment --method put oss://bucket payment_parameter 17 | ossutil request-payment --method get oss://bucket 18 | `, 19 | detailHelpText: ` 20 | request-payment命令通过设置method选项值为put、get, 可以设置、查询bucket的访问者付费配置 21 | 选项--method为put时,参数只能为Requester, BucketOwner 22 | 23 | 用法: 24 | 该命令有三种用法: 25 | 26 | 1) ossutil request-payment --method put oss://bucket Requester 27 | 这个命令设置由bucket的访问者付费 28 | 29 | 2) ossutil request-payment --method put oss://bucket BucketOwner 30 | 这个命令设置由bucket的拥有者付费 31 | 32 | 3) ossutil request-payment --method get oss://bucket 33 | 这个命令查询bucket的付费配置 34 | `, 35 | sampleText: ` 36 | 1) 设置由bucket的访问者付费 37 | ossutil request-payment --method put oss://bucket Requester 38 | 39 | 2) 设置由bucket的拥有者付费 40 | ossutil request-payment --method put oss://bucket BucketOwner 41 | 42 | 3) 查询bucket的付费配置 43 | ossutil request-payment --method get oss://bucket 44 | `, 45 | } 46 | 47 | var specEnglishRequestPayment = SpecText{ 48 | synopsisText: "Set, get bucket request payment configuration", 49 | 50 | paramText: "bucket_url [payment_parameter] [options]", 51 | 52 | syntaxText: ` 53 | ossutil request-payment --method put oss://bucket payment_parameter 54 | ossutil request-payment --method get oss://bucket 55 | `, 56 | detailHelpText: ` 57 | request-payment command can set, get the bucket request payment configuration by set method option value to put, get 58 | If the --method option value is put, the parameter can only be Requester, BucketOwner 59 | Usage: 60 | There are three usages for this command: 61 | 62 | 1) ossutil request-payment --method put oss://bucket Requester 63 | This command sets that request is paid by the requester of the bucket 64 | 65 | 2) ossutil request-payment --method put oss://bucket BucketOwner 66 | This command sets that request is paid by the owner of the bucket 67 | 68 | 3) ossutil request-payment --method get oss://bucket 69 | This command query the bucket request payment configuration 70 | `, 71 | sampleText: ` 72 | 1) setting request is paid by the requester of the bucket 73 | ossutil request-payment --method put oss://bucket Requester 74 | 75 | 2) setting request is paid by the owner of the bucket 76 | ossutil request-payment --method put oss://bucket BucketOwner 77 | 78 | 3) query the bucket request payment configuration 79 | ossutil request-payment --method get oss://bucket 80 | `, 81 | } 82 | 83 | type RequestPaymentCommand struct { 84 | command Command 85 | bucketName string 86 | paymentResult oss.RequestPaymentConfiguration 87 | } 88 | 89 | var requestPaymentCommand = RequestPaymentCommand{ 90 | command: Command{ 91 | name: "request-payment", 92 | nameAlias: []string{"request-payment"}, 93 | minArgc: 1, 94 | maxArgc: 2, 95 | specChinese: specChineseRequestPayment, 96 | specEnglish: specEnglishRequestPayment, 97 | group: GroupTypeNormalCommand, 98 | validOptionNames: []string{ 99 | OptionConfigFile, 100 | OptionEndpoint, 101 | OptionAccessKeyID, 102 | OptionAccessKeySecret, 103 | OptionSTSToken, 104 | OptionProxyHost, 105 | OptionProxyUser, 106 | OptionProxyPwd, 107 | OptionMethod, 108 | OptionLogLevel, 109 | OptionPassword, 110 | OptionMode, 111 | OptionECSRoleName, 112 | OptionTokenTimeout, 113 | OptionRamRoleArn, 114 | OptionRoleSessionName, 115 | OptionReadTimeout, 116 | OptionConnectTimeout, 117 | OptionSTSRegion, 118 | OptionSkipVerifyCert, 119 | OptionUserAgent, 120 | OptionSignVersion, 121 | OptionRegion, 122 | OptionCloudBoxID, 123 | OptionForcePathStyle, 124 | }, 125 | }, 126 | } 127 | 128 | // function for FormatHelper interface 129 | func (reqpc *RequestPaymentCommand) formatHelpForWhole() string { 130 | return reqpc.command.formatHelpForWhole() 131 | } 132 | 133 | func (reqpc *RequestPaymentCommand) formatIndependHelp() string { 134 | return reqpc.command.formatIndependHelp() 135 | } 136 | 137 | // Init simulate inheritance, and polymorphism 138 | func (reqpc *RequestPaymentCommand) Init(args []string, options OptionMapType) error { 139 | return reqpc.command.Init(args, options, reqpc) 140 | } 141 | 142 | // RunCommand simulate inheritance, and polymorphism 143 | func (reqpc *RequestPaymentCommand) RunCommand() error { 144 | strMethod, _ := GetString(OptionMethod, reqpc.command.options) 145 | if strMethod == "" { 146 | return fmt.Errorf("--method value is empty") 147 | } 148 | 149 | strMethod = strings.ToLower(strMethod) 150 | if strMethod != "put" && strMethod != "get" { 151 | return fmt.Errorf("--method value is not in the optional value:put|get") 152 | } 153 | 154 | srcBucketUrL, err := GetCloudUrl(reqpc.command.args[0], "") 155 | if err != nil { 156 | return err 157 | } 158 | 159 | reqpc.bucketName = srcBucketUrL.bucket 160 | 161 | if strMethod == "put" { 162 | err = reqpc.PutRequestPayment() 163 | } else if strMethod == "get" { 164 | err = reqpc.GetRequestPayment() 165 | } 166 | 167 | return err 168 | } 169 | 170 | func (reqpc *RequestPaymentCommand) PutRequestPayment() error { 171 | if len(reqpc.command.args) < 2 { 172 | return fmt.Errorf("missing parameter,payment parameter is empty") 173 | } 174 | 175 | strPayment := strings.ToLower(reqpc.command.args[1]) 176 | 177 | if strPayment != strings.ToLower(string(oss.Requester)) && 178 | strPayment != strings.ToLower(string(oss.BucketOwner)) { 179 | return fmt.Errorf("payment parameter must be %s or %s", string(oss.Requester), string(oss.BucketOwner)) 180 | } 181 | 182 | // put bucket payment 183 | client, err := reqpc.command.ossClient(reqpc.bucketName) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | var paymentConfig oss.RequestPaymentConfiguration 189 | if strPayment == strings.ToLower(string(oss.Requester)) { 190 | paymentConfig.Payer = string(oss.Requester) 191 | } else if strPayment == strings.ToLower(string(oss.BucketOwner)) { 192 | paymentConfig.Payer = string(oss.BucketOwner) 193 | 194 | } 195 | return client.SetBucketRequestPayment(reqpc.bucketName, paymentConfig) 196 | } 197 | 198 | func (reqpc *RequestPaymentCommand) GetRequestPayment() error { 199 | client, err := reqpc.command.ossClient(reqpc.bucketName) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | reqpc.paymentResult, err = client.GetBucketRequestPayment(reqpc.bucketName) 205 | if err != nil { 206 | return err 207 | } 208 | 209 | fmt.Printf("\n%s\n", string(reqpc.paymentResult.Payer)) 210 | 211 | return nil 212 | } 213 | -------------------------------------------------------------------------------- /lib/request_payment_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | . "gopkg.in/check.v1" 8 | ) 9 | 10 | func (s *OssutilCommandSuite) TestRequestPaymentPutSuccess(c *C) { 11 | bucketName := bucketNamePrefix + randLowStr(12) 12 | s.putBucket(bucketName, c) 13 | 14 | // request command test 15 | var str string 16 | strMethod := "get" 17 | options := OptionMapType{ 18 | "endpoint": &str, 19 | "accessKeyID": &str, 20 | "accessKeySecret": &str, 21 | "stsToken": &str, 22 | "configFile": &configFile, 23 | "method": &strMethod, 24 | } 25 | 26 | requestArgs := []string{CloudURLToString(bucketName, "")} 27 | _, err := cm.RunCommand("request-payment", requestArgs, options) 28 | c.Assert(err, IsNil) 29 | c.Assert(requestPaymentCommand.paymentResult.Payer, Equals, "BucketOwner") 30 | 31 | // set bucket request enabled 32 | strMethod = "put" 33 | requestArgs = []string{CloudURLToString(bucketName, ""), "Requester"} 34 | _, err = cm.RunCommand("request-payment", requestArgs, options) 35 | c.Assert(err, IsNil) 36 | 37 | time.Sleep(5 * time.Second) 38 | 39 | // check 40 | strMethod = "get" 41 | requestArgs = []string{CloudURLToString(bucketName, "")} 42 | _, err = cm.RunCommand("request-payment", requestArgs, options) 43 | c.Assert(err, IsNil) 44 | c.Assert(requestPaymentCommand.paymentResult.Payer, Equals, "Requester") 45 | 46 | // set bucket request suspend 47 | strMethod = "put" 48 | requestArgs = []string{CloudURLToString(bucketName, ""), "BucketOwner"} 49 | _, err = cm.RunCommand("request-payment", requestArgs, options) 50 | c.Assert(err, IsNil) 51 | time.Sleep(5 * time.Second) 52 | 53 | // check 54 | strMethod = "get" 55 | requestArgs = []string{CloudURLToString(bucketName, "")} 56 | _, err = cm.RunCommand("request-payment", requestArgs, options) 57 | c.Assert(err, IsNil) 58 | c.Assert(requestPaymentCommand.paymentResult.Payer, Equals, "BucketOwner") 59 | 60 | s.removeBucket(bucketName, true, c) 61 | } 62 | 63 | func (s *OssutilCommandSuite) TestRequestPaymentError(c *C) { 64 | bucketName := bucketNamePrefix + randLowStr(12) 65 | s.putBucket(bucketName, c) 66 | 67 | // request-payment command test 68 | var str string 69 | options := OptionMapType{ 70 | "endpoint": &str, 71 | "accessKeyID": &str, 72 | "accessKeySecret": &str, 73 | "stsToken": &str, 74 | "configFile": &configFile, 75 | } 76 | 77 | // method is empty 78 | requestArgs := []string{CloudURLToString(bucketName, "")} 79 | _, err := cm.RunCommand("request-payment", requestArgs, options) 80 | c.Assert(err, NotNil) 81 | 82 | // method is error 83 | strMethod := "puttt" 84 | options["method"] = &strMethod 85 | _, err = cm.RunCommand("request-payment", requestArgs, options) 86 | c.Assert(err, NotNil) 87 | 88 | // args is empty 89 | strMethod = "put" 90 | requestArgs = []string{CloudURLToString(bucketName, "")} 91 | _, err = cm.RunCommand("request-payment", requestArgs, options) 92 | c.Assert(err, NotNil) 93 | 94 | //value is error 95 | requestArgs = []string{CloudURLToString(bucketName, ""), "Bucket-Owner"} 96 | _, err = cm.RunCommand("request-payment", requestArgs, options) 97 | c.Assert(err, NotNil) 98 | 99 | s.removeBucket(bucketName, true, c) 100 | } 101 | 102 | func (s *OssutilCommandSuite) TestRequestPaymentPutEmptyEndpoint(c *C) { 103 | bucketName := bucketNamePrefix + randLowStr(12) 104 | s.putBucket(bucketName, c) 105 | 106 | cfile := randStr(10) 107 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 108 | s.createFile(cfile, data, c) 109 | 110 | // request-payment command test 111 | var str string 112 | strMethod := "put" 113 | options := OptionMapType{ 114 | "endpoint": &str, 115 | "accessKeyID": &str, 116 | "accessKeySecret": &str, 117 | "stsToken": &str, 118 | "configFile": &cfile, 119 | "method": &strMethod, 120 | } 121 | 122 | requestArgs := []string{CloudURLToString(bucketName, ""), "Requester"} 123 | _, err := cm.RunCommand("request-payment", requestArgs, options) 124 | c.Assert(err, NotNil) 125 | 126 | os.Remove(cfile) 127 | s.removeBucket(bucketName, true, c) 128 | } 129 | 130 | func (s *OssutilCommandSuite) TestRequestPaymentGetEmptyEndpoint(c *C) { 131 | bucketName := bucketNamePrefix + randLowStr(12) 132 | s.putBucket(bucketName, c) 133 | 134 | cfile := randStr(10) 135 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 136 | s.createFile(cfile, data, c) 137 | 138 | var str string 139 | strMethod := "get" 140 | options := OptionMapType{ 141 | "endpoint": &str, 142 | "accessKeyID": &str, 143 | "accessKeySecret": &str, 144 | "stsToken": &str, 145 | "configFile": &cfile, 146 | "method": &strMethod, 147 | } 148 | 149 | versioingArgs := []string{CloudURLToString(bucketName, "")} 150 | _, err := cm.RunCommand("request-payment", versioingArgs, options) 151 | c.Assert(err, NotNil) 152 | 153 | os.Remove(cfile) 154 | s.removeBucket(bucketName, true, c) 155 | } 156 | 157 | func (s *OssutilCommandSuite) TestRequestPaymentHelpInfo(c *C) { 158 | options := OptionMapType{} 159 | 160 | mkArgs := []string{"request-payment"} 161 | _, err := cm.RunCommand("help", mkArgs, options) 162 | c.Assert(err, IsNil) 163 | 164 | mkArgs = []string{} 165 | _, err = cm.RunCommand("help", mkArgs, options) 166 | c.Assert(err, IsNil) 167 | 168 | } 169 | -------------------------------------------------------------------------------- /lib/storage_url.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // SchemePrefix is the prefix of oss url 11 | const SchemePrefix string = "oss://" 12 | 13 | type CloudURLType string 14 | 15 | const ( 16 | CloudURLNone CloudURLType = "none" 17 | CloudURLService CloudURLType = "service" 18 | CloudURLBucket CloudURLType = "bucket" 19 | CloudURLObject CloudURLType = "object" 20 | ) 21 | 22 | // StorageURLer is the interface for all url 23 | type StorageURLer interface { 24 | IsCloudURL() bool 25 | IsFileURL() bool 26 | ToString() string 27 | } 28 | 29 | // CloudURL describes oss url 30 | type CloudURL struct { 31 | urlStr string 32 | bucket string 33 | object string 34 | } 35 | 36 | // Init is used to create a cloud url from a user input url 37 | func (cu *CloudURL) Init(urlStr, encodingType string) error { 38 | cu.urlStr = urlStr 39 | if err := cu.parseBucketObject(encodingType); err != nil { 40 | return err 41 | } 42 | if err := cu.checkBucketObject(encodingType); err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | func (cu *CloudURL) parseBucketObject(encodingType string) error { 49 | var err error 50 | path := cu.urlStr 51 | 52 | if strings.HasPrefix(strings.ToLower(path), SchemePrefix) { 53 | path = string(path[len(SchemePrefix):]) 54 | } else { 55 | // deal with the url: /bucket/object 56 | if strings.HasPrefix(path, "/") { 57 | path = string(path[1:]) 58 | } 59 | } 60 | 61 | sli := strings.SplitN(path, "/", 2) 62 | cu.bucket = sli[0] 63 | if len(sli) > 1 { 64 | cu.object = sli[1] 65 | if encodingType == URLEncodingType { 66 | if cu.object, err = url.QueryUnescape(cu.object); err != nil { 67 | return fmt.Errorf("invalid cloud url: %s, object name is not url encoded, %s", cu.urlStr, err.Error()) 68 | } 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | func (cu *CloudURL) checkBucketObject(encodingType string) error { 75 | if cu.bucket == "" && cu.object != "" { 76 | return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr) 77 | } 78 | if encodingType == URLEncodingType && cu.bucket != "" && cu.object == "" { 79 | if bucket, err := url.QueryUnescape(cu.bucket); err == nil && bucket != cu.bucket { 80 | return fmt.Errorf("invalid cloud url: %s, bucket url do not support --encoding-type option", cu.urlStr) 81 | } 82 | } 83 | return nil 84 | } 85 | 86 | func (cu *CloudURL) checkObjectPrefix() error { 87 | if strings.HasPrefix(cu.object, "/") { 88 | return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"/\"", cu.urlStr) 89 | } 90 | if strings.HasPrefix(cu.object, "\\") { 91 | return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"\\\"", cu.urlStr) 92 | } 93 | return nil 94 | } 95 | 96 | func (cu *CloudURL) checkIsObjectURL() error { 97 | if cu.bucket == "" { 98 | return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr) 99 | } 100 | if cu.object == "" { 101 | return fmt.Errorf("invalid cloud url: %s, miss object", cu.urlStr) 102 | } 103 | return nil 104 | } 105 | 106 | // IsCloudURL shows if the url is a cloud url 107 | func (cu CloudURL) IsCloudURL() bool { 108 | return true 109 | } 110 | 111 | // IsFileURL shows if the url is a file url 112 | func (cu CloudURL) IsFileURL() bool { 113 | return false 114 | } 115 | 116 | // ToString reconstruct url 117 | func (cu CloudURL) ToString() string { 118 | if cu.object == "" { 119 | return fmt.Sprintf("%s%s", SchemePrefix, cu.bucket) 120 | } 121 | return fmt.Sprintf("%s%s/%s", SchemePrefix, cu.bucket, cu.object) 122 | } 123 | 124 | // FileURL describes file url 125 | type FileURL struct { 126 | urlStr string 127 | } 128 | 129 | // Init simulate inheritance, and polymorphism 130 | func (fu *FileURL) Init(urlStr, encodingType string) error { 131 | if encodingType == URLEncodingType { 132 | vurl, err := url.QueryUnescape(urlStr) 133 | if err != nil { 134 | return fmt.Errorf("invalid cloud url: %s, file name is not url encoded, %s", urlStr, err.Error()) 135 | } 136 | urlStr = vurl 137 | } 138 | 139 | if len(urlStr) >= 2 && urlStr[:2] == "~"+string(os.PathSeparator) { 140 | homeDir := currentHomeDir() 141 | if homeDir != "" { 142 | urlStr = strings.Replace(urlStr, "~", homeDir, 1) 143 | } else { 144 | return fmt.Errorf("current home dir is empty") 145 | } 146 | } 147 | fu.urlStr = urlStr 148 | return nil 149 | } 150 | 151 | // IsCloudURL simulate inheritance, and polymorphism 152 | func (fu FileURL) IsCloudURL() bool { 153 | return false 154 | } 155 | 156 | // IsFileURL simulate inheritance, and polymorphism 157 | func (fu FileURL) IsFileURL() bool { 158 | return true 159 | } 160 | 161 | // ToString simulate inheritance, and polymorphism 162 | func (fu FileURL) ToString() string { 163 | return fu.urlStr 164 | } 165 | 166 | // StorageURLFromString analysis input url type and build a storage url from the url 167 | func StorageURLFromString(urlStr, encodingType string) (StorageURLer, error) { 168 | if strings.HasPrefix(strings.ToLower(urlStr), SchemePrefix) { 169 | var cloudURL CloudURL 170 | if err := cloudURL.Init(urlStr, encodingType); err != nil { 171 | return nil, err 172 | } 173 | return cloudURL, nil 174 | } 175 | var fileURL FileURL 176 | if err := fileURL.Init(urlStr, encodingType); err != nil { 177 | return nil, err 178 | } 179 | return fileURL, nil 180 | } 181 | 182 | // CloudURLFromString get a oss url from url, if url is not a cloud url, return error 183 | func CloudURLFromString(urlStr, encodingType string) (CloudURL, error) { 184 | storageURL, err := StorageURLFromString(urlStr, encodingType) 185 | if err != nil { 186 | return CloudURL{}, err 187 | } 188 | if !storageURL.IsCloudURL() { 189 | return CloudURL{}, fmt.Errorf("invalid cloud url: \"%s\", please make sure the url starts with: \"%s\"", urlStr, SchemePrefix) 190 | } 191 | return storageURL.(CloudURL), nil 192 | } 193 | 194 | // ObjectURLFromString get a oss url from url, if url is not a cloud url, return error 195 | func ObjectURLFromString(urlStr, encodingType string) (CloudURL, error) { 196 | cloudURL, err := CloudURLFromString(urlStr, encodingType) 197 | if err != nil { 198 | return cloudURL, err 199 | } 200 | return cloudURL, cloudURL.checkIsObjectURL() 201 | } 202 | 203 | // CloudURLToString format url string from input 204 | func CloudURLToString(bucket string, object string) string { 205 | cloudURL := CloudURL{ 206 | bucket: bucket, 207 | object: object, 208 | } 209 | return cloudURL.ToString() 210 | } 211 | -------------------------------------------------------------------------------- /lib/update_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | func (s *OssutilCommandSuite) rawUpdate(force bool, language string) (bool, error) { 15 | command := "update" 16 | var args []string 17 | options := OptionMapType{ 18 | OptionForce: &force, 19 | OptionLanguage: &language, 20 | } 21 | showElapse, err := cm.RunCommand(command, args, options) 22 | return showElapse, err 23 | } 24 | 25 | func (s *OssutilCommandSuite) TestUpdate(c *C) { 26 | showElapse, err := s.rawUpdate(false, "ch") 27 | c.Assert(err, IsNil) 28 | c.Assert(showElapse, Equals, false) 29 | 30 | showElapse, err = s.rawUpdate(false, "En") 31 | c.Assert(err, IsNil) 32 | c.Assert(showElapse, Equals, false) 33 | 34 | cmdline := []string{"ossutil", "update"} 35 | os.Args = cmdline 36 | 37 | showElapse, err = s.rawUpdate(true, "ch") 38 | 39 | showElapse, err = s.rawUpdate(true, "En") 40 | 41 | err = updateCommand.updateVersion(Version, "ch") 42 | 43 | err = updateCommand.updateVersion("1.0.0.Beta", "ch") 44 | 45 | fileName := "ossutil_test_not_exist" 46 | err = updateCommand.rewriteLoadConfig(fileName) 47 | c.Assert(err, IsNil) 48 | } 49 | 50 | func (s *OssutilCommandSuite) TestUpdateDiffVersion(c *C) { 51 | // error get lastest version 52 | ue := vUpdateBucket 53 | vUpdateBucket = "abc" 54 | version, err := updateCommand.getLastestVersion() 55 | c.Assert(err, NotNil) 56 | 57 | vUpdateBucket = ue 58 | version, err = updateCommand.getLastestVersion() 59 | c.Assert(err, IsNil) 60 | vVersion = version 61 | 62 | cmdline := []string{"ossutil", "update"} 63 | os.Args = cmdline 64 | 65 | err = updateCommand.RunCommand() 66 | c.Assert(err, IsNil) 67 | vVersion = version + "123" 68 | updateCommand.RunCommand() 69 | vVersion = Version 70 | } 71 | 72 | func (s *OssutilCommandSuite) TestRevertRename(c *C) { 73 | filePath := ".ossutil_tempf" + randStr(5) 74 | renameFilePath := ".ossutil_tempr" + randStr(5) 75 | 76 | s.createFile(filePath, filePath+"i", c) 77 | s.createFile(renameFilePath, renameFilePath+"i", c) 78 | 79 | updateCommand.revertRename(filePath, renameFilePath) 80 | _, err := os.Stat(renameFilePath) 81 | c.Assert(err, NotNil) 82 | 83 | str := s.readFile(filePath, c) 84 | c.Assert(str, Equals, renameFilePath+"i") 85 | 86 | os.Remove(filePath) 87 | os.Remove(renameFilePath) 88 | 89 | renameFilePath = ".ossutil_notexist" 90 | err = updateCommand.revertRename(filePath, renameFilePath) 91 | c.Assert(err, NotNil) 92 | } 93 | 94 | func (s *OssutilCommandSuite) TestDownloadLastestBinary(c *C) { 95 | tempBinaryFile := ".ossutil_test_update.temp" 96 | err := updateCommand.getBinary(tempBinaryFile, "1.0.0.Beta") 97 | c.Assert(err, IsNil) 98 | 99 | os.Remove(tempBinaryFile) 100 | } 101 | 102 | func (s *OssutilCommandSuite) TestAnonymousGetToFileError(c *C) { 103 | bucket := bucketNameNotExist 104 | object := "TestAnonymousGetToFileError" 105 | err := updateCommand.anonymousGetToFileRetry(bucket, object, object) 106 | c.Assert(err, NotNil) 107 | 108 | bucket = bucketNameDest 109 | s.putObject(bucket, object, uploadFileName, c) 110 | fileName := "*" 111 | err = updateCommand.anonymousGetToFileRetry(bucket, object, fileName) 112 | c.Assert(err, NotNil) 113 | } 114 | 115 | func (s *OssutilCommandSuite) TestUpdateSuccess(c *C) { 116 | nowVersion, err := updateCommand.getLastestVersion() 117 | c.Assert(err, IsNil) 118 | 119 | // get a version below current 120 | pSlice := strings.Split(nowVersion, ".") 121 | for index := len(pSlice) - 1; index >= 0; index-- { 122 | if pSlice[index] > "0" { 123 | b, err := strconv.Atoi(pSlice[index]) 124 | c.Assert(err, IsNil) 125 | pSlice[index] = strconv.Itoa(b - 1) 126 | break 127 | } 128 | } 129 | 130 | lowVersion := "" 131 | for k, v := range pSlice { 132 | if k == len(pSlice)-1 { 133 | lowVersion = lowVersion + v 134 | } else { 135 | lowVersion = lowVersion + v + "." 136 | } 137 | } 138 | 139 | // set path enviroment 140 | oldPathValue := os.Getenv("PATH") 141 | currentDiretory, _ := os.Getwd() 142 | if runtime.GOOS == "windows" { 143 | os.Setenv("PATH", currentDiretory+";"+oldPathValue) 144 | } else { 145 | os.Setenv("PATH", currentDiretory+":"+oldPathValue) 146 | } 147 | 148 | // binaryName file must be exist 149 | binaryName := updateCommand.getBinaryName() 150 | ioutil.WriteFile(binaryName, []byte("test-binary"), 0744) 151 | 152 | cmdline := []string{binaryName, "update", "-f"} 153 | os.Args = cmdline 154 | err = updateCommand.updateVersion(lowVersion, "ch") 155 | c.Assert(err, IsNil) 156 | os.Remove(binaryName) 157 | os.Remove(".temp_" + binaryName) 158 | } 159 | 160 | func (s *OssutilCommandSuite) TestUpdateWithLinuxArm(c *C) { 161 | nowVersion, err := updateCommand.getLastestVersion() 162 | c.Assert(err, IsNil) 163 | 164 | // get a version below current 165 | pSlice := strings.Split(nowVersion, ".") 166 | for index := len(pSlice) - 1; index >= 0; index-- { 167 | if pSlice[index] > "0" { 168 | b, err := strconv.Atoi(pSlice[index]) 169 | c.Assert(err, IsNil) 170 | pSlice[index] = strconv.Itoa(b - 1) 171 | break 172 | } 173 | } 174 | 175 | lowVersion := "" 176 | for k, v := range pSlice { 177 | if k == len(pSlice)-1 { 178 | lowVersion = lowVersion + v 179 | } else { 180 | lowVersion = lowVersion + v + "." 181 | } 182 | } 183 | 184 | // set path enviroment 185 | oldPathValue := os.Getenv("PATH") 186 | currentDiretory, _ := os.Getwd() 187 | if runtime.GOOS == "windows" { 188 | os.Setenv("PATH", currentDiretory+";"+oldPathValue) 189 | } else { 190 | os.Setenv("PATH", currentDiretory+":"+oldPathValue) 191 | } 192 | 193 | // binaryName file must be exist 194 | binaryName := updateCommand.getBinaryName() 195 | testLogger.Print("ossutil name:" + binaryName) 196 | ioutil.WriteFile(binaryName, []byte("test-binary"), 0744) 197 | 198 | cmdline := []string{binaryName, "update", "-f"} 199 | os.Args = cmdline 200 | err = updateCommand.updateVersion(lowVersion, "ch") 201 | c.Assert(err, IsNil) 202 | 203 | cmd := exec.Command(binaryName, "-h") 204 | cmdOut, err := cmd.CombinedOutput() 205 | c.Assert(err, IsNil) 206 | 207 | testLogger.Print(string(cmdOut)) 208 | 209 | cmd = exec.Command("go", "version") 210 | cmdOut, err = cmd.CombinedOutput() 211 | c.Assert(err, IsNil) 212 | if strings.Contains(string(cmdOut), "arm64") { 213 | c.Assert(binaryName, Equals, updateBinaryLinuxArm64) 214 | } 215 | 216 | if strings.Contains(string(cmdOut), "arm") && !strings.Contains(string(cmdOut), "arm64") { 217 | c.Assert(binaryName, Equals, updateBinaryLinuxArm32) 218 | } 219 | 220 | cmdline = []string{binaryName, "update", "-f"} 221 | os.Args = cmdline 222 | err = updateCommand.updateVersion(nowVersion, "ch") 223 | c.Assert(err, IsNil) 224 | if strings.Contains(string(cmdOut), "arm64") { 225 | c.Assert(binaryName, Equals, updateBinaryLinuxArm64) 226 | } 227 | 228 | if strings.Contains(string(cmdOut), "arm") && !strings.Contains(string(cmdOut), "arm64") { 229 | c.Assert(binaryName, Equals, updateBinaryLinuxArm32) 230 | } 231 | 232 | cmd = exec.Command(binaryName, "configggg") 233 | cmdOut, err = cmd.CombinedOutput() 234 | c.Assert(err, NotNil) 235 | 236 | testLogger.Print(string(cmdOut)) 237 | 238 | cmd = exec.Command(binaryName, "-v") 239 | cmdOut, err = cmd.CombinedOutput() 240 | c.Assert(err, IsNil) 241 | 242 | testLogger.Print(string(cmdOut)) 243 | 244 | os.Remove(binaryName) 245 | os.Remove(".temp_" + binaryName) 246 | } 247 | -------------------------------------------------------------------------------- /lib/user_qos.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | var specChineseUserQos = SpecText{ 11 | synopsisText: "查询用户的qos配置", 12 | 13 | paramText: "[local_file] [options]", 14 | 15 | syntaxText: ` 16 | ossutil user-qos --method get [local_file] [options] 17 | `, 18 | detailHelpText: ` 19 | user-qos命令通过设置method选项值为get可以查询用户的qos配置 20 | 21 | 用法: 22 | 该命令只有一种用法: 23 | 1) ossutil user-qos --method get [local_xml_file] [options] 24 | 这个命令查询用户的qos配置,如果输入参数local_xml_file,qos配置将输出到该文件,否则输出到屏幕上 25 | `, 26 | sampleText: ` 27 | 1) 查询用户的qos配置,结果输出到标准输出 28 | ossutil user-qos --method get 29 | 30 | 2) 查询用户的qos配置,结果输出到本地文件 31 | ossutil user-qos --method get local_xml_file 32 | `, 33 | } 34 | 35 | var specEnglishUserQos = SpecText{ 36 | synopsisText: "Get user's qos configuration", 37 | 38 | paramText: "[local_file] [options]", 39 | 40 | syntaxText: ` 41 | ossutil user-qos --method get [local_xml_file] [options] 42 | `, 43 | detailHelpText: ` 44 | user-qos command can get the user's qos configuration by set method option value to get 45 | 46 | Usage: 47 | There are only one usage for this command: 48 | 49 | 1) ossutil user-qos --method get [local_xml_file] [options] 50 | The command gets the user's qos configuration 51 | If you input parameter local_xml_file,the configuration will be output to local_xml_file 52 | If you don't input parameter local_xml_file,the configuration will be output to stdout 53 | `, 54 | sampleText: ` 55 | 1) get user's qos configuration to stdout 56 | ossutil user-qos --method get 57 | 58 | 2) get user's qos configuration to local file 59 | ossutil user-qos --method get local_xml_file 60 | `, 61 | } 62 | 63 | type UserQosCommand struct { 64 | command Command 65 | } 66 | 67 | var userQosCommand = UserQosCommand{ 68 | command: Command{ 69 | name: "user-qos", 70 | nameAlias: []string{"user-qos"}, 71 | minArgc: 0, 72 | maxArgc: 1, 73 | specChinese: specChineseUserQos, 74 | specEnglish: specEnglishUserQos, 75 | group: GroupTypeNormalCommand, 76 | validOptionNames: []string{ 77 | OptionConfigFile, 78 | OptionEndpoint, 79 | OptionAccessKeyID, 80 | OptionAccessKeySecret, 81 | OptionSTSToken, 82 | OptionProxyHost, 83 | OptionProxyUser, 84 | OptionProxyPwd, 85 | OptionLogLevel, 86 | OptionMethod, 87 | OptionPassword, 88 | OptionMode, 89 | OptionECSRoleName, 90 | OptionTokenTimeout, 91 | OptionRamRoleArn, 92 | OptionRoleSessionName, 93 | OptionReadTimeout, 94 | OptionConnectTimeout, 95 | OptionSTSRegion, 96 | OptionSkipVerifyCert, 97 | OptionUserAgent, 98 | OptionSignVersion, 99 | OptionRegion, 100 | OptionCloudBoxID, 101 | OptionForcePathStyle, 102 | }, 103 | }, 104 | } 105 | 106 | // function for FormatHelper interface 107 | func (uqc *UserQosCommand) formatHelpForWhole() string { 108 | return uqc.command.formatHelpForWhole() 109 | } 110 | 111 | func (uqc *UserQosCommand) formatIndependHelp() string { 112 | return uqc.command.formatIndependHelp() 113 | } 114 | 115 | // Init simulate inheritance, and polymorphism 116 | func (uqc *UserQosCommand) Init(args []string, options OptionMapType) error { 117 | return uqc.command.Init(args, options, uqc) 118 | } 119 | 120 | // RunCommand simulate inheritance, and polymorphism 121 | func (uqc *UserQosCommand) RunCommand() error { 122 | strMethod, _ := GetString(OptionMethod, uqc.command.options) 123 | if strMethod == "" { 124 | return fmt.Errorf("--method value is empty") 125 | } 126 | 127 | strMethod = strings.ToLower(strMethod) 128 | if strMethod != "get" { 129 | return fmt.Errorf("--method value must be get") 130 | } 131 | 132 | return uqc.GetUserQos() 133 | } 134 | 135 | func (uqc *UserQosCommand) confirm(str string) bool { 136 | var val string 137 | fmt.Printf(getClearStr(fmt.Sprintf("user qos: overwrite \"%s\"(y or N)? ", str))) 138 | if _, err := fmt.Scanln(&val); err != nil || (strings.ToLower(val) != "yes" && strings.ToLower(val) != "y") { 139 | return false 140 | } 141 | return true 142 | } 143 | 144 | func (uqc *UserQosCommand) GetUserQos() error { 145 | client, err := uqc.command.ossClient("") 146 | if err != nil { 147 | return err 148 | } 149 | 150 | qosRes, err := client.GetUserQoSInfo() 151 | if err != nil { 152 | return err 153 | } 154 | 155 | output, err := xml.MarshalIndent(qosRes, " ", " ") 156 | if err != nil { 157 | return err 158 | } 159 | 160 | var outFile *os.File 161 | if len(uqc.command.args) >= 1 { 162 | fileName := uqc.command.args[0] 163 | _, err = os.Stat(fileName) 164 | if err == nil { 165 | bConitnue := uqc.confirm(fileName) 166 | if !bConitnue { 167 | return nil 168 | } 169 | } 170 | 171 | outFile, err = os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660) 172 | if err != nil { 173 | return err 174 | } 175 | defer outFile.Close() 176 | } else { 177 | outFile = os.Stdout 178 | } 179 | 180 | outFile.Write([]byte(xml.Header)) 181 | outFile.Write(output) 182 | 183 | fmt.Printf("\n\n") 184 | 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /lib/user_qos_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | 6 | . "gopkg.in/check.v1" 7 | ) 8 | 9 | func (s *OssutilCommandSuite) TestUserQosGetError(c *C) { 10 | bucketName := bucketNamePrefix + randLowStr(12) 11 | s.putBucket(bucketName, c) 12 | 13 | // command test 14 | var str string 15 | strMethod := "" 16 | options := OptionMapType{ 17 | "endpoint": &str, 18 | "accessKeyID": &str, 19 | "accessKeySecret": &str, 20 | "stsToken": &str, 21 | "configFile": &configFile, 22 | "method": &strMethod, 23 | } 24 | 25 | // method is empty 26 | qosArgs := []string{CloudURLToString(bucketName, "")} 27 | _, err := cm.RunCommand("user-qos", qosArgs, options) 28 | c.Assert(err, NotNil) 29 | 30 | // method is error 31 | strMethod = "gett" 32 | _, err = cm.RunCommand("user-qos", qosArgs, options) 33 | c.Assert(err, NotNil) 34 | 35 | s.removeBucket(bucketName, true, c) 36 | } 37 | 38 | func (s *OssutilCommandSuite) TestUserQosOptionsEmptyEndpoint(c *C) { 39 | bucketName := bucketNamePrefix + randLowStr(12) 40 | s.putBucket(bucketName, c) 41 | 42 | cfile := randStr(10) 43 | data := "[Credentials]" + "\n" + "language=CH" + "\n" + "accessKeyID=123" + "\n" + "accessKeySecret=456" + "\n" + "endpoint=" 44 | s.createFile(cfile, data, c) 45 | 46 | var str string 47 | strMethod := "get" 48 | options := OptionMapType{ 49 | "endpoint": &str, 50 | "accessKeyID": &str, 51 | "accessKeySecret": &str, 52 | "stsToken": &str, 53 | "configFile": &cfile, 54 | "method": &strMethod, 55 | } 56 | 57 | qosArgs := []string{} 58 | _, err := cm.RunCommand("user-qos", qosArgs, options) 59 | c.Assert(err, NotNil) 60 | 61 | os.Remove(cfile) 62 | s.removeBucket(bucketName, true, c) 63 | } 64 | 65 | func (s *OssutilCommandSuite) TestUserQosGetConfirm(c *C) { 66 | bucketName := bucketNamePrefix + randLowStr(12) 67 | s.putBucket(bucketName, c) 68 | 69 | // user-qos command test 70 | var str string 71 | strMethod := "put" 72 | options := OptionMapType{ 73 | "endpoint": &str, 74 | "accessKeyID": &str, 75 | "accessKeySecret": &str, 76 | "stsToken": &str, 77 | "configFile": &configFile, 78 | "method": &strMethod, 79 | } 80 | 81 | qosArgs := []string{} 82 | _, err := cm.RunCommand("user-qos", qosArgs, options) 83 | c.Assert(err, NotNil) 84 | 85 | // get qos 86 | qosDownName := "ossutil-test-file-" + randLowStr(12) + "-down" 87 | strMethod = "get" 88 | options = OptionMapType{ 89 | "endpoint": &str, 90 | "accessKeyID": &str, 91 | "accessKeySecret": &str, 92 | "stsToken": &str, 93 | "configFile": &configFile, 94 | "method": &strMethod, 95 | } 96 | 97 | qosArgs = []string{} 98 | _, err = cm.RunCommand("user-qos", qosArgs, options) 99 | c.Assert(err, IsNil) 100 | 101 | qosArgs = []string{qosDownName} 102 | _, err = cm.RunCommand("user-qos", qosArgs, options) 103 | c.Assert(err, IsNil) 104 | 105 | qosArgs = []string{qosDownName} 106 | _, err = cm.RunCommand("user-qos", qosArgs, options) 107 | c.Assert(err, IsNil) 108 | 109 | os.Remove(qosDownName) 110 | s.removeBucket(bucketName, true, c) 111 | } 112 | 113 | func (s *OssutilCommandSuite) TestUserQosHelpInfo(c *C) { 114 | // mkdir command test 115 | options := OptionMapType{} 116 | 117 | mkArgs := []string{"user-qos"} 118 | _, err := cm.RunCommand("help", mkArgs, options) 119 | c.Assert(err, IsNil) 120 | 121 | mkArgs = []string{} 122 | _, err = cm.RunCommand("help", mkArgs, options) 123 | c.Assert(err, IsNil) 124 | } 125 | -------------------------------------------------------------------------------- /lib/util_sts.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "strconv" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | // Client sts client 19 | type Client struct { 20 | AccessKeyId string 21 | AccessKeySecret string 22 | RoleArn string 23 | SessionName string 24 | } 25 | 26 | // ServiceError sts service error 27 | type ServiceError struct { 28 | Code string 29 | Message string 30 | RequestId string 31 | HostId string 32 | RawMessage string 33 | StatusCode int 34 | } 35 | 36 | // Credentials the credentials obtained by AssumedRole, 37 | // used for the peration of Alibaba Cloud service. 38 | type Credentials struct { 39 | AccessKeyId string 40 | AccessKeySecret string 41 | Expiration time.Time 42 | SecurityToken string 43 | } 44 | 45 | // AssumedRoleUser the user to AssumedRole 46 | type AssumedRoleUser struct { 47 | Arn string 48 | AssumedRoleId string 49 | } 50 | 51 | // Response the response of AssumeRole 52 | type Response struct { 53 | Credentials Credentials 54 | AssumedRoleUser AssumedRoleUser 55 | RequestId string 56 | } 57 | 58 | // Error implement interface error 59 | func (e *ServiceError) Error() string { 60 | return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s", 61 | e.StatusCode, e.Code, e.Message, e.RequestId) 62 | } 63 | 64 | // NewClient New STS Client 65 | func NewClient(accessKeyId, accessKeySecret, roleArn, sessionName string) *Client { 66 | return &Client{ 67 | AccessKeyId: accessKeyId, 68 | AccessKeySecret: accessKeySecret, 69 | RoleArn: roleArn, 70 | SessionName: sessionName, 71 | } 72 | } 73 | 74 | const ( 75 | // StsSignVersion sts sign version 76 | StsSignVersion = "1.0" 77 | // StsAPIVersion sts api version 78 | StsAPIVersion = "2015-04-01" 79 | 80 | // // StsHost sts host 81 | // StsHost = "https://sts.aliyuncs.com/" 82 | 83 | // TimeFormat time fomrat 84 | TimeFormat = "2006-01-02T15:04:05Z" 85 | // RespBodyFormat respone body format 86 | RespBodyFormat = "JSON" 87 | // PercentEncode '/' 88 | PercentEncode = "%2F" 89 | // HTTPGet http get method 90 | HTTPGet = "GET" 91 | ) 92 | 93 | // StsHost sts host 94 | var StsHost = "https://sts.aliyuncs.com/" 95 | 96 | // AssumeRole assume role 97 | func (c *Client) AssumeRole(tokenTimeout uint, stsEndPoint string) (*Response, error) { 98 | if stsEndPoint != "" { 99 | StsHost = stsEndPoint 100 | } 101 | 102 | url, err := c.generateSignedURL(tokenTimeout) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | body, status, err := c.sendRequest(url) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return c.handleResponse(body, status) 113 | } 114 | 115 | // Private function 116 | func (c *Client) generateSignedURL(expiredTime uint) (string, error) { 117 | randId := strings.ToUpper(randStr(24)) 118 | 119 | queryStr := "SignatureVersion=" + StsSignVersion 120 | queryStr += "&Format=" + RespBodyFormat 121 | queryStr += "&Timestamp=" + url.QueryEscape(time.Now().UTC().Format(TimeFormat)) 122 | queryStr += "&RoleArn=" + url.QueryEscape(c.RoleArn) 123 | queryStr += "&RoleSessionName=" + c.SessionName 124 | queryStr += "&AccessKeyId=" + c.AccessKeyId 125 | queryStr += "&SignatureMethod=HMAC-SHA1" 126 | queryStr += "&Version=" + StsAPIVersion 127 | queryStr += "&Action=AssumeRole" 128 | queryStr += "&SignatureNonce=" + randId 129 | queryStr += "&DurationSeconds=" + strconv.FormatUint((uint64)(expiredTime), 10) 130 | 131 | // Sort query string 132 | queryParams, err := url.ParseQuery(queryStr) 133 | if err != nil { 134 | return "", err 135 | } 136 | result := queryParams.Encode() 137 | 138 | strToSign := HTTPGet + "&" + PercentEncode + "&" + url.QueryEscape(result) 139 | 140 | // Generate signature 141 | hashSign := hmac.New(sha1.New, []byte(c.AccessKeySecret+"&")) 142 | hashSign.Write([]byte(strToSign)) 143 | signature := base64.StdEncoding.EncodeToString(hashSign.Sum(nil)) 144 | 145 | // Build url 146 | assumeURL := StsHost + "?" + queryStr + "&Signature=" + url.QueryEscape(signature) 147 | 148 | return assumeURL, nil 149 | } 150 | 151 | func (c *Client) sendRequest(url string) ([]byte, int, error) { 152 | tr := &http.Transport{ 153 | TLSClientConfig: &tls.Config{}, 154 | } 155 | client := &http.Client{Transport: tr} 156 | 157 | resp, err := client.Get(url) 158 | if err != nil { 159 | return nil, -1, err 160 | } 161 | defer resp.Body.Close() 162 | 163 | body, err := ioutil.ReadAll(resp.Body) 164 | return body, resp.StatusCode, err 165 | } 166 | 167 | func (c *Client) handleResponse(responseBody []byte, statusCode int) (*Response, error) { 168 | if statusCode != http.StatusOK { 169 | se := ServiceError{StatusCode: statusCode, RawMessage: string(responseBody)} 170 | err := json.Unmarshal(responseBody, &se) 171 | if err != nil { 172 | return nil, err 173 | } 174 | return nil, &se 175 | } 176 | 177 | resp := Response{} 178 | err := json.Unmarshal(responseBody, &resp) 179 | if err != nil { 180 | return nil, err 181 | } 182 | return &resp, nil 183 | } 184 | -------------------------------------------------------------------------------- /lib/util_sts_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | type StsTestSuite struct { 13 | } 14 | 15 | var _ = Suite(&StsTestSuite{}) 16 | 17 | // Run once when the suite starts running 18 | func (s *StsTestSuite) SetUpSuite(c *C) { 19 | stsAccessID = os.Getenv("OSS_TEST_STS_ID") 20 | stsAccessKeySecret = os.Getenv("OSS_TEST_STS_KEY") 21 | stsARN = os.Getenv("OSS_TEST_STS_ARN") 22 | } 23 | 24 | // Run after each test or benchmark starts running 25 | func (s *StsTestSuite) TearDownSuite(c *C) { 26 | } 27 | 28 | func (s *StsTestSuite) TestSendRequest(c *C) { 29 | client := NewClient("", "", "", "") 30 | _, _, err := client.sendRequest(StsHost) 31 | c.Assert(err, IsNil) 32 | 33 | // negative 34 | _, _, err = client.sendRequest("https//x.y.z.com") 35 | c.Assert(err, NotNil) 36 | } 37 | 38 | func (s *StsTestSuite) TestHandleResponse(c *C) { 39 | client := NewClient("", "", "", "") 40 | 41 | body := "{\"RequestId\":\"784B99C1-895F-426C-8E1F-008955D418FB\"," + 42 | "\"HostId\":\"sts.aliyuncs.com\"," + 43 | "\"Code\":\"NoPermission\"," + 44 | "\"Message\":\"Roles may not be assumed by root accounts.\"}" 45 | resp, err := client.handleResponse([]byte(body), 400) 46 | _, isSuc := err.(*ServiceError) 47 | c.Assert(isSuc, Equals, true) 48 | c.Assert(resp, IsNil) 49 | 50 | body = "{{}}" 51 | resp, err = client.handleResponse([]byte(body), 400) 52 | _, isSuc = err.(*ServiceError) 53 | c.Assert(isSuc, Equals, false) 54 | c.Assert(resp, IsNil) 55 | 56 | body = "{\"RequestId\":\"4AB89022-25A3-4427-84A5-4C7E72BD63BE\"}" 57 | resp, err = client.handleResponse([]byte(body), 200) 58 | c.Assert(err, IsNil) 59 | c.Assert(resp, NotNil) 60 | 61 | body = "{{}}" 62 | resp, err = client.handleResponse([]byte(body), 200) 63 | _, isSuc = err.(*ServiceError) 64 | c.Assert(isSuc, Equals, false) 65 | c.Assert(resp, IsNil) 66 | } 67 | 68 | func (s *StsTestSuite) TestAssumeRoleSuccess(c *C) { 69 | now := time.Now() 70 | client := NewClient(stsAccessID, stsAccessKeySecret, stsARN, "sts_test") 71 | 72 | resp, err := client.AssumeRole(900, "") 73 | if err != nil { 74 | fmt.Println(err) 75 | } else { 76 | fmt.Println("success!") 77 | } 78 | c.Assert(err, IsNil) 79 | 80 | c.Assert(resp.RequestId, Not(Equals), "") 81 | 82 | c.Assert(resp.AssumedRoleUser.Arn, Not(Equals), "") 83 | c.Assert(resp.AssumedRoleUser.AssumedRoleId, Not(Equals), "") 84 | 85 | c.Assert(resp.Credentials.AccessKeyId, Not(Equals), "") 86 | c.Assert(resp.Credentials.AccessKeySecret, Not(Equals), "") 87 | c.Assert(resp.Credentials.SecurityToken, Not(Equals), "") 88 | c.Assert(resp.Credentials.Expiration.After(now), Equals, true) 89 | } 90 | 91 | func (s *StsTestSuite) TestAssumeRoleNegative(c *C) { 92 | // AccessKeyID invalid 93 | client := NewClient("", accessKeySecret, stsARN, "sts_test") 94 | resp, err := client.AssumeRole(900, "") 95 | c.Assert(resp, IsNil) 96 | c.Assert(err, NotNil) 97 | log.Println("Error:", err) 98 | 99 | srvErr, isSuc := err.(*ServiceError) 100 | c.Assert(isSuc, Equals, true) 101 | c.Assert(srvErr.StatusCode, Equals, 400) 102 | log.Println("ServiceError:", srvErr) 103 | 104 | // AccessKeySecret invalid 105 | client = NewClient(stsAccessID, stsAccessKeySecret+" ", stsARN, "sts_test") 106 | resp, err = client.AssumeRole(900, "") 107 | c.Assert(resp, IsNil) 108 | c.Assert(err, NotNil) 109 | 110 | srvErr, isSuc = err.(*ServiceError) 111 | c.Assert(isSuc, Equals, true) 112 | c.Assert(srvErr.StatusCode, Equals, 400) 113 | c.Assert(srvErr.Code, Equals, "SignatureDoesNotMatch") 114 | log.Println("ServiceError:", srvErr) 115 | 116 | // SessionName invalid 117 | client = NewClient(stsAccessID, stsAccessKeySecret, stsARN, "x") 118 | 119 | resp, err = client.AssumeRole(900, "") 120 | c.Assert(resp, IsNil) 121 | c.Assert(err, NotNil) 122 | 123 | srvErr, isSuc = err.(*ServiceError) 124 | c.Assert(isSuc, Equals, true) 125 | c.Assert(srvErr.StatusCode, Equals, 400) 126 | c.Assert(srvErr.Code, Equals, "InvalidParameter.RoleSessionName") 127 | log.Println("ServiceError:", srvErr) 128 | } 129 | -------------------------------------------------------------------------------- /ossutil.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/aliyun/ossutil/lib" 9 | ) 10 | 11 | func main() { 12 | if err := lib.ParseAndRunCommand(); err != nil { 13 | fmt.Printf("Error: %s\n", err) 14 | if strings.Contains(err.Error(), "ErrorCode=NoSuchUpload") { 15 | fmt.Printf("Will remove checkpoint dir '%s' automatically. Please try again.\n", lib.CheckpointDir) 16 | os.RemoveAll(lib.CheckpointDir) 17 | } 18 | if strings.Contains(err.Error(), ": EOF,") { 19 | fmt.Printf("Connection has been closed by remote peer. Please check the network. If you download/upload large file, You can reduce concurrency with the --parallel option and reduce part-size with --part-size (it must greater than the file size divided by 10000. By default, it will retry 10 times when failed, you can increse the retry times with --retry-times option.).\n") 20 | } 21 | os.Exit(1) 22 | } 23 | os.Exit(0) 24 | } 25 | -------------------------------------------------------------------------------- /scripts/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rootdir=$(cd `dirname $0`; cd ..; pwd) 3 | output=$rootdir/dist 4 | version=`cat ./lib/const.go | grep -E "Version.+ string =" | cut -d"=" -f2 | xargs` 5 | 6 | #output folder 7 | mkdir -p $output 8 | 9 | #ossutil-$version-mac-amd64.zip & ossutil-mac-amd64.zip, files: ossutil,ossutilmac64 10 | echo "start build ossutil for darwin on amd64" 11 | cd $rootdir 12 | env GOOS=darwin GOARCH=amd64 go build -o $output/ossutilmac64 13 | 14 | cd $output 15 | mkdir -p ossutil-$version-mac-amd64 16 | cp -f ossutilmac64 ossutil-$version-mac-amd64/ossutil 17 | cp -f ossutilmac64 ossutil-$version-mac-amd64/ossutilmac64 18 | zip -r ossutil-$version-mac-amd64.zip ossutil-$version-mac-amd64 19 | mv -f ossutil-$version-mac-amd64 ossutil-mac-amd64 20 | zip -r ossutil-mac-amd64.zip ossutil-mac-amd64 21 | rm -rf ossutil-mac-amd64 22 | echo "ossutil for darwin on amd64 built successfully" 23 | 24 | #ossutil-$version-mac-arm64.zip & ossutil-mac-arm64.zip, files: ossutil,ossutilmac64 25 | echo "start build ossutil for darwin on arm64" 26 | cd $rootdir 27 | env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o $output/ossutilmacarm64 28 | 29 | cd $output 30 | mkdir -p ossutil-$version-mac-arm64 31 | cp -f ossutilmacarm64 ossutil-$version-mac-arm64/ossutil 32 | cp -f ossutilmacarm64 ossutil-$version-mac-arm64/ossutilmac64 33 | zip -r ossutil-$version-mac-arm64.zip ossutil-$version-mac-arm64 34 | mv -f ossutil-$version-mac-arm64 ossutil-mac-arm64 35 | zip -r ossutil-mac-arm64.zip ossutil-mac-arm64 36 | rm -rf ossutil-mac-arm64 37 | 38 | echo "ossutil for darwin on arm64 built successfully" 39 | 40 | #ossutil-$version-windows-386 & ossutil-windows-386, files: ossutil.bat,ossutil.exe,ossutil32.exe 41 | echo "start build ossutil for windows on 386" 42 | cd $rootdir 43 | env GOOS=windows GOARCH=386 go build -o $output/ossutil32.exe 44 | 45 | cd $output 46 | mkdir -p ossutil-$version-windows-386 47 | cp -f ossutil32.exe ossutil-$version-windows-386/ossutil.exe 48 | cp -f ossutil32.exe ossutil-$version-windows-386/ossutil32.exe 49 | cp -f $rootdir/scripts/ossutil.bat ossutil-$version-windows-386/ossutil.bat 50 | zip -r ossutil-$version-windows-386.zip ossutil-$version-windows-386 51 | mv -f ossutil-$version-windows-386 ossutil32 52 | rm -f ossutil32/ossutil.exe 53 | zip -r ossutil32.zip ossutil32 54 | rm -rf ossutil32 55 | 56 | echo "ossutil for windows on 386 built successfully" 57 | 58 | 59 | #ossutil-$version-windows-amd64 & ossutil-windows-amd64, files: ossutil.bat,ossutil.exe,ossutil64.exe 60 | echo "start build ossutil for windows on amd64" 61 | cd $rootdir 62 | env GOOS=windows GOARCH=amd64 go build -o $output/ossutil64.exe 63 | 64 | cd $output 65 | mkdir -p ossutil-$version-windows-amd64 66 | cp -f ossutil64.exe ossutil-$version-windows-amd64/ossutil.exe 67 | cp -f ossutil64.exe ossutil-$version-windows-amd64/ossutil64.exe 68 | cp -f $rootdir/scripts/ossutil.bat ossutil-$version-windows-amd64/ossutil.bat 69 | zip -r ossutil-$version-windows-amd64.zip ossutil-$version-windows-amd64 70 | mv ossutil-$version-windows-amd64 ossutil64 71 | rm -f ossutil64/ossutil.exe 72 | zip -r ossutil64.zip ossutil64 73 | rm -rf ossutil64 74 | 75 | echo "ossutil for windows on amd64 built successfully" 76 | 77 | #ossutil-$version-linux-386 & ossutil-linux-386, files: ossutil,ossutil32 78 | echo "start build ossutil for linux on 386" 79 | cd $rootdir 80 | env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o $output/ossutil32 81 | 82 | cd $output 83 | mkdir -p ossutil-$version-linux-386 84 | cp -f ossutil32 ossutil-$version-linux-386/ossutil 85 | cp -f ossutil32 ossutil-$version-linux-386/ossutil32 86 | zip -r ossutil-$version-linux-386.zip ossutil-$version-linux-386 87 | mv -f ossutil-$version-linux-386 ossutil-linux-386 88 | zip -r ossutil-linux-386.zip ossutil-linux-386 89 | rm -rf ossutil-linux-386 90 | 91 | echo "ossutil for linux on 386 built successfully" 92 | 93 | #ossutil-$version-linux-amd64 & ossutil-linux-amd64, files: ossutil,ossutil64 94 | echo "start build ossutil for linux on amd64" 95 | cd $rootdir 96 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $output/ossutil64 97 | 98 | cd $output 99 | mkdir -p ossutil-$version-linux-amd64 100 | cp -f ossutil64 ossutil-$version-linux-amd64/ossutil 101 | cp -f ossutil64 ossutil-$version-linux-amd64/ossutil64 102 | zip -r ossutil-$version-linux-amd64.zip ossutil-$version-linux-amd64 103 | mv -f ossutil-$version-linux-amd64 ossutil-linux-amd64 104 | zip -r ossutil-linux-amd64.zip ossutil-linux-amd64 105 | rm -rf ossutil-linux-amd64 106 | 107 | echo "ossutil for linux on amd64 built successfully" 108 | 109 | #ossutil-$version-linux-arm & ossutil-linux-arm, files: ossutil,ossutil32 110 | echo "start build ossutil for linux on arm" 111 | cd $rootdir 112 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o $output/ossutilarm32 113 | 114 | cd $output 115 | mkdir -p ossutil-$version-linux-arm 116 | cp -f ossutilarm32 ossutil-$version-linux-arm/ossutil 117 | cp -f ossutilarm32 ossutil-$version-linux-arm/ossutil32 118 | zip -r ossutil-$version-linux-arm.zip ossutil-$version-linux-arm 119 | mv -f ossutil-$version-linux-arm ossutil-linux-arm 120 | zip -r ossutil-linux-arm.zip ossutil-linux-arm 121 | rm -rf ossutil-linux-arm 122 | 123 | echo "ossutil for linux on arm built successfully" 124 | 125 | #ossutil-$version-linux-arm64 & ossutil-linux-arm64, files: ossutil,ossutil64 126 | echo "start build ossutil for linux on arm64" 127 | cd $rootdir 128 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o $output/ossutilarm64 129 | 130 | cd $output 131 | mkdir -p ossutil-$version-linux-arm64 132 | cp -f ossutilarm64 ossutil-$version-linux-arm64/ossutil 133 | cp -f ossutilarm64 ossutil-$version-linux-arm64/ossutil64 134 | zip -r ossutil-$version-linux-arm64.zip ossutil-$version-linux-arm64 135 | mv -f ossutil-$version-linux-arm64 ossutil-linux-arm64 136 | zip -r ossutil-linux-arm64.zip ossutil-linux-arm64 137 | rm -rf ossutil-linux-arm64 138 | 139 | echo "ossutil for linux on arm64 built successfully" 140 | 141 | #calc hash for zip files 142 | cd $output 143 | for file in $(ls *.zip); do 144 | sha256sum $file >> sha256sum.log 145 | done -------------------------------------------------------------------------------- /scripts/ossutil.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | cmd /k cd /d . 3 | set PATH=.;%PATH% 4 | --------------------------------------------------------------------------------