├── .github ├── ISSUE_TEMPLATE │ ├── Bug_Report.md │ ├── Bug_Report_zh.md │ ├── Demand_Report.md │ └── Demand_Report_zh.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── go.yml │ ├── installer.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README-CN.md ├── README.md ├── SECURITY.md ├── bin ├── README.md ├── aliyun-common ├── aliyun-lookup ├── aliyun-reboot ├── aliyun-reload ├── aliyun-start ├── aliyun-stop ├── build.sh ├── describe_regions ├── print_table └── region_ids ├── cli ├── color.go ├── color_test.go ├── command.go ├── command_help.go ├── command_help_test.go ├── command_test.go ├── completion.go ├── completion_installer.go ├── completion_installer_test.go ├── completion_test.go ├── completion_utils.go ├── completion_utils_test.go ├── context.go ├── context_test.go ├── errors.go ├── errors_test.go ├── exit_code.go ├── exit_code_test.go ├── flag.go ├── flag_field.go ├── flag_field_test.go ├── flag_set.go ├── flag_set_test.go ├── flag_test.go ├── levenshtein.go ├── levenshtein_test.go ├── output.go ├── output_test.go ├── parser.go ├── parser_test.go ├── suggestion.go ├── suggestion_test.go ├── version.go └── version_test.go ├── cloudsso ├── access_configurations.go ├── access_configurations_test.go ├── login.go ├── login_test.go ├── refresh.go ├── refresh_test.go ├── user.go └── user_test.go ├── config ├── configuration.go ├── configuration_test.go ├── configure.go ├── configure_delete.go ├── configure_delete_test.go ├── configure_get.go ├── configure_get_test.go ├── configure_list.go ├── configure_list_test.go ├── configure_set.go ├── configure_set_test.go ├── configure_switch.go ├── configure_switch_test.go ├── configure_test.go ├── flags.go ├── flags_test.go ├── hello.go ├── hello_test.go ├── legacy.go ├── legacy_test.go ├── profile.go └── profile_test.go ├── go.mod ├── go.sum ├── i18n ├── environment.go ├── environment_test.go ├── text.go └── text_test.go ├── install.sh ├── integration ├── ecs_test.ps1 ├── ecs_test.sh ├── https_proxy.sh ├── oss_test.sh ├── parser_test.sh └── vpc_test.sh ├── main ├── main.go └── main_test.go ├── meta ├── api.go ├── api_test.go ├── errors.go ├── errors_test.go ├── product.go ├── product_test.go ├── reader.go ├── reader_test.go ├── repository.go ├── repository_test.go └── versions.json ├── newmeta ├── meta.go └── meta_test.go ├── openapi ├── commando.go ├── commando_test.go ├── errors.go ├── errors_test.go ├── flags.go ├── flags_test.go ├── force_rpc.go ├── force_rpc_test.go ├── invoker.go ├── invoker_test.go ├── library.go ├── library_test.go ├── output_filter.go ├── output_filter_test.go ├── pager.go ├── pager_test.go ├── response_utils.go ├── response_utils_test.go ├── restful.go ├── restful_test.go ├── rpc.go ├── rpc_test.go ├── waiter.go └── waiter_test.go ├── oss ├── CHANGELOG.md ├── LICENSE ├── README-CN.md ├── README.md └── 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 │ ├── cli_bridge.go │ ├── cli_bridge_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 │ ├── option_test.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 ├── pkg ├── osx_installer_logo.png └── productbuild │ ├── Resources │ └── en.lproj │ │ ├── conclusion.html.tmpl │ │ └── welcome.html.tmpl │ └── distribution.xml.tmpl ├── tools ├── build_pkg.sh ├── create_release.sh ├── download_assets.sh ├── finish_release.sh ├── osx-entitlements.plist └── upload_asset.sh ├── util ├── util.go └── util_test.go └── version /.github/ISSUE_TEMPLATE/Bug_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 18 | 19 | * **Command Format**: 20 | 21 | * **Execution Command**: 22 | 23 | * **Output**: 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_Report_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "缺陷问题反馈" 3 | about: 提交缺陷问题反馈 4 | 5 | --- 6 | 7 | 18 | 19 | * **命令格式**: 20 | 21 | * **执行命令**: 22 | 23 | * **输出**: 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Demand_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Demand Report" 3 | about: Create a demand report to help us improve 4 | --- 5 | 6 | 14 | 15 | * **Type of Demand**: 16 | 17 | * **Detailed Description**: 18 | 19 | * **Design**: 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Demand_Report_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "需求反馈" 3 | about: 提交需求问题 4 | 5 | --- 6 | 7 | 15 | 16 | * **需求类型**: 17 | 18 | * **需求描述**: 19 | 20 | * **设计方案**: 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '42 2 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-13, windows-latest] 15 | runs-on: ${{ matrix.os }} 16 | environment: CI 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: "1.23" 26 | 27 | - name: Unit Test 28 | run: make test 29 | - name: Upload coverage infomartion 30 | uses: codecov/codecov-action@v4 31 | env: 32 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 33 | 34 | - name: Smoking Test 35 | run: go run ./main/main.go 36 | 37 | - run: | 38 | make build 39 | make install 40 | bash ./integration/vpc_test.sh 41 | bash ./integration/oss_test.sh 42 | bash ./integration/https_proxy.sh 43 | if: env.ACCESS_KEY_ID != '' && env.ACCESS_KEY_SECRET != '' && matrix.os != 'windows-latest' 44 | env: 45 | ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} 46 | ACCESS_KEY_SECRET: ${{ secrets.ACCESS_KEY_SECRET }} 47 | REGION_ID: ${{ secrets.REGION_ID }} 48 | -------------------------------------------------------------------------------- /.github/workflows/installer.yml: -------------------------------------------------------------------------------- 1 | name: Installer test 2 | 3 | on: 4 | release: 5 | types: [published] 6 | pull_request: 7 | paths: 8 | - 'install.sh' 9 | 10 | jobs: 11 | installer: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-13] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - run: /bin/bash -c "$(cat install.sh)" 21 | - run: | 22 | which aliyun 23 | echo "local version: $(cat ./version)" 24 | echo "aliyun version: $(aliyun version)" 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | workspace.xml 2 | .DS_Store 3 | .tmp 4 | .settings 5 | *~ 6 | .pyc 7 | *.py[co] 8 | aliyuncli/aliyun 9 | # IDEA IDE 10 | .idea* 11 | aliyuncli.egg-info/ 12 | build/ 13 | dist/ 14 | # VSCode 15 | .vscode 16 | # CLI 17 | out/ 18 | releases/ 19 | resource/metas.go 20 | local-* 21 | coverage.txt 22 | oss/lib/ossutil_test* 23 | coverage.html 24 | coverage/ 25 | integration/*.txt 26 | cli-metadata/ 27 | __debug_bin* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "aliyun-openapi-meta"] 2 | path = aliyun-openapi-meta 3 | url = https://github.com/aliyun/aliyun-openapi-meta.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGE LOG 2 | Newest change logs published with release description at https://github.com/aliyun/aliyun-cli/releases . This file just display change logs before version v3.0.1. 3 | 4 | ### v3.0.0 GA Version 5 | 6 | - refactoring cli package design, support composite flag with fields 7 | - refactoring openapi package design, make it more extensible 8 | - support `--quiet` flag 9 | - support `--dryrun` flag 10 | - support `aliyun oss --profile xxx` 11 | 12 | ### 0.81 13 | 14 | - support `--output` 15 | - support `--waiter` 16 | - use `go -ldflags` to enable single Version in Makefile 17 | 18 | ### 0.80 19 | 20 | - support auto completion for zsh/bash 21 | - fix bugs for RepeatList parameter 22 | - refactoring RamRoleArn and EcsRamRole authenticate flow 23 | - oss command can support RamRoleArn and EcsRamRole authenticate mode 24 | - oss command can support --profile and other configure flags 25 | 26 | ### 0.70 27 | 28 | - integrate `ossutil` toolset with aliyun-cli 29 | - optimize `--help` command messages 30 | - config flags (such as ak, profile, sts) can used with openapi call 31 | - support `configure delete` 32 | - fix bug with restful force call 33 | 34 | ### 0.61 35 | 36 | - support --all-pages flags to merge pager APIs 37 | 38 | ### 0.60 39 | 40 | - support suggestions 41 | - optimized error and help message 42 | - integrate more completion of metadata 43 | - fix some caller bugs 44 | 45 | ### 0.50 46 | 47 | - support i18n `aliyun-openapi-meta` 48 | - full support `configure [get|set|list]` command 49 | - optimize help 50 | - support `--quiet` flag 51 | 52 | ### 0.33 53 | 54 | - fix bug for error processing when rpc/restful call 55 | - auto add Content-Type header for restful call 56 | 57 | ### 0.32 58 | 59 | - auto migrate legacy settings 60 | 61 | ### 0.31 62 | 63 | - fix bug of check parameters, skip Action, Region parameters 64 | - support `aliyun configure list` command 65 | 66 | ### 0.30 67 | 68 | - integrate with 64 products meta 69 | - implemented help command for product and api 70 | - support fully certificated mode AK|StsToken|RamRoleArn|EcsRamRole|RsaKeyPair, 71 | 72 | ### 0.16 73 | 74 | - support --content-type flag to set Header 75 | - support --body-file flat to use file as body input 76 | 77 | ### 0.15 78 | 79 | - support ecs-ram-role 80 | - fix cross platform build problem 81 | - test after configure 82 | 83 | ### 0.12 84 | 85 | - fix bug for configure 86 | - ignore case of ProductName 87 | 88 | ### 0.11 89 | 90 | - Support simple ROA call 91 | 92 | ### 0.1 93 | 94 | - Refactoring with golang 95 | - Basic configure 96 | - Auto endpoint locator 97 | - 2018.1.11 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export VERSION=3.0.0-beta 2 | export RELEASE_PATH="releases/aliyun-cli-${VERSION}" 3 | 4 | all: build 5 | publish: build build_mac build_linux build_windows build_linux_arm64 gen_version 6 | 7 | deps: 8 | git submodule update --init --recursive 9 | 10 | clean: 11 | rm -rf out/* 12 | 13 | build: deps 14 | go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 15 | 16 | install: build 17 | cp out/aliyun /usr/local/bin 18 | 19 | build_mac: 20 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 21 | tar zcvf out/aliyun-cli-macosx-${VERSION}-amd64.tgz -C out aliyun 22 | aliyun oss cp out/aliyun-cli-macosx-${VERSION}-amd64.tgz oss://aliyun-cli --force --profile oss 23 | 24 | build_linux: 25 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 26 | tar zcvf out/aliyun-cli-linux-${VERSION}-amd64.tgz -C out aliyun 27 | aliyun oss cp out/aliyun-cli-linux-${VERSION}-amd64.tgz oss://aliyun-cli --force --profile oss 28 | 29 | build_windows: 30 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o aliyun.exe main/main.go 31 | zip -r out/aliyun-cli-windows-${VERSION}-amd64.zip aliyun.exe 32 | aliyun oss cp out/aliyun-cli-windows-${VERSION}-amd64.zip oss://aliyun-cli --force --profile oss 33 | rm aliyun.exe 34 | 35 | build_linux_arm64: 36 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 37 | tar zcvf out/aliyun-cli-linux-${VERSION}-arm64.tgz -C out aliyun 38 | aliyun oss cp out/aliyun-cli-linux-${VERSION}-arm64.tgz oss://aliyun-cli --force --profile oss 39 | 40 | gen_version: 41 | -rm out/version 42 | echo ${VERSION} >> out/version 43 | aliyun oss cp out/version oss://aliyun-cli --force --profile oss 44 | 45 | git_release: clean build make_release_dir release_mac release_linux release_linux_arm64 release_windows 46 | 47 | make_release_dir: 48 | mkdir -p ${RELEASE_PATH} 49 | 50 | release_mac: 51 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 52 | tar zcvf ${RELEASE_PATH}/aliyun-cli-darwin-amd64.tar.gz -C out aliyun 53 | 54 | release_mac_arm64: 55 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 56 | tar zcvf ${RELEASE_PATH}/aliyun-cli-darwin-arm64.tar.gz -C out aliyun 57 | 58 | release_linux: 59 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 60 | tar zcvf ${RELEASE_PATH}/aliyun-cli-linux-amd64.tar.gz -C out aliyun 61 | 62 | release_linux_arm64: 63 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o out/aliyun main/main.go 64 | tar zcvf ${RELEASE_PATH}/aliyun-cli-linux-arm64.tar.gz -C out aliyun 65 | 66 | release_windows: 67 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o aliyun.exe main/main.go 68 | zip -r ${RELEASE_PATH}/aliyun-cli-windows-amd64.exe.zip aliyun.exe 69 | rm aliyun.exe 70 | 71 | fmt: 72 | go fmt ./util/... ./cli/... ./config/... ./i18n/... ./main/... ./openapi/... ./oss/... ./resource/... ./meta/... 73 | 74 | test: 75 | LANG="en_US.UTF-8" go test -race -coverprofile=coverage.txt -covermode=atomic ./util/... ./cli/... ./config/... ./i18n/... ./main/... ./openapi/... ./meta/... 76 | go tool cover -html=coverage.txt -o coverage.html 77 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Vulnerability Reporting 2 | 3 | We consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. 4 | 5 | If you discover a security vulnerability within our project, we would like you to inform us as soon as possible in a responsible manner. Please follow these steps for reporting: 6 | 7 | - Send your report directly to Alibaba Security via the vulnerability reporting page: . This will ensure that your report is handled in a timely and secure manner. 8 | - Do not disclose the issue publicly until we’ve had a chance to address it. Public disclosure of a security vulnerability could put the entire community at risk. 9 | - Provide as much information as possible about the potential vulnerability, so we can reproduce and fix the issue quickly. -------------------------------------------------------------------------------- /bin/aliyun-common: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -x 3 | function lookup_any() 4 | { 5 | 6 | instanceId=${1} 7 | 8 | for region in $(cat ./region_ids); 9 | do 10 | aliyun Ecs DescribeInstances --RegionId $region --PageSize 100 |jq '.Instances.Instance'| jq '.[] | select(.InstanceId == "'$instanceId'" or .PublicIpAddress.IpAddress[0] == "'$instanceId'" or .NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress == "'$instanceId'" or .HostName == "'$instanceId'")' > .instance_temp 11 | if [[ $(cat .instance_temp | wc -l) -gt 0 ]]; 12 | then 13 | mv .instance_temp .instance_found 14 | [[ $DEBUG == true ]] && cat .instance_found 15 | export instance_id=$(jq -r '.InstanceId' .instance_found) 16 | export region_id=$(jq -r '.RegionId' .instance_found) 17 | export image_id=$(jq -r '.ImageId' .instance_found) 18 | export instance_status=$(jq -r '.Status' .instance_found) 19 | jq -r '["ID ", "Hostname ", "Public IP", "Internal IP", "ZoneId", " Status", "ImageId"], ["---------------------", "---------------------", "-------------", "-------------", "-------------", "------", "------------------"], ( [.InstanceId, .HostName, .PublicIpAddress.IpAddress[0], .NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress, .ZoneId, .Status, .ImageId]) | @tsv' .instance_found 20 | break 21 | fi 22 | done 23 | } 24 | 25 | function wait_until() 26 | { 27 | expected_status=${1-Running} 28 | while [[ $instance_status != $expected_status ]]; 29 | do 30 | sleep 5 31 | lookup_any $instance_id 32 | done 33 | } 34 | -------------------------------------------------------------------------------- /bin/aliyun-lookup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | source aliyun-common 5 | 6 | lookup_any $* 7 | -------------------------------------------------------------------------------- /bin/aliyun-reboot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | source aliyun-common 5 | 6 | lookup_any $1 7 | 8 | [[ $instance_status == 'Running' ]] && aliyun Ecs RebootInstance --RegionId $region_id --InstanceId $instance_id && lookup_any $1 9 | -------------------------------------------------------------------------------- /bin/aliyun-reload: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | source aliyun-common 5 | 6 | lookup_any $1 7 | imageid=$image_id # using default image id 8 | # Overide image id with $2 9 | [[ $2 != '' ]] && imageid=$2 10 | ##Try to stop and start instance 11 | if [[ $FORCE != 'true' ]]; 12 | then 13 | echo "\nConfirm to restart instance "$instance_id" at region "$region_id" yes(y/n)": 14 | read ans 15 | fi 16 | #echo $ans 17 | if [[ $FORCE == 'true' ]] || [[ $ans == 'y' ]] || [[ $ans == 'yes' ]]; 18 | then 19 | echo Stopping $instance_id at $region_id, current status: $instance_status 20 | [[ $instance_status == 'Running' ]] && aliyun Ecs StopInstance --RegionId $region_id --InstanceId $instance_id 21 | #lookup_any $instance_id 22 | echo Replacing system of $instance_id at $region_id with $imageid, current status: $instance_status 23 | wait_until Stopped && aliyun Ecs ReplaceSystemDisk --InstanceId $instance_id --ImageId $imageid 24 | #lookup_any $instance_id 25 | echo Starting system of $instance_id at $region_id, current status: $instance_status 26 | sleep 5 && aliyun Ecs StartInstance --RegionId $region_id --InstanceId $instance_id 27 | wait_until Running 28 | fi 29 | -------------------------------------------------------------------------------- /bin/aliyun-start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | source aliyun-common 5 | 6 | lookup_any $1 7 | 8 | [[ $instance_status == Stopped ]] || [[ $instance_status == Stopping ]] && aliyun Ecs StartInstance --RegionId $region_id --InstanceId $instance_id && lookup_any $1 9 | -------------------------------------------------------------------------------- /bin/aliyun-stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | source aliyun-common 5 | 6 | lookup_any $1 7 | [[ $instance_status == 'Stopped' ]] && exit 8 | 9 | force=${FORCE-false} 10 | ##Try to stop instance 11 | echo 12 | if [[ $FORCE != 'true' ]]; 13 | then 14 | echo "Confirm to stop instance "$instance_id" at region "$region_id" yes(y/n)": 15 | read ans 16 | fi 17 | #echo $ans 18 | if [[ $FORCE == 'true' ]] || [[ $ans == 'y' ]] || [[ $ans == 'yes' ]]; 19 | then 20 | echo Stopping $instance_id at $region_id 21 | aliyun Ecs StopInstance --RegionId $region_id --InstanceId $instance_id --ForceStop $force 22 | lookup_any $instance_id 23 | fi 24 | -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o ${EXECUTABLE} $(MAINFILE) -------------------------------------------------------------------------------- /bin/describe_regions: -------------------------------------------------------------------------------- 1 | aliyun ecs DescribeRegions | jq '.Regions.Region'| jq -r '["RegionId ", "LocalName "], ["---------------------", "---------------------"],(.[] | [.RegionId, .LocalName]) | @tsv' -------------------------------------------------------------------------------- /bin/print_table: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | region=${1-cn-hangzhou} 3 | #aliyun Ecs DescribeInstances --RegionId $region | jq '.Instances.Instance' | jq -r '["ID ", "Hostname ", "Public IP", "Internal IP"], ["---------------------", "---------------------", "-------------", "-------------"],(.[] | [.InstanceId, .HostName, .PublicIpAddress.IpAddress[0], .NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress]) | @tsv' 4 | 5 | aliyun Ecs DescribeInstances --RegionId $region | jq '.Instances.Instance'| jq -r '["ID ", "Hostname ", "Public IP", "Internal IP", "ZoneId", " Status"], ["---------------------", "---------------------", "-------------", "-------------", "-------------", "------"], ( .[] | [.InstanceId, .HostName, .PublicIpAddress.IpAddress[0], .NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress, .ZoneId, .Status]) | @tsv' 6 | -------------------------------------------------------------------------------- /bin/region_ids: -------------------------------------------------------------------------------- 1 | cn-qingdao 2 | cn-beijing 3 | cn-zhangjiakou 4 | cn-huhehaote 5 | cn-hangzhou 6 | cn-shanghai 7 | cn-shenzhen 8 | cn-hongkong 9 | ap-northeast-1 10 | ap-southeast-1 11 | ap-southeast-2 12 | ap-southeast-3 13 | us-east-1 14 | us-west-1 15 | me-east-1 16 | eu-central-1 17 | -------------------------------------------------------------------------------- /cli/color_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestColor(t *testing.T) { 25 | assert.False(t, isNoColor()) 26 | os.Setenv("NO_COLOR", "1") 27 | assert.True(t, isNoColor()) 28 | os.Setenv("NO_COLOR", "true") 29 | assert.True(t, isNoColor()) 30 | os.Setenv("NO_COLOR", "") 31 | assert.False(t, isNoColor()) 32 | } 33 | 34 | func TestColorized(t *testing.T) { 35 | assert.Equal(t, "\x1b[0;31mtext\x1b[0m", Colorized(Red, "text")) 36 | os.Setenv("NO_COLOR", "1") 37 | assert.Equal(t, "text", Colorized(Red, "text")) 38 | os.Setenv("NO_COLOR", "") 39 | } 40 | 41 | func TestCotainWriter(t *testing.T) { 42 | writer := new(bytes.Buffer) 43 | n, err := PrintWithColor(writer, "red", "a") 44 | assert.Equal(t, "reda\033[0m", writer.String()) 45 | assert.Equal(t, 8, n) 46 | assert.Nil(t, err) 47 | 48 | writer.Reset() 49 | n, err = Notice(writer, "a") 50 | assert.Equal(t, "\033[1;33ma\x1b[0m", writer.String()) 51 | assert.Equal(t, 12, n) 52 | assert.Nil(t, err) 53 | 54 | writer.Reset() 55 | n, err = Error(writer, "a") 56 | assert.Equal(t, "\033[1;31ma\x1b[0m", writer.String()) 57 | assert.Equal(t, 12, n) 58 | assert.Nil(t, err) 59 | 60 | writer.Reset() 61 | n, err = Noticef(writer, "%s", "a") 62 | assert.Equal(t, "\033[1;33ma\x1b[0m", writer.String()) 63 | assert.Equal(t, 12, n) 64 | assert.Nil(t, err) 65 | 66 | writer.Reset() 67 | n, err = Errorf(writer, "%s", "a") 68 | assert.Equal(t, "\033[1;31ma\x1b[0m", writer.String()) 69 | assert.Equal(t, 12, n) 70 | assert.Nil(t, err) 71 | 72 | writer.Reset() 73 | n, err = PrintfWithColor(writer, "red", "%s", "a") 74 | assert.Equal(t, "reda\033[0m", writer.String()) 75 | assert.Equal(t, 8, n) 76 | assert.Nil(t, err) 77 | } 78 | -------------------------------------------------------------------------------- /cli/command_help.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "fmt" 18 | "text/tabwriter" 19 | ) 20 | 21 | func (c *Command) PrintHead(ctx *Context) { 22 | Printf(ctx.Stdout(), "%s\n", c.Short.Text()) 23 | } 24 | 25 | func (c *Command) PrintUsage(ctx *Context) { 26 | if c.Usage != "" { 27 | Printf(ctx.Stdout(), "\nUsage:\n %s\n", c.GetUsageWithParent()) 28 | } else { 29 | c.PrintSubCommands(ctx) 30 | } 31 | } 32 | 33 | func (c *Command) PrintSample(ctx *Context) { 34 | if c.Sample != "" { 35 | Printf(ctx.Stdout(), "\nSample:\n %s\n", c.Sample) 36 | } 37 | } 38 | 39 | func (c *Command) PrintSubCommands(ctx *Context) { 40 | if len(c.subCommands) > 0 { 41 | Printf(ctx.Stdout(), "\nCommands:\n") 42 | w := tabwriter.NewWriter(ctx.Stdout(), 8, 0, 1, ' ', 0) 43 | for _, cmd := range c.subCommands { 44 | if cmd.Hidden { 45 | continue 46 | } 47 | fmt.Fprintf(w, " %s\t%s\n", cmd.Name, cmd.Short.Text()) 48 | } 49 | w.Flush() 50 | } 51 | } 52 | 53 | func (c *Command) PrintFlags(ctx *Context) { 54 | if len(c.Flags().Flags()) == 0 { 55 | return 56 | } 57 | Printf(ctx.Stdout(), "\nFlags:\n") 58 | w := tabwriter.NewWriter(ctx.Stdout(), 8, 0, 1, ' ', 0) 59 | fs := c.Flags() 60 | if ctx != nil { 61 | fs = ctx.Flags() 62 | } 63 | for _, flag := range fs.Flags() { 64 | if flag.Hidden { 65 | continue 66 | } 67 | s := "--" + flag.Name 68 | if flag.Shorthand != 0 { 69 | s = s + ",-" + string(flag.Shorthand) 70 | } 71 | fmt.Fprintf(w, " %s\t%s\n", s, flag.Short.Text()) 72 | } 73 | w.Flush() 74 | } 75 | 76 | func (c *Command) PrintFailed(ctx *Context, err error, suggestion string) { 77 | Errorf(ctx.Stderr(), "ERROR: %v\n", err) 78 | Printf(ctx.Stderr(), "%s\n", suggestion) 79 | } 80 | 81 | func (c *Command) PrintTail(ctx *Context) { 82 | Printf(ctx.Stdout(), "\nUse `%s --help` for more information.\n", c.Name) 83 | } 84 | -------------------------------------------------------------------------------- /cli/command_help_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "testing" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/i18n" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestCmdPrint(t *testing.T) { 26 | w := new(bytes.Buffer) 27 | stderr := new(bytes.Buffer) 28 | ctx := NewCommandContext(w, stderr) 29 | c := &Command{ 30 | Name: "aliyun", 31 | EnableUnknownFlag: true, 32 | SuggestDistance: 2, 33 | Usage: "aliyun [subcmd]", 34 | Short: i18n.T( 35 | "use `--profile ` to select profile", 36 | "使用 `--profile ` 指定操作的配置集", 37 | ), 38 | Sample: "aliyun oss", 39 | flags: NewFlagSet(), 40 | } 41 | 42 | // PrintHead 43 | c.PrintHead(ctx) 44 | assert.Equal(t, "use `--profile ` to select profile\n", w.String()) 45 | 46 | //PrintUsage 47 | w.Reset() 48 | stderr.Reset() 49 | c.PrintUsage(ctx) 50 | assert.Equal(t, "\nUsage:\n aliyun [subcmd]\n", w.String()) 51 | w.Reset() 52 | stderr.Reset() 53 | c.Usage = "" 54 | assert.Empty(t, w.String()) 55 | 56 | //PrintSample 57 | w.Reset() 58 | stderr.Reset() 59 | c.PrintSample(ctx) 60 | assert.Equal(t, "\nSample:\n aliyun oss\n", w.String()) 61 | 62 | //PrintSubCommands 63 | w.Reset() 64 | stderr.Reset() 65 | subcmd := &Command{ 66 | Name: "oss", 67 | SuggestDistance: 2, 68 | Usage: "oss flag", 69 | Short: i18n.T( 70 | "subcmd test", 71 | "子命令测试", 72 | ), 73 | } 74 | c.AddSubCommand(subcmd) 75 | c.PrintSubCommands(ctx) 76 | assert.Equal(t, "\nCommands:\n oss subcmd test\n", w.String()) 77 | 78 | //PrintFlags 79 | w.Reset() 80 | stderr.Reset() 81 | c.PrintFlags(ctx) 82 | assert.Empty(t, w.String()) 83 | c.flags.flags = []*Flag{{Name: "output", Shorthand: 'o'}} 84 | w.Reset() 85 | stderr.Reset() 86 | c.PrintFlags(ctx) 87 | assert.Equal(t, "\nFlags:\n", w.String()) 88 | w.Reset() 89 | stderr.Reset() 90 | ctx.flags.flags = []*Flag{{Name: "output", Shorthand: 'o', Short: i18n.T("o test", "")}, {Name: "filter", Short: i18n.T("", "")}, {Name: "hidden", Hidden: true}} 91 | c.PrintFlags(ctx) 92 | assert.Equal(t, "\nFlags:\n --output,-o o test\n --filter \n", w.String()) 93 | 94 | //PrintFailed 95 | w.Reset() 96 | stderr.Reset() 97 | c.PrintFailed(ctx, errors.New("you failed"), "come on again") 98 | assert.Equal(t, "\x1b[1;31mERROR: you failed\n\x1b[0mcome on again\n", stderr.String()) 99 | 100 | //PrintTail 101 | w.Reset() 102 | stderr.Reset() 103 | c.PrintTail(ctx) 104 | assert.Equal(t, "\nUse `aliyun --help` for more information.\n", w.String()) 105 | } 106 | -------------------------------------------------------------------------------- /cli/completion.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "strconv" 20 | "strings" 21 | ) 22 | 23 | type Completion struct { 24 | Current string 25 | Args []string 26 | line string 27 | point int 28 | } 29 | 30 | func ParseCompletionForShell() *Completion { 31 | return ParseCompletion(os.Getenv("COMP_LINE"), os.Getenv("COMP_POINT")) 32 | } 33 | 34 | func ParseCompletion(line, point string) *Completion { 35 | if line == "" { 36 | return nil 37 | } 38 | 39 | p, err := strconv.Atoi(point) 40 | 41 | if err != nil { 42 | return nil 43 | } 44 | 45 | if p >= len(line) { 46 | p = len(line) 47 | } 48 | 49 | args := parseLineForCompletion(line, p) 50 | current := "" 51 | 52 | if strings.HasSuffix(line, " ") { 53 | if len(args) == 1 { 54 | args = []string{} 55 | } else { 56 | args = args[1:] 57 | } 58 | } else { 59 | if len(args) > 1 { 60 | current = args[len(args)-1] 61 | args = args[1 : len(args)-1] 62 | } else { 63 | panic(fmt.Errorf("unexcepted args %v for line '%s'", args, line)) 64 | } 65 | } 66 | 67 | return &Completion{ 68 | Current: current, 69 | Args: args, 70 | line: line, 71 | point: p, 72 | } 73 | } 74 | 75 | func (c *Completion) GetCurrent() string { 76 | return c.Current 77 | } 78 | 79 | func (c *Completion) GetArgs() []string { 80 | return c.Args 81 | } 82 | 83 | func parseLineForCompletion(line string, point int) []string { 84 | if point > len(line) { 85 | panic(fmt.Errorf("%s[%d] out of range", line, point)) 86 | } 87 | 88 | var quote rune 89 | var backslash bool 90 | var word []rune 91 | cl := make([]string, 0) 92 | for _, char := range line[:point] { 93 | if backslash { 94 | word = append(word, char) 95 | backslash = false 96 | continue 97 | } 98 | if char == '\\' { 99 | word = append(word, char) 100 | backslash = true 101 | continue 102 | } 103 | 104 | switch quote { 105 | case 0: 106 | switch char { 107 | case '\'', '"': 108 | word = append(word, char) 109 | quote = char 110 | case ' ', '\t': 111 | if word != nil { 112 | cl = append(cl, string(word)) 113 | } 114 | word = nil 115 | default: 116 | word = append(word, char) 117 | } 118 | case '\'': 119 | word = append(word, char) 120 | if char == '\'' { 121 | quote = 0 122 | } 123 | case '"': 124 | word = append(word, char) 125 | if char == '"' { 126 | quote = 0 127 | } 128 | } 129 | } 130 | 131 | return append(cl, string(word)) 132 | } 133 | -------------------------------------------------------------------------------- /cli/completion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestParseCompletionForShell(t *testing.T) { 23 | cp := ParseCompletionForShell() 24 | assert.Nil(t, cp) 25 | } 26 | 27 | func TestParseCompletion(t *testing.T) { 28 | cp := ParseCompletion("", "") 29 | assert.Nil(t, cp) 30 | 31 | cp = ParseCompletion("", "s") 32 | assert.Nil(t, cp) 33 | 34 | cp = ParseCompletion("line", "invalid number") 35 | assert.Nil(t, cp) 36 | 37 | cp = ParseCompletion("cdn ", "5") 38 | assert.Equal(t, &Completion{Current: "", Args: []string{""}, line: "cdn ", point: 4}, cp) 39 | 40 | cp = ParseCompletion(" ", "5") 41 | assert.Equal(t, &Completion{Current: "", Args: []string{}, line: " ", point: 1}, cp) 42 | 43 | cp = ParseCompletion("name Mrx aa", "13") 44 | assert.Equal(t, &Completion{Current: "aa", Args: []string{"Mrx"}, line: "name Mrx aa", point: 11}, cp) 45 | 46 | defer func() { 47 | reerr := recover() 48 | err, ok := reerr.(error) 49 | assert.True(t, ok) 50 | assert.EqualError(t, err, "unexcepted args [name] for line 'name'") 51 | }() 52 | ParseCompletion("name", "5") 53 | } 54 | 55 | func TestParseLineForCompletion(t *testing.T) { 56 | cl := parseLineForCompletion(`c\d'a'"hao" c\alok`, 18) 57 | assert.Subset(t, cl, []string{`c\d'a'"hao"`, `c\alok`}) 58 | assert.Len(t, cl, 2) 59 | 60 | defer func() { 61 | err := recover() 62 | assert.NotNil(t, err) 63 | }() 64 | parseLineForCompletion(`c\d'a'"hao" c\alok`, 20) 65 | } 66 | func TestCompletionGet(t *testing.T) { 67 | cp := ParseCompletion("name Mrx aa", "13") 68 | assert.Equal(t, "aa", cp.GetCurrent()) 69 | assert.Equal(t, []string{"Mrx"}, cp.GetArgs()) 70 | } 71 | -------------------------------------------------------------------------------- /cli/completion_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bufio" 18 | "fmt" 19 | "io" 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | func lineInFile(name string, lookFor string) bool { 25 | f, err := os.Open(name) 26 | if err != nil { 27 | return false 28 | } 29 | defer f.Close() 30 | r := bufio.NewReader(f) 31 | prefix := []byte{} 32 | for { 33 | line, isPrefix, err := r.ReadLine() 34 | if err == io.EOF { 35 | return false 36 | } 37 | if err != nil { 38 | return false 39 | } 40 | if isPrefix { 41 | prefix = append(prefix, line...) 42 | continue 43 | } 44 | line = append(prefix, line...) 45 | if string(line) == lookFor { 46 | return true 47 | } 48 | prefix = prefix[:0] 49 | } 50 | } 51 | 52 | func createFile(name string, content string) error { 53 | f, err := os.Create(name) 54 | if err != nil { 55 | return err 56 | } 57 | defer f.Close() 58 | _, err = f.WriteString(fmt.Sprintf("%s\n", content)) 59 | return err 60 | } 61 | 62 | func appendToFile(name string, content string) error { 63 | f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0) 64 | if err != nil { 65 | return err 66 | } 67 | defer f.Close() 68 | _, err = f.WriteString(fmt.Sprintf("\n%s\n", content)) 69 | return err 70 | } 71 | 72 | func removeFromFile(name string, content string) error { 73 | backup := name + ".bck" 74 | err := copyFile(name, backup) 75 | if err != nil { 76 | return err 77 | } 78 | temp, err := removeContentToTempFile(name, content) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | err = copyFile(temp, name) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | return os.Remove(backup) 89 | } 90 | 91 | func removeContentToTempFile(name, content string) (string, error) { 92 | rf, err := os.Open(name) 93 | if err != nil { 94 | return "", err 95 | } 96 | defer rf.Close() 97 | wf, err := os.CreateTemp("", "complete-") 98 | if err != nil { 99 | return "", err 100 | } 101 | defer wf.Close() 102 | 103 | r := bufio.NewReader(rf) 104 | prefix := []byte{} 105 | for { 106 | line, isPrefix, err := r.ReadLine() 107 | if err == io.EOF { 108 | break 109 | } 110 | if err != nil { 111 | return "", err 112 | } 113 | if isPrefix { 114 | prefix = append(prefix, line...) 115 | continue 116 | } 117 | line = append(prefix, line...) 118 | str := string(line) 119 | if str == content { 120 | continue 121 | } 122 | wf.WriteString(str + "\n") 123 | prefix = prefix[:0] 124 | } 125 | return wf.Name(), nil 126 | } 127 | 128 | func copyFile(src string, dst string) error { 129 | in, err := os.Open(src) 130 | if err != nil { 131 | return err 132 | } 133 | defer in.Close() 134 | out, err := os.Create(dst) 135 | if err != nil { 136 | return err 137 | } 138 | defer out.Close() 139 | _, err = io.Copy(out, in) 140 | return err 141 | } 142 | 143 | func getBinaryPath() (string, error) { 144 | bin, err := os.Executable() 145 | if err != nil { 146 | return "", err 147 | } 148 | return filepath.Abs(bin) 149 | } 150 | 151 | func rcFile(name string) string { 152 | path := filepath.Join(getHomeDir(), name) 153 | fmt.Println(path) 154 | if _, err := os.Stat(path); err != nil { 155 | return "" 156 | } 157 | 158 | return path 159 | } 160 | -------------------------------------------------------------------------------- /cli/completion_utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bufio" 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func readfile(t *testing.T, name string) string { 25 | file, err := os.Open(name) 26 | assert.Nil(t, err) 27 | 28 | reader := bufio.NewReader(file) 29 | var str string 30 | buf := make([]byte, 1024) 31 | for { 32 | n, err := reader.Read(buf) 33 | if n < 1024 || err != nil { 34 | str += string(buf[:n]) 35 | break 36 | } 37 | str += string(buf) 38 | } 39 | file.Close() 40 | return str 41 | } 42 | 43 | func TestCreateFile(t *testing.T) { 44 | err := createFile("test.txt", "oss") 45 | assert.Nil(t, err) 46 | err1 := createFile(" /?&^%$#*/***/./.", "OOO") 47 | assert.NotNil(t, err1) 48 | str := readfile(t, "test.txt") 49 | assert.Equal(t, "oss\n", str) 50 | assert.Nil(t, os.Remove("test.txt")) 51 | } 52 | 53 | func TestOpToFile(t *testing.T) { 54 | err := createFile("test.txt", "oss") 55 | assert.Nil(t, err) 56 | 57 | //appendToFile 58 | err = appendToFile("test.txt", "cdn") 59 | assert.Nil(t, err) 60 | str := readfile(t, "test.txt") 61 | assert.Equal(t, "oss\n\ncdn\n", str) 62 | 63 | //removeFromFile 64 | err = removeFromFile("test.txt", "cdn") 65 | assert.Nil(t, err) 66 | str = readfile(t, "test.txt") 67 | assert.Equal(t, "oss\n\n", str) 68 | 69 | assert.Nil(t, os.Remove("test.txt")) 70 | } 71 | 72 | func TestRemoveContentToTempFile(t *testing.T) { 73 | err := createFile("test.txt", "oss") 74 | assert.Nil(t, err) 75 | _, err = removeContentToTempFile("test.txt", "oss") 76 | assert.Nil(t, err) 77 | assert.Nil(t, os.Remove("test.txt")) 78 | } 79 | 80 | func TestCopyFile(t *testing.T) { 81 | err := createFile("test.txt", "oss") 82 | assert.Nil(t, err) 83 | err = copyFile("test.txt", "testcp.txt") 84 | assert.Nil(t, err) 85 | 86 | defer func() { 87 | if _, err = os.Stat("test.txt"); err == nil { 88 | os.Remove("test.txt") 89 | } 90 | if _, err = os.Stat("testcp.txt"); err == nil { 91 | os.Remove("testcp.txt") 92 | } 93 | }() 94 | 95 | test, err := os.Open("test.txt") 96 | assert.Nil(t, err) 97 | var teststr string 98 | test.WriteString(teststr) 99 | test.Close() 100 | testcp, err := os.Open("testcp.txt") 101 | assert.Nil(t, err) 102 | var testcpstr string 103 | testcp.WriteString(testcpstr) 104 | testcp.Close() 105 | assert.Equal(t, teststr, testcpstr) 106 | 107 | } 108 | 109 | func TestGetBinaryPath(t *testing.T) { 110 | bpath, err := getBinaryPath() 111 | assert.Nil(t, err) 112 | assert.NotNil(t, bpath) 113 | } 114 | 115 | func TestRCFile(t *testing.T) { 116 | path := rcFile("aa") 117 | assert.Empty(t, path) 118 | name := getHomeDir() + "/hh" 119 | file, err := os.Create(name) 120 | defer func() { 121 | err := file.Close() 122 | assert.Nil(t, err) 123 | }() 124 | assert.Nil(t, err) 125 | path = rcFile("hh") 126 | assert.NotNil(t, path) 127 | os.Remove(name) 128 | } 129 | -------------------------------------------------------------------------------- /cli/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import "fmt" 17 | 18 | // If command.Execute return Noticeable error, print i18n Notice under error information 19 | type ErrorWithTip interface { 20 | GetTip(lang string) string 21 | } 22 | 23 | type errorWithTip struct { 24 | err error 25 | tip string 26 | } 27 | 28 | func NewErrorWithTip(err error, tipFormat string, args ...interface{}) error { 29 | return &errorWithTip{ 30 | err: err, 31 | tip: fmt.Sprintf(tipFormat, args...), 32 | } 33 | } 34 | 35 | func (e *errorWithTip) Error() string { 36 | return e.err.Error() 37 | } 38 | 39 | func (e *errorWithTip) GetTip(lang string) string { 40 | return e.tip 41 | } 42 | 43 | // OUTPUT: 44 | // Error: "'%s' is not a valid command 45 | // 46 | // {Hint} 47 | type InvalidCommandError struct { 48 | Name string 49 | ctx *Context 50 | } 51 | 52 | func NewInvalidCommandError(name string, ctx *Context) error { 53 | return &InvalidCommandError{ 54 | Name: name, 55 | ctx: ctx, 56 | } 57 | } 58 | 59 | func (e *InvalidCommandError) Error() string { 60 | return fmt.Sprintf("'%s' is not a vaild command", e.Name) 61 | } 62 | 63 | func (e *InvalidCommandError) GetSuggestions() []string { 64 | cmd := e.ctx.command 65 | return cmd.GetSuggestions(e.Name) 66 | } 67 | 68 | type InvalidFlagError struct { 69 | Flag string 70 | ctx *Context 71 | } 72 | 73 | func NewInvalidFlagError(name string, ctx *Context) error { 74 | return &InvalidFlagError{ 75 | Flag: name, 76 | ctx: ctx, 77 | } 78 | } 79 | 80 | func (e *InvalidFlagError) Error() string { 81 | return fmt.Sprintf("invalid flag %s", e.Flag) 82 | } 83 | 84 | func (e *InvalidFlagError) GetSuggestions() []string { 85 | distance := e.ctx.command.GetSuggestDistance() 86 | return e.ctx.Flags().GetSuggestions(e.Flag, distance) 87 | } 88 | -------------------------------------------------------------------------------- /cli/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "fmt" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestErrorWithTip(t *testing.T) { 26 | err := NewErrorWithTip(errors.New("err test"), "%s-%d", "nicai", 1) 27 | e, ok := err.(*errorWithTip) 28 | assert.True(t, ok) 29 | assert.Equal(t, &errorWithTip{err: errors.New("err test"), tip: fmt.Sprintf("%s-%d", "nicai", 1)}, e) 30 | assert.Equal(t, e.Error(), "err test") 31 | assert.Equal(t, "nicai-1", e.GetTip("ch")) 32 | } 33 | 34 | func TestInvalidCommandError(t *testing.T) { 35 | w := new(bytes.Buffer) 36 | stderr := new(bytes.Buffer) 37 | ctx := NewCommandContext(w, stderr) 38 | err := NewInvalidCommandError("MrX", ctx) 39 | e, ok := err.(*InvalidCommandError) 40 | assert.True(t, ok) 41 | assert.Equal(t, "'MrX' is not a vaild command", e.Error()) 42 | e.ctx.EnterCommand(&Command{Name: "oss", flags: NewFlagSet()}) 43 | assert.Nil(t, e.GetSuggestions()) 44 | } 45 | 46 | func TestInvalidFlagError(t *testing.T) { 47 | w := new(bytes.Buffer) 48 | stderr := new(bytes.Buffer) 49 | ctx := NewCommandContext(w, stderr) 50 | err := NewInvalidFlagError("MrX", ctx) 51 | e, ok := err.(*InvalidFlagError) 52 | assert.True(t, ok) 53 | assert.Equal(t, "invalid flag MrX", e.Error()) 54 | 55 | e.ctx.EnterCommand(&Command{Name: "oss", flags: NewFlagSet()}) 56 | assert.NotNil(t, e.GetSuggestions()) 57 | assert.Len(t, e.GetSuggestions(), 0) 58 | } 59 | -------------------------------------------------------------------------------- /cli/exit_code.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import "os" 17 | 18 | var ( 19 | withExitCode = true 20 | ) 21 | 22 | func EnableExitCode() { 23 | withExitCode = true 24 | } 25 | 26 | func DisableExitCode() { 27 | withExitCode = false 28 | } 29 | 30 | func Exit(code int) { 31 | if withExitCode { 32 | os.Exit(code) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cli/exit_code_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestEnableExitCode(t *testing.T) { 23 | assert.True(t, withExitCode) 24 | DisableExitCode() 25 | assert.False(t, withExitCode) 26 | EnableExitCode() 27 | assert.True(t, withExitCode) 28 | } 29 | -------------------------------------------------------------------------------- /cli/flag_field.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/aliyun/aliyun-cli/v3/i18n" 20 | ) 21 | 22 | type Field struct { 23 | // 24 | // appear in `--flag key1=value1, key2=value2` 25 | // if Key assigned with "", it can used with `--flag value1 value2` 26 | Key string 27 | 28 | // 29 | // if Required is true, this field must be assigned 30 | Required bool 31 | 32 | // 33 | // if Repeatable is true, this field can appear multiply times, eg: "--flag key1=value1 key2=value2" 34 | Repeatable bool 35 | 36 | // 37 | // if field not appear, use this value, not used with Required 38 | DefaultValue string 39 | 40 | // 41 | // Message show 42 | Short *i18n.Text 43 | 44 | assigned bool 45 | value string 46 | values []string 47 | } 48 | 49 | func (f *Field) assign(v string) { 50 | f.assigned = true 51 | f.value = v 52 | f.values = append(f.values, v) 53 | } 54 | 55 | func (f *Field) SetAssigned(istrue bool) { 56 | f.assigned = istrue 57 | } 58 | 59 | func (f *Field) SetValue(value string) { 60 | f.value = value 61 | } 62 | 63 | func (f *Field) getValue() (string, bool) { 64 | if f.assigned { 65 | return f.value, true 66 | } else if f.DefaultValue != "" { 67 | return f.DefaultValue, false 68 | } else { 69 | return "", false 70 | } 71 | } 72 | 73 | func (f *Field) check() error { 74 | if f.Required && !f.assigned { 75 | if f.Key != "" { 76 | return fmt.Errorf("%s= required", f.Key) 77 | } 78 | return fmt.Errorf("value required") 79 | 80 | } 81 | if !f.Repeatable && len(f.values) > 1 { 82 | if f.Key != "" { 83 | return fmt.Errorf("%s= duplicated", f.Key) 84 | } 85 | return fmt.Errorf("value duplicated") 86 | 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /cli/flag_field_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func resetField() *Field { 23 | return &Field{ 24 | Key: "first", 25 | values: make([]string, 0), 26 | } 27 | } 28 | func TestField(t *testing.T) { 29 | //assign 30 | field := resetField() 31 | field.assign("hello") 32 | assert.True(t, field.assigned) 33 | assert.Equal(t, "hello", field.value) 34 | assert.Len(t, field.values, 1) 35 | assert.Equal(t, "hello", field.values[0]) 36 | 37 | //GetValue 38 | value, ok := field.getValue() 39 | assert.True(t, ok) 40 | assert.Equal(t, "hello", value) 41 | field.assigned = false 42 | value, ok = field.getValue() 43 | assert.False(t, ok) 44 | assert.Empty(t, value) 45 | field.DefaultValue = "default" 46 | value, ok = field.getValue() 47 | assert.False(t, ok) 48 | assert.Equal(t, "default", value) 49 | 50 | //check 51 | assert.Nil(t, field.check()) 52 | field.assigned = false 53 | field.Required = true 54 | assert.EqualError(t, field.check(), "first= required") 55 | field.Key = "" 56 | assert.EqualError(t, field.check(), "value required") 57 | 58 | field.Required = false 59 | field.values = []string{"first", "second"} 60 | assert.EqualError(t, field.check(), "value duplicated") 61 | field.Key = "first" 62 | assert.EqualError(t, field.check(), "first= duplicated") 63 | 64 | field.SetAssigned(true) 65 | assert.True(t, field.assigned) 66 | 67 | field.SetValue("test") 68 | assert.Equal(t, "test", field.value) 69 | } 70 | -------------------------------------------------------------------------------- /cli/flag_set.go: -------------------------------------------------------------------------------- 1 | // Package cli Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | ) 20 | 21 | type FlagSet struct { 22 | // flags 23 | flags []*Flag 24 | // key: "--name" "-c" 25 | index map[string]*Flag 26 | } 27 | 28 | func NewFlagSet() *FlagSet { 29 | return &FlagSet{ 30 | flags: make([]*Flag, 0), 31 | index: make(map[string]*Flag), 32 | } 33 | } 34 | 35 | // traverse all values 36 | func (fs *FlagSet) Flags() []*Flag { 37 | return fs.flags 38 | } 39 | 40 | // call from user program, if flag duplicated, panic! 41 | func (fs *FlagSet) Add(f *Flag) { 42 | f.checkValid() 43 | for _, s := range f.GetFormations() { 44 | if _, ok := fs.index[s]; ok { 45 | panic(fmt.Errorf("flag duplicated %s", string(s))) 46 | } 47 | fs.index[s] = f 48 | } 49 | fs.flags = append(fs.flags, f) 50 | } 51 | 52 | // add by name, not support add by shorthand 53 | func (fs *FlagSet) AddByName(name string) (*Flag, error) { 54 | if _, ok := fs.index["--"+name]; ok { 55 | return nil, fmt.Errorf("flag duplicated --%s", name) 56 | } 57 | f := &Flag{ 58 | Name: name, 59 | } 60 | if strings.HasSuffix(name, "-FILE") { 61 | f.Aliases = append(f.Aliases, strings.TrimSuffix(name, "-FILE")) 62 | } 63 | fs.Add(f) 64 | return f, nil 65 | } 66 | 67 | // Get fetch flag by name, sample --name 68 | func (fs *FlagSet) Get(name string) *Flag { 69 | if fs == nil || fs.index == nil { 70 | return nil 71 | } 72 | if f, ok := fs.index["--"+name]; ok { 73 | return f 74 | } 75 | return nil 76 | } 77 | 78 | // get flag by shorthand, sample -a 79 | func (fs *FlagSet) GetByShorthand(c rune) *Flag { 80 | if f, ok := fs.index["-"+string(c)]; ok { 81 | return f 82 | } 83 | return nil 84 | } 85 | 86 | // get suggestions 87 | func (fs *FlagSet) GetSuggestions(name string, distance int) []string { 88 | sr := NewSuggester(name, distance) 89 | for k := range fs.index { 90 | sr.Apply(k) 91 | } 92 | ss := make([]string, 0) 93 | ss = append(ss, sr.GetResults()...) 94 | 95 | return ss 96 | } 97 | 98 | // get value by flag name, not recommended 99 | func (fs *FlagSet) GetValue(name string) (string, bool) { 100 | f := fs.Get(name) 101 | if f == nil { 102 | return "", false 103 | } 104 | return f.GetValue() 105 | } 106 | 107 | // merge FlagSet with from 108 | func (fs *FlagSet) mergeWith(from *FlagSet, applier func(f *Flag) bool) *FlagSet { 109 | if from == nil { 110 | return fs 111 | } 112 | r := NewFlagSet() 113 | if fs != nil { 114 | for _, f := range fs.flags { 115 | r.Add(f) 116 | } 117 | } 118 | for _, rv := range from.Flags() { 119 | if applier(rv) { 120 | r.put(rv) 121 | } 122 | } 123 | return r 124 | } 125 | 126 | // put flag, replace old value if duplicated 127 | func (fs *FlagSet) put(f *Flag) { 128 | 129 | for _, lv := range fs.flags { 130 | if lv == f { 131 | return 132 | } 133 | } 134 | fs.flags = append(fs.flags, f) 135 | for _, s := range f.GetFormations() { 136 | fs.index[s] = f 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /cli/flag_set_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewFlagSet(t *testing.T) { 24 | fs := NewFlagSet() 25 | assert.NotNil(t, fs.flags) 26 | assert.NotNil(t, fs.index) 27 | } 28 | 29 | func TestFlags(t *testing.T) { 30 | fs := NewFlagSet() 31 | assert.NotNil(t, fs.flags) 32 | assert.Len(t, fs.flags, 0) 33 | } 34 | 35 | func TestAdd(t *testing.T) { 36 | defer func() { 37 | a := recover() 38 | err, _ := a.(error) 39 | assert.EqualError(t, err, "flag duplicated --MrX") 40 | //the err message has "---MrX" before, and it should be "--MrX" 41 | // assert.EqualError(t, err, "flag duplicated ---MrX") 42 | }() 43 | fs := NewFlagSet() 44 | f := &Flag{Name: "MrX"} 45 | fs.Add(f) 46 | assert.Subset(t, fs.flags, []*Flag{{Name: "MrX"}}) 47 | assert.Len(t, fs.flags, 1) 48 | fs.Add(f) 49 | } 50 | 51 | func TestAddByName(t *testing.T) { 52 | fs := NewFlagSet() 53 | f, err := fs.AddByName("MrX2") 54 | assert.Equal(t, &Flag{Name: "MrX2"}, f) 55 | assert.Nil(t, err) 56 | 57 | f, err = fs.AddByName("MrX2") 58 | assert.Nil(t, f) 59 | assert.EqualError(t, err, "flag duplicated --MrX2") 60 | 61 | f, err = fs.AddByName("Mrx-FILE") 62 | assert.Nil(t, err) 63 | assert.Equal(t, 1, len(f.Aliases)) 64 | assert.Equal(t, "Mrx", f.Aliases[0]) 65 | 66 | } 67 | 68 | func TestGet(t *testing.T) { 69 | fs := NewFlagSet() 70 | fs.AddByName("MrX") 71 | assert.Equal(t, &Flag{Name: "MrX"}, fs.Get("MrX")) 72 | assert.Nil(t, fs.Get("MrX2")) 73 | } 74 | 75 | func TestGetByShorthand(t *testing.T) { 76 | fs := NewFlagSet() 77 | assert.Nil(t, fs.GetByShorthand('X')) 78 | fs.Add(&Flag{Name: "profile", Shorthand: 'p'}) 79 | exf := &Flag{Name: "profile", Shorthand: 'p'} 80 | fs.GetByShorthand('p') 81 | assert.Equal(t, exf, fs.GetByShorthand('p')) 82 | } 83 | 84 | func TestGetSuggestions(t *testing.T) { 85 | //TODO after prefected cli/suggestion.go testcase 86 | } 87 | 88 | func TestGetValue(t *testing.T) { 89 | fs := NewFlagSet() 90 | str, ok := fs.GetValue("NonExist") 91 | assert.False(t, ok) 92 | assert.Empty(t, str) 93 | fs.AddByName("MrX") 94 | str, ok = fs.GetValue("MrX") 95 | assert.False(t, ok) 96 | assert.Empty(t, str) 97 | } 98 | 99 | func TestPut(t *testing.T) { 100 | fs := NewFlagSet() 101 | fs.put(&Flag{Name: "profile", Shorthand: 'p'}) 102 | assert.Len(t, fs.flags, 1) 103 | fs.put(&Flag{Name: "profile", Shorthand: 'r'}) 104 | assert.Len(t, fs.flags, 2) 105 | assert.Equal(t, 'p', fs.flags[0].Shorthand) 106 | fs.put(&Flag{Name: "profil", Shorthand: 'a'}) 107 | assert.Len(t, fs.flags, 3) 108 | } 109 | 110 | func TestMergeWith(t *testing.T) { 111 | var a = func(f *Flag) bool { 112 | //merge with the rule that you need 113 | //in this case , I need merge all 114 | return true 115 | } 116 | fs := NewFlagSet() 117 | assert.True(t, reflect.DeepEqual(fs, fs.mergeWith(nil, a))) 118 | fs2 := NewFlagSet() 119 | fs2.Add(&Flag{Name: "profile", Shorthand: 'p'}) 120 | fs2.Add(&Flag{Name: "mode", Shorthand: 'm'}) 121 | assert.Len(t, fs.mergeWith(fs2, a).flags, 2) 122 | fs.Add(&Flag{Name: "AK", Shorthand: 'a'}) 123 | assert.Len(t, fs.mergeWith(fs2, a).flags, 3) 124 | } 125 | -------------------------------------------------------------------------------- /cli/output.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | "os" 20 | ) 21 | 22 | var ( 23 | defaultStdoutWriter = os.Stdout 24 | defaultStderrWriter = os.Stderr 25 | ) 26 | 27 | func DefaultStdoutWriter() io.Writer { 28 | return defaultStdoutWriter 29 | } 30 | 31 | func DefaultStderrWriter() io.Writer { 32 | return defaultStderrWriter 33 | } 34 | 35 | func Print(w io.Writer, a ...interface{}) (n int, err error) { 36 | return fmt.Fprint(w, a...) 37 | } 38 | 39 | func Println(w io.Writer, a ...interface{}) (n int, err error) { 40 | return fmt.Fprintln(w, a...) 41 | } 42 | 43 | func Printf(w io.Writer, format string, a ...interface{}) (n int, err error) { 44 | return fmt.Fprintf(w, format, a...) 45 | } 46 | -------------------------------------------------------------------------------- /cli/output_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestDefaultWriter(t *testing.T) { 25 | w := DefaultStdoutWriter() 26 | stderr, ok := w.(*os.File) 27 | assert.True(t, ok) 28 | assert.ObjectsAreEqual(os.Stdout, stderr) 29 | 30 | buf := new(bytes.Buffer) 31 | n, err := Print(buf, "I am night") 32 | assert.Equal(t, 10, n) 33 | assert.Nil(t, err) 34 | 35 | buf.Reset() 36 | n, err = Println(buf, "How are you") 37 | assert.Equal(t, 12, n) 38 | assert.Nil(t, err) 39 | buf.Reset() 40 | n, err = Printf(buf, "I am %s", "fine") 41 | assert.Equal(t, 9, n) 42 | assert.Nil(t, err) 43 | } 44 | 45 | func TestOutput(t *testing.T) { 46 | w := new(bytes.Buffer) 47 | index, err := Print(w, "who are you") 48 | assert.Equal(t, 11, index) 49 | assert.Nil(t, err) 50 | assert.Equal(t, "who are you", w.String()) 51 | 52 | w.Reset() 53 | index, err = Println(w, "I am MrX") 54 | assert.Equal(t, 9, index) 55 | assert.Nil(t, err) 56 | assert.Equal(t, "I am MrX\n", w.String()) 57 | 58 | w.Reset() 59 | index, err = Printf(w, "and you%s", "?") 60 | assert.Equal(t, 8, index) 61 | assert.Nil(t, err) 62 | assert.Equal(t, "and you?", w.String()) 63 | } 64 | -------------------------------------------------------------------------------- /cli/suggestion.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | const DefaultSuggestDistance = 2 17 | 18 | func CalculateStringDistance(source string, target string) int { 19 | return DistanceForStrings([]rune(source), []rune(target), DefaultOptions) 20 | } 21 | 22 | // error with suggestions 23 | type SuggestibleError interface { 24 | GetSuggestions() []string 25 | } 26 | 27 | func PrintSuggestions(ctx *Context, lang string, ss []string) { 28 | if len(ss) > 0 { 29 | Noticef(ctx.Stdout(), "\nDid you mean:\n") 30 | for _, s := range ss { 31 | Noticef(ctx.Stdout(), " %s\n", s) 32 | } 33 | } 34 | } 35 | 36 | // helper class for Suggester 37 | type Suggester struct { 38 | suggestFor string 39 | distance int 40 | results []string 41 | } 42 | 43 | func NewSuggester(v string, distance int) *Suggester { 44 | return &Suggester{ 45 | suggestFor: v, 46 | distance: distance, 47 | } 48 | } 49 | 50 | func (a *Suggester) Apply(s string) { 51 | d := CalculateStringDistance(a.suggestFor, s) 52 | if d <= a.distance { 53 | if d < a.distance { 54 | a.distance = d 55 | a.results = make([]string, 0) 56 | } 57 | a.results = append(a.results, s) 58 | } 59 | } 60 | 61 | func (a *Suggester) GetResults() []string { 62 | return a.results 63 | } 64 | -------------------------------------------------------------------------------- /cli/suggestion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewSuggester(t *testing.T) { 24 | suggestion := NewSuggester("flags", 2) 25 | assert.Equal(t, &Suggester{suggestFor: "flags", distance: 2}, suggestion) 26 | } 27 | 28 | func TestApply(t *testing.T) { 29 | s := NewSuggester("aaa", 2) 30 | s.Apply("aab") 31 | s.Apply("aa2") 32 | s.Apply("aa2b") 33 | s.Apply("baa2b") 34 | 35 | result := s.GetResults() 36 | assert.Subset(t, result, []string{"aab", "aa2"}) 37 | assert.Len(t, result, 2) 38 | } 39 | 40 | func TestPrintSuggestions(t *testing.T) { 41 | w := new(bytes.Buffer) 42 | stderr := new(bytes.Buffer) 43 | ctx := NewCommandContext(w, stderr) 44 | PrintSuggestions(ctx, "en", []string{"hello", "nihao"}) 45 | assert.Equal(t, "\x1b[1;33m\nDid you mean:\n\x1b[0m\x1b[1;33m hello\n\x1b[0m\x1b[1;33m nihao\n\x1b[0m", w.String()) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /cli/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cli 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/aliyun/aliyun-cli/v3/i18n" 21 | ) 22 | 23 | // This variable is replaced in compile time 24 | // `-ldflags "-X 'github.com/aliyun/aliyun-cli/v3/cli.Version=${VERSION}'"` 25 | var ( 26 | Version = "0.0.1" 27 | ) 28 | 29 | func GetVersion() string { 30 | return strings.Replace(Version, " ", "-", -1) 31 | } 32 | 33 | func NewVersionCommand() *Command { 34 | return &Command{ 35 | Name: "version", 36 | Short: i18n.T("print current version", "打印当前版本号"), 37 | Hidden: true, 38 | Run: func(ctx *Context, args []string) error { 39 | Printf(ctx.Stdout(), "%s\n", Version) 40 | return nil 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cli/version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cli 15 | 16 | import ( 17 | "bytes" 18 | "testing" 19 | 20 | "github.com/aliyun/aliyun-cli/v3/i18n" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestGetVersion(t *testing.T) { 25 | assert.Equal(t, Version, GetVersion()) 26 | } 27 | 28 | func TestNewVersionCommand(t *testing.T) { 29 | excmd := &Command{ 30 | Name: "version", 31 | Short: i18n.T("print current version", "打印当前版本号"), 32 | Hidden: true, 33 | Run: func(ctx *Context, args []string) error { 34 | Printf(ctx.Stdout(), "%s\n", Version) 35 | return nil 36 | }, 37 | } 38 | cmd := NewVersionCommand() 39 | assert.ObjectsAreEqualValues(excmd, cmd) 40 | 41 | w := new(bytes.Buffer) 42 | stderr := new(bytes.Buffer) 43 | ctx := NewCommandContext(w, stderr) 44 | err := cmd.Run(ctx, []string{}) 45 | assert.Nil(t, err) 46 | assert.Equal(t, Version+"\n", w.String()) 47 | } 48 | -------------------------------------------------------------------------------- /cloudsso/login_test.go: -------------------------------------------------------------------------------- 1 | package cloudsso 2 | 3 | import ( 4 | "github.com/aliyun/aliyun-cli/v3/util" 5 | "testing" 6 | ) 7 | 8 | func TestSsoLogin_JudgeLogin(t *testing.T) { 9 | // 保存原始GetCurrentUnixTime函数,测试结束后恢复 10 | originalGetCurrentUnixTime := util.GetCurrentUnixTime 11 | 12 | // 过期时间:当前时间前后5分钟 13 | expiredTime := originalGetCurrentUnixTime() - 300 // 5分钟前(已过期) 14 | validTime := originalGetCurrentUnixTime() + 300 // 5分钟后(有效) 15 | 16 | tests := []struct { 17 | name string 18 | login SsoLogin 19 | wantLogin bool 20 | wantErr bool 21 | errMessage string 22 | }{ 23 | { 24 | name: "SignInUrl为空应返回错误", 25 | login: SsoLogin{ 26 | SignInUrl: "", 27 | AccessToken: "token", 28 | AccountId: "account", 29 | AccessConfig: "config", 30 | ExpireTime: validTime, 31 | }, 32 | wantLogin: false, 33 | wantErr: true, 34 | errMessage: "signInUrl is required", 35 | }, 36 | { 37 | name: "AccessToken为空时需要登录", 38 | login: SsoLogin{ 39 | SignInUrl: "https://example.com", 40 | AccessToken: "", 41 | AccountId: "account", 42 | AccessConfig: "config", 43 | ExpireTime: validTime, 44 | }, 45 | wantLogin: true, 46 | wantErr: false, 47 | }, 48 | { 49 | name: "AccountId为空时需要登录", 50 | login: SsoLogin{ 51 | SignInUrl: "https://example.com", 52 | AccessToken: "token", 53 | AccountId: "", 54 | AccessConfig: "config", 55 | ExpireTime: validTime, 56 | }, 57 | wantLogin: true, 58 | wantErr: false, 59 | }, 60 | { 61 | name: "AccessConfig为空时需要登录", 62 | login: SsoLogin{ 63 | SignInUrl: "https://example.com", 64 | AccessToken: "token", 65 | AccountId: "account", 66 | AccessConfig: "", 67 | ExpireTime: validTime, 68 | }, 69 | wantLogin: true, 70 | wantErr: false, 71 | }, 72 | { 73 | name: "ExpireTime为0时需要登录", 74 | login: SsoLogin{ 75 | SignInUrl: "https://example.com", 76 | AccessToken: "token", 77 | AccountId: "account", 78 | AccessConfig: "config", 79 | ExpireTime: 0, 80 | }, 81 | wantLogin: true, 82 | wantErr: false, 83 | }, 84 | { 85 | name: "ExpireTime已过期时需要登录", 86 | login: SsoLogin{ 87 | SignInUrl: "https://example.com", 88 | AccessToken: "token", 89 | AccountId: "account", 90 | AccessConfig: "config", 91 | ExpireTime: expiredTime, 92 | }, 93 | wantLogin: true, 94 | wantErr: false, 95 | }, 96 | { 97 | name: "所有条件都满足时不需要登录", 98 | login: SsoLogin{ 99 | SignInUrl: "https://example.com", 100 | AccessToken: "token", 101 | AccountId: "account", 102 | AccessConfig: "config", 103 | ExpireTime: validTime, 104 | }, 105 | wantLogin: false, 106 | wantErr: false, 107 | }, 108 | } 109 | 110 | for _, tt := range tests { 111 | t.Run(tt.name, func(t *testing.T) { 112 | login, err := tt.login.JudgeLogin() 113 | 114 | // 检查是否应该返回错误 115 | if (err != nil) != tt.wantErr { 116 | t.Errorf("JudgeLogin() error = %v, wantErr %v", err, tt.wantErr) 117 | return 118 | } 119 | 120 | // 如果期望有错误,检查错误消息 121 | if tt.wantErr && err.Error() != tt.errMessage { 122 | t.Errorf("JudgeLogin() error message = %v, want %v", err.Error(), tt.errMessage) 123 | return 124 | } 125 | 126 | // 检查返回值是否符合预期 127 | if login != tt.wantLogin { 128 | t.Errorf("JudgeLogin() = %v, want %v", login, tt.wantLogin) 129 | } 130 | }) 131 | } 132 | } 133 | 134 | func TestSsoLogin_GetAccessToken_Manual(t *testing.T) { 135 | t.Skip("默认跳过,需要手工移除此行执行真实测试") 136 | 137 | sso := SsoLogin{ 138 | SignInUrl: "https://signin-cn-shanghai.alibabacloudsso.com/start/login", 139 | } 140 | token, err := sso.GetAccessToken() 141 | if err != nil { 142 | t.Errorf("GetAccessToken error: %v", err) 143 | } else { 144 | t.Logf("GetAccessToken: %s", token.AccessToken) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /cloudsso/user.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudsso 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "github.com/aliyun/aliyun-cli/v3/cli" 21 | "io/ioutil" 22 | "net/http" 23 | "net/url" 24 | "time" 25 | ) 26 | 27 | type ListUserParameter struct { 28 | BaseUrl string `json:"base_url"` 29 | AccessToken string `json:"access_token"` 30 | HttpClient *http.Client `json:"-"` 31 | } 32 | 33 | type AccountDetailResponse struct { 34 | AccountId string `json:"AccountId"` 35 | DisplayName string `json:"DisplayName"` 36 | } 37 | 38 | // ListUsersResponse 保存列出用户的响应 39 | type ListUsersResponse struct { 40 | Accounts []AccountDetailResponse `json:"Accounts"` 41 | IsTruncated bool `json:"IsTruncated"` 42 | NextToken string `json:"NextToken"` 43 | } 44 | 45 | // ErrorResponse 用于处理错误响应 46 | type ErrorResponse struct { 47 | ErrorCode string `json:"ErrorCode"` 48 | ErrorMessage string `json:"ErrorMessage"` 49 | RequestId string `json:"RequestId"` 50 | } 51 | 52 | // ListUsers 获取账户列表,支持分页 53 | func (p *ListUserParameter) ListUsers(nextToken string, maxResults int) (*ListUsersResponse, error) { 54 | apiUrl, err := url.Parse(fmt.Sprintf("%s/access-assignments/accounts", p.BaseUrl)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | query := apiUrl.Query() 60 | if nextToken != "" { 61 | query.Add("NextToken", nextToken) 62 | } 63 | if maxResults > 0 { 64 | query.Add("MaxResults", fmt.Sprintf("%d", maxResults)) 65 | } 66 | apiUrl.RawQuery = query.Encode() 67 | 68 | req, err := http.NewRequest("GET", apiUrl.String(), nil) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | req.Header.Add("accept", "application/json") 74 | req.Header.Add("content-type", "application/json") 75 | req.Header.Add("authorization", fmt.Sprintf("Bearer %s", p.AccessToken)) 76 | req.Header.Add("user-agent", "aliyun/CLI-"+cli.Version) 77 | 78 | p.HttpClient.Timeout = 10000 * time.Millisecond 79 | 80 | resp, err := p.HttpClient.Do(req) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer resp.Body.Close() 85 | 86 | body, err := ioutil.ReadAll(resp.Body) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | if resp.StatusCode >= 400 && resp.StatusCode < 500 { 92 | var errResp ErrorResponse 93 | if err := json.Unmarshal(body, &errResp); err != nil { 94 | return nil, err 95 | } 96 | return nil, fmt.Errorf("%s: %s %s", errResp.ErrorCode, errResp.ErrorMessage, errResp.RequestId) 97 | } 98 | 99 | var result ListUsersResponse 100 | if err := json.Unmarshal(body, &result); err != nil { 101 | return nil, err 102 | } 103 | 104 | return &result, nil 105 | } 106 | 107 | // ListAllUsers 获取所有账户列表 108 | func (p *ListUserParameter) ListAllUsers() ([]AccountDetailResponse, error) { 109 | var accounts []AccountDetailResponse 110 | response, err := p.ListUsers("", 100) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | accounts = append(accounts, response.Accounts...) 116 | 117 | for response.IsTruncated { 118 | response, err = p.ListUsers(response.NextToken, 100) 119 | if err != nil { 120 | return nil, err 121 | } 122 | accounts = append(accounts, response.Accounts...) 123 | } 124 | 125 | return accounts, nil 126 | } 127 | -------------------------------------------------------------------------------- /config/configure_delete.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "github.com/aliyun/aliyun-cli/v3/cli" 19 | "github.com/aliyun/aliyun-cli/v3/i18n" 20 | ) 21 | 22 | func NewConfigureDeleteCommand() *cli.Command { 23 | return &cli.Command{ 24 | Name: "delete", 25 | Usage: "delete --profile ", 26 | Short: i18n.T("delete the specified profile", "删除指定配置"), 27 | Run: func(c *cli.Context, args []string) error { 28 | profileName, ok := ProfileFlag(c.Flags()).GetValue() 29 | if !ok { 30 | cli.Errorf(c.Stderr(), "missing --profile \n") 31 | cli.Noticef(c.Stderr(), "\nusage:\n aliyun configure delete --profile \n") 32 | return nil 33 | } 34 | doConfigureDelete(c, profileName) 35 | return nil 36 | }, 37 | } 38 | } 39 | 40 | func doConfigureDelete(ctx *cli.Context, profileName string) { 41 | conf, err := loadConfiguration() 42 | if err != nil { 43 | cli.Errorf(ctx.Stderr(), "ERROR: load configure failed: %v\n", err) 44 | } 45 | deleted := false 46 | r := make([]Profile, 0) 47 | for _, p := range conf.Profiles { 48 | if p.Name != profileName { 49 | r = append(r, p) 50 | } else { 51 | deleted = true 52 | } 53 | } 54 | 55 | if !deleted { 56 | cli.Errorf(ctx.Stderr(), "Error: configuration profile `%s` not found\n", profileName) 57 | return 58 | } 59 | 60 | conf.Profiles = r 61 | if conf.CurrentProfile == profileName { 62 | if len(conf.Profiles) > 0 { 63 | conf.CurrentProfile = conf.Profiles[0].Name 64 | } else { 65 | conf.CurrentProfile = DefaultConfigProfileName 66 | } 67 | } 68 | 69 | err = hookSaveConfiguration(SaveConfiguration)(conf) 70 | if err != nil { 71 | cli.Errorf(ctx.Stderr(), "Error: save configuration failed %s\n", err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /config/configure_delete_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "testing" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/cli" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestDoConfigureDelete(t *testing.T) { 26 | w := new(bytes.Buffer) 27 | stderr := new(bytes.Buffer) 28 | ctx := cli.NewCommandContext(w, stderr) 29 | 30 | originhook := hookLoadConfiguration 31 | originhookSave := hookSaveConfiguration 32 | defer func() { 33 | hookLoadConfiguration = originhook 34 | hookSaveConfiguration = originhookSave 35 | }() 36 | 37 | //testcase 1 38 | hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) { 39 | return func(path string) (*Configuration, error) { 40 | return &Configuration{CurrentProfile: "default", Profiles: []Profile{ 41 | {Name: "default", Mode: AK, AccessKeyId: "default_aliyun_access_key_id", AccessKeySecret: "default_aliyun_access_key_secret", OutputFormat: "json"}, 42 | {Name: "bbb", Mode: AK, AccessKeyId: "sdf", AccessKeySecret: "ddf", OutputFormat: "json"}}}, nil 43 | } 44 | } 45 | hookSaveConfiguration = func(fn func(config *Configuration) error) func(config *Configuration) error { 46 | return func(config *Configuration) error { 47 | return nil 48 | } 49 | } 50 | 51 | doConfigureDelete(ctx, "bbb") 52 | assert.Empty(t, w.String()) 53 | 54 | //testcase 2 55 | w.Reset() 56 | stderr.Reset() 57 | doConfigureDelete(ctx, "aaa") 58 | assert.Equal(t, "\x1b[1;31mError: configuration profile `aaa` not found\n\x1b[0m", stderr.String()) 59 | 60 | //testcase 3 61 | hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) { 62 | return func(path string) (*Configuration, error) { 63 | return &Configuration{}, errors.New("error") 64 | } 65 | } 66 | 67 | w.Reset() 68 | stderr.Reset() 69 | doConfigureDelete(ctx, "bbb") 70 | assert.Equal(t, "\x1b[1;31mERROR: load configure failed: error\n\x1b[0m\x1b[1;31mError: configuration profile `bbb` not found\n\x1b[0m", stderr.String()) 71 | 72 | //testcase 4 73 | hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) { 74 | return func(path string) (*Configuration, error) { 75 | return &Configuration{CurrentProfile: "default", Profiles: []Profile{ 76 | {Name: "default", Mode: AK, AccessKeyId: "default_aliyun_access_key_id", AccessKeySecret: "default_aliyun_access_key_secret", OutputFormat: "json"}, 77 | {Name: "bbb", Mode: AK, AccessKeyId: "sdf", AccessKeySecret: "ddf", OutputFormat: "json"}}}, nil 78 | } 79 | } 80 | hookSaveConfiguration = func(fn func(config *Configuration) error) func(config *Configuration) error { 81 | return func(config *Configuration) error { 82 | return errors.New("save error") 83 | } 84 | } 85 | w.Reset() 86 | stderr.Reset() 87 | doConfigureDelete(ctx, "bbb") 88 | assert.Equal(t, "\x1b[1;31mError: save configuration failed save error\n\x1b[0m", stderr.String()) 89 | } 90 | -------------------------------------------------------------------------------- /config/configure_get.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "encoding/json" 19 | "reflect" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/cli" 22 | "github.com/aliyun/aliyun-cli/v3/i18n" 23 | ) 24 | 25 | func NewConfigureGetCommand() *cli.Command { 26 | return &cli.Command{ 27 | Name: "get", 28 | Short: i18n.T( 29 | "print configuration values", 30 | "打印配置信息"), 31 | Usage: "get [profile] [language] ...", 32 | Run: func(c *cli.Context, args []string) error { 33 | doConfigureGet(c, args) 34 | return nil 35 | }, 36 | } 37 | } 38 | 39 | func doConfigureGet(c *cli.Context, args []string) { 40 | config, err := loadConfiguration() 41 | if err != nil { 42 | cli.Errorf(c.Stderr(), "load configuration failed %s", err) 43 | cli.Printf(c.Stderr(), "\n") 44 | return 45 | } 46 | 47 | profile := config.GetCurrentProfile(c) 48 | 49 | if pn, ok := ProfileFlag(c.Flags()).GetValue(); ok { 50 | profile, ok = config.GetProfile(pn) 51 | if !ok { 52 | cli.Errorf(c.Stderr(), "profile %s not found!", pn) 53 | cli.Printf(c.Stderr(), "\n") 54 | return 55 | } 56 | } 57 | 58 | if len(args) == 0 && !reflect.DeepEqual(profile, Profile{}) { 59 | data, err := json.MarshalIndent(profile, "", "\t") 60 | if err != nil { 61 | cli.Printf(c.Stderr(), "ERROR:"+err.Error()) 62 | cli.Printf(c.Stderr(), "\n") 63 | return 64 | } 65 | cli.Println(c.Stdout(), string(data)) 66 | cli.Printf(c.Stdout(), "\n") 67 | return 68 | } 69 | 70 | for _, arg := range args { 71 | switch arg { 72 | case ProfileFlagName: 73 | cli.Printf(c.Stdout(), "profile=%s\n", profile.Name) 74 | case ModeFlagName: 75 | cli.Printf(c.Stdout(), "mode=%s\n", profile.Mode) 76 | case AccessKeyIdFlagName: 77 | cli.Printf(c.Stdout(), "access-key-id=%s\n", MosaicString(profile.AccessKeyId, 3)) 78 | case AccessKeySecretFlagName: 79 | cli.Printf(c.Stdout(), "access-key-secret=%s\n", MosaicString(profile.AccessKeySecret, 3)) 80 | case StsTokenFlagName: 81 | cli.Printf(c.Stdout(), "sts-token=%s\n", profile.StsToken) 82 | case StsRegionFlagName: 83 | cli.Printf(c.Stdout(), "sts-region=%s\n", profile.StsRegion) 84 | case RamRoleNameFlagName: 85 | cli.Printf(c.Stdout(), "ram-role-name=%s\n", profile.RamRoleName) 86 | case RamRoleArnFlagName: 87 | cli.Printf(c.Stdout(), "ram-role-arn=%s\n", profile.RamRoleArn) 88 | case ExternalIdFlagName: 89 | cli.Printf(c.Stdout(), "external-id=%s\n", profile.ExternalId) 90 | case RoleSessionNameFlagName: 91 | cli.Printf(c.Stdout(), "role-session-name=%s\n", profile.RoleSessionName) 92 | case KeyPairNameFlagName: 93 | cli.Printf(c.Stdout(), "key-pair-name=%s\n", profile.KeyPairName) 94 | case PrivateKeyFlagName: 95 | cli.Printf(c.Stdout(), "private-key=%s\n", profile.PrivateKey) 96 | case RegionFlagName: 97 | cli.Printf(c.Stdout(), profile.RegionId) 98 | case LanguageFlagName: 99 | cli.Printf(c.Stdout(), "language=%s\n", profile.Language) 100 | case CloudSSOSignInUrlFlagName: 101 | cli.Printf(c.Stdout(), "cloud-sso-sign-in-url=%s\n", profile.CloudSSOSignInUrl) 102 | case CloudSSOAccessConfigFlagName: 103 | cli.Printf(c.Stdout(), "cloud-sso-access-config=%s\n", profile.CloudSSOAccessConfig) 104 | case CloudSSOAccountIdFlagName: 105 | cli.Printf(c.Stdout(), "cloud-sso-account-id=%s\n", profile.CloudSSOAccountId) 106 | } 107 | } 108 | 109 | cli.Printf(c.Stdout(), "\n") 110 | } 111 | -------------------------------------------------------------------------------- /config/configure_list.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | "text/tabwriter" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/cli" 22 | "github.com/aliyun/aliyun-cli/v3/i18n" 23 | ) 24 | 25 | func NewConfigureListCommand() *cli.Command { 26 | return &cli.Command{ 27 | Name: "list", 28 | Usage: "list", 29 | Short: i18n.T("list all config profile", "列出所有配置集"), 30 | Run: func(c *cli.Context, args []string) error { 31 | doConfigureList(c.Stdout()) 32 | return nil 33 | }, 34 | } 35 | } 36 | 37 | func doConfigureList(w io.Writer) { 38 | conf, err := loadConfiguration() 39 | if err != nil { 40 | cli.Errorf(w, "ERROR: load configure failed: %v\n", err) 41 | } 42 | tw := tabwriter.NewWriter(w, 8, 0, 1, ' ', 0) 43 | fmt.Fprint(tw, "Profile\t| Credential \t| Valid\t| Region\t| Language\n") 44 | fmt.Fprint(tw, "---------\t| ------------------\t| -------\t| ----------------\t| --------\n") 45 | for _, pf := range conf.Profiles { 46 | name := pf.Name 47 | if name == conf.CurrentProfile { 48 | name = name + " *" 49 | } 50 | err := pf.Validate() 51 | valid := "Valid" 52 | if err != nil { 53 | valid = "Invalid" 54 | } 55 | 56 | cred := "" 57 | switch pf.Mode { 58 | case AK: 59 | cred = "AK:" + "***" + GetLastChars(pf.AccessKeyId, 3) 60 | case StsToken: 61 | cred = "StsToken:" + "***" + GetLastChars(pf.AccessKeyId, 3) 62 | case RamRoleArn: 63 | cred = "RamRoleArn:" + "***" + GetLastChars(pf.AccessKeyId, 3) 64 | if pf.ExternalId != "" { 65 | cred = cred + ":" + GetLastChars(pf.ExternalId, 3) 66 | } 67 | case EcsRamRole: 68 | cred = "EcsRamRole:" + pf.RamRoleName 69 | case RamRoleArnWithEcs: 70 | cred = "arn:" + "***" + GetLastChars(pf.AccessKeyId, 3) 71 | case ChainableRamRoleArn: 72 | cred = "ChainableRamRoleArn:" + pf.SourceProfile + ":" + pf.RamRoleArn 73 | if pf.ExternalId != "" { 74 | cred = cred + ":" + GetLastChars(pf.ExternalId, 3) 75 | } 76 | case RsaKeyPair: 77 | cred = "RsaKeyPair:" + pf.KeyPairName 78 | case External: 79 | cred = "ProcessCommand:" + pf.ProcessCommand 80 | case CredentialsURI: 81 | cred = "CredentialsURI:" + pf.CredentialsURI 82 | case OIDC: 83 | cred = "OIDC:" + "***" + GetLastChars(pf.OIDCProviderARN, 5) + "@***" + GetLastChars(pf.OIDCTokenFile, 5) + "@" + pf.RamRoleArn 84 | case CloudSSO: 85 | cred = "CloudSSO:" + pf.CloudSSOAccountId + "@" + pf.CloudSSOAccessConfig 86 | } 87 | fmt.Fprintf(tw, "%s\t| %s\t| %s\t| %s\t| %s\n", name, cred, valid, pf.RegionId, pf.Language) 88 | } 89 | tw.Flush() 90 | } 91 | -------------------------------------------------------------------------------- /config/configure_switch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | 20 | "github.com/aliyun/aliyun-cli/v3/cli" 21 | "github.com/aliyun/aliyun-cli/v3/i18n" 22 | ) 23 | 24 | func NewConfigureSwitchCommand() *cli.Command { 25 | cmd := &cli.Command{ 26 | Name: "switch", 27 | Usage: "switch [--profile ]", 28 | Short: i18n.T("switch default profile", "切换默认配置"), 29 | Run: func(c *cli.Context, args []string) error { 30 | return doConfigureSwitch(c.Stdout(), c.Flags()) 31 | }, 32 | } 33 | 34 | AddFlags(cmd.Flags()) 35 | return cmd 36 | } 37 | 38 | func doConfigureSwitch(stdout io.Writer, flags *cli.FlagSet) error { 39 | config, err := loadConfiguration() 40 | if err != nil { 41 | return fmt.Errorf("load configuration failed: %s", err) 42 | } 43 | 44 | profileName, ok := ProfileFlag(flags).GetValue() 45 | if !ok { 46 | return fmt.Errorf("the --profile is required") 47 | } 48 | 49 | _, ok = config.GetProfile(profileName) 50 | if !ok { 51 | return fmt.Errorf("the profile `%s` is inexist", profileName) 52 | } 53 | 54 | config.CurrentProfile = profileName 55 | err = hookSaveConfiguration(SaveConfiguration)(config) 56 | if err != nil { 57 | return fmt.Errorf("save configuration failed: %s", err) 58 | } 59 | 60 | cli.Println(stdout, fmt.Sprintf("The default profile is `%s` now.", profileName)) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /config/configure_switch_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "testing" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/cli" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestDoConfigureSwitch(t *testing.T) { 26 | stdout := new(bytes.Buffer) 27 | fs := cli.NewFlagSet() 28 | AddFlags(fs) 29 | originhook := hookLoadConfiguration 30 | originSaveHook := hookSaveConfiguration 31 | defer func() { 32 | hookLoadConfiguration = originhook 33 | hookSaveConfiguration = originSaveHook 34 | }() 35 | 36 | // test case 1: load configuration failed 37 | hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) { 38 | return func(path string) (*Configuration, error) { 39 | return &Configuration{}, errors.New("mock error") 40 | } 41 | } 42 | stdout.Reset() 43 | err := doConfigureSwitch(stdout, fs) 44 | assert.NotNil(t, err) 45 | assert.EqualError(t, err, "load configuration failed: mock error") 46 | 47 | //testcase 2: no --profile flag 48 | hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) { 49 | return func(path string) (*Configuration, error) { 50 | return &Configuration{ 51 | CurrentProfile: "default", 52 | Profiles: []Profile{ 53 | { 54 | Name: "default", 55 | Mode: AK, 56 | AccessKeyId: "default_aliyun_access_key_id", 57 | AccessKeySecret: "default_aliyun_access_key_secret", 58 | OutputFormat: "json", 59 | }, 60 | { 61 | Name: "aaa", 62 | Mode: StsToken, 63 | AccessKeyId: "sdf", 64 | AccessKeySecret: "ddf", 65 | OutputFormat: "json", 66 | StsToken: "StsToken", 67 | }, 68 | }, 69 | }, nil 70 | } 71 | } 72 | err = doConfigureSwitch(stdout, fs) 73 | assert.EqualError(t, err, "the --profile is required") 74 | 75 | // test case 3: inexist profile 76 | fs.Get(ProfileFlagName).SetAssigned(true) 77 | fs.Get(ProfileFlagName).SetValue("inexist") 78 | err = doConfigureSwitch(stdout, fs) 79 | assert.EqualError(t, err, "the profile `inexist` is inexist") 80 | 81 | // test case 4: save configuration failed 82 | hookSaveConfiguration = func(fn func(config *Configuration) error) func(config *Configuration) error { 83 | return func(config *Configuration) error { 84 | return errors.New("mock save error") 85 | } 86 | } 87 | fs.Get(ProfileFlagName).SetValue("aaa") 88 | err = doConfigureSwitch(stdout, fs) 89 | assert.EqualError(t, err, "save configuration failed: mock save error") 90 | 91 | // test case 5: it ok 92 | hookSaveConfiguration = func(fn func(config *Configuration) error) func(config *Configuration) error { 93 | return func(config *Configuration) error { 94 | return nil 95 | } 96 | } 97 | fs.Get(ProfileFlagName).SetValue("aaa") 98 | err = doConfigureSwitch(stdout, fs) 99 | assert.Nil(t, err) 100 | assert.Equal(t, "The default profile is `aaa` now.\n", stdout.String()) 101 | } 102 | -------------------------------------------------------------------------------- /config/hello.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | 20 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" 21 | util "github.com/alibabacloud-go/tea-utils/v2/service" 22 | "github.com/alibabacloud-go/tea/tea" 23 | "github.com/aliyun/aliyun-cli/v3/cli" 24 | ) 25 | 26 | func doHello(ctx *cli.Context, profile *Profile) (err error) { 27 | profile.OverwriteWithFlags(ctx) 28 | credential, err := profile.GetCredential(ctx, nil) 29 | if err != nil { 30 | return 31 | } 32 | 33 | config := &openapi.Config{ 34 | Credential: credential, 35 | } 36 | 37 | config.Endpoint = tea.String(getSTSEndpoint(profile.StsRegion)) 38 | client, err := openapi.NewClient(config) 39 | if err != nil { 40 | return 41 | } 42 | 43 | params := &openapi.Params{ 44 | // 接口名称 45 | Action: tea.String("GetCallerIdentity"), 46 | // 接口版本 47 | Version: tea.String("2015-04-01"), 48 | // 接口协议 49 | Protocol: tea.String("HTTPS"), 50 | // 接口 HTTP 方法 51 | Method: tea.String("POST"), 52 | AuthType: tea.String("AK"), 53 | Style: tea.String("RPC"), 54 | // 接口 PATH 55 | Pathname: tea.String("/"), 56 | // 接口请求体内容格式 57 | ReqBodyType: tea.String("json"), 58 | // 接口响应体内容格式 59 | BodyType: tea.String("json"), 60 | } 61 | // runtime options 62 | runtime := &util.RuntimeOptions{} 63 | request := &openapi.OpenApiRequest{} 64 | 65 | ua := "Aliyun-CLI/" + cli.GetVersion() 66 | if vendorEnv, ok := os.LookupEnv("ALIBABA_CLOUD_VENDOR"); ok { 67 | ua += " vendor/" + vendorEnv 68 | } 69 | 70 | client.UserAgent = tea.String(ua) 71 | _, err = client.CallApi(params, request, runtime) 72 | return 73 | } 74 | 75 | func DoHello(ctx *cli.Context, profile *Profile) { 76 | w := ctx.Stdout() 77 | err := doHello(ctx, profile) 78 | if err != nil { 79 | cli.Println(w, "-----------------------------------------------") 80 | cli.Println(w, "!!! Configure Failed please configure again !!!") 81 | cli.Println(w, "-----------------------------------------------") 82 | cli.Println(w, err) 83 | cli.Println(w, "-----------------------------------------------") 84 | cli.Println(w, "!!! Configure Failed please configure again !!!") 85 | cli.Println(w, "-----------------------------------------------") 86 | return 87 | } 88 | 89 | fmt.Println(icon) 90 | } 91 | 92 | var icon = string(` 93 | Configure Done!!! 94 | ..............888888888888888888888 ........=8888888888888888888D=.............. 95 | ...........88888888888888888888888 ..........D8888888888888888888888I........... 96 | .........,8888888888888ZI: ...........................=Z88D8888888888D.......... 97 | .........+88888888 ..........................................88888888D.......... 98 | .........+88888888 .......Welcome to use Alibaba Cloud.......O8888888D.......... 99 | .........+88888888 ............. ************* ..............O8888888D.......... 100 | .........+88888888 .... Command Line Interface(Reloaded) ....O8888888D.......... 101 | .........+88888888...........................................88888888D.......... 102 | ..........D888888888888DO+. ..........................?ND888888888888D.......... 103 | ...........O8888888888888888888888...........D8888888888888888888888=........... 104 | ............ .:D8888888888888888888.........78888888888888888888O ..............`) 105 | -------------------------------------------------------------------------------- /config/hello_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "bytes" 18 | "os" 19 | "strings" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "github.com/aliyun/aliyun-cli/v3/cli" 25 | ) 26 | 27 | func TestDoHello(t *testing.T) { 28 | os.Setenv("ALIBABA_CLOUD_VENDOR", "cli_test_VendorTest") 29 | 30 | w := new(bytes.Buffer) 31 | stderr := new(bytes.Buffer) 32 | ctx := cli.NewCommandContext(w, stderr) 33 | ctx.Flags().AddByName("skip-secure-verify") 34 | profile := NewProfile("default") 35 | profile.Mode = AK 36 | 37 | exw := "-----------------------------------------------\n" + 38 | "!!! Configure Failed please configure again !!!\n" + 39 | "-----------------------------------------------\n" + 40 | "AccessKeyId/AccessKeySecret is empty! run `aliyun configure` first\n" + 41 | "-----------------------------------------------\n" + 42 | "!!! Configure Failed please configure again !!!\n" + 43 | "-----------------------------------------------\n" 44 | DoHello(ctx, &profile) 45 | assert.Equal(t, exw, w.String()) 46 | 47 | w.Reset() 48 | os.Setenv("DEBUG", "sdk") 49 | profile.AccessKeyId = "AccessKeyId" 50 | profile.AccessKeySecret = "AccessKeySecret" 51 | profile.RegionId = "cn-hangzhou" 52 | DoHello(ctx, &profile) 53 | assert.True(t, strings.Contains(w.String(), "-----------------------------------------------\n"+ 54 | "!!! Configure Failed please configure again !!!\n"+ 55 | "-----------------------------------------------")) 56 | } 57 | -------------------------------------------------------------------------------- /config/legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | 20 | ini "gopkg.in/ini.v1" 21 | ) 22 | 23 | func MigrateLegacyConfiguration() (conf *Configuration, err error) { 24 | path := hookGetHomePath(GetHomePath)() + "/.aliyuncli/credentials" 25 | _, statErr := os.Stat(path) 26 | if os.IsNotExist(statErr) { 27 | return 28 | } 29 | 30 | conf, migrateErr := MigrateCredentials(path) 31 | if migrateErr != nil { 32 | return 33 | } 34 | 35 | path = hookGetHomePath(GetHomePath)() + "/.aliyuncli/configure" 36 | mfErr := MigrateConfigure(path, conf) 37 | if mfErr != nil { 38 | return 39 | } 40 | 41 | return 42 | } 43 | 44 | func MigrateCredentials(path string) (conf *Configuration, err error) { 45 | conf = &Configuration{} 46 | ini, err := ini.Load(path) 47 | if err != nil { 48 | err = fmt.Errorf(" parse failed: %v", err) 49 | return 50 | } 51 | 52 | for _, section := range ini.Sections() { 53 | if section.Name() == "DEFAULT" { 54 | continue 55 | } 56 | profileName := section.Name() 57 | if section.Name() == "default" { 58 | conf.CurrentProfile = "default" 59 | } else { 60 | profileName = section.Name()[len("profile "):] 61 | if conf.CurrentProfile != "default" { 62 | conf.CurrentProfile = profileName 63 | } 64 | } 65 | // fmt.Printf("\n %s:", profileName) 66 | k1, e1 := section.GetKey("aliyun_access_key_id") 67 | k2, e2 := section.GetKey("aliyun_access_key_secret") 68 | if e1 == nil && e2 == nil { 69 | conf.Profiles = append(conf.Profiles, Profile{ 70 | Name: profileName, 71 | Mode: AK, 72 | AccessKeyId: k1.String(), 73 | AccessKeySecret: k2.String(), 74 | OutputFormat: "json", 75 | }) 76 | } 77 | } 78 | return 79 | } 80 | 81 | func MigrateConfigure(path string, conf *Configuration) (err error) { 82 | ini, err := ini.Load(path) 83 | if err != nil { 84 | err = fmt.Errorf("parse failed: %s", err) 85 | return 86 | } 87 | 88 | for _, section := range ini.Sections() { 89 | profile, ok := conf.GetProfile(section.Name()) 90 | if !ok { 91 | continue 92 | } 93 | 94 | r, err := section.GetKey("region") 95 | if err == nil { 96 | profile.RegionId = r.String() 97 | conf.PutProfile(profile) 98 | } 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aliyun/aliyun-cli/v3 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.8 6 | 7 | require ( 8 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 9 | github.com/alibabacloud-go/tea v1.3.9 10 | github.com/alibabacloud-go/tea-utils/v2 v2.0.7 11 | github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 12 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible 13 | github.com/aliyun/credentials-go v1.4.6 14 | github.com/alyu/configparser v0.0.0-20191103060215-744e9a66e7bc 15 | github.com/droundy/goopt v0.0.0-20220217183150-48d6390ad4d1 16 | github.com/jmespath/go-jmespath v0.4.0 17 | github.com/stretchr/testify v1.10.0 18 | github.com/syndtr/goleveldb v1.0.0 19 | golang.org/x/crypto v0.40.0 20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 21 | gopkg.in/ini.v1 v1.67.0 22 | ) 23 | 24 | require ( 25 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect 26 | github.com/alibabacloud-go/debug v1.0.1 // indirect 27 | github.com/clbanning/mxj/v2 v2.7.0 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/golang/snappy v1.0.0 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/kr/pretty v0.3.1 // indirect 32 | github.com/kr/text v0.2.0 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 34 | github.com/modern-go/reflect2 v1.0.2 // indirect 35 | github.com/onsi/ginkgo v1.16.5 // indirect 36 | github.com/onsi/gomega v1.30.0 // indirect 37 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect 38 | github.com/pmezard/go-difflib v1.0.0 // indirect 39 | github.com/rogpeppe/go-internal v1.14.1 // indirect 40 | github.com/tjfoc/gmsm v1.4.1 // indirect 41 | golang.org/x/net v0.41.0 // indirect 42 | golang.org/x/sys v0.34.0 // indirect 43 | golang.org/x/term v0.33.0 // indirect 44 | golang.org/x/time v0.11.0 // indirect 45 | gopkg.in/yaml.v3 v3.0.1 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /i18n/environment.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package i18n 16 | 17 | import ( 18 | "os" 19 | "strings" 20 | ) 21 | 22 | type LanguageCode string 23 | 24 | const ( 25 | Zh = LanguageCode("zh") 26 | En = LanguageCode("en") 27 | ) 28 | 29 | func getLanguageFromEnv() string { 30 | lang := os.Getenv("LANG") 31 | if strings.HasPrefix(lang, "zh_CN") { 32 | return "zh" 33 | } else { 34 | return "en" 35 | } 36 | } 37 | 38 | var language = getLanguageFromEnv() 39 | 40 | func SetLanguage(lang string) { 41 | language = lang 42 | } 43 | 44 | func GetLanguage() string { 45 | return language 46 | } 47 | -------------------------------------------------------------------------------- /i18n/environment_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package i18n 15 | 16 | import ( 17 | "os" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestEnvironment(t *testing.T) { 24 | SetLanguage("en") 25 | assert.Equal(t, "en", GetLanguage()) 26 | SetLanguage("zh") 27 | assert.Equal(t, "zh", GetLanguage()) 28 | } 29 | 30 | func TestGetLanguageFromEnv(t *testing.T) { 31 | originLANG := os.Getenv("LANG") 32 | defer os.Setenv("LANG", originLANG) 33 | os.Setenv("LANG", "zh_CN.UTF-8") 34 | assert.Equal(t, "zh", getLanguageFromEnv()) 35 | os.Setenv("LANG", "en_US.UTF-8") 36 | assert.Equal(t, "en", getLanguageFromEnv()) 37 | } 38 | -------------------------------------------------------------------------------- /i18n/text.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package i18n 16 | 17 | func T(en string, zh string) *Text { 18 | t := &Text{ 19 | dic: make(map[string]string), 20 | } 21 | t.dic["en"] = en 22 | if zh != "" { 23 | t.dic["zh"] = zh 24 | } 25 | return t 26 | } 27 | 28 | type Text struct { 29 | dic map[string]string 30 | } 31 | 32 | func (a *Text) Text() string { 33 | lang := GetLanguage() 34 | return a.Get(lang) 35 | } 36 | 37 | func (a *Text) Get(lang string) string { 38 | s, ok := a.dic[lang] 39 | if !ok { 40 | return "" 41 | } 42 | return s 43 | } 44 | 45 | func (a *Text) GetData() map[string]string { 46 | return a.dic 47 | } 48 | 49 | func (a *Text) GetMessage() string { 50 | // determine language 51 | lang := GetLanguage() 52 | return a.Get(lang) 53 | } 54 | -------------------------------------------------------------------------------- /i18n/text_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package i18n 15 | 16 | import ( 17 | "os" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestGet(t *testing.T) { 24 | tx := T("hello", "你好") 25 | assert.Equal(t, "hello", tx.Get("en")) 26 | assert.Equal(t, "你好", tx.Get("zh")) 27 | assert.Equal(t, "", tx.Get("jp")) 28 | } 29 | 30 | func TestText(t *testing.T) { 31 | tx := T("hello", "你好") 32 | language = "en" 33 | assert.Equal(t, "hello", tx.Text()) 34 | language = "zh" 35 | assert.Equal(t, "你好", tx.Text()) 36 | } 37 | 38 | func TestGetData(t *testing.T) { 39 | tx := T("hello", "你好") 40 | expected := make(map[string]string) 41 | expected["en"] = "hello" 42 | expected["zh"] = "你好" 43 | assert.Equal(t, expected, tx.GetData()) 44 | } 45 | 46 | func TestLibrary(t *testing.T) { 47 | //Test T(en string, zh string)*Text 48 | text := T("hello", "你好") 49 | assert.Equal(t, "hello", text.dic["en"]) 50 | assert.Equal(t, "你好", text.dic["zh"]) 51 | 52 | text = T("", "你好") 53 | assert.Equal(t, "", text.dic["en"]) 54 | assert.Equal(t, "你好", text.dic["zh"]) 55 | 56 | text = T("hello", "") 57 | assert.Equal(t, "hello", text.dic["en"]) 58 | assert.Equal(t, "", text.dic["zh"]) 59 | } 60 | 61 | // 临时保存原始的GetLanguage函数 62 | var originalGetLanguage func() string 63 | 64 | // 创建mock语言设置函数 65 | func mockGetLanguage(lang string) { 66 | // set env LANG = "zh_CN.UTF-8" 67 | switch lang { 68 | case "zh": 69 | os.Setenv("LANG", "zh_CN.UTF-8") 70 | case "en": 71 | os.Setenv("LANG", "en_US.UTF-8") 72 | case "fr": 73 | os.Setenv("LANG", "fr_FR.UTF-8") 74 | } 75 | language = lang 76 | } 77 | 78 | func TestText_GetMessage(t *testing.T) { 79 | // 测试数据 80 | testEn := "Hello World" 81 | testZh := "你好,世界" 82 | 83 | // 创建Text实例 84 | text := T(testEn, testZh) 85 | 86 | // 测试英文环境 87 | mockGetLanguage("en") 88 | if msg := text.GetMessage(); msg != testEn { 89 | t.Errorf("Expected English message to be %q, got %q", testEn, msg) 90 | } 91 | 92 | // 测试中文环境 93 | mockGetLanguage("zh") 94 | if msg := text.GetMessage(); msg != testZh { 95 | t.Errorf("Expected Chinese message to be %q, got %q", testZh, msg) 96 | } 97 | 98 | // 测试不支持的语言 99 | mockGetLanguage("fr") 100 | if msg := text.GetMessage(); msg != "" { 101 | t.Errorf("Expected unsupported language message to be empty, got %q", msg) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e +o pipefail 4 | 5 | show_help() { 6 | cat << EOF 7 | 8 | Alibaba Cloud Command Line Interface Installer 9 | 10 | -h Display this help and exit 11 | 12 | -V VERSION Custom CLI version. Default is 'latest' 13 | 14 | EOF 15 | } 16 | 17 | abort() { 18 | printf "%s\n" "$@" >&2 19 | exit 1 20 | } 21 | 22 | # First check OS. 23 | OS="$(uname)" 24 | if [[ "${OS}" == "Linux" ]] 25 | then 26 | CLI_ON_LINUX=1 27 | elif [[ "${OS}" == "Darwin" ]] 28 | then 29 | CLI_ON_MACOS=1 30 | else 31 | abort "Currently is only supported on macOS and Linux." 32 | fi 33 | 34 | VERSION="latest" 35 | 36 | if [ $# != 0 ]; 37 | then 38 | while getopts "hV:-" o 39 | do 40 | case "$o" in 41 | "h") 42 | show_help 43 | exit 0; 44 | ;; 45 | "V") 46 | VERSION="$OPTARG" 47 | ;; 48 | *) 49 | echo -e "Unexpected flag not supported" 50 | exit 1 51 | ;; 52 | esac 53 | done 54 | fi 55 | 56 | echo -e " 57 | ..............888888888888888888888 ........=8888888888888888888D=.............. 58 | ...........88888888888888888888888 ..........D8888888888888888888888I........... 59 | .........,8888888888888ZI: ...........................=Z88D8888888888D.......... 60 | .........+88888888 ..........................................88888888D.......... 61 | .........+88888888 .......Welcome to use Alibaba Cloud.......O8888888D.......... 62 | .........+88888888 ............. ************* ..............O8888888D.......... 63 | .........+88888888 .... Command Line Interface(Reloaded) ....O8888888D.......... 64 | .........+88888888...........................................88888888D.......... 65 | ..........D888888888888DO+. ..........................?ND888888888888D.......... 66 | ...........O8888888888888888888888...........D8888888888888888888888=........... 67 | ............ .:D8888888888888888888.........78888888888888888888O .............. 68 | " 69 | 70 | if [[ -n "${CLI_ON_MACOS-}" ]] 71 | then 72 | curl -O -fsSL https://aliyuncli.alicdn.com/aliyun-cli-macosx-"$VERSION"-universal.tgz 73 | tar zxf aliyun-cli-macosx-"$VERSION"-universal.tgz 74 | mv ./aliyun /usr/local/bin/ 75 | fi 76 | 77 | if [[ -n "${CLI_ON_LINUX-}" ]] 78 | then 79 | UNAME_MACHINE="$(uname -m)" 80 | if [[ "${UNAME_MACHINE}" == "arm64" || "${UNAME_MACHINE}" == "aarch64" ]] 81 | then 82 | curl -O -fsSL https://aliyuncli.alicdn.com/aliyun-cli-linux-"$VERSION"-arm64.tgz 83 | tar zxf aliyun-cli-linux-"$VERSION"-arm64.tgz 84 | else 85 | curl -O -fsSL https://aliyuncli.alicdn.com/aliyun-cli-linux-"$VERSION"-amd64.tgz 86 | tar zxf aliyun-cli-linux-"$VERSION"-amd64.tgz 87 | fi 88 | mv ./aliyun /usr/local/bin/ 89 | fi 90 | -------------------------------------------------------------------------------- /integration/ecs_test.ps1: -------------------------------------------------------------------------------- 1 | $global:g_var= 2 | $global:g_error=0 3 | $global:force="force" 4 | 5 | function do_command() { 6 | if (-not $($args[1]) -eq $force -and $g_error -eq 1) { 7 | $global:g_var = 8 | return $g_error 9 | } 10 | $cmd="$($args[0]) --access-key-id $env:ACCESS_KEY_ID --access-key-secret $env:ACCESS_KEY_SECRET --region cn-hangzhou 2>&1" 11 | Write-Output "Command<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 12 | Write-Output $cmd 13 | Write-Output ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Command" 14 | $global:g_var=Invoke-Expression $cmd 15 | Write-Output "Result<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 16 | Write-Output $g_var 17 | Write-Output ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Result" 18 | $err= Write-Output $g_var | Select-String -Pattern 'error' 19 | if (-not $($args[1]) -eq $force -and -not $err -eq ""){ 20 | $global:g_var = 21 | $global:g_error=1 22 | return $g_error 23 | } 24 | return 0 25 | } 26 | 27 | function ecs_create_instance() { 28 | do_command "aliyun ecs CreateInstance --ImageId ubuntu_18_04_64_20G_alibase_20190624.vhd --InstanceType ecs.xn4.small" $($args[0]) 29 | } 30 | 31 | function ecs_start_instance() { 32 | do_command "aliyun ecs StartInstance --InstanceId $($args[0])" $($args[1]) 33 | } 34 | 35 | function ecs_stop_instance() { 36 | do_command "aliyun ecs StopInstance --InstanceId $($args[0])" $($args[1]) 37 | } 38 | 39 | function ecs_instance_status_wait_until() { 40 | do_command "aliyun ecs DescribeInstances --InstanceIds `"['$($args[0])']`" --waiter expr=Instances.Instance[0].Status to=$($args[1]) timeout=600" $($args[2]) 41 | } 42 | 43 | function ecs_describe_instances() { 44 | do_command "aliyun ecs DescribeInstances" $($args[0]) 45 | } 46 | 47 | function ecs_delete_instance() { 48 | do_command "aliyun ecs DeleteInstance --InstanceId $($args[0])" $($args[1]) 49 | } 50 | 51 | function ecs_get_instance_ids() { 52 | ecs_describe_instances $force 53 | $ids=Write-Output $g_var | jq '.Instances.Instance[].InstanceId' -r 54 | $err=Write-Output $ids | Select-String -Pattern 'error' 55 | if (-not $err -eq ""){ 56 | $global:g_var="" 57 | }else { 58 | $global:g_var=$ids 59 | } 60 | } 61 | 62 | function ecs_clear_all_instances() { 63 | ecs_get_instance_ids 64 | $ids=$g_var 65 | foreach ($id in $ids) { 66 | Write-Output "###### Try to stop instance $id ######" 67 | ecs_stop_instance $id $force 68 | } 69 | 70 | foreach ($id in $ids) { 71 | Write-Output "###### Try to delete instance $id ######" 72 | ecs_instance_status_wait_until $id Stopped $force 73 | ecs_delete_instance $id $force 74 | } 75 | } 76 | 77 | function ecs_test () { 78 | 79 | ecs_create_instance 80 | $id= Write-Output $g_var | jq '.InstanceId' -r 81 | Write-Output "###### Try to test instance $id ######" 82 | ecs_instance_status_wait_until $id Stopped 83 | ecs_start_instance $id 84 | ecs_instance_status_wait_until $id Running 85 | ecs_stop_instance $id $force 86 | ecs_instance_status_wait_until $id Stopped 87 | ecs_delete_instance $id 88 | if ($g_error -eq 1){ 89 | ecs_clear_all_instances 90 | } 91 | return $g_error 92 | } 93 | 94 | 95 | ecs_test 96 | 97 | Write-Output "test result is $g_error" 98 | 99 | exit $g_error 100 | 101 | -------------------------------------------------------------------------------- /integration/ecs_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | g_var= 4 | g_error=0 5 | force="force" 6 | 7 | do_command() { 8 | if [[ $2 != $force && $g_error -eq 1 ]] 9 | then 10 | g_var= 11 | return $g_error 12 | fi 13 | 14 | cmd="$1 --access-key-id $ACCESS_KEY_ID --access-key-secret $ACCESS_KEY_SECRET --region $REGION_ID 2>&1" 15 | 16 | echo "Command<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 17 | echo $cmd 18 | echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Command" 19 | 20 | g_var=$(eval $cmd) 21 | 22 | echo "Result<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 23 | echo $g_var 24 | echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Result" 25 | 26 | err=$(echo $g_var | grep -i -e "error" -e "panic") 27 | 28 | if [[ $2 != $force && $err != "" ]] 29 | then 30 | g_var= 31 | g_error=1 32 | return $g_error 33 | fi 34 | 35 | return 0 36 | } 37 | 38 | ecs_create_instance() { 39 | do_command "aliyun ecs CreateInstance --ImageId 'aliyun_2_1903_x64_20G_alibase_20231221.vhd' --InstanceType 'ecs.t5-lc1m1.small' --InstanceChargeType PostPaid" $1 40 | } 41 | 42 | ecs_start_instance() { 43 | do_command "aliyun ecs StartInstance --InstanceId $1" $2 44 | } 45 | 46 | ecs_stop_instance() { 47 | do_command "aliyun ecs StopInstance --InstanceId $1" $2 48 | } 49 | 50 | ecs_instance_status_wait_until() { 51 | do_command "aliyun ecs DescribeInstances --InstanceIds ['$1'] --waiter expr=Instances.Instance[0].Status to=$2 timeout=600" $3 52 | } 53 | 54 | ecs_describe_instances() { 55 | do_command "aliyun ecs DescribeInstances" $1 56 | } 57 | 58 | ecs_delete_instance() { 59 | do_command "aliyun ecs DeleteInstance --InstanceId $1" $2 60 | } 61 | 62 | ecs_get_instance_ids() { 63 | ecs_describe_instances $force 64 | ids=$(echo $g_var | jq '.Instances.Instance[].InstanceId') 65 | err=$(echo ids | grep -i "error") 66 | if [[ $err != "" ]] 67 | then 68 | g_var="" 69 | else 70 | g_var=$ids 71 | fi 72 | } 73 | 74 | ecs_clear_all_instances() { 75 | ecs_get_instance_ids 76 | 77 | ids=$g_var 78 | 79 | for id in ${ids[@]}; do 80 | echo "###### Try to stop instance $id ######" 81 | ecs_stop_instance $id $force 82 | done 83 | 84 | for id in ${ids[@]}; do 85 | echo "###### Try to delete instance $id ######" 86 | 87 | ecs_instance_status_wait_until $id Stopped $force 88 | 89 | ecs_delete_instance $id $force 90 | done 91 | } 92 | 93 | ecs_test() { 94 | ecs_create_instance 95 | 96 | id=$(echo $g_var | jq '.InstanceId') 97 | 98 | echo "###### Try to test instance $id ######" 99 | 100 | ecs_instance_status_wait_until $id Stopped 101 | 102 | ecs_start_instance $id 103 | 104 | ecs_instance_status_wait_until $id Running 105 | 106 | ecs_stop_instance $id $force 107 | 108 | ecs_instance_status_wait_until $id Stopped 109 | 110 | ecs_delete_instance $id 111 | 112 | if [[ $g_error -eq 1 ]] 113 | then 114 | ecs_clear_all_instances 115 | fi 116 | 117 | return $g_error 118 | } 119 | 120 | ecs_test 121 | 122 | echo "test result is $g_error" 123 | 124 | exit $g_error 125 | -------------------------------------------------------------------------------- /integration/https_proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export HTTPS_PROXY=https://1.2.3.4:8080/ 4 | 5 | cmd="aliyun sts GetCallerIdentity --access-key-id $ACCESS_KEY_ID --access-key-secret $ACCESS_KEY_SECRET --region $REGION_ID 2>&1" 6 | g_var=$(eval $cmd) 7 | 8 | err=$(echo $g_var | grep -i -e "proxyconnect tcp" -e "timeout") 9 | 10 | if [[ $err == "" ]] 11 | then 12 | exit 1 13 | fi 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /integration/oss_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUCKET="sdk-oss-test" 4 | 5 | which aliyun 6 | 7 | # sign v4 8 | aliyun oss cat oss://$BUCKET/123.txt --region cn-hangzhou --sign-version v4 9 | aliyun oss ls oss://$BUCKET//123.txt --region cn-hangzhou --sign-version v4 10 | 11 | # cleanup 12 | aliyun oss rm oss://$BUCKET/test.txt --region cn-hangzhou 13 | 14 | if [ -f ./downloaded.txt ]; then 15 | rm ./downloaded.txt 16 | fi 17 | 18 | # ready to go 19 | echo "version1" > ./test.txt 20 | aliyun oss cp ./test.txt oss://$BUCKET/test.txt --region cn-hangzhou 21 | 22 | echo "version2" > ./test.txt 23 | aliyun oss cp ./test.txt oss://$BUCKET/test.txt -f --region cn-hangzhou 24 | 25 | aliyun oss cp oss://$BUCKET/test.txt ./downloaded.txt --region cn-hangzhou 26 | 27 | OUTPUT=$(cat ./downloaded.txt) 28 | 29 | if [[ "$OUTPUT" == "version2" ]]; 30 | then 31 | exit 0; 32 | else 33 | exit 1; 34 | fi 35 | -------------------------------------------------------------------------------- /integration/parser_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "configure list" 4 | go run main/main.go configure list 5 | 6 | echo "oss ls" 7 | go run main/main.go oss ls 8 | 9 | echo "oss ls --region cn-hangzhou" 10 | go run main/main.go oss ls --region cn-hangzhou 11 | 12 | echo "ls oss://weeping/tingwu --region cn-shanghai --sign-version v4" 13 | go run main/main.go oss ls oss://weeping/tingwu --region cn-shanghai --sign-version v4 14 | 15 | echo "sts GetCallerIdentity" 16 | go run main/main.go sts GetCallerIdentity 17 | 18 | echo "sts GetCallerIdentity --region cn-beijing" 19 | go run main/main.go sts GetCallerIdentity --region cn-beijing 20 | -------------------------------------------------------------------------------- /integration/vpc_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | g_var= 4 | g_error=0 5 | 6 | do_command() { 7 | if [[ $2 != $force && $g_error -eq 1 ]] 8 | then 9 | g_var= 10 | return $g_error 11 | fi 12 | 13 | cmd="$1 --access-key-id $ACCESS_KEY_ID --access-key-secret $ACCESS_KEY_SECRET --region $REGION_ID 2>&1" 14 | 15 | echo "Command<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 16 | echo $cmd 17 | echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Command" 18 | 19 | g_var=$(eval $cmd) 20 | 21 | echo "Result<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 22 | echo $g_var 23 | echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Result" 24 | 25 | err=$(echo $g_var | grep -i -e "error" -e "panic") 26 | 27 | if [[ $2 != $force && $err != "" ]] 28 | then 29 | g_var= 30 | g_error=1 31 | return $g_error 32 | fi 33 | 34 | return 0 35 | } 36 | 37 | vpc_test() { 38 | which aliyun 39 | 40 | do_command "aliyun vpc CreateVpc" 41 | 42 | id=$(echo $g_var | jq '.VpcId') 43 | 44 | echo "###### Try to test instance $id ######" 45 | 46 | sleep 3 47 | 48 | do_command "aliyun vpc DeleteVpc --VpcId $id" 49 | 50 | return $g_error 51 | } 52 | 53 | vpc_test 54 | 55 | echo "test result is $g_error" 56 | 57 | exit $g_error 58 | -------------------------------------------------------------------------------- /main/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMain(m *testing.M) { 8 | Main([]string{}) 9 | } 10 | 11 | func TestParseInSecure(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | args []string 15 | expected bool 16 | }{ 17 | { 18 | name: "Insecure flag present", 19 | args: []string{"--insecure"}, 20 | expected: true, 21 | }, 22 | { 23 | name: "Insecure flag with value", 24 | args: []string{"--insecure", "true"}, 25 | expected: true, 26 | }, 27 | { 28 | name: "Insecure flag absent", 29 | args: []string{"--secure"}, 30 | expected: false, 31 | }, 32 | { 33 | name: "Empty args", 34 | args: []string{}, 35 | expected: false, 36 | }, 37 | } 38 | 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | result, _ := ParseInSecure(tt.args) 42 | if result != tt.expected { 43 | t.Errorf("ParseInSecure(%v) = %v; want %v", tt.args, result, tt.expected) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /meta/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | ) 20 | 21 | type InvalidEndpointError struct { 22 | LocationError error 23 | Region string 24 | Product *Product 25 | } 26 | 27 | func (e *InvalidEndpointError) Error() string { 28 | s := fmt.Sprintf("unknown endpoint for region %s", e.Region) 29 | if e.Product != nil { 30 | // s = s + fmt", try add --endpoint %s", e.Suggestion 31 | s = s + "\n you need to add --endpoint xxx.aliyuncs.com" 32 | if e.LocationError != nil { 33 | s = s + fmt.Sprintf("\n LC_Error: %s", e.LocationError.Error()) 34 | } 35 | if e.Product.RegionalEndpointPattern != "" { 36 | ep := strings.Replace(e.Product.RegionalEndpointPattern, "[RegionId]", e.Region, 1) 37 | s = s + fmt.Sprintf(", sample: --endpoint %s", ep) 38 | } 39 | } 40 | return s 41 | } 42 | -------------------------------------------------------------------------------- /meta/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "github.com/stretchr/testify/assert" 18 | 19 | "fmt" 20 | "testing" 21 | ) 22 | 23 | func TestInvalidEndpointError_Error(t *testing.T) { 24 | err := &InvalidEndpointError{ 25 | Product: &Product{ 26 | RegionalEndpointPattern: "endpoint", 27 | }, 28 | Region: "cn-hangzhou", 29 | } 30 | err.LocationError = fmt.Errorf("here is a error.") 31 | msg := err.Error() 32 | assert.Contains(t, msg, "here is a error.") 33 | 34 | err.LocationError = nil 35 | msg = err.Error() 36 | assert.Contains(t, msg, "cn-hangzhou") 37 | } 38 | -------------------------------------------------------------------------------- /meta/product.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "strings" 18 | 19 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 20 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints" 21 | ) 22 | 23 | type ProductSet struct { 24 | Products []Product `json:"products"` 25 | } 26 | 27 | type Product struct { 28 | Code string `json:"code"` 29 | Version string `json:"version"` 30 | Catalog1 map[string]string `json:"catalog1"` 31 | Catalog2 map[string]string `json:"catalog2"` 32 | Name map[string]string `json:"name"` 33 | LocationServiceCode string `json:"location_service_code"` 34 | RegionalEndpoints map[string]string `json:"regional_endpoints"` 35 | GlobalEndpoint string `json:"global_endpoint"` 36 | RegionalEndpointPattern string `json:"regional_endpoint_patterns"` 37 | ApiStyle string `json:"api_style"` 38 | ApiNames []string `json:"apis"` 39 | } 40 | 41 | func (a *Product) GetLowerCode() string { 42 | return strings.ToLower(a.Code) 43 | } 44 | 45 | func (a *Product) GetEndpoint(region string, client *sdk.Client) (endpoint string, err error) { 46 | var le error 47 | if a.LocationServiceCode != "" { 48 | // resolve endpoint from location service 49 | rp := endpoints.ResolveParam{ 50 | Product: a.Code, 51 | RegionId: region, 52 | LocationProduct: a.LocationServiceCode, 53 | LocationEndpointType: "openAPI", 54 | CommonApi: client.ProcessCommonRequest, 55 | } 56 | ep, err := endpoints.Resolve(&rp) 57 | if err == nil { 58 | return ep, nil 59 | } else { 60 | le = err 61 | } 62 | } 63 | 64 | ep, ok := a.RegionalEndpoints[region] 65 | if ok { 66 | return ep, nil 67 | } 68 | 69 | if a.GlobalEndpoint != "" { 70 | return a.GlobalEndpoint, nil 71 | } 72 | 73 | return "", &InvalidEndpointError{le, region, a} 74 | } 75 | -------------------------------------------------------------------------------- /meta/product_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 18 | "github.com/stretchr/testify/assert" 19 | 20 | "testing" 21 | ) 22 | 23 | func TestProduct_GetLowerCode(t *testing.T) { 24 | product := &Product{ 25 | Code: "code", 26 | } 27 | code := product.GetLowerCode() 28 | assert.Equal(t, code, "code") 29 | } 30 | 31 | func TestProduct_GetEndpoint(t *testing.T) { 32 | product := &Product{ 33 | Code: "arms", 34 | RegionalEndpoints: map[string]string{ 35 | "cn-hangzhou": "arms.cn-hangzhou.aliyuncs.com", 36 | }, 37 | LocationServiceCode: "arms", 38 | } 39 | client, err := sdk.NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret") 40 | assert.Nil(t, err) 41 | endpoint, err := product.GetEndpoint("cn-hangzhou", client) 42 | assert.Nil(t, err) 43 | assert.Equal(t, endpoint, "arms.cn-hangzhou.aliyuncs.com") 44 | 45 | product.LocationServiceCode = "" 46 | product.GlobalEndpoint = "arms.aliyuncs.com" 47 | endpoint, err = product.GetEndpoint("us-west-1", client) 48 | assert.Nil(t, err) 49 | assert.Equal(t, endpoint, "arms.aliyuncs.com") 50 | 51 | product.GlobalEndpoint = "" 52 | _, err = product.GetEndpoint("us-west-1", client) 53 | assert.NotNil(t, err) 54 | assert.Contains(t, err.Error(), "us-west-1") 55 | } 56 | -------------------------------------------------------------------------------- /meta/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | 20 | aliyunopenapimeta "github.com/aliyun/aliyun-cli/v3/aliyun-openapi-meta" 21 | ) 22 | 23 | func ReadJsonFrom(path string, v interface{}) error { 24 | buf, err := aliyunopenapimeta.Metadatas.ReadFile("metadatas/" + path) 25 | if err != nil { 26 | return fmt.Errorf("read json from %s failed", path) 27 | } 28 | err = json.Unmarshal(buf, v) 29 | if err != nil { 30 | return fmt.Errorf("unmarshal json %s failed %v", path, err) 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /meta/reader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package meta 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestReadJsonFrom(t *testing.T) { 23 | path := "" 24 | err := ReadJsonFrom(path, nil) 25 | assert.NotNil(t, err) 26 | assert.Equal(t, "read json from failed", err.Error()) 27 | 28 | api := &Api{} 29 | path = `invalid path` 30 | 31 | err = ReadJsonFrom(path, api) 32 | assert.NotNil(t, err) 33 | assert.Equal(t, "read json from invalid path failed", err.Error()) 34 | 35 | err = ReadJsonFrom("ecs/DescribeRegions.json", api) 36 | assert.Equal(t, "DescribeRegions", api.Name) 37 | assert.Nil(t, err) 38 | 39 | str := "" 40 | err = ReadJsonFrom("ecs/DescribeRegions.json", &str) 41 | assert.NotNil(t, err) 42 | assert.Equal(t, "unmarshal json ecs/DescribeRegions.json failed json: cannot unmarshal object into Go value of type string", err.Error()) 43 | } 44 | -------------------------------------------------------------------------------- /newmeta/meta_test.go: -------------------------------------------------------------------------------- 1 | package newmeta 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetMetadataPrefix(t *testing.T) { 10 | assert.Equal(t, "zh-CN", GetMetadataPrefix("")) 11 | assert.Equal(t, "en-US", GetMetadataPrefix("en")) 12 | assert.Equal(t, "zh-CN", GetMetadataPrefix("zh")) 13 | } 14 | 15 | func TestGetMetadata(t *testing.T) { 16 | content, err := GetMetadata("en", "/products.json") 17 | assert.Nil(t, err) 18 | assert.Greater(t, len(content), 100) 19 | } 20 | 21 | func TestGetProductName(t *testing.T) { 22 | name, err := GetProductName("en", "ecs") 23 | assert.Nil(t, err) 24 | assert.Equal(t, "Elastic Compute Service", name) 25 | name, err = GetProductName("zh", "ecs") 26 | assert.Nil(t, err) 27 | assert.Equal(t, "云服务器 ECS", name) 28 | } 29 | 30 | func TestGetAPI(t *testing.T) { 31 | api, err := GetAPI("en", "ecs", "DescribeRegions") 32 | assert.Nil(t, err) 33 | assert.Equal(t, "DescribeRegions", api.Title) 34 | assert.Equal(t, "Queries available Alibaba Cloud regions.", api.Summary) 35 | assert.Equal(t, false, api.Deprecated) 36 | 37 | api2, err := GetAPI("en", "ecs", "Invalid") 38 | assert.Nil(t, err) 39 | assert.Nil(t, api2) 40 | } 41 | 42 | func TestGetAPIDetail(t *testing.T) { 43 | api, err := GetAPIDetail("en", "ecs", "DescribeRegions") 44 | assert.Nil(t, err) 45 | assert.Equal(t, "DescribeRegions", api.Name) 46 | assert.Equal(t, "GET|POST", api.Method) 47 | assert.Equal(t, false, api.Deprecated) 48 | } 49 | 50 | func TestIsAnonymousAPI(t *testing.T) { 51 | akapi, err := GetAPIDetail("en", "ecs", "DescribeRegions") 52 | assert.Nil(t, err) 53 | assert.False(t, akapi.IsAnonymousAPI()) 54 | api, err := GetAPIDetail("en", "sts", "AssumeRoleWithOIDC") 55 | assert.Nil(t, err) 56 | assert.True(t, api.IsAnonymousAPI()) 57 | } 58 | -------------------------------------------------------------------------------- /openapi/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | 20 | "github.com/aliyun/aliyun-cli/v3/cli" 21 | "github.com/aliyun/aliyun-cli/v3/meta" 22 | ) 23 | 24 | // return when use unknown product 25 | type InvalidProductError struct { 26 | Code string 27 | library *Library 28 | } 29 | 30 | func (e *InvalidProductError) Error() string { 31 | return fmt.Sprintf("'%s' is not a valid command or product. See `aliyun help`.", strings.ToLower(e.Code)) 32 | } 33 | 34 | func (e *InvalidProductError) GetSuggestions() []string { 35 | sr := cli.NewSuggester(strings.ToLower(e.Code), 2) 36 | for _, p := range e.library.GetProducts() { 37 | sr.Apply(strings.ToLower(p.Code)) 38 | } 39 | return sr.GetResults() 40 | } 41 | 42 | // return when use unknown api 43 | type InvalidApiError struct { 44 | Name string 45 | product *meta.Product 46 | } 47 | 48 | func (e *InvalidApiError) Error() string { 49 | return fmt.Sprintf("'%s' is not a valid api. See `aliyun help %s`.", e.Name, e.product.GetLowerCode()) 50 | } 51 | 52 | func (e *InvalidApiError) GetSuggestions() []string { 53 | sr := cli.NewSuggester(e.Name, 2) 54 | for _, s := range e.product.ApiNames { 55 | sr.Apply(s) 56 | } 57 | return sr.GetResults() 58 | } 59 | 60 | // return when use unknown parameter 61 | type InvalidParameterError struct { 62 | Name string 63 | api *meta.Api 64 | flags *cli.FlagSet 65 | } 66 | 67 | func (e *InvalidParameterError) Error() string { 68 | return fmt.Sprintf("'--%s' is not a valid parameter or flag. See `aliyun help %s %s`.", 69 | e.Name, e.api.Product.GetLowerCode(), e.api.Name) 70 | } 71 | 72 | func (e *InvalidParameterError) GetSuggestions() []string { 73 | sr := cli.NewSuggester(e.Name, 2) 74 | for _, p := range e.api.Parameters { 75 | sr.Apply(p.Name) 76 | } 77 | for _, f := range e.flags.Flags() { 78 | sr.Apply(f.Name) 79 | } 80 | return sr.GetResults() 81 | } 82 | -------------------------------------------------------------------------------- /openapi/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "strings" 18 | "testing" 19 | 20 | "github.com/aliyun/aliyun-cli/v3/cli" 21 | "github.com/aliyun/aliyun-cli/v3/meta" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestInvalidProductError_Error(t *testing.T) { 26 | err := &InvalidProductError{ 27 | Code: "ecs", 28 | } 29 | str := err.Error() 30 | assert.Equal(t, "'ecs' is not a valid command or product. See `aliyun help`.", str) 31 | } 32 | 33 | func TestInvalidProductError_GetSuggestions(t *testing.T) { 34 | err := &InvalidProductError{ 35 | Code: "ecs", 36 | library: &Library{ 37 | builtinRepo: &meta.Repository{ 38 | Products: []meta.Product{ 39 | { 40 | Code: "ecs", 41 | }, 42 | }, 43 | }, 44 | }, 45 | } 46 | arrstr := err.GetSuggestions() 47 | str := strings.Join(arrstr, ",") 48 | assert.Contains(t, str, "ecs") 49 | } 50 | 51 | func TestInvalidApiError_Error(t *testing.T) { 52 | err := &InvalidApiError{ 53 | Name: "describeregion", 54 | product: &meta.Product{ 55 | Code: "ecs", 56 | }, 57 | } 58 | str := err.Error() 59 | assert.Equal(t, "'describeregion' is not a valid api. See `aliyun help ecs`.", str) 60 | } 61 | 62 | func TestInvalidApiError_GetSuggestions(t *testing.T) { 63 | err := &InvalidApiError{ 64 | Name: "describeregion", 65 | product: &meta.Product{ 66 | Code: "ecs", 67 | ApiNames: []string{"describeregion"}, 68 | }, 69 | } 70 | arrstr := err.GetSuggestions() 71 | str := strings.Join(arrstr, ",") 72 | assert.Contains(t, str, "describeregion") 73 | } 74 | 75 | func TestInvalidParameterError_Error(t *testing.T) { 76 | err := &InvalidParameterError{ 77 | Name: "ak", 78 | api: &meta.Api{ 79 | Name: "describeregion", 80 | Product: &meta.Product{ 81 | Code: "ecs", 82 | }, 83 | }, 84 | } 85 | str := err.Error() 86 | assert.Equal(t, "'--ak' is not a valid parameter or flag. See `aliyun help ecs describeregion`.", str) 87 | } 88 | 89 | func TestInvalidParameterError_GetSuggestions(t *testing.T) { 90 | err := &InvalidParameterError{ 91 | Name: "secure", 92 | api: &meta.Api{ 93 | Name: "describeregion", 94 | Parameters: []meta.Parameter{ 95 | { 96 | Name: "test", 97 | }, 98 | }, 99 | }, 100 | flags: cli.NewFlagSet(), 101 | } 102 | AddFlags(err.flags) 103 | err.GetSuggestions() 104 | arrstr := err.GetSuggestions() 105 | str := strings.Join(arrstr, ",") 106 | assert.Contains(t, str, "secure") 107 | } 108 | -------------------------------------------------------------------------------- /openapi/flags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/aliyun/aliyun-cli/v3/cli" 20 | "github.com/aliyun/aliyun-cli/v3/i18n" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestAFlags(t *testing.T) { 25 | i18n.SetLanguage("en") 26 | flagset := cli.NewFlagSet() 27 | AddFlags(flagset) 28 | secureflag := SecureFlag(flagset) 29 | assert.Equal(t, "secure", secureflag.Name) 30 | assert.Equal(t, "use `--secure` to force https", secureflag.Short.Text()) 31 | 32 | forceflag := ForceFlag(flagset) 33 | assert.Equal(t, "force", forceflag.Name) 34 | assert.Equal(t, "use `--force` to skip api and parameters check", forceflag.Short.Text()) 35 | 36 | endpointflag := EndpointFlag(flagset) 37 | assert.Equal(t, "endpoint", endpointflag.Name) 38 | assert.Equal(t, "use `--endpoint ` to assign endpoint", endpointflag.Short.Text()) 39 | 40 | versionflag := VersionFlag(flagset) 41 | assert.Equal(t, "version", versionflag.Name) 42 | assert.Equal(t, "use `--version ` to assign product api version", versionflag.Short.Text()) 43 | 44 | headerflag := HeaderFlag(flagset) 45 | assert.Equal(t, "header", headerflag.Name) 46 | assert.Equal(t, "use `--header X-foo=bar` to add custom HTTP header, repeatable", headerflag.Short.Text()) 47 | 48 | bodyflag := BodyFlag(flagset) 49 | assert.Equal(t, "body", bodyflag.Name) 50 | assert.Equal(t, "use `--body $(cat foo.json)` to assign http body in RESTful call", bodyflag.Short.Text()) 51 | 52 | bodyfileflag := BodyFileFlag(flagset) 53 | assert.Equal(t, "body-file", bodyfileflag.Name) 54 | assert.Equal(t, "assign http body in Restful call with local file", bodyfileflag.Short.Text()) 55 | 56 | acceptflag := AcceptFlag(flagset) 57 | assert.Equal(t, "accept", acceptflag.Name) 58 | assert.Equal(t, "add `--accept {json|xml}` to add Accept header", acceptflag.Short.Text()) 59 | 60 | roaflag := RoaFlag(flagset) 61 | assert.Equal(t, "roa", roaflag.Name) 62 | assert.Equal(t, "use `--roa {GET|PUT|POST|DELETE}` to assign restful call.[DEPRECATED]", roaflag.Short.Text()) 63 | 64 | dryrunflag := DryRunFlag(flagset) 65 | assert.Equal(t, "dryrun", dryrunflag.Name) 66 | assert.Equal(t, "add `--dryrun` to validate and print request without running.", dryrunflag.Short.Text()) 67 | 68 | quietflag := QuietFlag(flagset) 69 | assert.Equal(t, "quiet", quietflag.Name) 70 | assert.Equal(t, "add `--quiet` to hide normal output", quietflag.Short.Text()) 71 | 72 | outputflag := OutputFlag(flagset) 73 | assert.Equal(t, "output", outputflag.Name) 74 | assert.Equal(t, "use `--output cols=Field1,Field2 [rows=jmesPath]` to print output as table", outputflag.Short.Text()) 75 | 76 | methodflag := MethodFlag(flagset) 77 | assert.Equal(t, "method", methodflag.Name) 78 | assert.Equal(t, "add `--method {GET|POST}` to assign rpc call method.", methodflag.Short.Text()) 79 | } 80 | -------------------------------------------------------------------------------- /openapi/force_rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 20 | "github.com/aliyun/aliyun-cli/v3/cli" 21 | ) 22 | 23 | type ForceRpcInvoker struct { 24 | *BasicInvoker 25 | method string 26 | } 27 | 28 | func (a *ForceRpcInvoker) Prepare(ctx *cli.Context) (err error) { 29 | // assign api name 30 | a.request.ApiName = a.method 31 | // default to use https 32 | a.request.Scheme = "https" 33 | 34 | // assign parameters 35 | for _, f := range ctx.UnknownFlags().Flags() { 36 | a.request.QueryParams[f.Name], _ = f.GetValue() 37 | } 38 | 39 | // --insecure use http 40 | if _, ok := InsecureFlag(ctx.Flags()).GetValue(); ok { 41 | a.request.Scheme = "http" 42 | } 43 | 44 | // --secure use https 45 | if _, ok := SecureFlag(ctx.Flags()).GetValue(); ok { 46 | a.request.Scheme = "https" 47 | } 48 | 49 | // if '--method' assigned, reset method 50 | if method, ok := MethodFlag(ctx.Flags()).GetValue(); ok { 51 | if method == "GET" || method == "POST" { 52 | a.request.Method = method 53 | } else { 54 | err = fmt.Errorf("--method value %s is not supported, please set method in {GET|POST}", method) 55 | return 56 | } 57 | } 58 | 59 | return 60 | } 61 | 62 | func (a *ForceRpcInvoker) Call() (resp *responses.CommonResponse, err error) { 63 | resp, err = a.client.ProcessCommonRequest(a.request) 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /openapi/force_rpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "bufio" 18 | "testing" 19 | 20 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 21 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 22 | "github.com/aliyun/aliyun-cli/v3/cli" 23 | "github.com/aliyun/aliyun-cli/v3/config" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestForceRpcInvoker_Prepare(t *testing.T) { 28 | a := &ForceRpcInvoker{ 29 | BasicInvoker: &BasicInvoker{ 30 | request: requests.NewCommonRequest(), 31 | }, 32 | method: "DescribeRegion", 33 | } 34 | a.BasicInvoker.request.QueryParams = make(map[string]string) 35 | w := new(bufio.Writer) 36 | stderr := new(bufio.Writer) 37 | ctx := cli.NewCommandContext(w, stderr) 38 | cmd := config.NewConfigureCommand() 39 | cmd.EnableUnknownFlag = true 40 | ctx.EnterCommand(cmd) 41 | 42 | secureflag := NewSecureFlag() 43 | methodflag := NewMethodFlag() 44 | secureflag.SetAssigned(true) 45 | methodflag.SetAssigned(true) 46 | methodflag.SetValue("POST") 47 | ctx.Flags().Add(secureflag) 48 | ctx.Flags().Add(NewInsecureFlag()) 49 | ctx.Flags().Add(methodflag) 50 | ctx.UnknownFlags().Add(NewSecureFlag()) 51 | err := a.Prepare(ctx) 52 | assert.Nil(t, err) 53 | } 54 | 55 | func TestForceRpcInvoker_Call(t *testing.T) { 56 | a := &ForceRpcInvoker{ 57 | BasicInvoker: &BasicInvoker{ 58 | request: requests.NewCommonRequest(), 59 | }, 60 | method: "DescribeRegion", 61 | } 62 | client, err := sdk.NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret") 63 | assert.Nil(t, err) 64 | a.client = client 65 | _, err = a.Call() 66 | assert.NotNil(t, err) 67 | } 68 | -------------------------------------------------------------------------------- /openapi/invoker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "bytes" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | 22 | "github.com/aliyun/aliyun-cli/v3/cli" 23 | "github.com/aliyun/aliyun-cli/v3/config" 24 | "github.com/aliyun/aliyun-cli/v3/meta" 25 | ) 26 | 27 | func TestBasicInvoker_Init(t *testing.T) { 28 | cp := &config.Profile{ 29 | Mode: config.AuthenticateMode("AK"), 30 | AccessKeyId: "akid", 31 | AccessKeySecret: "aksecret", 32 | } 33 | invoker := NewBasicInvoker(cp) 34 | client := invoker.getClient() 35 | assert.Nil(t, client) 36 | 37 | req := invoker.getRequest() 38 | assert.Nil(t, req) 39 | 40 | w := new(bytes.Buffer) 41 | stderr := new(bytes.Buffer) 42 | ctx := cli.NewCommandContext(w, stderr) 43 | 44 | regionflag := config.NewRegionFlag() 45 | regionflag.SetAssigned(true) 46 | regionflag.SetValue("cn-hangzhou") 47 | ctx.Flags().Add(regionflag) 48 | ctx.Flags().Add(config.NewRegionIdFlag()) 49 | 50 | endpointflag := NewEndpointFlag() 51 | endpointflag.SetAssigned(true) 52 | endpointflag.SetValue("ecs.cn-hangzhou.aliyuncs") 53 | ctx.Flags().Add(endpointflag) 54 | 55 | versionflag := NewVersionFlag() 56 | versionflag.SetAssigned(true) 57 | versionflag.SetValue("v1.0") 58 | ctx.Flags().Add(versionflag) 59 | 60 | headerflag := NewHeaderFlag() 61 | headerflag.SetValues([]string{"Accept=xml", "Accept=json", "Content-Type=json", "testfail"}) 62 | ctx.Flags().Add(headerflag) 63 | 64 | ctx.Flags().Add(config.NewSkipSecureVerify()) 65 | 66 | product := &meta.Product{} 67 | 68 | invoker.profile.Mode = config.AuthenticateMode("StsToken") 69 | err := invoker.Init(ctx, product) 70 | assert.NotNil(t, err) 71 | assert.Equal(t, "invaild flag --header `testfail` use `--header HeaderName=Value`", err.Error()) 72 | 73 | regionflag.SetAssigned(false) 74 | endpointflag.SetAssigned(false) 75 | versionflag.SetAssigned(false) 76 | headerflag.SetValues([]string{}) 77 | err = invoker.Init(ctx, product) 78 | assert.NotNil(t, err) 79 | assert.Equal(t, "missing version for product ", err.Error()) 80 | 81 | invoker.profile.Mode = config.AuthenticateMode("StsToken") 82 | invoker.profile.StsToken = "ststoken" 83 | product.Version = "v1.0" 84 | err = invoker.Init(ctx, product) 85 | assert.NotNil(t, err) 86 | assert.Equal(t, "missing region for product ", err.Error()) 87 | 88 | invoker.profile.RegionId = "cn-hangzhou" 89 | err = invoker.Init(ctx, product) 90 | assert.NotNil(t, err) 91 | assert.Equal(t, "unknown endpoint for /cn-hangzhou! failed unknown endpoint for region cn-hangzhou\n you need to add --endpoint xxx.aliyuncs.com", err.Error()) 92 | 93 | endpointflag.SetAssigned(true) 94 | err = invoker.Init(ctx, product) 95 | assert.Nil(t, err) 96 | } 97 | -------------------------------------------------------------------------------- /openapi/library_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "github.com/aliyun/aliyun-cli/v3/meta" 18 | "github.com/aliyun/aliyun-cli/v3/newmeta" 19 | "github.com/stretchr/testify/assert" 20 | 21 | "bytes" 22 | "testing" 23 | ) 24 | 25 | func TestLibrary_PrintProducts(t *testing.T) { 26 | w := new(bytes.Buffer) 27 | library := NewLibrary(w, "en") 28 | 29 | _, isexist := library.GetApi("aos", "v1.0", "describe") 30 | assert.False(t, isexist) 31 | 32 | products := library.GetProducts() 33 | assert.NotNil(t, products) 34 | 35 | library.builtinRepo.Products = []meta.Product{ 36 | { 37 | Code: "ecs", 38 | }, 39 | } 40 | library.PrintProducts() 41 | } 42 | 43 | func TestLibrary_PrintProductUsage(t *testing.T) { 44 | w := new(bytes.Buffer) 45 | library := NewLibrary(w, "en") 46 | library.builtinRepo = getRepository() 47 | err := library.PrintProductUsage("aos", true) 48 | assert.Equal(t, "'aos' is not a valid command or product. See `aliyun help`.", err.Error()) 49 | 50 | err = library.PrintProductUsage("ecs", true) 51 | assert.Nil(t, err) 52 | 53 | library.builtinRepo = getRepository() 54 | err = library.PrintProductUsage("ecs", true) 55 | assert.Nil(t, err) 56 | } 57 | 58 | func TestLibrary_PrintApiUsage(t *testing.T) { 59 | w := new(bytes.Buffer) 60 | library := NewLibrary(w, "en") 61 | library.builtinRepo = getRepository() 62 | err := library.PrintApiUsage("aos", "DescribeRegions") 63 | assert.Equal(t, "'aos' is not a valid command or product. See `aliyun help`.", err.Error()) 64 | 65 | err = library.PrintApiUsage("ecs", "DescribeRegions") 66 | assert.Nil(t, err) 67 | 68 | library.builtinRepo = getRepository() 69 | err = library.PrintApiUsage("ecs", "DescribeRegions") 70 | assert.Nil(t, err) 71 | } 72 | 73 | func Test_printParameters(t *testing.T) { 74 | w := new(bytes.Buffer) 75 | params := []meta.Parameter{ 76 | { 77 | Hidden: true, 78 | }, 79 | { 80 | Position: "Domain", 81 | }, 82 | { 83 | Type: "RepeatList", 84 | Required: true, 85 | }, 86 | { 87 | Required: false, 88 | }, 89 | { 90 | SubParameters: []meta.Parameter{ 91 | { 92 | Name: "test", 93 | }, 94 | }, 95 | }, 96 | } 97 | printParameters(w, params, "", &newmeta.APIDetail{}) 98 | } 99 | 100 | func getRepository() *meta.Repository { 101 | repository := meta.LoadRepository() 102 | return repository 103 | } 104 | -------------------------------------------------------------------------------- /openapi/response_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | 20 | jmespath "github.com/jmespath/go-jmespath" 21 | ) 22 | 23 | // find array in json object 24 | func detectArrayPath(d interface{}) string { 25 | m, ok := d.(map[string]interface{}) 26 | if !ok { 27 | return "" 28 | } 29 | for k, v := range m { 30 | // t.Logf("%v %v\n", k, v) 31 | if m2, ok := v.(map[string]interface{}); ok { 32 | for k2, v2 := range m2 { 33 | if _, ok := v2.([]interface{}); ok { 34 | return fmt.Sprintf("%s.%s[]", k, k2) 35 | } 36 | } 37 | } 38 | } 39 | return "" 40 | } 41 | 42 | func evaluateExpr(body []byte, expr string) (string, error) { 43 | var entity interface{} 44 | err := json.Unmarshal(body, &entity) 45 | if err != nil { 46 | return "", fmt.Errorf("unmarshal failed %s", err) 47 | } 48 | 49 | obj, err := jmespath.Search(expr, entity) 50 | if err != nil { 51 | return "", fmt.Errorf("jmes search failed %s", err) 52 | } 53 | 54 | if s, ok := obj.(string); ok { 55 | return s, nil 56 | } else { 57 | return "", fmt.Errorf("object %v isn't string", obj) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /openapi/response_utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "github.com/stretchr/testify/assert" 18 | 19 | "testing" 20 | ) 21 | 22 | func TestDetectArrayPath(t *testing.T) { 23 | str := detectArrayPath("test") 24 | assert.Equal(t, "", str) 25 | 26 | array := map[string]interface{}{ 27 | "test": map[string]interface{}{ 28 | "utils": []interface{}{ 29 | "inter", 30 | }, 31 | }, 32 | } 33 | str = detectArrayPath(array) 34 | assert.Equal(t, "test.utils[]", str) 35 | } 36 | 37 | func TestEvaluateExpr(t *testing.T) { 38 | body := []byte("test") 39 | str, err := evaluateExpr(body, "") 40 | assert.NotNil(t, err) 41 | assert.Equal(t, "unmarshal failed invalid character 'e' in literal true (expecting 'r')", err.Error()) 42 | assert.Equal(t, "", str) 43 | 44 | body = []byte(`"test"`) 45 | str, err = evaluateExpr(body, "") 46 | assert.NotNil(t, err) 47 | assert.Equal(t, "jmes search failed SyntaxError: Incomplete expression", err.Error()) 48 | assert.Equal(t, "", str) 49 | 50 | body = []byte(`{ 51 | "PageNumber": 5, 52 | "TotalCount": 37, 53 | "PageSize": 5, 54 | "RegionId": "cn-beijing", 55 | "Images": { 56 | "Image": [{ 57 | "ImageId": "win2016_64_dtc_1607_en-us_40G_alibase_20170915.vhd" 58 | }] 59 | } 60 | }`) 61 | str, err = evaluateExpr(body, "RegionId") 62 | assert.Nil(t, err) 63 | assert.Equal(t, "cn-beijing", str) 64 | 65 | str, err = evaluateExpr(body, "Images.Image[]") 66 | assert.NotNil(t, err) 67 | assert.Equal(t, "object [map[ImageId:win2016_64_dtc_1607_en-us_40G_alibase_20170915.vhd]] isn't string", err.Error()) 68 | assert.Equal(t, "", str) 69 | } 70 | -------------------------------------------------------------------------------- /openapi/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "strings" 20 | "time" 21 | 22 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 23 | "github.com/aliyun/aliyun-cli/v3/cli" 24 | "github.com/aliyun/aliyun-cli/v3/meta" 25 | ) 26 | 27 | type RpcInvoker struct { 28 | *BasicInvoker 29 | api *meta.Api 30 | } 31 | 32 | func (a *RpcInvoker) Prepare(ctx *cli.Context) error { 33 | // tidy names 34 | api := a.api 35 | request := a.request 36 | 37 | // assign api name, scheme method 38 | request.ApiName = api.Name 39 | request.Scheme = api.GetProtocol() 40 | request.Method = api.GetMethod() 41 | 42 | // if `--insecure` assigned, use http 43 | if _, ok := InsecureFlag(ctx.Flags()).GetValue(); ok { 44 | a.request.Scheme = "http" 45 | } 46 | 47 | // if `--secure` assigned, use https 48 | if _, ok := SecureFlag(ctx.Flags()).GetValue(); ok { 49 | a.request.Scheme = "https" 50 | } 51 | 52 | // if '--method' assigned, reset method 53 | if method, ok := MethodFlag(ctx.Flags()).GetValue(); ok { 54 | if method == "GET" || method == "POST" { 55 | a.request.Method = method 56 | } else { 57 | return fmt.Errorf("--method value %s is not supported, please set method in {GET|POST}", method) 58 | } 59 | } 60 | 61 | // assign parameters 62 | for _, f := range ctx.UnknownFlags().Flags() { 63 | if strings.HasSuffix(f.Name, "-FILE") { 64 | f.Name = strings.TrimSuffix(f.Name, "-FILE") 65 | replaceValueWithFile(f) 66 | } 67 | param := api.FindParameter(f.Name) 68 | if param == nil { 69 | return &InvalidParameterError{Name: f.Name, api: api, flags: ctx.Flags()} 70 | } 71 | 72 | if param.Position == "Query" { 73 | request.QueryParams[f.Name], _ = f.GetValue() 74 | } else if param.Position == "Body" { 75 | request.FormParams[f.Name], _ = f.GetValue() 76 | } else if param.Position == "Domain" { 77 | continue 78 | } else { 79 | return fmt.Errorf("unknown parameter position; %s is %s", param.Name, param.Position) 80 | } 81 | } 82 | 83 | err := a.api.CheckRequiredParameters(func(s string) bool { 84 | switch s { 85 | case "RegionId": 86 | return request.RegionId != "" 87 | case "Action": 88 | return request.ApiName != "" 89 | default: 90 | f := ctx.UnknownFlags().Get(s) 91 | return f != nil && f.IsAssigned() 92 | } 93 | }) 94 | 95 | if err != nil { 96 | return cli.NewErrorWithTip(err, 97 | "use `aliyun %s %s --help` to get more information", 98 | api.Product.GetLowerCode(), api.Name) 99 | } 100 | return nil 101 | } 102 | 103 | func (a *RpcInvoker) Call() (*responses.CommonResponse, error) { 104 | var resp *responses.CommonResponse 105 | var err error 106 | 107 | for i := 0; i < 5; i++ { 108 | resp, err = a.client.ProcessCommonRequest(a.request) 109 | if err != nil && strings.Contains(err.Error(), "Throttling.User") { 110 | time.Sleep(time.Duration(i+1) * time.Second) 111 | continue 112 | } 113 | break 114 | } 115 | 116 | return resp, err 117 | } 118 | 119 | func replaceValueWithFile(f *cli.Flag) { 120 | value, _ := f.GetValue() 121 | data, err := os.ReadFile(value) 122 | if err != nil { 123 | panic(err) 124 | } 125 | f.SetValue(string(data)) 126 | } 127 | -------------------------------------------------------------------------------- /openapi/rpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "bufio" 18 | "testing" 19 | 20 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 21 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 22 | "github.com/aliyun/aliyun-cli/v3/cli" 23 | "github.com/aliyun/aliyun-cli/v3/meta" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestRpcInvoker_Prepare(t *testing.T) { 28 | a := &RpcInvoker{ 29 | BasicInvoker: &BasicInvoker{ 30 | request: requests.NewCommonRequest(), 31 | }, 32 | api: &meta.Api{ 33 | Product: &meta.Product{ 34 | Code: "ecs", 35 | }, 36 | Name: "ecs", 37 | Protocol: "https", 38 | Method: "GET", 39 | }, 40 | } 41 | w := new(bufio.Writer) 42 | stderr := new(bufio.Writer) 43 | ctx := cli.NewCommandContext(w, stderr) 44 | 45 | secureflag := NewSecureFlag() 46 | secureflag.SetAssigned(true) 47 | ctx.Flags().Add(secureflag) 48 | ctx.Flags().Add(NewInsecureFlag()) 49 | methodflag := NewMethodFlag() 50 | methodflag.SetAssigned(true) 51 | methodflag.SetValue("POST") 52 | ctx.Flags().Add(methodflag) 53 | 54 | ctx.SetUnknownFlags(cli.NewFlagSet()) 55 | a.Prepare(ctx) 56 | assert.Equal(t, "POST", a.request.Method) 57 | ctx.UnknownFlags().Add(NewBodyFlag()) 58 | err := a.Prepare(ctx) 59 | assert.NotNil(t, err) 60 | assert.Equal(t, "'--body' is not a valid parameter or flag. See `aliyun help ecs ecs`.", err.Error()) 61 | 62 | a.api.Parameters = []meta.Parameter{ 63 | { 64 | Name: "body", 65 | Position: "Domain", 66 | }, 67 | { 68 | Name: "secure", 69 | }, 70 | } 71 | ctx.UnknownFlags().Add(NewSecureFlag()) 72 | err = a.Prepare(ctx) 73 | assert.NotNil(t, err) 74 | assert.Equal(t, "unknown parameter position; secure is ", err.Error()) 75 | 76 | a.api.Parameters = []meta.Parameter{ 77 | { 78 | Name: "body", 79 | Position: "Query", 80 | Required: true, 81 | }, 82 | { 83 | Name: "secure", 84 | Position: "Query", 85 | Required: true, 86 | }, 87 | { 88 | Name: "RegionId", 89 | Required: true, 90 | }, 91 | { 92 | Name: "Action", 93 | Required: true, 94 | }, 95 | } 96 | err = a.Prepare(ctx) 97 | assert.NotNil(t, err) 98 | assert.Equal(t, "required parameters not assigned: \n --body\n --secure\n --RegionId", err.Error()) 99 | 100 | a.api.Parameters = []meta.Parameter{ 101 | { 102 | Name: "body", 103 | Position: "Body", 104 | }, 105 | { 106 | Name: "secure", 107 | Position: "Body", 108 | }, 109 | } 110 | err = a.Prepare(ctx) 111 | assert.Nil(t, err) 112 | 113 | a.api.Parameters = []meta.Parameter{ 114 | { 115 | Name: "body", 116 | Position: "Domain", 117 | }, 118 | } 119 | ctx.SetUnknownFlags(cli.NewFlagSet()) 120 | ctx.UnknownFlags().AddByName("body-FILE") 121 | defer func() { 122 | e := recover() 123 | assert.NotNil(t, e) 124 | }() 125 | a.Prepare(ctx) 126 | 127 | } 128 | 129 | func TestRpcInvoker_Call(t *testing.T) { 130 | client, err := sdk.NewClientWithAccessKey("regionid", "accesskeyid", "accesskeysecret") 131 | assert.Nil(t, err) 132 | 133 | a := &RpcInvoker{ 134 | BasicInvoker: &BasicInvoker{ 135 | client: client, 136 | request: requests.NewCommonRequest(), 137 | }, 138 | } 139 | _, err = a.Call() 140 | assert.NotNil(t, err) 141 | assert.Contains(t, err.Error(), "[SDK.CanNotResolveEndpoint] Can not resolve endpoint") 142 | } 143 | -------------------------------------------------------------------------------- /openapi/waiter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "fmt" 18 | "strconv" 19 | "time" 20 | 21 | "github.com/aliyun/aliyun-cli/v3/cli" 22 | "github.com/aliyun/aliyun-cli/v3/i18n" 23 | ) 24 | 25 | var WaiterFlag = &cli.Flag{Category: "helper", 26 | Name: "waiter", 27 | AssignedMode: cli.AssignedRepeatable, 28 | Short: i18n.T( 29 | "use `--waiter expr= to=` to pull api until result equal to expected value", 30 | "使用 `--waiter expr= to=` 来轮询调用OpenAPI,直到返回期望的值"), 31 | Long: i18n.T( 32 | "", 33 | ""), 34 | Fields: []cli.Field{ 35 | {Key: "expr", Required: true, Short: i18n.T("", "")}, 36 | {Key: "to", Required: true, Short: i18n.T("", "")}, 37 | {Key: "timeout", DefaultValue: "180", Short: i18n.T("", "")}, 38 | {Key: "interval", DefaultValue: "5", Short: i18n.T("", "")}, 39 | }, 40 | ExcludeWith: []string{"pager"}, 41 | } 42 | 43 | type Waiter struct { 44 | expr string 45 | to string 46 | // timeout time.Duration TODO use Flag.Field to validate 47 | // interval time.Duration TODO use Flag.Field to validate 48 | } 49 | 50 | func GetWaiter() *Waiter { 51 | if !WaiterFlag.IsAssigned() { 52 | return nil 53 | } 54 | 55 | waiter := &Waiter{} 56 | waiter.expr, _ = WaiterFlag.GetFieldValue("expr") 57 | waiter.to, _ = WaiterFlag.GetFieldValue("to") 58 | //waiter.timeout = time.Duration(time.Second * 180) 59 | //waiter.interval = time.Duration(time.Second * 5) 60 | 61 | return waiter 62 | } 63 | 64 | func (a *Waiter) CallWith(invoker Invoker) (string, error) { 65 | // 66 | // timeout is 1-600 seconds, default is 180 67 | timeout := time.Duration(time.Second * 180) 68 | if s, ok := WaiterFlag.GetFieldValue("timeout"); ok { 69 | if n, err := strconv.Atoi(s); err == nil { 70 | if n <= 0 && n > 600 { 71 | return "", fmt.Errorf("--waiter timeout=%s must between 1-600 (seconds)", s) 72 | } 73 | timeout = time.Duration(time.Second * time.Duration(n)) 74 | } else { 75 | return "", fmt.Errorf("--waiter timeout=%s must be integer", s) 76 | } 77 | } 78 | // 79 | // interval is 2-10 seconds, default is 5 80 | interval := time.Duration(time.Second * 5) 81 | if s, ok := WaiterFlag.GetFieldValue("interval"); ok { 82 | if n, err := strconv.Atoi(s); err == nil { 83 | if n <= 1 && n > 10 { 84 | return "", fmt.Errorf("--waiter interval=%s must between 2-10 (seconds)", s) 85 | } 86 | interval = time.Duration(time.Second * time.Duration(n)) 87 | } else { 88 | return "", fmt.Errorf("--waiter interval=%s must be integer", s) 89 | } 90 | } 91 | 92 | begin := time.Now() 93 | for { 94 | resp, err := invoker.Call() 95 | if err != nil { 96 | return "", err 97 | } 98 | 99 | v, err := evaluateExpr(resp.GetHttpContentBytes(), a.expr) 100 | if err != nil { 101 | return "", err 102 | } 103 | 104 | if v == a.to { 105 | return resp.GetHttpContentString(), nil 106 | } 107 | duration := time.Since(begin) 108 | if duration > timeout { 109 | return "", fmt.Errorf("wait '%s' to '%s' timeout(%dseconds), last='%s'", 110 | a.expr, a.to, timeout/time.Second, v) 111 | } 112 | time.Sleep(interval) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /openapi/waiter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-present, Alibaba Cloud All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package openapi 15 | 16 | import ( 17 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk" 18 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 19 | "github.com/stretchr/testify/assert" 20 | "testing" 21 | ) 22 | 23 | func TestWaiter_CallWith(t *testing.T) { 24 | waiter := GetWaiter() 25 | assert.Nil(t, waiter) 26 | 27 | originWaiterFlag := WaiterFlag 28 | WaiterFlag.SetAssigned(true) 29 | waiter = GetWaiter() 30 | assert.NotNil(t, waiter) 31 | 32 | client, err := sdk.NewClientWithAccessKey("regionid", "accesskeyid", "accesskeysecret") 33 | assert.Nil(t, err) 34 | 35 | invoker := &RpcInvoker{ 36 | BasicInvoker: &BasicInvoker{ 37 | client: client, 38 | request: requests.NewCommonRequest(), 39 | }, 40 | } 41 | 42 | WaiterFlag.Fields[2].SetAssigned(true) 43 | WaiterFlag.Fields[3].SetAssigned(true) 44 | str, err := waiter.CallWith(invoker) 45 | assert.Equal(t, "", str) 46 | assert.NotNil(t, err) 47 | assert.Equal(t, "--waiter timeout= must be integer", err.Error()) 48 | 49 | WaiterFlag.Fields[2].SetValue("180") 50 | str, err = waiter.CallWith(invoker) 51 | assert.Equal(t, "", str) 52 | assert.NotNil(t, err) 53 | assert.Equal(t, "--waiter interval= must be integer", err.Error()) 54 | 55 | WaiterFlag.Fields[3].SetValue("5") 56 | str, err = waiter.CallWith(invoker) 57 | WaiterFlag = originWaiterFlag 58 | assert.Equal(t, "", str) 59 | assert.NotNil(t, err) 60 | assert.Contains(t, err.Error(), "[SDK.CanNotResolveEndpoint] Can not resolve endpoint") 61 | } 62 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | - 此工具采用go语言,基于OSS[阿里云对象存储服务](http://www.aliyun.com/product/oss/)官方GO SDK 构建。 11 | - 阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量,安全,低成本,高可靠的云存储服务。 12 | - OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。 13 | - 该工具旨在为用户提供一个方便的,以命令行方式管理OSS数据的途径。 14 | - 当前版本提供了列举和删除Multipart Uploads功能。 15 | - 当前版本未提供Bucket管理功能功能,相关功能会在后续版本中开发。 16 | 17 | ## 版本 18 | - 当前版本:v1.7.19 19 | 20 | ## 运行环境 21 | - Linux 22 | - Windows 23 | - Mac 24 | 25 | ## 依赖的库 26 | - goopt (github.com/droundy/goopt) 27 | - configparser (github.com/alyu/configparser) 28 | - leveldb (github.com/syndtr/goleveldb/leveldb) 29 | - oss (github.com/aliyun/aliyun-oss-go-sdk/oss) 30 | - gopkg.in/check.v1 (gopkg.in/check.v1) 31 | 32 | ## 快速使用 33 | #### 获取命令列表 34 | ```go 35 | ./ossutil 36 | 或 ./ossutil help 37 | ``` 38 | 39 | #### 查看某命令的帮助文档 40 | ```go 41 | ./ossutil help cmd 42 | ``` 43 | 44 | #### 配置ossutil 45 | ```go 46 | ./ossutil config 47 | ``` 48 | 49 | #### 列举Buckets 50 | ```go 51 | ./ossutil ls 52 | 或 ./ossutil ls oss:// 53 | ``` 54 | 55 | #### 列举objects和Multipart Uploads 56 | ```go 57 | ./ossutil ls -a 58 | 或 ./ossutil ls oss:// -a 59 | ``` 60 | 61 | #### 上传文件 62 | ```go 63 | ./ossutil cp localfile oss://bucket 64 | ``` 65 | 66 | #### 其它 67 | 请使用./ossutil help cmd来查看想要使用的命令的帮助文档。 68 | 69 | ## 注意事项 70 | ### 运行 71 | - 首先配置您的go工程目录。 72 | - go get该工程依赖的库。 73 | - go get github.com/aliyun/ossutil。 74 | - 进入go工程目录下的src目录,build代码生成ossutil工具,例如:在linux下可以运行go build github.com/aliyun/ossutil/ossutil.go。 75 | - 参考上面示例运行ossutil工具。 76 | 77 | ### 测试 78 | - 进入go工程目录下的src目录,修改github.com/aliyun/ossutil/lib/command_test.go里的endpoint、AccessKeyId、AccessKeySecret、STSToken等配置。 79 | - 请在lib目录下执行`go test`。 80 | 81 | ## 联系我们 82 | - [阿里云OSS官方网站](http://oss.aliyun.com) 83 | - [阿里云OSS官方论坛](http://bbs.aliyun.com) 84 | - [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs) 85 | 86 | ## 作者 87 | - [Ting Zhang](https://github.com/dengwu12) 88 | 89 | ## License 90 | - [MIT](https://github.com/aliyun/ossutil/blob/master/LICENSE) 91 | -------------------------------------------------------------------------------- /oss/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 | - 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/). 11 | - OSS is a cloud storage service provided by Alibaba Cloud, featuring massive capacity, security, low cost, and high reliability. 12 | - OSS can store any type of files. It applies to various websites, development enterprises and developers. 13 | - This tool aims to provide a convenient-to-use command line for users to manage data in OSS. 14 | - The current version provides to list and delete multipart upload tasks. 15 | - The current version does not support bucket management. The feature will be available in future versions. 16 | 17 | ## Version 18 | - Current version: v1.7.19 19 | 20 | ## Run environment 21 | - Linux 22 | - Windows 23 | - Mac 24 | 25 | ## Dependent libraries 26 | - goopt (github.com/droundy/goopt) 27 | - configparser (github.com/alyu/configparser) 28 | - leveldb (github.com/syndtr/goleveldb/leveldb) 29 | - oss (github.com/aliyun/aliyun-oss-go-sdk/oss) 30 | - gopkg.in/check.v1 (gopkg.in/check.v1) 31 | 32 | ## Quick use 33 | #### Get the command list 34 | ```go 35 | ./ossutil 36 | or ./ossutil help 37 | ``` 38 | 39 | #### View the help documentation for a command 40 | ```go 41 | ./ossutil help cmd 42 | ``` 43 | 44 | #### Configure OSSUTIL 45 | ```go 46 | ./ossutil config 47 | ``` 48 | 49 | #### List buckets 50 | ```go 51 | ./ossutil ls 52 | or ./ossutil ls oss:// 53 | ``` 54 | 55 | #### List objects and multipart upload tasks 56 | ```go 57 | ./ossutil ls -a 58 | or ./ossutil ls oss:// -a 59 | ``` 60 | 61 | #### Upload a file 62 | ```go 63 | ./ossutil cp localfile oss://bucket 64 | ``` 65 | 66 | #### Others 67 | You can use `./ossutil help cmd` to view the help documentation for the command you want to use. 68 | 69 | ## Notes 70 | ### Run OSSUTIL 71 | - First, configure your Go project directory. 72 | - Use `go get` to get the library that ossutil depends on. 73 | - Run `go get github.com/aliyun/ossutil`. 74 | - 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`. 75 | - Refer to the example above to run the OSSUTIL tool. 76 | 77 | ### Test 78 | - 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*. 79 | - Run `go test` under the *lib* directory. 80 | 81 | ## Contact us 82 | - [Alibaba Cloud OSS official website](http://oss.aliyun.com). 83 | - [Alibaba Cloud OSS official forum](http://bbs.aliyun.com). 84 | - [Alibaba Cloud OSS official documentation center](http://www.aliyun.com/product/oss#Docs). 85 | 86 | ## Author 87 | - [Ting Zhang](https://github.com/dengwu12) 88 | 89 | ## License 90 | - [MIT](https://github.com/aliyun/ossutil/blob/master/LICENSE) 91 | -------------------------------------------------------------------------------- /oss/lib/cli_bridge_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/aliyun/aliyun-cli/v3/cli" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestCliBridge(t *testing.T) { 12 | NewCommandBridge(configCommand.command) 13 | } 14 | 15 | func TestParseAndGetEndpoint(t *testing.T) { 16 | type args struct { 17 | ctx *cli.Context 18 | args []string 19 | } 20 | w := new(bytes.Buffer) 21 | stderr := new(bytes.Buffer) 22 | context := cli.NewCommandContext(w, stderr) 23 | flag := cli.Flag{ 24 | Name: "endpoint", 25 | } 26 | flag.SetValue("oss-cn-hangzhou.aliyuncs.com") 27 | context.Flags().Add(&flag) 28 | 29 | tests := []struct { 30 | name string 31 | args args 32 | want string 33 | wantErr assert.ErrorAssertionFunc 34 | }{ 35 | { 36 | name: "Valid endpoint from args", 37 | args: args{ 38 | ctx: new(cli.Context), 39 | args: []string{"--endpoint", "oss-cn-shenzhen.aliyuncs.com"}, 40 | }, 41 | want: "oss-cn-shenzhen.aliyuncs.com", 42 | wantErr: assert.NoError, 43 | }, 44 | { 45 | name: "Valid region from args", 46 | args: args{ 47 | ctx: new(cli.Context), 48 | args: []string{"--region", "cn-shenzhen"}, 49 | }, 50 | want: "oss-cn-shenzhen.aliyuncs.com", 51 | wantErr: assert.NoError, 52 | }, 53 | { 54 | name: "Fetch endpoint flag from context", 55 | args: args{ 56 | ctx: context, 57 | }, 58 | want: "oss-cn-hangzhou.aliyuncs.com", 59 | wantErr: assert.NoError, 60 | }, 61 | } 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | got, err := ParseAndGetEndpoint(tt.args.ctx, tt.args.args) 65 | if !tt.wantErr(t, err, fmt.Sprintf("ParseAndGetEndpoint(%v, %v)", tt.args.ctx, tt.args.args)) { 66 | return 67 | } 68 | assert.Equalf(t, tt.want, got, "ParseAndGetEndpoint(%v, %v)", tt.args.ctx, tt.args.args) 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/lib/option_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParseArgOptions(t *testing.T) { 11 | os.Args = []string{"oss", "ls", "--region", "cn-hangzhou"} 12 | args, maps, err := ParseArgOptions() 13 | assert.Equal(t, []string{"ls"}, args) 14 | assert.Nil(t, err) 15 | assert.NotNil(t, maps) 16 | region, ok := maps["region"].(*string) 17 | assert.True(t, ok) 18 | assert.Equal(t, "cn-hangzhou", *region) 19 | } 20 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /oss/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 | -------------------------------------------------------------------------------- /pkg/osx_installer_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliyun/aliyun-cli/68cb73835a04757910c95d09d4b26ea0c7ec2c55/pkg/osx_installer_logo.png -------------------------------------------------------------------------------- /pkg/productbuild/Resources/en.lproj/conclusion.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 |
15 |

This package has installed:

16 |
    17 |
  • aliyun v{cli_version} to /usr/local/bin/aliyun
  • 18 |
19 |

Make sure that /usr/local/bin is in your $PATH.

20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /pkg/productbuild/Resources/en.lproj/welcome.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 |
12 |

This package will install:

13 |
    14 |
  • aliyun v{cli_version} to /usr/local/bin/aliyun
  • 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /pkg/productbuild/distribution.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | Alibaba Cloud Command Line Interface 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | aliyun-cli-{cli_version}.pkg 17 | 18 | -------------------------------------------------------------------------------- /tools/build_pkg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | mkdir -p out/dist/usr/local/bin/ 6 | mkdir -p out/productbuild 7 | cp out/aliyun out/dist/usr/local/bin/ 8 | 9 | mkdir -p out/pkgs 10 | pkgbuild --version "$VERSION" \ 11 | --identifier com.aliyun.cli.pkg \ 12 | --root out/dist out/pkgs/aliyun-cli-"${VERSION}".pkg 13 | 14 | cat pkg/productbuild/distribution.xml.tmpl | \ 15 | sed -E "s/\\{cli_version\\}/$VERSION/g" > out/productbuild/distribution.xml 16 | 17 | for dirname in pkg/productbuild/Resources/*/; do 18 | lang=$(basename "$dirname") 19 | mkdir -p out/productbuild/Resources/"$lang" 20 | printf "Found localization directory %s\n" "$dirname" 21 | cat "$dirname"/welcome.html.tmpl | \ 22 | sed -E "s/\\{cli_version\\}/$VERSION/g" > out/productbuild/Resources/"$lang"/welcome.html 23 | cat "$dirname"/conclusion.html.tmpl | \ 24 | sed -E "s/\\{cli_version\\}/$VERSION/g" > out/productbuild/Resources/"$lang"/conclusion.html 25 | done 26 | 27 | cp pkg/osx_installer_logo.png out/productbuild/Resources 28 | cp LICENSE out/productbuild/Resources/license.txt 29 | 30 | productbuild --distribution out/productbuild/distribution.xml \ 31 | --resources out/productbuild/Resources \ 32 | --package-path out/pkgs \ 33 | out/aliyun-cli-"${VERSION}".pkg 34 | -------------------------------------------------------------------------------- /tools/create_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TAGNAME=$1 4 | 5 | DATA='{"tag_name":"'$TAGNAME'","name":"'$TAGNAME'","draft":false,"prerelease":true,"generate_release_notes":true}' 6 | 7 | curl -fsSL \ 8 | -H "Accept: application/vnd.github+json" \ 9 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 10 | -H "X-GitHub-Api-Version: 2022-11-28" \ 11 | https://api.github.com/repos/aliyun/aliyun-cli/releases \ 12 | -d "$DATA" 13 | -------------------------------------------------------------------------------- /tools/download_assets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | LIST=( 6 | "aliyun-cli-macosx-$VERSION-amd64.tgz" 7 | "aliyun-cli-macosx-$VERSION-arm64.tgz" 8 | "aliyun-cli-$VERSION.pkg" 9 | "aliyun-cli-macosx-$VERSION-universal.tgz" 10 | "aliyun-cli-linux-$VERSION-amd64.tgz" 11 | "aliyun-cli-linux-$VERSION-arm64.tgz" 12 | "aliyun-cli-windows-$VERSION-amd64.zip" 13 | ) 14 | 15 | for filename in "${LIST[@]}" 16 | do 17 | curl -fsSL -O \ 18 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 19 | https://github.com/aliyun/aliyun-cli/releases/download/v"$VERSION"/"$filename" 20 | shasum -a 256 "$filename" >> SHASUMS256.txt 21 | done 22 | 23 | cat ./SHASUMS256.txt 24 | -------------------------------------------------------------------------------- /tools/finish_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | VERSION=$1 6 | 7 | ALIYUN="./out/aliyun" 8 | 9 | go build -ldflags "-X 'github.com/aliyun/aliyun-cli/cli.Version=${VERSION}'" -o $ALIYUN main/main.go 10 | 11 | FLAGS="oss://aliyun-cli --force --access-key-id ${ACCESS_KEY_ID} --access-key-secret ${ACCESS_KEY_SECRET} --region cn-hangzhou" 12 | 13 | # mac amd64 14 | ${ALIYUN} oss cp ./aliyun-cli-macosx-"${VERSION}"-amd64.tgz $FLAGS 15 | # mac arm64 16 | ${ALIYUN} oss cp ./aliyun-cli-macosx-"${VERSION}"-arm64.tgz $FLAGS 17 | # mac universal 18 | ${ALIYUN} oss cp ./aliyun-cli-macosx-"${VERSION}"-universal.tgz $FLAGS 19 | # mac pkg 20 | ${ALIYUN} oss cp ./aliyun-cli-"${VERSION}".pkg $FLAGS 21 | # linux amd64 22 | ${ALIYUN} oss cp ./aliyun-cli-linux-"${VERSION}"-amd64.tgz $FLAGS 23 | # linux arm64 24 | ${ALIYUN} oss cp ./aliyun-cli-linux-"${VERSION}"-arm64.tgz $FLAGS 25 | # windows 26 | ${ALIYUN} oss cp ./aliyun-cli-windows-"${VERSION}"-amd64.zip $FLAGS 27 | 28 | if [[ "$VERSION" == *"-beta" ]]; then 29 | echo "beta. skip." 30 | else 31 | cp ./aliyun-cli-macosx-"${VERSION}"-amd64.tgz ./aliyun-cli-macosx-latest-amd64.tgz 32 | ${ALIYUN} oss cp ./aliyun-cli-macosx-latest-amd64.tgz $FLAGS 33 | 34 | cp ./aliyun-cli-macosx-"${VERSION}"-arm64.tgz ./aliyun-cli-macosx-latest-arm64.tgz 35 | ${ALIYUN} oss cp ./aliyun-cli-macosx-latest-arm64.tgz $FLAGS 36 | 37 | cp ./aliyun-cli-macosx-"${VERSION}"-universal.tgz ./aliyun-cli-macosx-latest-universal.tgz 38 | ${ALIYUN} oss cp ./aliyun-cli-macosx-latest-universal.tgz $FLAGS 39 | 40 | cp ./aliyun-cli-"${VERSION}".pkg ./aliyun-cli-latest.pkg 41 | ${ALIYUN} oss cp ./aliyun-cli-latest.pkg $FLAGS 42 | 43 | cp ./aliyun-cli-linux-"${VERSION}"-amd64.tgz ./aliyun-cli-linux-latest-amd64.tgz 44 | ${ALIYUN} oss cp ./aliyun-cli-linux-latest-amd64.tgz $FLAGS 45 | 46 | cp ./aliyun-cli-linux-"${VERSION}"-arm64.tgz ./aliyun-cli-linux-latest-arm64.tgz 47 | ${ALIYUN} oss cp ./aliyun-cli-linux-latest-arm64.tgz $FLAGS 48 | 49 | cp ./aliyun-cli-windows-"${VERSION}"-amd64.zip ./aliyun-cli-windows-latest-amd64.zip 50 | ${ALIYUN} oss cp ./aliyun-cli-windows-latest-amd64.zip $FLAGS 51 | # local version 52 | 53 | echo "${VERSION}" > out/version 54 | ${ALIYUN} oss cp out/version $FLAGS 55 | 56 | RELEASE_ID=$(curl -fsSL \ 57 | -H "Accept: application/vnd.github+json" \ 58 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 59 | -H "X-GitHub-Api-Version: 2022-11-28" \ 60 | https://api.github.com/repos/aliyun/aliyun-cli/releases/tags/v"$VERSION" | jq '.["id"]') 61 | 62 | DATA='{"draft":false,"prerelease":false,"make_latest":true}' 63 | 64 | curl -fsSL \ 65 | -X PATCH \ 66 | -H "Accept: application/vnd.github+json" \ 67 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 68 | -H "X-GitHub-Api-Version: 2022-11-28" \ 69 | https://api.github.com/repos/aliyun/aliyun-cli/releases/"$RELEASE_ID" \ 70 | -d "$DATA" 71 | fi 72 | -------------------------------------------------------------------------------- /tools/osx-entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-executable-page-protection 10 | 11 | com.apple.security.cs.allow-dyld-environment-variables 12 | 13 | com.apple.security.cs.disable-library-validation 14 | 15 | com.apple.security.get-task-allow 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tools/upload_asset.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TAG=v$1 4 | ASSET=$2 5 | 6 | if [[ $ASSET == *.tgz ]] 7 | then 8 | TYPE=application/x-compressed-tar 9 | elif [[ $ASSET == *.txt ]] 10 | then 11 | TYPE=text/plain 12 | else 13 | TYPE=application/zip 14 | fi 15 | 16 | RELEASE_ID=$(curl -fsSL \ 17 | -H "Accept: application/vnd.github+json" \ 18 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 19 | -H "X-GitHub-Api-Version: 2022-11-28" \ 20 | https://api.github.com/repos/aliyun/aliyun-cli/releases/tags/"$TAG" | jq '.["id"]') 21 | 22 | if [ -z "$RELEASE_ID" ]; then 23 | echo "Failed to get release ID for tag $TAG" 24 | exit 1 25 | fi 26 | 27 | # 获取现有资产列表 28 | ASSET_ID=$(curl -fsSL \ 29 | -H "Accept: application/vnd.github+json" \ 30 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 31 | -H "X-GitHub-Api-Version: 2022-11-28" \ 32 | https://api.github.com/repos/aliyun/aliyun-cli/releases/$RELEASE_ID/assets | jq -r ".[] | select(.name == \"$(basename "$ASSET")\") | .id") 33 | 34 | # 如果资产已存在,删除它 35 | if [ -n "$ASSET_ID" ]; then 36 | echo "Asset already exists. Deleting asset ID $ASSET_ID" 37 | curl -fsSL \ 38 | -X DELETE \ 39 | -H "Accept: application/vnd.github+json" \ 40 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 41 | -H "X-GitHub-Api-Version: 2022-11-28" \ 42 | https://api.github.com/repos/aliyun/aliyun-cli/releases/assets/$ASSET_ID 43 | fi 44 | 45 | printf "Uploading %s to release %s\n" "$ASSET" "$RELEASE_ID" 46 | 47 | curl -fsSL \ 48 | -X PUT \ 49 | -H "Accept: application/vnd.github+json" \ 50 | -H "Authorization: Bearer $GITHUB_TOKEN" \ 51 | -H "X-GitHub-Api-Version: 2022-11-28" \ 52 | -H "Content-Type: $TYPE" \ 53 | "https://uploads.github.com/repos/aliyun/aliyun-cli/releases/$RELEASE_ID/assets?name=$(basename "$ASSET")" \ 54 | --data-binary "@$ASSET" 55 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "time" 7 | ) 8 | 9 | func GetFromEnv(args ...string) string { 10 | for _, key := range args { 11 | if value := os.Getenv(key); value != "" { 12 | return value 13 | } 14 | } 15 | 16 | return "" 17 | } 18 | 19 | func GetCurrentUnixTime() int64 { 20 | return time.Now().Unix() 21 | } 22 | 23 | func NewHttpClient() *http.Client { 24 | return &http.Client{ 25 | Timeout: time.Second * 10, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetFromEnv(t *testing.T) { 12 | os.Setenv("test1", "test1") 13 | os.Setenv("test2", "test2") 14 | assert.Equal(t, "test1", GetFromEnv("test1", "test2")) 15 | assert.Equal(t, "test1", GetFromEnv("test3", "test1", "test2")) 16 | assert.Equal(t, "", GetFromEnv("test3")) 17 | } 18 | 19 | func TestGetCurrentUnixTime(t *testing.T) { 20 | // 获取函数返回的时间戳 21 | timestamp := GetCurrentUnixTime() 22 | 23 | // 验证时间戳不为0 24 | assert.NotEqual(t, int64(0), timestamp) 25 | 26 | // 验证时间戳是近期的时间(在过去一分钟内) 27 | now := time.Now().Unix() 28 | assert.True(t, timestamp <= now) 29 | assert.True(t, timestamp >= now-60, "时间戳应该在当前时间的一分钟内") 30 | } 31 | 32 | func TestNewHttpClient(t *testing.T) { 33 | // 获取HTTP客户端 34 | client := NewHttpClient() 35 | 36 | // 验证客户端不为nil 37 | assert.NotNil(t, client) 38 | 39 | // 验证超时设置为10秒 40 | assert.Equal(t, time.Second*10, client.Timeout) 41 | } 42 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 3.0.290 2 | --------------------------------------------------------------------------------