├── .github ├── CODEOWNERS ├── dependabot.yml ├── labeler.yml └── workflows │ ├── codeql-analysis.yml │ ├── golangci-lint.yml │ ├── labeler.yml │ ├── pull-request.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── api ├── account │ └── v3 │ │ ├── account_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── applesilicon │ └── v1alpha1 │ │ ├── apple_silicon_utils.go │ │ ├── applesilicon_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── audit_trail │ └── v1alpha1 │ │ └── audit_trail_sdk.go ├── autoscaling │ └── v1alpha1 │ │ └── autoscaling_sdk.go ├── baremetal │ ├── v1 │ │ ├── baremetal_sdk.go │ │ ├── server_utils.go │ │ └── sweepers │ │ │ └── sweepers.go │ └── v3 │ │ ├── baremetal_sdk.go │ │ └── server_utils.go ├── billing │ └── v2beta1 │ │ └── billing_sdk.go ├── block │ ├── v1 │ │ └── block_sdk.go │ └── v1alpha1 │ │ ├── block_sdk.go │ │ ├── snapshot_utils.go │ │ ├── sweepers │ │ └── sweepers.go │ │ └── volume_utils.go ├── cockpit │ └── v1 │ │ ├── cockpit_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── container │ └── v1beta1 │ │ ├── container_helpers.go │ │ ├── container_sdk.go │ │ ├── container_sdk_test.go │ │ ├── sweepers │ │ └── sweepers.go │ │ └── testdata │ │ └── container-list-regions.yaml ├── dedibox │ └── v1 │ │ ├── dedibox_sdk.go │ │ └── dedibox_utils.go ├── documentdb │ └── v1beta1 │ │ ├── documentdb_sdk.go │ │ └── documentdb_utils.go ├── domain │ └── v2beta1 │ │ ├── domain_sdk.go │ │ └── domain_utils.go ├── edge_services │ └── v1beta1 │ │ ├── edge_services_sdk.go │ │ └── edge_services_utils.go ├── file │ └── v1alpha1 │ │ ├── file_sdk.go │ │ └── file_utils.go ├── flexibleip │ └── v1alpha1 │ │ ├── flexibleip_helpers.go │ │ ├── flexibleip_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── function │ └── v1beta1 │ │ ├── function_helpers.go │ │ ├── function_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── iam │ └── v1alpha1 │ │ ├── iam_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── inference │ ├── v1 │ │ ├── inference_sdk.go │ │ ├── inference_utils.go │ │ └── sweepers │ │ │ └── sweepers.go │ └── v1beta1 │ │ ├── inference_sdk.go │ │ ├── inference_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── instance │ └── v1 │ │ ├── image_utils.go │ │ ├── image_utils_test.go │ │ ├── instance_metadata_sdk.go │ │ ├── instance_sdk.go │ │ ├── instance_sdk_server_test.go │ │ ├── instance_utils.go │ │ ├── instance_utils_test.go │ │ ├── security_group_utils_test.go │ │ ├── server_utils.go │ │ ├── server_utils_test.go │ │ ├── snapshot_utils.go │ │ ├── snapshot_utils_test.go │ │ ├── sweepers │ │ └── sweepers.go │ │ ├── testdata │ │ ├── all-server-user-data.yaml │ │ ├── create-server.yaml │ │ ├── get-server-type.yaml │ │ ├── image-wait-test.yaml │ │ ├── security-group-rule-test.yaml │ │ ├── security-group-test.yaml │ │ ├── server-incorrect-body.yaml │ │ ├── server-list-zones.yaml │ │ ├── server-test.yaml │ │ ├── server-user-data.yaml │ │ ├── snapshot-test.yaml │ │ ├── snapshot-wait-test.yaml │ │ ├── utils-test-block.yaml │ │ ├── utils-test.yaml │ │ └── volume-utils-test.yaml │ │ ├── volume_utils.go │ │ └── volume_utils_test.go ├── interlink │ └── v1beta1 │ │ └── interlink_sdk.go ├── iot │ └── v1 │ │ ├── iot_helpers.go │ │ ├── iot_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── ipam │ ├── v1 │ │ ├── ipam_sdk.go │ │ └── sweepers │ │ │ └── sweepers.go │ └── v1alpha1 │ │ └── ipam_sdk.go ├── jobs │ └── v1alpha1 │ │ ├── custom_job_run.go │ │ ├── jobs_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── k8s │ └── v1 │ │ ├── k8s_helpers.go │ │ ├── k8s_sdk.go │ │ ├── kubeconfig.go │ │ └── sweepers │ │ └── sweepers.go ├── key_manager │ └── v1alpha1 │ │ └── key_manager_sdk.go ├── lb │ └── v1 │ │ ├── lb_sdk.go │ │ ├── lb_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── marketplace │ └── v2 │ │ ├── marketplace_sdk.go │ │ ├── marketplace_utils.go │ │ ├── marketplace_utils_test.go │ │ └── testdata │ │ └── go-vcr.yaml ├── mnq │ └── v1beta1 │ │ ├── mnq_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── mongodb │ └── v1alpha1 │ │ ├── mongodb_sdk.go │ │ ├── mongodb_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── product_catalog │ └── v2alpha1 │ │ └── product_catalog_sdk.go ├── qaas │ └── v1alpha1 │ │ └── qaas_sdk.go ├── rdb │ └── v1 │ │ ├── rdb_sdk.go │ │ ├── rdb_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── redis │ └── v1 │ │ ├── redis_sdk.go │ │ ├── redis_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── registry │ └── v1 │ │ ├── image_utils.go │ │ ├── registry_sdk.go │ │ ├── registry_utils.go │ │ ├── sweepers │ │ └── sweepers.go │ │ └── tag_utils.go ├── secret │ └── v1beta1 │ │ ├── secret_sdk.go │ │ └── sweepers │ │ └── sweepers.go ├── serverless_sqldb │ └── v1alpha1 │ │ ├── serverless_sqldb_sdk.go │ │ ├── serverless_sqldb_utils.go │ │ └── sweepers │ │ └── sweepers.go ├── std │ └── std_sdk.go ├── tem │ └── v1alpha1 │ │ ├── sweepers │ │ └── sweepers.go │ │ ├── tem_sdk.go │ │ └── tem_utils.go ├── test │ └── v1 │ │ └── test_sdk.go ├── vpc │ └── v2 │ │ ├── sweepers │ │ └── sweepers.go │ │ └── vpc_sdk.go ├── vpcgw │ ├── v1 │ │ ├── sweepers │ │ │ └── sweepers.go │ │ ├── vpcgw_sdk.go │ │ └── vpcgw_utils.go │ └── v2 │ │ ├── vpcgw_sdk.go │ │ └── vpcgw_utils.go └── webhosting │ └── v1 │ ├── sweepers │ └── sweepers.go │ ├── webhosting_sdk.go │ └── webhosting_utils.go ├── doc.go ├── docs ├── CONTINUOUS_CODE_DEPLOYMENT.md └── static_files │ └── sdk-artwork.png ├── errors └── error.go ├── example_test.go ├── go.mod ├── go.sum ├── internal ├── async │ ├── wait.go │ └── wait_test.go ├── auth │ ├── access_key.go │ ├── auth.go │ ├── jwt.go │ ├── no_auth.go │ ├── token.go │ └── token_test.go ├── e2e │ ├── errors_test.go │ └── human_test.go ├── generic │ ├── fields.go │ ├── fields_test.go │ ├── ptr.go │ ├── sort.go │ └── sort_test.go └── testhelpers │ ├── httprecorder │ └── recorder.go │ ├── test_helpers.go │ └── test_resources.go ├── logger ├── default_logger.go ├── logger.go └── logger_test.go ├── marshaler └── duration.go ├── namegenerator ├── name_generator.go └── name_generator_test.go ├── parameter └── query.go ├── scripts ├── README.md ├── check_for_tokens.sh ├── lint.sh └── release.sh ├── scw ├── README.md ├── client.go ├── client_option.go ├── client_option_test.go ├── client_test.go ├── config.go ├── config_test.go ├── convert.go ├── convert_test.go ├── custom_types.go ├── custom_types_test.go ├── env.go ├── env_test.go ├── errors.go ├── errors_test.go ├── load_config_test.go ├── locality.go ├── locality_test.go ├── path.go ├── request.go ├── request_header.go ├── request_header_wasm.go ├── request_option.go ├── request_test.go ├── transport.go └── version.go ├── sdk_compilation_test.go ├── strcase ├── bash_arg.go ├── bash_arg_test.go ├── camel.go ├── camel_test.go ├── goname.go ├── goname_test.go ├── kebab.go ├── kebab_test.go ├── number.go ├── pascal.go ├── pascal_test.go ├── snake.go ├── snake_test.go └── strcase_test.go └── validation └── is.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | * @remyleone 4 | 5 | /api/account/ @scaleway/devtools-core 6 | /api/applesilicon/ @scaleway/devtools-compute-foundation 7 | /api/baremetal/ @scaleway/devtools-compute-foundation 8 | /api/billing/ @scaleway/devtools-core 9 | /api/block/ @scaleway/devtools-storage 10 | /api/cockpit/ @scaleway/devtools-dms 11 | /api/container/ @scaleway/devtools-compute 12 | /api/dedibox/ @scaleway/devtools-compute-foundation 13 | /api/documentdb/ @scaleway/devtools-dms 14 | /api/domain/ @scaleway/devtools-dms 15 | /api/edge_services @scaleway/devtools-network 16 | /api/flexibleip/ @scaleway/devtools-network 17 | /api/function/ @scaleway/devtools-compute 18 | /api/iam/ @scaleway/devtools-core 19 | /api/inference/ @scaleway/devtools-ai 20 | /api/instance/ @scaleway/devtools-compute 21 | /api/iot/ @scaleway/devtools-dms 22 | /api/ipam/ @scaleway/devtools-network 23 | /api/jobs/ @scaleway/devtools-compute 24 | /api/k8s/ @scaleway/devtools-compute 25 | /api/key_manager/ @scaleway/devtools-core 26 | /api/lb/ @scaleway/devtools-network 27 | /api/marketplace/ @scaleway/devtools-compute 28 | /api/mnq/ @scaleway/devtools-dms 29 | /api/rdb/ @scaleway/devtools-dms 30 | /api/redis/ @scaleway/devtools-dms 31 | /api/registry/ @scaleway/devtools-compute 32 | /api/secret/ @scaleway/devtools-core 33 | /api/serverless_sqldb/ @scaleway/devtools-dms 34 | /api/tem/ @scaleway/devtools-dms 35 | /api/vpc/ @scaleway/devtools-network 36 | /api/vpcgw/ @scaleway/devtools-network 37 | /api/webhosting/ @scaleway/devtools-dms 38 | -------------------------------------------------------------------------------- /.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://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: gomod 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: monthly 17 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | account: 2 | - changed-files: 3 | - any-glob-to-any-file: 4 | - api/account/** 5 | 6 | apple-silicon: 7 | - changed-files: 8 | - any-glob-to-any-file: 9 | - api/applesilicon/** 10 | 11 | baremetal: 12 | - changed-files: 13 | - any-glob-to-any-file: 14 | - api/baremetal/** 15 | 16 | billing: 17 | - changed-files: 18 | - any-glob-to-any-file: 19 | - api/billing/** 20 | 21 | container: 22 | - changed-files: 23 | - any-glob-to-any-file: 24 | - api/container/** 25 | 26 | dedibox: 27 | - changed-files: 28 | - any-glob-to-any-file: 29 | - api/dedibox/** 30 | 31 | domain: 32 | - changed-files: 33 | - any-glob-to-any-file: 34 | - api/domain/** 35 | 36 | flexible-ip: 37 | - changed-files: 38 | - any-glob-to-any-file: 39 | - api/flexibleip/** 40 | 41 | function: 42 | - changed-files: 43 | - any-glob-to-any-file: 44 | - api/function/** 45 | 46 | iam: 47 | - changed-files: 48 | - any-glob-to-any-file: 49 | - api/iam/** 50 | 51 | instance: 52 | - changed-files: 53 | - any-glob-to-any-file: 54 | - api/instance/** 55 | 56 | iot: 57 | - changed-files: 58 | - any-glob-to-any-file: 59 | - api/iot/** 60 | 61 | k8s: 62 | - changed-files: 63 | - any-glob-to-any-file: 64 | - api/k8s/** 65 | 66 | load-balancer: 67 | - changed-files: 68 | - any-glob-to-any-file: 69 | - api/lb/** 70 | 71 | mnq: 72 | - changed-files: 73 | - any-glob-to-any-file: 74 | - api/mnq/** 75 | 76 | rdb: 77 | - changed-files: 78 | - any-glob-to-any-file: 79 | - api/rdb/** 80 | 81 | redis: 82 | - changed-files: 83 | - any-glob-to-any-file: 84 | - api/redis/** 85 | 86 | registry: 87 | - changed-files: 88 | - any-glob-to-any-file: 89 | - api/registry/** 90 | 91 | sdb: 92 | - changed-files: 93 | - any-glob-to-any-file: 94 | - api/sdb/** 95 | 96 | secret: 97 | - changed-files: 98 | - any-glob-to-any-file: 99 | - api/secret/** 100 | 101 | tem: 102 | - changed-files: 103 | - any-glob-to-any-file: 104 | - api/tem/** 105 | 106 | vpc: 107 | - changed-files: 108 | - any-glob-to-any-file: 109 | - api/vpc/** 110 | 111 | vpcgw: 112 | - changed-files: 113 | - any-glob-to-any-file: 114 | - api/vpcgw/** 115 | 116 | webhosting: 117 | - changed-files: 118 | - any-glob-to-any-file: 119 | - api/webhosting/** 120 | -------------------------------------------------------------------------------- /.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: '26 20 * * 2' 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@v3 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@v3 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@v3 71 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | merge_group: 4 | pull_request: 5 | permissions: 6 | contents: read 7 | checks: write 8 | pull-requests: read 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v8 17 | with: 18 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 19 | version: latest 20 | args: --timeout 5m 21 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | triage: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Labeler 15 | uses: actions/labeler@v5 16 | with: 17 | sync-labels: true 18 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | 3 | on: 4 | - pull_request 5 | - merge_group 6 | 7 | jobs: 8 | unit-test: 9 | strategy: 10 | matrix: 11 | go-version: [1.23.x, 1.24.x] 12 | platform: [ubuntu-latest] 13 | runs-on: ${{ matrix.platform }} 14 | steps: 15 | # Checkout should always be before setup-go to ensure caching is working 16 | - name: checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 1 20 | - name: Install Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | - name: Run unit tests 25 | run: go test -v ./... 26 | 27 | build-arch-test: 28 | strategy: 29 | matrix: 30 | go-version: [1.23.x, 1.24.x] 31 | platform: [ubuntu-latest] 32 | arch: [386, amd64, arm, arm64] 33 | runs-on: ${{ matrix.platform }} 34 | steps: 35 | # Checkout should always be before setup-go to ensure caching is working 36 | - name: checkout 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 1 40 | - name: Install Go 41 | uses: actions/setup-go@v5 42 | with: 43 | go-version: ${{ matrix.go-version }} 44 | - name: Test to build binary 45 | run: GOARCH=${{ matrix.arch }} go build ./... 46 | 47 | build-platform-test: 48 | strategy: 49 | matrix: 50 | go-version: [1.23.x, 1.24.x] 51 | platform: [windows-latest, macos-latest] 52 | runs-on: ${{ matrix.platform }} 53 | steps: 54 | # Checkout should always be before setup-go to ensure caching is working 55 | - name: checkout 56 | uses: actions/checkout@v4 57 | with: 58 | fetch-depth: 1 59 | - name: Install Go 60 | uses: actions/setup-go@v5 61 | with: 62 | go-version: ${{ matrix.go-version }} 63 | - name: Test to build binary 64 | run: go build ./... 65 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | - 21 | name: Set up Go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version: 1.23 25 | - 26 | name: Run GoReleaser 27 | uses: goreleaser/goreleaser-action@v6 28 | with: 29 | distribution: goreleaser 30 | version: latest 31 | args: release 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | node_modules 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | version: 2 4 | before: 5 | hooks: 6 | - go mod tidy 7 | builds: 8 | - skip: true 9 | source: 10 | enabled: true 11 | name_template: '{{ .ProjectName }}-{{ .Version }}' 12 | format: 'tar.gz' 13 | prefix_template: '{{ .ProjectName }}-{{ .Version }}/' 14 | checksum: 15 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 16 | algorithm: sha256 17 | release: 18 | # Manually examine the release before it's live 19 | draft: true 20 | changelog: 21 | use: github-native 22 | groups: 23 | - title: Features 24 | regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' 25 | order: 0 26 | - title: Bug fixes 27 | regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' 28 | order: 1 29 | - title: Documentation 30 | regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$' 31 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@scaleway.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | This page lists all active maintainers of `scaleway-sdk-go`. This can be used for 2 | routing PRs, questions, etc. to the right place. 3 | 4 | - See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines. 5 | 6 | ### Maintainers 7 | 8 | | Name | Tag | 9 | | :------------- | :-------------- | 10 | | Jérôme Quéré | @jerome-quere | 11 | | Quentin Brosse | @QuentinBrosse | 12 | | Loïc Bourgois | @loicbourgois | 13 | | Olivier Cano | @kindermoumoute | 14 | | Rémy Léone | @remyleone | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | PkgGoDev 5 | GitHub Actions 6 | GoReportCard 7 |

8 | 9 | # Scaleway GO SDK 10 | 11 | **:warning: This is an early release, keep in mind that the API can break** 12 | 13 | Scaleway is a single way to create, deploy and scale your infrastructure in the cloud. We help thousands of businesses to run their infrastructures easily. 14 | 15 | ## Documentation 16 | 17 | - [Godoc](https://pkg.go.dev/github.com/scaleway/scaleway-sdk-go?tab=doc) 18 | - [Developers website](https://www.scaleway.com/en/developers/) (API documentation) 19 | - [Products availability guide](https://www.scaleway.com/en/docs/console/my-account/reference-content/products-availability/) 20 | - [The community tools](https://www.scaleway.com/en/developers/#official-repos) 21 | 22 | ## Installation 23 | 24 | ```bash 25 | go get github.com/scaleway/scaleway-sdk-go 26 | ``` 27 | 28 | ## Getting Started 29 | 30 | ```go 31 | package main 32 | 33 | import ( 34 | "fmt" 35 | 36 | "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 37 | "github.com/scaleway/scaleway-sdk-go/scw" 38 | "github.com/scaleway/scaleway-sdk-go/utils" 39 | ) 40 | 41 | func main() { 42 | 43 | // Create a Scaleway client 44 | client, err := scw.NewClient( 45 | // Get your organization ID at https://console.scaleway.com/organization/settings 46 | scw.WithDefaultOrganizationID("SCW_DEFAULT_ORGANIZATION_ID"), 47 | // Get your credentials at https://console.scaleway.com/iam/api-keys 48 | scw.WithAuth("SCW_ACCESS_KEY", "SCW_SECRET_KEY"), 49 | // Get more about our availability zones at https://www.scaleway.com/en/docs/console/my-account/reference-content/products-availability/ 50 | scw.WithDefaultRegion("SCW_REGION"), 51 | ) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | // Create SDK objects for Scaleway Instance product 57 | instanceApi := instance.NewAPI(client) 58 | 59 | // Call the ListServers method on the Instance SDK 60 | response, err := instanceApi.ListServers(&instance.ListServersRequest{ 61 | Zone: scw.ZoneFrPar1, 62 | }) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | // Do something with the response... 68 | for _, server := range response.Servers { 69 | fmt.Println("Server", server.ID, server.Name) 70 | } 71 | 72 | } 73 | ``` 74 | 75 | ## Examples 76 | 77 | You can find additional examples in the [GoDoc](https://pkg.go.dev/github.com/scaleway/scaleway-sdk-go?tab=doc). 78 | 79 | ## Development 80 | 81 | This repository is at its early stage and is still in active development. 82 | If you are looking for a way to contribute please read [CONTRIBUTING.md](CONTRIBUTING.md). 83 | 84 | ## Reach us 85 | 86 | We love feedback. 87 | Feel free to reach us on [Scaleway Slack community](https://slack.scaleway.com/), we are waiting for you on [#opensource](https://scaleway-community.slack.com/app_redirect?channel=opensource). 88 | -------------------------------------------------------------------------------- /api/account/v3/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/account/v3" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepProjects(scwClient *scw.Client) error { 12 | accountAPI := account.NewProjectAPI(scwClient) 13 | 14 | req := &account.ProjectAPIListProjectsRequest{} 15 | listProjects, err := accountAPI.ListProjects(req, scw.WithAllPages()) 16 | if err != nil { 17 | return fmt.Errorf("failed to list projects: %w", err) 18 | } 19 | for _, project := range listProjects.Projects { 20 | // Do not delete default project 21 | if project.ID == req.OrganizationID || !testhelpers.IsTestResource(project.Name) { 22 | continue 23 | } 24 | err = accountAPI.DeleteProject(&account.ProjectAPIDeleteProjectRequest{ 25 | ProjectID: project.ID, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("failed to delete project: %w", err) 29 | } 30 | } 31 | return nil 32 | } 33 | 34 | func SweepAll(scwClient *scw.Client) error { 35 | if err := SweepProjects(scwClient); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /api/applesilicon/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | applesilicon "github.com/scaleway/scaleway-sdk-go/api/applesilicon/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | func SweepServer(scwClient *scw.Client, zone scw.Zone) error { 11 | asAPI := applesilicon.NewAPI(scwClient) 12 | 13 | listServers, err := asAPI.ListServers(&applesilicon.ListServersRequest{Zone: zone}, scw.WithAllPages()) 14 | if err != nil { 15 | return fmt.Errorf("error listing apple silicon servers in (%s) in sweeper: %s", zone, err) 16 | } 17 | 18 | for _, server := range listServers.Servers { 19 | errDelete := asAPI.DeleteServer(&applesilicon.DeleteServerRequest{ 20 | ServerID: server.ID, 21 | Zone: zone, 22 | }) 23 | if errDelete != nil { 24 | return fmt.Errorf("error deleting apple silicon server in sweeper: %s", err) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func SweepAllLocalities(scwClient *scw.Client) error { 32 | for _, zone := range (&applesilicon.API{}).Zones() { 33 | err := SweepServer(scwClient, zone) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /api/baremetal/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/baremetal/v1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | func SweepServers(scwClient *scw.Client, zone scw.Zone) error { 11 | baremetalAPI := baremetal.NewAPI(scwClient) 12 | 13 | listServers, err := baremetalAPI.ListServers(&baremetal.ListServersRequest{Zone: zone}, scw.WithAllPages()) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | for _, server := range listServers.Servers { 19 | _, err := baremetalAPI.DeleteServer(&baremetal.DeleteServerRequest{ 20 | Zone: zone, 21 | ServerID: server.ID, 22 | }) 23 | if err != nil { 24 | return fmt.Errorf("error deleting server in sweeper: %s", err) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func SweepAllLocalities(scwClient *scw.Client) error { 32 | for _, zone := range (&baremetal.API{}).Zones() { 33 | err := SweepServers(scwClient, zone) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /api/baremetal/v3/server_utils.go: -------------------------------------------------------------------------------- 1 | package baremetal 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 2 * time.Hour 14 | ) 15 | 16 | // WaitForServerPrivateNetworksRequest is used by WaitForServerPrivateNetworks method. 17 | type WaitForServerPrivateNetworksRequest struct { 18 | ServerID string 19 | Zone scw.Zone 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForServerPrivateNetworks wait for all server private networks to be in a "terminal state" before returning. 25 | // This function can be used to wait for all server private networks to be set. 26 | func (s *PrivateNetworkAPI) WaitForServerPrivateNetworks(req *WaitForServerPrivateNetworksRequest, opts ...scw.RequestOption) ([]*ServerPrivateNetwork, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[ServerPrivateNetworkStatus]struct{}{ 37 | ServerPrivateNetworkStatusAttached: {}, 38 | ServerPrivateNetworkStatusError: {}, 39 | ServerPrivateNetworkStatusUnknownStatus: {}, 40 | ServerPrivateNetworkStatusLocked: {}, 41 | } 42 | 43 | serverPrivateNetwork, err := async.WaitSync(&async.WaitSyncConfig{ 44 | Get: func() (interface{}, bool, error) { 45 | res, err := s.ListServerPrivateNetworks(&PrivateNetworkAPIListServerPrivateNetworksRequest{ 46 | ServerID: &req.ServerID, 47 | Zone: req.Zone, 48 | }, opts...) 49 | if err != nil { 50 | return nil, false, err 51 | } 52 | 53 | for i := range res.ServerPrivateNetworks { 54 | _, isTerminal := terminalStatus[res.ServerPrivateNetworks[i].Status] 55 | if !isTerminal { 56 | return res.ServerPrivateNetworks, isTerminal, nil 57 | } 58 | } 59 | return res.ServerPrivateNetworks, true, err 60 | }, 61 | Timeout: timeout, 62 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 63 | }) 64 | if err != nil { 65 | return nil, errors.Wrap(err, "waiting for server private networks failed") 66 | } 67 | 68 | return serverPrivateNetwork.([]*ServerPrivateNetwork), nil 69 | } 70 | -------------------------------------------------------------------------------- /api/block/v1alpha1/snapshot_utils.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | // WaitForSnapshotRequest is used by WaitForSnapshot method. 12 | type WaitForSnapshotRequest struct { 13 | SnapshotID string 14 | Zone scw.Zone 15 | Timeout *time.Duration 16 | RetryInterval *time.Duration 17 | 18 | // If set, will wait until this specific status has been reached or the 19 | // snapshot has an error status. This is useful when we need to wait for 20 | // the snapshot to transition from "in_use" to "available". 21 | TerminalStatus *SnapshotStatus 22 | } 23 | 24 | // WaitForSnapshot wait for the snapshot to be in a "terminal state" before returning. 25 | func (s *API) WaitForSnapshot(req *WaitForSnapshotRequest, opts ...scw.RequestOption) (*Snapshot, error) { 26 | timeout := defaultTimeout 27 | if req.Timeout != nil { 28 | timeout = *req.Timeout 29 | } 30 | retryInterval := defaultRetryInterval 31 | if req.RetryInterval != nil { 32 | retryInterval = *req.RetryInterval 33 | } 34 | 35 | terminalStatus := map[SnapshotStatus]struct{}{ 36 | SnapshotStatusError: {}, 37 | SnapshotStatusLocked: {}, 38 | SnapshotStatusDeleted: {}, 39 | } 40 | 41 | if req.TerminalStatus != nil { 42 | terminalStatus[*req.TerminalStatus] = struct{}{} 43 | } else { 44 | terminalStatus[SnapshotStatusAvailable] = struct{}{} 45 | terminalStatus[SnapshotStatusInUse] = struct{}{} 46 | } 47 | 48 | snapshot, err := async.WaitSync(&async.WaitSyncConfig{ 49 | Get: func() (interface{}, bool, error) { 50 | res, err := s.GetSnapshot(&GetSnapshotRequest{ 51 | SnapshotID: req.SnapshotID, 52 | Zone: req.Zone, 53 | }, opts...) 54 | if err != nil { 55 | return nil, false, err 56 | } 57 | _, isTerminal := terminalStatus[res.Status] 58 | 59 | return res, isTerminal, err 60 | }, 61 | Timeout: timeout, 62 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 63 | }) 64 | if err != nil { 65 | return nil, errors.Wrap(err, "waiting for snapshot failed") 66 | } 67 | return snapshot.(*Snapshot), nil 68 | } 69 | -------------------------------------------------------------------------------- /api/block/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | func SweepVolumes(scwClient *scw.Client, zone scw.Zone) error { 11 | blockAPI := block.NewAPI(scwClient) 12 | 13 | listVolumes, err := blockAPI.ListVolumes( 14 | &block.ListVolumesRequest{ 15 | Zone: zone, 16 | }, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("error listing volume in (%s) in sweeper: %s", zone, err) 19 | } 20 | 21 | for _, volume := range listVolumes.Volumes { 22 | err := blockAPI.DeleteVolume(&block.DeleteVolumeRequest{ 23 | VolumeID: volume.ID, 24 | Zone: zone, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting volume in sweeper: %s", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepSnapshots(scwClient *scw.Client, zone scw.Zone) error { 35 | blockAPI := block.NewAPI(scwClient) 36 | 37 | listSnapshots, err := blockAPI.ListSnapshots( 38 | &block.ListSnapshotsRequest{ 39 | Zone: zone, 40 | }, scw.WithAllPages()) 41 | if err != nil { 42 | return fmt.Errorf("error listing snapshot in (%s) in sweeper: %s", zone, err) 43 | } 44 | 45 | for _, snapshot := range listSnapshots.Snapshots { 46 | err := blockAPI.DeleteSnapshot(&block.DeleteSnapshotRequest{ 47 | SnapshotID: snapshot.ID, 48 | Zone: zone, 49 | }) 50 | if err != nil { 51 | return fmt.Errorf("error deleting snapshot in sweeper: %s", err) 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func SweepAllLocalities(scwClient *scw.Client) error { 59 | for _, zone := range (&block.API{}).Zones() { 60 | err := SweepVolumes(scwClient, zone) 61 | if err != nil { 62 | return err 63 | } 64 | err = SweepSnapshots(scwClient, zone) 65 | if err != nil { 66 | return err 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /api/cockpit/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | accountSDK "github.com/scaleway/scaleway-sdk-go/api/account/v3" 8 | "github.com/scaleway/scaleway-sdk-go/api/cockpit/v1" 9 | "github.com/scaleway/scaleway-sdk-go/scw" 10 | ) 11 | 12 | func SweepToken(scwClient *scw.Client) error { 13 | accountAPI := accountSDK.NewProjectAPI(scwClient) 14 | cockpitAPI := cockpit.NewRegionalAPI(scwClient) 15 | 16 | listProjects, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{}, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("failed to list projects: %w", err) 19 | } 20 | 21 | for _, project := range listProjects.Projects { 22 | if !strings.HasPrefix(project.Name, "tf_tests") { 23 | continue 24 | } 25 | 26 | listTokens, err := cockpitAPI.ListTokens(&cockpit.RegionalAPIListTokensRequest{ 27 | ProjectID: project.ID, 28 | }, scw.WithAllPages()) 29 | if err != nil { 30 | return fmt.Errorf("failed to list tokens: %w", err) 31 | } 32 | 33 | for _, token := range listTokens.Tokens { 34 | err = cockpitAPI.DeleteToken(&cockpit.RegionalAPIDeleteTokenRequest{ 35 | TokenID: token.ID, 36 | }) 37 | if err != nil { 38 | return fmt.Errorf("failed to delete token: %w", err) 39 | } 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func SweepGrafanaUser(scwClient *scw.Client) error { 47 | accountAPI := accountSDK.NewProjectAPI(scwClient) 48 | cockpitAPI := cockpit.NewGlobalAPI(scwClient) 49 | 50 | listProjects, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{}, scw.WithAllPages()) 51 | if err != nil { 52 | return fmt.Errorf("failed to list projects: %w", err) 53 | } 54 | 55 | for _, project := range listProjects.Projects { 56 | if !strings.HasPrefix(project.Name, "tf_tests") { 57 | continue 58 | } 59 | 60 | listGrafanaUsers, err := cockpitAPI.ListGrafanaUsers(&cockpit.GlobalAPIListGrafanaUsersRequest{ 61 | ProjectID: project.ID, 62 | }, scw.WithAllPages()) 63 | if err != nil { 64 | return fmt.Errorf("failed to list grafana users: %w", err) 65 | } 66 | 67 | for _, grafanaUser := range listGrafanaUsers.GrafanaUsers { 68 | err = cockpitAPI.DeleteGrafanaUser(&cockpit.GlobalAPIDeleteGrafanaUserRequest{ 69 | ProjectID: project.ID, 70 | GrafanaUserID: grafanaUser.ID, 71 | }) 72 | if err != nil { 73 | return fmt.Errorf("failed to delete grafana user: %w", err) 74 | } 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func SweepSource(scwClient *scw.Client, region scw.Region) error { 82 | accountAPI := accountSDK.NewProjectAPI(scwClient) 83 | cockpitAPI := cockpit.NewRegionalAPI(scwClient) 84 | 85 | listProjects, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{}, scw.WithAllPages()) 86 | if err != nil { 87 | return fmt.Errorf("failed to list projects: %w", err) 88 | } 89 | 90 | for _, project := range listProjects.Projects { 91 | if !strings.HasPrefix(project.Name, "tf_tests") { 92 | continue 93 | } 94 | 95 | listDatasources, err := cockpitAPI.ListDataSources(&cockpit.RegionalAPIListDataSourcesRequest{ 96 | ProjectID: project.ID, 97 | Region: region, 98 | }, scw.WithAllPages()) 99 | if err != nil { 100 | return fmt.Errorf("failed to list sources: %w", err) 101 | } 102 | 103 | for _, datsource := range listDatasources.DataSources { 104 | err = cockpitAPI.DeleteDataSource(&cockpit.RegionalAPIDeleteDataSourceRequest{ 105 | DataSourceID: datsource.ID, 106 | Region: region, 107 | }) 108 | if err != nil { 109 | return fmt.Errorf("failed to delete cockpit source: %w", err) 110 | } 111 | } 112 | } 113 | 114 | return nil 115 | } 116 | 117 | func SweepAllLocalities(scwClient *scw.Client) error { 118 | if err := SweepToken(scwClient); err != nil { 119 | return err 120 | } 121 | if err := SweepGrafanaUser(scwClient); err != nil { 122 | return err 123 | } 124 | for _, region := range (&cockpit.RegionalAPI{}).Regions() { 125 | if err := SweepSource(scwClient, region); err != nil { 126 | return err 127 | } 128 | } 129 | 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /api/container/v1beta1/container_sdk_test.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func TestListContainerNamespaceMultipleRegions(t *testing.T) { 12 | client, r, err := httprecorder.CreateRecordedScwClient("container-list-regions") 13 | testhelpers.AssertNoError(t, err) 14 | defer func() { 15 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 16 | }() 17 | 18 | containerAPI := NewAPI(client) 19 | 20 | // Create server 21 | _, err = containerAPI.ListNamespaces(&ListNamespacesRequest{}, scw.WithRegions(containerAPI.Regions()...)) 22 | testhelpers.Assert(t, err == nil, "This request should not error: %s", err) 23 | } 24 | -------------------------------------------------------------------------------- /api/container/v1beta1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | container "github.com/scaleway/scaleway-sdk-go/api/container/v1beta1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepTrigger(scwClient *scw.Client, region scw.Region) error { 12 | containerAPI := container.NewAPI(scwClient) 13 | 14 | logger.Warningf("sweeper: destroying the container triggers in (%s)", region) 15 | listTriggers, err := containerAPI.ListTriggers( 16 | &container.ListTriggersRequest{ 17 | Region: region, 18 | }, scw.WithAllPages()) 19 | if err != nil { 20 | return fmt.Errorf("error listing trigger in (%s) in sweeper: %s", region, err) 21 | } 22 | 23 | for _, trigger := range listTriggers.Triggers { 24 | _, err := containerAPI.DeleteTrigger(&container.DeleteTriggerRequest{ 25 | TriggerID: trigger.ID, 26 | Region: region, 27 | }) 28 | if err != nil { 29 | return fmt.Errorf("error deleting trigger in sweeper: %s", err) 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func SweepContainer(scwClient *scw.Client, region scw.Region) error { 37 | containerAPI := container.NewAPI(scwClient) 38 | logger.Warningf("sweeper: destroying the container in (%s)", region) 39 | listNamespaces, err := containerAPI.ListContainers( 40 | &container.ListContainersRequest{ 41 | Region: region, 42 | }, scw.WithAllPages()) 43 | if err != nil { 44 | return fmt.Errorf("error listing containers in (%s) in sweeper: %s", region, err) 45 | } 46 | 47 | for _, cont := range listNamespaces.Containers { 48 | _, err := containerAPI.DeleteContainer(&container.DeleteContainerRequest{ 49 | ContainerID: cont.ID, 50 | Region: region, 51 | }) 52 | if err != nil { 53 | return fmt.Errorf("error deleting container in sweeper: %s", err) 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func SweepNamespace(scwClient *scw.Client, region scw.Region) error { 61 | containerAPI := container.NewAPI(scwClient) 62 | logger.Warningf("sweeper: destroying the container namespaces in (%s)", region) 63 | listNamespaces, err := containerAPI.ListNamespaces( 64 | &container.ListNamespacesRequest{ 65 | Region: region, 66 | }, scw.WithAllPages()) 67 | if err != nil { 68 | return fmt.Errorf("error listing namespaces in (%s) in sweeper: %s", region, err) 69 | } 70 | 71 | for _, ns := range listNamespaces.Namespaces { 72 | _, err := containerAPI.DeleteNamespace(&container.DeleteNamespaceRequest{ 73 | NamespaceID: ns.ID, 74 | Region: region, 75 | }) 76 | if err != nil { 77 | return fmt.Errorf("error deleting namespace in sweeper: %s", err) 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func SweepAllLocalities(scwClient *scw.Client) error { 85 | for _, region := range (&container.API{}).Regions() { 86 | err := SweepTrigger(scwClient, region) 87 | if err != nil { 88 | return err 89 | } 90 | err = SweepContainer(scwClient, region) 91 | if err != nil { 92 | return err 93 | } 94 | err = SweepNamespace(scwClient, region) 95 | if err != nil { 96 | return err 97 | } 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /api/container/v1beta1/testdata/container-list-regions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | interactions: 4 | - request: 5 | body: "" 6 | form: {} 7 | headers: 8 | User-Agent: 9 | - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.20.1; linux; amd64) 10 | url: https://api.scaleway.com/containers/v1beta1/regions/fr-par/namespaces?order_by=created_at_asc 11 | method: GET 12 | response: 13 | body: '{"namespaces":[], "total_count":0}' 14 | headers: 15 | Content-Length: 16 | - "34" 17 | Content-Security-Policy: 18 | - default-src 'none'; frame-ancestors 'none' 19 | Content-Type: 20 | - application/json 21 | Date: 22 | - Fri, 03 Mar 2023 11:09:20 GMT 23 | Server: 24 | - Scaleway API-Gateway 25 | Strict-Transport-Security: 26 | - max-age=63072000 27 | X-Content-Type-Options: 28 | - nosniff 29 | X-Frame-Options: 30 | - DENY 31 | X-Request-Id: 32 | - 64534ead-60ca-4176-a37c-89f2781ec10e 33 | status: 200 OK 34 | code: 200 35 | duration: "" 36 | - request: 37 | body: "" 38 | form: {} 39 | headers: 40 | User-Agent: 41 | - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.20.1; linux; amd64) 42 | url: https://api.scaleway.com/containers/v1beta1/regions/nl-ams/namespaces?order_by=created_at_asc 43 | method: GET 44 | response: 45 | body: '{"namespaces":[{"id":"5113d87a-3a49-4e8d-be27-e1a20b9a59c1", "name":"dfdsffsfsqf", 46 | "environment_variables":{}, "organization_id":"ee7bd9e1-9cbd-4724-b2f4-19e50f3cf38b", 47 | "project_id":"ee7bd9e1-9cbd-4724-b2f4-19e50f3cf38b", "status":"ready", "registry_namespace_id":"d0afb8a1-6417-4e7e-8a1c-29e348f96f05", 48 | "error_message":null, "registry_endpoint":"rg.nl-ams.scw.cloud/funcscwdfdsffsfsqfkvsfbdbr", 49 | "description":"", "secret_environment_variables":[], "region":"nl-ams"}], "total_count":1}' 50 | headers: 51 | Content-Length: 52 | - "486" 53 | Content-Security-Policy: 54 | - default-src 'none'; frame-ancestors 'none' 55 | Content-Type: 56 | - application/json 57 | Date: 58 | - Fri, 03 Mar 2023 11:09:20 GMT 59 | Server: 60 | - Scaleway API-Gateway 61 | Strict-Transport-Security: 62 | - max-age=63072000 63 | X-Content-Type-Options: 64 | - nosniff 65 | X-Frame-Options: 66 | - DENY 67 | X-Request-Id: 68 | - ef764335-0239-4aa5-92ca-a91a9a22db3a 69 | status: 200 OK 70 | code: 200 71 | duration: "" 72 | - request: 73 | body: "" 74 | form: {} 75 | headers: 76 | User-Agent: 77 | - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.20.1; linux; amd64) 78 | url: https://api.scaleway.com/containers/v1beta1/regions/pl-waw/namespaces?order_by=created_at_asc 79 | method: GET 80 | response: 81 | body: '{"namespaces":[], "total_count":0}' 82 | headers: 83 | Content-Length: 84 | - "34" 85 | Content-Security-Policy: 86 | - default-src 'none'; frame-ancestors 'none' 87 | Content-Type: 88 | - application/json 89 | Date: 90 | - Fri, 03 Mar 2023 11:09:20 GMT 91 | Server: 92 | - Scaleway API-Gateway 93 | Strict-Transport-Security: 94 | - max-age=63072000 95 | X-Content-Type-Options: 96 | - nosniff 97 | X-Frame-Options: 98 | - DENY 99 | X-Request-Id: 100 | - 33115f4e-1a87-41cb-8ba4-0ec491271287 101 | status: 200 OK 102 | code: 200 103 | duration: "" 104 | -------------------------------------------------------------------------------- /api/dedibox/v1/dedibox_utils.go: -------------------------------------------------------------------------------- 1 | package dedibox 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = time.Second * 15 13 | defaultTimeout = time.Minute * 30 14 | ) 15 | 16 | type WaitForServiceRequest struct { 17 | ServiceID uint64 18 | Zone scw.Zone 19 | Timeout *time.Duration 20 | RetryInterval *time.Duration 21 | } 22 | 23 | func (s *API) WaitForService(req *WaitForServiceRequest, opts ...scw.RequestOption) (*Service, error) { 24 | timeout := defaultTimeout 25 | if req.Timeout != nil { 26 | timeout = *req.Timeout 27 | } 28 | retryInterval := defaultRetryInterval 29 | if req.RetryInterval != nil { 30 | retryInterval = *req.RetryInterval 31 | } 32 | terminalStatus := map[ServiceProvisioningStatus]struct{}{ 33 | ServiceProvisioningStatusReady: {}, 34 | ServiceProvisioningStatusError: {}, 35 | ServiceProvisioningStatusExpired: {}, 36 | } 37 | service, err := async.WaitSync(&async.WaitSyncConfig{ 38 | Get: func() (interface{}, bool, error) { 39 | service, err := s.GetService(&GetServiceRequest{ 40 | Zone: req.Zone, 41 | ServiceID: req.ServiceID, 42 | }, opts...) 43 | if err != nil { 44 | return nil, false, err 45 | } 46 | _, isTerminal := terminalStatus[service.ProvisioningStatus] 47 | return service, isTerminal, nil 48 | }, 49 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 50 | Timeout: timeout, 51 | }) 52 | if err != nil { 53 | return nil, errors.Wrap(err, "waiting for service failed") 54 | } 55 | return service.(*Service), nil 56 | } 57 | 58 | type WaitForServerRequest struct { 59 | ServerID uint64 60 | Zone scw.Zone 61 | Timeout *time.Duration 62 | RetryInterval *time.Duration 63 | } 64 | 65 | func (s *API) WaitForServer(req *WaitForServerRequest, opts ...scw.RequestOption) (*Server, error) { 66 | timeout := defaultTimeout 67 | if req.Timeout != nil { 68 | timeout = *req.Timeout 69 | } 70 | retryInterval := defaultRetryInterval 71 | if req.RetryInterval != nil { 72 | retryInterval = *req.RetryInterval 73 | } 74 | terminalStatus := map[ServerStatus]struct{}{ 75 | ServerStatusReady: {}, 76 | ServerStatusError: {}, 77 | ServerStatusLocked: {}, 78 | ServerStatusStopped: {}, 79 | ServerStatusBusy: {}, 80 | ServerStatusRescue: {}, 81 | } 82 | server, err := async.WaitSync(&async.WaitSyncConfig{ 83 | Get: func() (interface{}, bool, error) { 84 | server, err := s.GetServer(&GetServerRequest{ 85 | Zone: req.Zone, 86 | ServerID: req.ServerID, 87 | }, opts...) 88 | if err != nil { 89 | return nil, false, err 90 | } 91 | _, isTerminal := terminalStatus[server.Status] 92 | return server, isTerminal, nil 93 | }, 94 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 95 | Timeout: timeout, 96 | }) 97 | if err != nil { 98 | return nil, errors.Wrap(err, "waiting for server failed") 99 | } 100 | return server.(*Server), nil 101 | } 102 | -------------------------------------------------------------------------------- /api/edge_services/v1beta1/edge_services_utils.go: -------------------------------------------------------------------------------- 1 | package edge_services 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 5 * time.Second 13 | defaultTimeout = 5 * time.Minute 14 | ) 15 | 16 | // WaitForPipelineRequest is used by WaitForPipeline method. 17 | type WaitForPipelineRequest struct { 18 | PipelineID string 19 | Timeout *time.Duration 20 | RetryInterval *time.Duration 21 | } 22 | 23 | // WaitForPipeline wait for a pipeline to be in a "terminal state" before returning. 24 | func (s *API) WaitForPipeline(req *WaitForPipelineRequest, opts ...scw.RequestOption) (*Pipeline, error) { 25 | timeout := defaultTimeout 26 | if req.Timeout != nil { 27 | timeout = *req.Timeout 28 | } 29 | retryInterval := defaultRetryInterval 30 | if req.RetryInterval != nil { 31 | retryInterval = *req.RetryInterval 32 | } 33 | 34 | terminalStatus := map[PipelineStatus]struct{}{ 35 | PipelineStatusReady: {}, 36 | PipelineStatusError: {}, 37 | PipelineStatusUnknownStatus: {}, 38 | PipelineStatusWarning: {}, 39 | } 40 | 41 | res, err := async.WaitSync(&async.WaitSyncConfig{ 42 | Get: func() (interface{}, bool, error) { 43 | pipeline, err := s.GetPipeline(&GetPipelineRequest{ 44 | PipelineID: req.PipelineID, 45 | }, opts...) 46 | if err != nil { 47 | return nil, false, err 48 | } 49 | 50 | _, isTerminal := terminalStatus[pipeline.Status] 51 | return pipeline, isTerminal, nil 52 | }, 53 | Timeout: timeout, 54 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 55 | }) 56 | if err != nil { 57 | return nil, errors.Wrap(err, "waiting for pipeline failed") 58 | } 59 | 60 | return res.(*Pipeline), nil 61 | } 62 | 63 | // WaitForPurgeRequestRequest is used by WaitForPurgeRequest method. 64 | type WaitForPurgeRequestRequest struct { 65 | PurgeRequestID string 66 | Timeout *time.Duration 67 | RetryInterval *time.Duration 68 | } 69 | 70 | // WaitForPurgeRequest wait for a purge request to be in a "terminal state" before returning. 71 | func (s *API) WaitForPurgeRequest(req *WaitForPurgeRequestRequest, opts ...scw.RequestOption) (*PurgeRequest, error) { 72 | timeout := defaultTimeout 73 | if req.Timeout != nil { 74 | timeout = *req.Timeout 75 | } 76 | retryInterval := defaultRetryInterval 77 | if req.RetryInterval != nil { 78 | retryInterval = *req.RetryInterval 79 | } 80 | 81 | terminalStatus := map[PurgeRequestStatus]struct{}{ 82 | PurgeRequestStatusDone: {}, 83 | PurgeRequestStatusError: {}, 84 | PurgeRequestStatusUnknownStatus: {}, 85 | } 86 | 87 | res, err := async.WaitSync(&async.WaitSyncConfig{ 88 | Get: func() (interface{}, bool, error) { 89 | purgeRequest, err := s.GetPurgeRequest(&GetPurgeRequestRequest{ 90 | PurgeRequestID: req.PurgeRequestID, 91 | }, opts...) 92 | if err != nil { 93 | return nil, false, err 94 | } 95 | 96 | _, isTerminal := terminalStatus[purgeRequest.Status] 97 | return purgeRequest, isTerminal, nil 98 | }, 99 | Timeout: timeout, 100 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 101 | }) 102 | if err != nil { 103 | return nil, errors.Wrap(err, "waiting for purge request failed") 104 | } 105 | 106 | return res.(*PurgeRequest), nil 107 | } 108 | -------------------------------------------------------------------------------- /api/file/v1alpha1/file_utils.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 10 * time.Minute 14 | ) 15 | 16 | type WaitForFileSystemRequest struct { 17 | FileSystemID string 18 | Region scw.Region 19 | Timeout *time.Duration 20 | RetryInterval *time.Duration 21 | } 22 | 23 | func (s *API) WaitForFileSystem(req *WaitForFileSystemRequest, opts ...scw.RequestOption) (*FileSystem, error) { 24 | timeout := defaultTimeout 25 | if req.Timeout != nil { 26 | timeout = *req.Timeout 27 | } 28 | 29 | retryInterval := defaultRetryInterval 30 | if req.RetryInterval != nil { 31 | retryInterval = *req.RetryInterval 32 | } 33 | 34 | terminalStatus := map[FileSystemStatus]struct{}{ 35 | FileSystemStatusAvailable: {}, 36 | FileSystemStatusError: {}, 37 | } 38 | 39 | fileSystem, err := async.WaitSync(&async.WaitSyncConfig{ 40 | Get: func() (interface{}, bool, error) { 41 | res, err := s.GetFileSystem(&GetFileSystemRequest{ 42 | Region: req.Region, 43 | FilesystemID: req.FileSystemID, 44 | }, opts...) 45 | if err != nil { 46 | return nil, false, err 47 | } 48 | _, isTerminal := terminalStatus[res.Status] 49 | 50 | return res, isTerminal, nil 51 | }, 52 | Timeout: timeout, 53 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 54 | }) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "waiting for fileSystem failed") 57 | } 58 | 59 | return fileSystem.(*FileSystem), nil 60 | } 61 | -------------------------------------------------------------------------------- /api/flexibleip/v1alpha1/flexibleip_helpers.go: -------------------------------------------------------------------------------- 1 | package flexibleip 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | waitForFlexibleIPDefaultTimeout = 15 * time.Minute 13 | defaultRetryInterval = 5 * time.Second 14 | ) 15 | 16 | // WaitForFlexibleIPRequest is used by WaitForFlexibleIP method. 17 | type WaitForFlexibleIPRequest struct { 18 | FipID string 19 | Zone scw.Zone 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForFlexibleIP waits for the FlexibleIP to be in a ready state before returning. 25 | func (s *API) WaitForFlexibleIP(req *WaitForFlexibleIPRequest, opts ...scw.RequestOption) (*FlexibleIP, error) { 26 | timeout := waitForFlexibleIPDefaultTimeout 27 | if req.Timeout != nil { 28 | timeout = *req.Timeout 29 | } 30 | retryInterval := defaultRetryInterval 31 | if req.RetryInterval != nil { 32 | retryInterval = *req.RetryInterval 33 | } 34 | 35 | fipTerminalStatus := map[FlexibleIPStatus]struct{}{ 36 | FlexibleIPStatusError: {}, 37 | FlexibleIPStatusReady: {}, 38 | FlexibleIPStatusAttached: {}, 39 | FlexibleIPStatusLocked: {}, 40 | } 41 | 42 | macAddressTerminalStatus := map[MACAddressStatus]struct{}{ 43 | MACAddressStatusUnknown: {}, 44 | MACAddressStatusReady: {}, 45 | MACAddressStatusUsed: {}, 46 | MACAddressStatusError: {}, 47 | } 48 | 49 | fip, err := async.WaitSync(&async.WaitSyncConfig{ 50 | Get: func() (interface{}, bool, error) { 51 | fip, err := s.GetFlexibleIP(&GetFlexibleIPRequest{ 52 | FipID: req.FipID, 53 | Zone: req.Zone, 54 | }, opts...) 55 | if err != nil { 56 | return nil, false, err 57 | } 58 | 59 | // Check if the MACAddress is in a terminal state 60 | isMacAddressTerminal := true 61 | if fip.MacAddress != nil { 62 | _, isMacAddressTerminal = macAddressTerminalStatus[fip.MacAddress.Status] 63 | } 64 | 65 | _, isTerminal := fipTerminalStatus[fip.Status] 66 | return fip, isTerminal && isMacAddressTerminal, nil 67 | }, 68 | Timeout: timeout, 69 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 70 | }) 71 | if err != nil { 72 | return nil, errors.Wrap(err, "waiting for FlexibleIP failed") 73 | } 74 | 75 | return fip.(*FlexibleIP), nil 76 | } 77 | -------------------------------------------------------------------------------- /api/flexibleip/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | flexibleip "github.com/scaleway/scaleway-sdk-go/api/flexibleip/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepFlexibleIP(scwClient *scw.Client, zone scw.Zone) error { 12 | fipAPI := flexibleip.NewAPI(scwClient) 13 | 14 | listIPs, err := fipAPI.ListFlexibleIPs(&flexibleip.ListFlexibleIPsRequest{Zone: zone}, scw.WithAllPages()) 15 | if err != nil { 16 | logger.Warningf("error listing ips in (%s) in sweeper: %s", zone, err) 17 | return nil 18 | } 19 | 20 | for _, ip := range listIPs.FlexibleIPs { 21 | err := fipAPI.DeleteFlexibleIP(&flexibleip.DeleteFlexibleIPRequest{ 22 | FipID: ip.ID, 23 | Zone: zone, 24 | }) 25 | if err != nil { 26 | return fmt.Errorf("error deleting ip in sweeper: %s", err) 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func SweepAllLocalities(scwClient *scw.Client) error { 34 | for _, zone := range (&flexibleip.API{}).Zones() { 35 | err := SweepFlexibleIP(scwClient, zone) 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /api/function/v1beta1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | functionSDK "github.com/scaleway/scaleway-sdk-go/api/function/v1beta1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepTriggers(scwClient *scw.Client, region scw.Region) error { 12 | functionAPI := functionSDK.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the function triggers in (%s)", region) 14 | listTriggers, err := functionAPI.ListTriggers( 15 | &functionSDK.ListTriggersRequest{ 16 | Region: region, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing trigger in (%s) in sweeper: %s", region, err) 20 | } 21 | 22 | for _, trigger := range listTriggers.Triggers { 23 | _, err := functionAPI.DeleteTrigger(&functionSDK.DeleteTriggerRequest{ 24 | TriggerID: trigger.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting trigger in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepNamespaces(scwClient *scw.Client, region scw.Region) error { 36 | functionAPI := functionSDK.NewAPI(scwClient) 37 | logger.Debugf("sweeper: destroying the function namespaces in (%s)", region) 38 | listNamespaces, err := functionAPI.ListNamespaces( 39 | &functionSDK.ListNamespacesRequest{ 40 | Region: region, 41 | }, scw.WithAllPages()) 42 | if err != nil { 43 | return fmt.Errorf("error listing namespaces in (%s) in sweeper: %s", region, err) 44 | } 45 | 46 | for _, ns := range listNamespaces.Namespaces { 47 | _, err := functionAPI.DeleteNamespace(&functionSDK.DeleteNamespaceRequest{ 48 | NamespaceID: ns.ID, 49 | Region: region, 50 | }) 51 | if err != nil { 52 | logger.Debugf("sweeper: error (%s)", err) 53 | 54 | return fmt.Errorf("error deleting namespace in sweeper: %s", err) 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func SweepFunctions(scwClient *scw.Client, region scw.Region) error { 62 | functionAPI := functionSDK.NewAPI(scwClient) 63 | logger.Warningf("sweeper: destroying the function in (%s)", region) 64 | listFunctions, err := functionAPI.ListFunctions( 65 | &functionSDK.ListFunctionsRequest{ 66 | Region: region, 67 | }, scw.WithAllPages()) 68 | if err != nil { 69 | return fmt.Errorf("error listing functions in (%s) in sweeper: %s", region, err) 70 | } 71 | 72 | for _, f := range listFunctions.Functions { 73 | _, err := functionAPI.DeleteFunction(&functionSDK.DeleteFunctionRequest{ 74 | FunctionID: f.ID, 75 | Region: region, 76 | }) 77 | if err != nil { 78 | return fmt.Errorf("error deleting functions in sweeper: %s", err) 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func SweepCrons(scwClient *scw.Client, region scw.Region) error { 86 | functionAPI := functionSDK.NewAPI(scwClient) 87 | logger.Warningf("sweeper: destroying the function cron in (%s)", region) 88 | listCron, err := functionAPI.ListCrons( 89 | &functionSDK.ListCronsRequest{ 90 | Region: region, 91 | }, scw.WithAllPages()) 92 | if err != nil { 93 | return fmt.Errorf("error listing cron in (%s) in sweeper: %s", region, err) 94 | } 95 | 96 | for _, cron := range listCron.Crons { 97 | _, err := functionAPI.DeleteCron(&functionSDK.DeleteCronRequest{ 98 | CronID: cron.ID, 99 | Region: region, 100 | }) 101 | if err != nil { 102 | return fmt.Errorf("error deleting cron in sweeper: %s", err) 103 | } 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func SweepAllLocalities(scwClient *scw.Client) error { 110 | for _, region := range (&functionSDK.API{}).Regions() { 111 | err := SweepTriggers(scwClient, region) 112 | if err != nil { 113 | return err 114 | } 115 | err = SweepNamespaces(scwClient, region) 116 | if err != nil { 117 | return err 118 | } 119 | err = SweepFunctions(scwClient, region) 120 | if err != nil { 121 | return err 122 | } 123 | err = SweepCrons(scwClient, region) 124 | if err != nil { 125 | return err 126 | } 127 | } 128 | 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /api/inference/v1/inference_utils.go: -------------------------------------------------------------------------------- 1 | package inference 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 30 * time.Minute 14 | ) 15 | 16 | type WaitForDeploymentRequest struct { 17 | DeploymentID string 18 | Region scw.Region 19 | Status DeploymentStatus 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | func (s *API) WaitForDeployment(req *WaitForDeploymentRequest, opts ...scw.RequestOption) (*Deployment, error) { 25 | timeout := defaultTimeout 26 | if req.Timeout != nil { 27 | timeout = *req.Timeout 28 | } 29 | retryInterval := defaultRetryInterval 30 | if req.RetryInterval != nil { 31 | retryInterval = *req.RetryInterval 32 | } 33 | 34 | terminalStatus := map[DeploymentStatus]struct{}{ 35 | DeploymentStatusReady: {}, 36 | DeploymentStatusError: {}, 37 | DeploymentStatusLocked: {}, 38 | } 39 | 40 | deployment, err := async.WaitSync(&async.WaitSyncConfig{ 41 | Get: func() (interface{}, bool, error) { 42 | deployment, err := s.GetDeployment(&GetDeploymentRequest{ 43 | Region: req.Region, 44 | DeploymentID: req.DeploymentID, 45 | }, opts...) 46 | if err != nil { 47 | return nil, false, err 48 | } 49 | _, isTerminal := terminalStatus[deployment.Status] 50 | return deployment, isTerminal, nil 51 | }, 52 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 53 | Timeout: timeout, 54 | }) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "waiting for deployment failed") 57 | } 58 | return deployment.(*Deployment), nil 59 | } 60 | 61 | type WaitForModelRequest struct { 62 | ModelID string 63 | Region scw.Region 64 | Timeout *time.Duration 65 | RetryInterval *time.Duration 66 | } 67 | 68 | func (s *API) WaitForModel(req *WaitForModelRequest, opts ...scw.RequestOption) (*Model, error) { 69 | timeout := defaultTimeout 70 | if req.Timeout != nil { 71 | timeout = *req.Timeout 72 | } 73 | retryInterval := defaultRetryInterval 74 | if req.RetryInterval != nil { 75 | retryInterval = *req.RetryInterval 76 | } 77 | 78 | terminalStatus := map[ModelStatus]struct{}{ 79 | ModelStatusReady: {}, 80 | ModelStatusError: {}, 81 | } 82 | 83 | model, err := async.WaitSync(&async.WaitSyncConfig{ 84 | Get: func() (interface{}, bool, error) { 85 | model, err := s.GetModel(&GetModelRequest{ 86 | Region: req.Region, 87 | ModelID: req.ModelID, 88 | }, opts...) 89 | if err != nil { 90 | return nil, false, err 91 | } 92 | _, isTerminal := terminalStatus[model.Status] 93 | return model, isTerminal, nil 94 | }, 95 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 96 | Timeout: timeout, 97 | }) 98 | if err != nil { 99 | return nil, errors.Wrap(err, "waiting for model failed") 100 | } 101 | 102 | return model.(*Model), nil 103 | } 104 | -------------------------------------------------------------------------------- /api/inference/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/inference/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepDeployment(scwClient *scw.Client, region scw.Region) error { 12 | inferenceAPI := inference.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the inference deployments in (%s)", region) 14 | listDeployments, err := inferenceAPI.ListDeployments( 15 | &inference.ListDeploymentsRequest{ 16 | Region: region, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing deployment in (%s) in sweeper: %s", region, err) 20 | } 21 | 22 | for _, deployment := range listDeployments.Deployments { 23 | _, err := inferenceAPI.DeleteDeployment(&inference.DeleteDeploymentRequest{ 24 | DeploymentID: deployment.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting deployment in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepAllLocalities(scwClient *scw.Client) error { 36 | for _, locality := range (&inference.API{}).Regions() { 37 | err := SweepDeployment(scwClient, locality) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /api/inference/v1beta1/inference_utils.go: -------------------------------------------------------------------------------- 1 | package inference 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 30 * time.Minute 14 | ) 15 | 16 | type WaitForDeploymentRequest struct { 17 | DeploymentID string 18 | Region scw.Region 19 | Status DeploymentStatus 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | func (s *API) WaitForDeployment(req *WaitForDeploymentRequest, opts ...scw.RequestOption) (*Deployment, error) { 25 | timeout := defaultTimeout 26 | if req.Timeout != nil { 27 | timeout = *req.Timeout 28 | } 29 | retryInterval := defaultRetryInterval 30 | if req.RetryInterval != nil { 31 | retryInterval = *req.RetryInterval 32 | } 33 | 34 | terminalStatus := map[DeploymentStatus]struct{}{ 35 | DeploymentStatusReady: {}, 36 | DeploymentStatusError: {}, 37 | DeploymentStatusLocked: {}, 38 | } 39 | 40 | deployment, err := async.WaitSync(&async.WaitSyncConfig{ 41 | Get: func() (interface{}, bool, error) { 42 | deployment, err := s.GetDeployment(&GetDeploymentRequest{ 43 | Region: req.Region, 44 | DeploymentID: req.DeploymentID, 45 | }, opts...) 46 | if err != nil { 47 | return nil, false, err 48 | } 49 | _, isTerminal := terminalStatus[deployment.Status] 50 | return deployment, isTerminal, nil 51 | }, 52 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 53 | Timeout: timeout, 54 | }) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "waiting for deployment failed") 57 | } 58 | return deployment.(*Deployment), nil 59 | } 60 | -------------------------------------------------------------------------------- /api/inference/v1beta1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | inference "github.com/scaleway/scaleway-sdk-go/api/inference/v1beta1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepDeployment(scwClient *scw.Client, region scw.Region) error { 12 | inferenceAPI := inference.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the inference deployments in (%s)", region) 14 | listDeployments, err := inferenceAPI.ListDeployments( 15 | &inference.ListDeploymentsRequest{ 16 | Region: region, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing deployment in (%s) in sweeper: %s", region, err) 20 | } 21 | 22 | for _, deployment := range listDeployments.Deployments { 23 | _, err := inferenceAPI.DeleteDeployment(&inference.DeleteDeploymentRequest{ 24 | DeploymentID: deployment.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting deployment in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepAllLocalities(scwClient *scw.Client) error { 36 | for _, locality := range (&inference.API{}).Regions() { 37 | err := SweepDeployment(scwClient, locality) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /api/instance/v1/image_utils.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | // WaitForImageRequest is used by WaitForImage method. 12 | type WaitForImageRequest struct { 13 | ImageID string 14 | Zone scw.Zone 15 | Timeout *time.Duration 16 | RetryInterval *time.Duration 17 | } 18 | 19 | // WaitForImage wait for the image to be in a "terminal state" before returning. 20 | func (s *API) WaitForImage(req *WaitForImageRequest, opts ...scw.RequestOption) (*Image, error) { 21 | timeout := defaultTimeout 22 | if req.Timeout != nil { 23 | timeout = *req.Timeout 24 | } 25 | retryInterval := defaultRetryInterval 26 | if req.RetryInterval != nil { 27 | retryInterval = *req.RetryInterval 28 | } 29 | 30 | terminalStatus := map[ImageState]struct{}{ 31 | ImageStateAvailable: {}, 32 | ImageStateError: {}, 33 | } 34 | 35 | image, err := async.WaitSync(&async.WaitSyncConfig{ 36 | Get: func() (interface{}, bool, error) { 37 | res, err := s.GetImage(&GetImageRequest{ 38 | ImageID: req.ImageID, 39 | Zone: req.Zone, 40 | }, opts...) 41 | if err != nil { 42 | return nil, false, err 43 | } 44 | _, isTerminal := terminalStatus[res.Image.State] 45 | 46 | return res.Image, isTerminal, err 47 | }, 48 | Timeout: timeout, 49 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 50 | }) 51 | if err != nil { 52 | return nil, errors.Wrap(err, "waiting for image failed") 53 | } 54 | return image.(*Image), nil 55 | } 56 | -------------------------------------------------------------------------------- /api/instance/v1/image_utils_test.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 8 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder" 9 | "github.com/scaleway/scaleway-sdk-go/scw" 10 | ) 11 | 12 | func TestWaitForImage(t *testing.T) { 13 | client, r, err := httprecorder.CreateRecordedScwClient("image-wait-test") 14 | testhelpers.AssertNoError(t, err) 15 | defer func() { 16 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 17 | }() 18 | 19 | instanceAPI := NewAPI(client) 20 | imageName := "backup" 21 | image, cleanup := createImage(t, instanceAPI, imageName) 22 | defer cleanup() 23 | 24 | res, err := instanceAPI.WaitForImage(&WaitForImageRequest{ 25 | ImageID: image.ID, 26 | }) 27 | 28 | testhelpers.AssertNoError(t, err) 29 | testhelpers.Equals(t, image.ID, res.ID) 30 | testhelpers.Equals(t, ImageStateAvailable, res.State) 31 | testhelpers.Equals(t, imageName, res.Name) 32 | } 33 | 34 | // createImage cis a helper that create an image. 35 | // It return the newly created image and a cleanup function 36 | func createImage(t *testing.T, instanceAPI *API, imageName string) (*Image, func()) { 37 | t.Helper() 38 | serverRes, err := instanceAPI.CreateServer(&CreateServerRequest{ 39 | CommercialType: "DEV1-M", 40 | Image: scw.StringPtr("ubuntu_focal"), 41 | }) 42 | testhelpers.AssertNoError(t, err) 43 | 44 | // Backup will create a snapshot for each volume + an image base on all snapshots. 45 | backupRes, err := instanceAPI.ServerAction(&ServerActionRequest{ 46 | ServerID: serverRes.Server.ID, 47 | Action: ServerActionBackup, 48 | Name: &imageName, 49 | }) 50 | testhelpers.AssertNoError(t, err) 51 | 52 | tmp := strings.Split(backupRes.Task.HrefResult, "/") 53 | imageID := tmp[2] 54 | imageRes, err := instanceAPI.GetImage(&GetImageRequest{ 55 | ImageID: imageID, 56 | }) 57 | testhelpers.AssertNoError(t, err) 58 | 59 | return imageRes.Image, func() { 60 | // Delete all created resources 61 | 62 | err := instanceAPI.DeleteServer(&DeleteServerRequest{ 63 | ServerID: serverRes.Server.ID, 64 | }) 65 | testhelpers.AssertNoError(t, err) 66 | 67 | err = instanceAPI.DeleteVolume(&DeleteVolumeRequest{ 68 | VolumeID: serverRes.Server.Volumes["0"].ID, 69 | }) 70 | testhelpers.AssertNoError(t, err) 71 | 72 | err = instanceAPI.DeleteImage(&DeleteImageRequest{ 73 | ImageID: imageRes.Image.ID, 74 | }) 75 | testhelpers.AssertNoError(t, err) 76 | 77 | err = instanceAPI.DeleteSnapshot(&DeleteSnapshotRequest{ 78 | SnapshotID: imageRes.Image.RootVolume.ID, 79 | }) 80 | testhelpers.AssertNoError(t, err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /api/instance/v1/snapshot_utils.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | // WaitForImageRequest is used by WaitForImage method. 12 | type WaitForSnapshotRequest struct { 13 | SnapshotID string 14 | Zone scw.Zone 15 | Timeout *time.Duration 16 | RetryInterval *time.Duration 17 | } 18 | 19 | // WaitForSnapshot wait for the snapshot to be in a "terminal state" before returning. 20 | func (s *API) WaitForSnapshot(req *WaitForSnapshotRequest, opts ...scw.RequestOption) (*Snapshot, error) { 21 | timeout := defaultTimeout 22 | if req.Timeout != nil { 23 | timeout = *req.Timeout 24 | } 25 | retryInterval := defaultRetryInterval 26 | if req.RetryInterval != nil { 27 | retryInterval = *req.RetryInterval 28 | } 29 | 30 | terminalStatus := map[SnapshotState]struct{}{ 31 | SnapshotStateAvailable: {}, 32 | SnapshotStateError: {}, 33 | } 34 | 35 | snapshot, err := async.WaitSync(&async.WaitSyncConfig{ 36 | Get: func() (interface{}, bool, error) { 37 | res, err := s.GetSnapshot(&GetSnapshotRequest{ 38 | SnapshotID: req.SnapshotID, 39 | Zone: req.Zone, 40 | }, opts...) 41 | if err != nil { 42 | return nil, false, err 43 | } 44 | _, isTerminal := terminalStatus[res.Snapshot.State] 45 | 46 | return res.Snapshot, isTerminal, err 47 | }, 48 | Timeout: timeout, 49 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 50 | }) 51 | if err != nil { 52 | return nil, errors.Wrap(err, "waiting for snapshot failed") 53 | } 54 | return snapshot.(*Snapshot), nil 55 | } 56 | -------------------------------------------------------------------------------- /api/instance/v1/snapshot_utils_test.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func TestWaitForSnapshot(t *testing.T) { 12 | client, r, err := httprecorder.CreateRecordedScwClient("snapshot-wait-test") 13 | testhelpers.AssertNoError(t, err) 14 | defer func() { 15 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 16 | }() 17 | 18 | instanceAPI := NewAPI(client) 19 | snapshotName := "backup" 20 | snapshot, cleanup := createSnapshot(t, instanceAPI, snapshotName) 21 | defer cleanup() 22 | 23 | res, err := instanceAPI.WaitForSnapshot(&WaitForSnapshotRequest{ 24 | SnapshotID: snapshot.ID, 25 | }) 26 | 27 | testhelpers.AssertNoError(t, err) 28 | testhelpers.Equals(t, snapshot.ID, res.ID) 29 | testhelpers.Equals(t, SnapshotStateAvailable, res.State) 30 | testhelpers.Equals(t, snapshotName, res.Name) 31 | } 32 | 33 | // createSnapshot is a helper that create an snapshot. 34 | // It returns the newly created snapshot and a cleanup function 35 | func createSnapshot(t *testing.T, instanceAPI *API, snapshotName string) (*Snapshot, func()) { 36 | t.Helper() 37 | serverRes, err := instanceAPI.CreateServer(&CreateServerRequest{ 38 | CommercialType: "DEV1-M", 39 | Image: scw.StringPtr("ubuntu_focal"), 40 | }) 41 | testhelpers.AssertNoError(t, err) 42 | 43 | // Backup will create a snapshot for each volume + an image base on all snapshots. 44 | snapshot, err := instanceAPI.CreateSnapshot(&CreateSnapshotRequest{ 45 | Name: snapshotName, 46 | VolumeID: &serverRes.Server.Volumes["0"].ID, 47 | }) 48 | testhelpers.AssertNoError(t, err) 49 | 50 | snapshotRes, err := instanceAPI.GetSnapshot(&GetSnapshotRequest{ 51 | SnapshotID: snapshot.Snapshot.ID, 52 | }) 53 | testhelpers.AssertNoError(t, err) 54 | 55 | return snapshotRes.Snapshot, func() { 56 | // Delete all created resources 57 | 58 | err := instanceAPI.DeleteServer(&DeleteServerRequest{ 59 | ServerID: serverRes.Server.ID, 60 | }) 61 | testhelpers.AssertNoError(t, err) 62 | 63 | err = instanceAPI.DeleteSnapshot(&DeleteSnapshotRequest{ 64 | SnapshotID: snapshotRes.Snapshot.ID, 65 | }) 66 | testhelpers.AssertNoError(t, err) 67 | } 68 | } 69 | 70 | func TestAPI_UpdateSnapshot(t *testing.T) { 71 | client, r, err := httprecorder.CreateRecordedScwClient("snapshot-test") 72 | testhelpers.AssertNoError(t, err) 73 | defer func() { 74 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 75 | }() 76 | 77 | instanceAPI := NewAPI(client) 78 | 79 | volumeSize := 1 * scw.GB 80 | 81 | createVolume, err := instanceAPI.CreateVolume(&CreateVolumeRequest{ 82 | Name: "volume_name", 83 | VolumeType: VolumeVolumeTypeBSSD, 84 | Size: &volumeSize, 85 | }) 86 | testhelpers.AssertNoError(t, err) 87 | 88 | createResponse, err := instanceAPI.CreateSnapshot(&CreateSnapshotRequest{ 89 | Name: "name", 90 | VolumeID: &createVolume.Volume.ID, 91 | }) 92 | testhelpers.AssertNoError(t, err) 93 | 94 | updateResponse, err := instanceAPI.UpdateSnapshot(&UpdateSnapshotRequest{ 95 | SnapshotID: createResponse.Snapshot.ID, 96 | Name: scw.StringPtr("new_name"), 97 | Tags: scw.StringsPtr([]string{"foo", "bar"}), 98 | }) 99 | testhelpers.AssertNoError(t, err) 100 | testhelpers.Equals(t, "new_name", updateResponse.Snapshot.Name) 101 | testhelpers.Equals(t, []string{"foo", "bar"}, updateResponse.Snapshot.Tags) 102 | 103 | _, err = instanceAPI.WaitForSnapshot(&WaitForSnapshotRequest{ 104 | SnapshotID: createResponse.Snapshot.ID, 105 | }) 106 | testhelpers.AssertNoError(t, err) 107 | 108 | err = instanceAPI.DeleteSnapshot(&DeleteSnapshotRequest{ 109 | SnapshotID: createResponse.Snapshot.ID, 110 | }) 111 | testhelpers.AssertNoError(t, err) 112 | } 113 | -------------------------------------------------------------------------------- /api/instance/v1/testdata/server-incorrect-body.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | interactions: 4 | - request: 5 | body: '{}' 6 | form: {} 7 | headers: 8 | Content-Type: 9 | - application/json 10 | User-Agent: 11 | - scaleway-sdk-go/0.0.0 12 | url: https://api.scaleway.com/instance/v1/zones/fr-par-1/servers 13 | method: POST 14 | response: 15 | body: '{"fields": {"organization": ["required key not provided"], "image": ["required 16 | key not provided"], "name": ["required key not provided"], "volumes": ["required 17 | key not provided"]}, "message": "Validation Error", "type": "invalid_request_error"}' 18 | headers: 19 | Content-Length: 20 | - "244" 21 | Content-Security-Policy: 22 | - default-src 'none'; frame-ancestors 'none' 23 | Content-Type: 24 | - application/json 25 | Date: 26 | - Thu, 23 May 2019 15:49:33 GMT 27 | Server: 28 | - scaleway_api 29 | Strict-Transport-Security: 30 | - max-age=63072000 31 | X-Content-Type-Options: 32 | - nosniff 33 | X-Frame-Options: 34 | - DENY 35 | status: 400 Bad Request 36 | code: 400 37 | duration: "" 38 | -------------------------------------------------------------------------------- /api/instance/v1/volume_utils.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | goerrors "errors" 5 | "time" 6 | 7 | block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" 8 | "github.com/scaleway/scaleway-sdk-go/errors" 9 | "github.com/scaleway/scaleway-sdk-go/internal/async" 10 | "github.com/scaleway/scaleway-sdk-go/scw" 11 | ) 12 | 13 | // WaitForImageRequest is used by WaitForImage method. 14 | type WaitForVolumeRequest struct { 15 | VolumeID string 16 | Zone scw.Zone 17 | Timeout *time.Duration 18 | RetryInterval *time.Duration 19 | } 20 | 21 | // WaitForSnapshot wait for the snapshot to be in a "terminal state" before returning. 22 | func (s *API) WaitForVolume(req *WaitForVolumeRequest, opts ...scw.RequestOption) (*Volume, error) { 23 | timeout := defaultTimeout 24 | if req.Timeout != nil { 25 | timeout = *req.Timeout 26 | } 27 | retryInterval := defaultRetryInterval 28 | if req.RetryInterval != nil { 29 | retryInterval = *req.RetryInterval 30 | } 31 | 32 | terminalStatus := map[VolumeState]struct{}{ 33 | VolumeStateAvailable: {}, 34 | VolumeStateError: {}, 35 | } 36 | 37 | volume, err := async.WaitSync(&async.WaitSyncConfig{ 38 | Get: func() (interface{}, bool, error) { 39 | res, err := s.GetVolume(&GetVolumeRequest{ 40 | VolumeID: req.VolumeID, 41 | Zone: req.Zone, 42 | }, opts...) 43 | if err != nil { 44 | return nil, false, err 45 | } 46 | _, isTerminal := terminalStatus[res.Volume.State] 47 | 48 | return res.Volume, isTerminal, err 49 | }, 50 | Timeout: timeout, 51 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 52 | }) 53 | if err != nil { 54 | return nil, errors.Wrap(err, "waiting for volume failed") 55 | } 56 | return volume.(*Volume), nil 57 | } 58 | 59 | type unknownVolume struct { 60 | ID string 61 | ServerID *string 62 | Type VolumeVolumeType 63 | } 64 | 65 | type getUnknownVolumeRequest struct { 66 | Zone scw.Zone 67 | VolumeID string 68 | IsBlockVolume *bool 69 | } 70 | 71 | // getUnknownVolume is used to get a volume that can be either from instance or block API 72 | func (s *API) getUnknownVolume(req *getUnknownVolumeRequest, opts ...scw.RequestOption) (*unknownVolume, error) { 73 | volume := &unknownVolume{ 74 | ID: req.VolumeID, 75 | } 76 | 77 | // Try instance API 78 | if req.IsBlockVolume == nil || !*req.IsBlockVolume { 79 | getVolumeResponse, err := s.GetVolume(&GetVolumeRequest{ 80 | Zone: req.Zone, 81 | VolumeID: req.VolumeID, 82 | }, opts...) 83 | notFoundErr := &scw.ResourceNotFoundError{} 84 | if err != nil && !goerrors.As(err, ¬FoundErr) { 85 | return nil, err 86 | } 87 | 88 | if getVolumeResponse != nil { 89 | if getVolumeResponse.Volume != nil && getVolumeResponse.Volume.Server != nil { 90 | volume.ServerID = &getVolumeResponse.Volume.Server.ID 91 | } 92 | volume.Type = getVolumeResponse.Volume.VolumeType 93 | } 94 | } 95 | 96 | if volume.Type == "" && (req.IsBlockVolume == nil || *req.IsBlockVolume) { 97 | getVolumeResponse, err := block.NewAPI(s.client).GetVolume(&block.GetVolumeRequest{ 98 | Zone: req.Zone, 99 | VolumeID: req.VolumeID, 100 | }, opts...) 101 | if err != nil { 102 | return nil, err 103 | } 104 | for _, reference := range getVolumeResponse.References { 105 | if reference.ProductResourceType == "instance_server" { 106 | volume.ServerID = &reference.ProductResourceID 107 | } 108 | } 109 | volume.Type = VolumeVolumeTypeSbsVolume 110 | } 111 | 112 | return volume, nil 113 | } 114 | -------------------------------------------------------------------------------- /api/instance/v1/volume_utils_test.go: -------------------------------------------------------------------------------- 1 | package instance 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func TestUpdateVolume(t *testing.T) { 12 | client, r, err := httprecorder.CreateRecordedScwClient("volume-utils-test") 13 | testhelpers.AssertNoError(t, err) 14 | defer func() { 15 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 16 | }() 17 | 18 | instanceAPI := NewAPI(client) 19 | 20 | var ( 21 | zone = scw.ZoneFrPar1 22 | project = "951df375-e094-4d26-97c1-ba548eeb9c42" 23 | volumeName = "test volume" 24 | volumeSize = 20 * scw.GB 25 | volumeType = VolumeVolumeTypeLSSD 26 | newVolumeName = "some new volume name" 27 | 28 | volumeID string 29 | ) 30 | 31 | // Create volume 32 | createVolumeResponse, err := instanceAPI.CreateVolume(&CreateVolumeRequest{ 33 | Zone: zone, 34 | Name: volumeName, 35 | Project: &project, 36 | Size: &volumeSize, 37 | VolumeType: volumeType, 38 | }) 39 | 40 | testhelpers.AssertNoError(t, err) 41 | 42 | volumeID = createVolumeResponse.Volume.ID 43 | 44 | // Update volume and test whether successfully updated 45 | updateVolumeResponse, err := instanceAPI.UpdateVolume(&UpdateVolumeRequest{ 46 | Zone: zone, 47 | Name: &newVolumeName, 48 | VolumeID: volumeID, 49 | }) 50 | 51 | testhelpers.AssertNoError(t, err) 52 | testhelpers.Assert(t, updateVolumeResponse.Volume != nil, "Should have volume in response") 53 | testhelpers.Equals(t, newVolumeName, updateVolumeResponse.Volume.Name) 54 | testhelpers.Equals(t, volumeSize, updateVolumeResponse.Volume.Size) // check that server is not changed 55 | 56 | // Delete Volume 57 | err = instanceAPI.DeleteVolume(&DeleteVolumeRequest{ 58 | Zone: zone, 59 | VolumeID: volumeID, 60 | }) 61 | testhelpers.AssertNoError(t, err) 62 | } 63 | -------------------------------------------------------------------------------- /api/iot/v1/iot_helpers.go: -------------------------------------------------------------------------------- 1 | package iot 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | waitForHubDefaultTimeout = 15 * time.Minute 13 | defaultRetryInterval = 5 * time.Second 14 | ) 15 | 16 | // WaitForHubRequest is used by WaitForHub method. 17 | type WaitForHubRequest struct { 18 | HubID string 19 | Region scw.Region 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForHub waits for the hub to be in a ready state before returning. 25 | func (s *API) WaitForHub(req *WaitForHubRequest, opts ...scw.RequestOption) (*Hub, error) { 26 | timeout := waitForHubDefaultTimeout 27 | if req.Timeout != nil { 28 | timeout = *req.Timeout 29 | } 30 | retryInterval := defaultRetryInterval 31 | if req.RetryInterval != nil { 32 | retryInterval = *req.RetryInterval 33 | } 34 | 35 | terminalStatus := map[HubStatus]struct{}{ 36 | HubStatusError: {}, 37 | HubStatusReady: {}, 38 | HubStatusDisabled: {}, 39 | } 40 | 41 | hub, err := async.WaitSync(&async.WaitSyncConfig{ 42 | Get: func() (interface{}, bool, error) { 43 | hub, err := s.GetHub(&GetHubRequest{ 44 | HubID: req.HubID, 45 | Region: req.Region, 46 | }, opts...) 47 | if err != nil { 48 | return nil, false, err 49 | } 50 | 51 | _, isTerminal := terminalStatus[hub.Status] 52 | return hub, isTerminal, nil 53 | }, 54 | Timeout: timeout, 55 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 56 | }) 57 | if err != nil { 58 | return nil, errors.Wrap(err, "waiting for hub failed") 59 | } 60 | 61 | return hub.(*Hub), nil 62 | } 63 | -------------------------------------------------------------------------------- /api/iot/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/iot/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepHub(scwClient *scw.Client, region scw.Region) error { 12 | iotAPI := iot.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the iot hub in (%s)", region) 14 | listHubs, err := iotAPI.ListHubs(&iot.ListHubsRequest{Region: region}, scw.WithAllPages()) 15 | if err != nil { 16 | return fmt.Errorf("error listing hubs in (%s) in sweeper: %s", region, err) 17 | } 18 | 19 | deleteDevices := true 20 | for _, hub := range listHubs.Hubs { 21 | err := iotAPI.DeleteHub(&iot.DeleteHubRequest{ 22 | HubID: hub.ID, 23 | Region: hub.Region, 24 | DeleteDevices: &deleteDevices, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting hub in sweeper: %s", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, region := range (&iot.API{}).Regions() { 36 | err := SweepHub(scwClient, region) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /api/ipam/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/ipam/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepIP(scwClient *scw.Client, region scw.Region) error { 12 | ipamAPI := ipam.NewAPI(scwClient) 13 | 14 | logger.Warningf("sweeper: deleting the IPs in (%s)", region) 15 | 16 | listIPs, err := ipamAPI.ListIPs(&ipam.ListIPsRequest{Region: region}, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("error listing ips in (%s) in sweeper: %s", region, err) 19 | } 20 | 21 | for _, v := range listIPs.IPs { 22 | err := ipamAPI.ReleaseIP(&ipam.ReleaseIPRequest{ 23 | IPID: v.ID, 24 | Region: region, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error releasing IP in sweeper: %s", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, region := range (&ipam.API{}).Regions() { 36 | err := SweepIP(scwClient, region) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /api/jobs/v1alpha1/custom_job_run.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 15 * time.Minute 14 | ) 15 | 16 | type WaitForJobRunRequest struct { 17 | JobRunID string 18 | Region scw.Region 19 | Timeout *time.Duration 20 | RetryInterval *time.Duration 21 | } 22 | 23 | // WaitForJobRun waits for the job run to be in a "terminal state" before returning. 24 | // This function can be used to wait for a job run to fail for example. 25 | func (s *API) WaitForJobRun(req *WaitForJobRunRequest, opts ...scw.RequestOption) (*JobRun, error) { 26 | timeout := defaultTimeout 27 | if req.Timeout != nil { 28 | timeout = *req.Timeout 29 | } 30 | retryInterval := defaultRetryInterval 31 | if req.RetryInterval != nil { 32 | retryInterval = *req.RetryInterval 33 | } 34 | 35 | terminalStatus := map[JobRunState]struct{}{ 36 | JobRunStateSucceeded: {}, 37 | JobRunStateFailed: {}, 38 | JobRunStateCanceled: {}, 39 | } 40 | 41 | jobRun, err := async.WaitSync(&async.WaitSyncConfig{ 42 | Get: func() (interface{}, bool, error) { 43 | res, err := s.GetJobRun(&GetJobRunRequest{ 44 | JobRunID: req.JobRunID, 45 | Region: req.Region, 46 | }, opts...) 47 | if err != nil { 48 | return nil, false, err 49 | } 50 | _, isTerminal := terminalStatus[res.State] 51 | 52 | return res, isTerminal, nil 53 | }, 54 | Timeout: timeout, 55 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 56 | }) 57 | if err != nil { 58 | return nil, errors.Wrap(err, "waiting for job run failed") 59 | } 60 | 61 | return jobRun.(*JobRun), nil 62 | } 63 | -------------------------------------------------------------------------------- /api/jobs/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | jobs "github.com/scaleway/scaleway-sdk-go/api/jobs/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepJobDefinition(scwClient *scw.Client, region scw.Region) error { 12 | jobsAPI := jobs.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the jobs definitions in (%s)", region) 14 | listJobDefinitions, err := jobsAPI.ListJobDefinitions( 15 | &jobs.ListJobDefinitionsRequest{ 16 | Region: region, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing definition in (%s) in sweeper: %s", region, err) 20 | } 21 | 22 | for _, definition := range listJobDefinitions.JobDefinitions { 23 | err := jobsAPI.DeleteJobDefinition(&jobs.DeleteJobDefinitionRequest{ 24 | JobDefinitionID: definition.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting definition in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepAllLocalities(scwClient *scw.Client) error { 36 | for _, region := range (&jobs.API{}).Regions() { 37 | err := SweepJobDefinition(scwClient, region) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /api/k8s/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/k8s/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepCluster(scwClient *scw.Client, region scw.Region) error { 12 | k8sAPI := k8s.NewAPI(scwClient) 13 | 14 | logger.Warningf("sweeper: destroying the k8s cluster in (%s)", region) 15 | listClusters, err := k8sAPI.ListClusters(&k8s.ListClustersRequest{Region: region}, scw.WithAllPages()) 16 | if err != nil { 17 | return fmt.Errorf("error listing clusters in (%s) in sweeper: %s", region, err) 18 | } 19 | 20 | for _, cluster := range listClusters.Clusters { 21 | // remove pools 22 | listPools, err := k8sAPI.ListPools(&k8s.ListPoolsRequest{ 23 | Region: region, 24 | ClusterID: cluster.ID, 25 | }, scw.WithAllPages()) 26 | if err != nil { 27 | return fmt.Errorf("error listing pool in (%s) in sweeper: %s", region, err) 28 | } 29 | 30 | for _, pool := range listPools.Pools { 31 | _, err := k8sAPI.DeletePool(&k8s.DeletePoolRequest{ 32 | Region: region, 33 | PoolID: pool.ID, 34 | }) 35 | if err != nil { 36 | return fmt.Errorf("error deleting pool in sweeper: %s", err) 37 | } 38 | } 39 | _, err = k8sAPI.DeleteCluster(&k8s.DeleteClusterRequest{ 40 | Region: region, 41 | ClusterID: cluster.ID, 42 | WithAdditionalResources: true, 43 | }) 44 | if err != nil { 45 | return fmt.Errorf("error deleting cluster in sweeper: %s", err) 46 | } 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func SweepAllLocalities(scwClient *scw.Client) error { 53 | for _, region := range (&k8s.API{}).Regions() { 54 | err := SweepCluster(scwClient, region) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /api/lb/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/lb/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepLB(scwClient *scw.Client, zone scw.Zone) error { 12 | lbAPI := lb.NewZonedAPI(scwClient) 13 | 14 | logger.Warningf("sweeper: destroying the lbs in (%s)", zone) 15 | listLBs, err := lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{ 16 | Zone: zone, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing lbs in (%s) in sweeper: %s", zone, err) 20 | } 21 | 22 | for _, l := range listLBs.LBs { 23 | _, err := lbAPI.WaitForLbInstances(&lb.ZonedAPIWaitForLBInstancesRequest{ 24 | Zone: zone, 25 | LBID: l.ID, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error waiting for lb in sweeper: %s", err) 29 | } 30 | err = lbAPI.DeleteLB(&lb.ZonedAPIDeleteLBRequest{ 31 | LBID: l.ID, 32 | ReleaseIP: true, 33 | Zone: zone, 34 | }) 35 | if err != nil { 36 | return fmt.Errorf("error deleting lb in sweeper: %s", err) 37 | } 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func SweepIP(scwClient *scw.Client, zone scw.Zone) error { 44 | lbAPI := lb.NewZonedAPI(scwClient) 45 | 46 | logger.Warningf("sweeper: destroying the lb ips in zone (%s)", zone) 47 | listIPs, err := lbAPI.ListIPs(&lb.ZonedAPIListIPsRequest{Zone: zone}, scw.WithAllPages()) 48 | if err != nil { 49 | return fmt.Errorf("error listing lb ips in (%s) in sweeper: %s", zone, err) 50 | } 51 | 52 | for _, ip := range listIPs.IPs { 53 | if ip.LBID == nil { 54 | err := lbAPI.ReleaseIP(&lb.ZonedAPIReleaseIPRequest{ 55 | Zone: zone, 56 | IPID: ip.ID, 57 | }) 58 | if err != nil { 59 | return fmt.Errorf("error deleting lb ip in sweeper: %s", err) 60 | } 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func SweepAllLocalities(scwClient *scw.Client) error { 68 | for _, zone := range (&lb.ZonedAPI{}).Zones() { 69 | err := SweepLB(scwClient, zone) 70 | if err != nil { 71 | return err 72 | } 73 | err = SweepIP(scwClient, zone) 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /api/marketplace/v2/marketplace_utils.go: -------------------------------------------------------------------------------- 1 | package marketplace 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | // FindByLabel returns the first image with the given label in the image list 11 | // Cannot find an image if it is not in the ListImagesResponse struct 12 | // Use scw.WithAllPages when listing image to get all images 13 | func (r *ListImagesResponse) FindByLabel(label string) *Image { 14 | for _, image := range r.Images { 15 | if image.Label == label { 16 | return image 17 | } 18 | } 19 | return nil 20 | } 21 | 22 | type GetImageByLabelRequest struct { 23 | Label string 24 | } 25 | 26 | // GetImageByLabel returns the image with the given label 27 | func (s *API) GetImageByLabel(req *GetImageByLabelRequest, opts ...scw.RequestOption) (*Image, error) { 28 | listImagesRequest := &ListImagesRequest{} 29 | opts = append(opts, scw.WithAllPages()) 30 | 31 | listImagesResponse, err := s.ListImages(listImagesRequest, opts...) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | image := listImagesResponse.FindByLabel(req.Label) 37 | if image == nil { 38 | return nil, errors.New("couldn't find a matching image for the given label (%s)", req.Label) 39 | } 40 | 41 | return image, nil 42 | } 43 | 44 | type GetLocalImageByLabelRequest struct { 45 | ImageLabel string 46 | Zone scw.Zone 47 | CommercialType string 48 | Type LocalImageType 49 | } 50 | 51 | // GetLocalImageByLabel returns the local image for the given image label in the given zone and compatible with given commercial type 52 | func (s *API) GetLocalImageByLabel(req *GetLocalImageByLabelRequest, opts ...scw.RequestOption) (*LocalImage, error) { 53 | if req.Zone == "" { 54 | defaultZone, _ := s.client.GetDefaultZone() 55 | req.Zone = defaultZone 56 | } 57 | req.CommercialType = strings.ToUpper(req.CommercialType) 58 | 59 | resp, err := s.ListLocalImages(&ListLocalImagesRequest{ 60 | ImageLabel: scw.StringPtr(req.ImageLabel), 61 | Zone: &req.Zone, 62 | Type: req.Type, 63 | }, opts...) 64 | if err != nil { 65 | return nil, err 66 | } 67 | for _, localImage := range resp.LocalImages { 68 | if localImage.IsCompatible(req.CommercialType) { 69 | return localImage, nil 70 | } 71 | } 72 | 73 | return nil, errors.New("couldn't find a local image for the given zone (%s) and commercial type (%s)", req.Zone, req.CommercialType) 74 | } 75 | 76 | // IsCompatible returns true if a local image is compatible with the given instance type 77 | // commercialType should be an uppercase string ex: DEV1-S 78 | func (li *LocalImage) IsCompatible(commercialType string) bool { 79 | for _, compatibleCommercialType := range li.CompatibleCommercialTypes { 80 | if compatibleCommercialType == commercialType { 81 | return true 82 | } 83 | } 84 | return false 85 | } 86 | -------------------------------------------------------------------------------- /api/marketplace/v2/marketplace_utils_test.go: -------------------------------------------------------------------------------- 1 | package marketplace 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func TestGetImageByLabel(t *testing.T) { 12 | client, r, err := httprecorder.CreateRecordedScwClient("go-vcr") 13 | testhelpers.AssertNoError(t, err) 14 | defer func() { 15 | testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it 16 | }() 17 | 18 | t.Run("matching input for GetLocalImageIDByLabel", func(t *testing.T) { 19 | // Create SDK objects for Scaleway Instance product 20 | marketplaceAPI := NewAPI(client) 21 | 22 | image, err := marketplaceAPI.GetLocalImageByLabel(&GetLocalImageByLabelRequest{ 23 | Zone: scw.ZoneFrPar1, 24 | CommercialType: "DEV1-S", 25 | ImageLabel: "ubuntu_focal", 26 | }) 27 | testhelpers.AssertNoError(t, err) 28 | 29 | // ubuntu_focal DEV1-S at par1: 68cf470e-6c35-4741-bbff-4ce788616461 30 | testhelpers.Equals(t, "9c41e95b-add2-4ef8-b1b1-af8899748eda", image.ID) 31 | }) 32 | 33 | t.Run("matching input for GetLocalImageIDByLabel with lowercase image label", func(t *testing.T) { 34 | // Create SDK objects for Scaleway Instance product 35 | marketplaceAPI := NewAPI(client) 36 | 37 | image, err := marketplaceAPI.GetLocalImageByLabel(&GetLocalImageByLabelRequest{ 38 | Zone: scw.ZoneFrPar1, 39 | CommercialType: "dev1-s", 40 | ImageLabel: "ubuntu_focal", 41 | }) 42 | testhelpers.AssertNoError(t, err) 43 | 44 | // ubuntu_focal DEV1-S at par1: 9c41e95b-add2-4ef8-b1b1-af8899748eda 45 | testhelpers.Equals(t, "9c41e95b-add2-4ef8-b1b1-af8899748eda", image.ID) 46 | }) 47 | 48 | t.Run("non-matching label for GetLocalImageIDByLabel", func(t *testing.T) { 49 | // Create SDK objects for Scaleway Instance product 50 | marketplaceAPI := NewAPI(client) 51 | 52 | _, err := marketplaceAPI.GetLocalImageByLabel(&GetLocalImageByLabelRequest{ 53 | Zone: scw.ZoneFrPar1, 54 | CommercialType: "DEV1-S", 55 | ImageLabel: "foo-bar-image", 56 | }) 57 | testhelpers.Assert(t, err != nil, "Should have error") 58 | // testhelpers.Equals(t, "scaleway-sdk-go: couldn't find a matching image for the given label (foo-bar-image)", err.Error()) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /api/mongodb/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | mongodb "github.com/scaleway/scaleway-sdk-go/api/mongodb/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepInstances(scwClient *scw.Client, region scw.Region) error { 12 | mongodbAPI := mongodb.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the mongodb instance in (%s)", region) 14 | listInstance, err := mongodbAPI.ListInstances(&mongodb.ListInstancesRequest{ 15 | Region: region, 16 | }) 17 | if err != nil { 18 | return fmt.Errorf("error listing mongodb instance in (%s) in sweeper: %w", region, err) 19 | } 20 | 21 | for _, instance := range listInstance.Instances { 22 | _, err := mongodbAPI.DeleteInstance(&mongodb.DeleteInstanceRequest{ 23 | Region: region, 24 | InstanceID: instance.ID, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting mongodb instance in sweeper: %w", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, region := range (&mongodb.API{}).Regions() { 36 | err := SweepInstances(scwClient, region) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /api/rdb/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/rdb/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepInstance(scwClient *scw.Client, region scw.Region) error { 12 | rdbAPI := rdb.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the rdb instance in (%s)", region) 14 | listInstances, err := rdbAPI.ListInstances(&rdb.ListInstancesRequest{ 15 | Region: region, 16 | }, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("error listing rdb instances in (%s) in sweeper: %s", region, err) 19 | } 20 | 21 | for _, instance := range listInstances.Instances { 22 | _, err := rdbAPI.DeleteInstance(&rdb.DeleteInstanceRequest{ 23 | Region: region, 24 | InstanceID: instance.ID, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting rdb instance in sweeper: %s", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, region := range (&rdb.API{}).Regions() { 36 | err := SweepInstance(scwClient, region) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /api/redis/v1/redis_utils.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 15 * time.Minute 14 | ) 15 | 16 | // WaitForClusterRequest is used by WaitForCluster method. 17 | type WaitForClusterRequest struct { 18 | ClusterID string 19 | Zone scw.Zone 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForCluster waits for the cluster to be in a "terminal state" before returning. 25 | // This function can be used to wait for a cluster to be ready for example. 26 | func (s *API) WaitForCluster(req *WaitForClusterRequest, opts ...scw.RequestOption) (*Cluster, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[ClusterStatus]struct{}{ 37 | ClusterStatusReady: {}, 38 | ClusterStatusLocked: {}, 39 | ClusterStatusError: {}, 40 | ClusterStatusSuspended: {}, 41 | } 42 | 43 | cluster, err := async.WaitSync(&async.WaitSyncConfig{ 44 | Get: func() (interface{}, bool, error) { 45 | res, err := s.GetCluster(&GetClusterRequest{ 46 | Zone: req.Zone, 47 | ClusterID: req.ClusterID, 48 | }, opts...) 49 | if err != nil { 50 | return nil, false, err 51 | } 52 | 53 | _, isTerminal := terminalStatus[res.Status] 54 | 55 | return res, isTerminal, nil 56 | }, 57 | Timeout: timeout, 58 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 59 | }) 60 | if err != nil { 61 | return nil, errors.Wrap(err, "waiting for cluster failed") 62 | } 63 | return cluster.(*Cluster), nil 64 | } 65 | -------------------------------------------------------------------------------- /api/redis/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/redis/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepCluster(scwClient *scw.Client, zone scw.Zone) error { 12 | redisAPI := redis.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the redis cluster in (%s)", zone) 14 | listClusters, err := redisAPI.ListClusters(&redis.ListClustersRequest{ 15 | Zone: zone, 16 | }, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("error listing redis clusters in (%s) in sweeper: %w", zone, err) 19 | } 20 | 21 | for _, cluster := range listClusters.Clusters { 22 | _, err := redisAPI.DeleteCluster(&redis.DeleteClusterRequest{ 23 | Zone: zone, 24 | ClusterID: cluster.ID, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting redis cluster in sweeper: %w", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, zone := range (&redis.API{}).Zones() { 36 | err := SweepCluster(scwClient, zone) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /api/registry/v1/image_utils.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | // WaitForNamespaceRequest is used by WaitForNamespace method 12 | type WaitForImageRequest struct { 13 | ImageID string 14 | Region scw.Region 15 | Timeout *time.Duration 16 | RetryInterval *time.Duration 17 | } 18 | 19 | // WaitForImage wait for the image to be in a "terminal state" before returning. 20 | // This function can be used to wait for an image to be ready for example. 21 | func (s *API) WaitForImage(req *WaitForImageRequest, opts ...scw.RequestOption) (*Image, error) { 22 | timeout := defaultTimeout 23 | if req.Timeout != nil { 24 | timeout = *req.Timeout 25 | } 26 | retryInterval := defaultRetryInterval 27 | if req.RetryInterval != nil { 28 | retryInterval = *req.RetryInterval 29 | } 30 | 31 | terminalStatus := map[ImageStatus]struct{}{ 32 | ImageStatusReady: {}, 33 | ImageStatusLocked: {}, 34 | ImageStatusError: {}, 35 | ImageStatusUnknown: {}, 36 | } 37 | 38 | image, err := async.WaitSync(&async.WaitSyncConfig{ 39 | Get: func() (interface{}, bool, error) { 40 | img, err := s.GetImage(&GetImageRequest{ 41 | Region: req.Region, 42 | ImageID: req.ImageID, 43 | }, opts...) 44 | if err != nil { 45 | return nil, false, err 46 | } 47 | 48 | _, isTerminal := terminalStatus[img.Status] 49 | 50 | return img, isTerminal, err 51 | }, 52 | Timeout: timeout, 53 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 54 | }) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "waiting for image failed") 57 | } 58 | return image.(*Image), nil 59 | } 60 | -------------------------------------------------------------------------------- /api/registry/v1/registry_utils.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultTimeout = 5 * time.Minute 13 | defaultRetryInterval = 15 * time.Second 14 | ) 15 | 16 | // WaitForNamespaceRequest is used by WaitForNamespace method 17 | type WaitForNamespaceRequest struct { 18 | NamespaceID string 19 | Region scw.Region 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForNamespace wait for the namespace to be in a "terminal state" before returning. 25 | // This function can be used to wait for a namespace to be ready for example. 26 | func (s *API) WaitForNamespace(req *WaitForNamespaceRequest, opts ...scw.RequestOption) (*Namespace, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[NamespaceStatus]struct{}{ 37 | NamespaceStatusReady: {}, 38 | NamespaceStatusLocked: {}, 39 | NamespaceStatusError: {}, 40 | NamespaceStatusUnknown: {}, 41 | } 42 | 43 | namespace, err := async.WaitSync(&async.WaitSyncConfig{ 44 | Get: func() (interface{}, bool, error) { 45 | ns, err := s.GetNamespace(&GetNamespaceRequest{ 46 | Region: req.Region, 47 | NamespaceID: req.NamespaceID, 48 | }, opts...) 49 | if err != nil { 50 | return nil, false, err 51 | } 52 | 53 | _, isTerminal := terminalStatus[ns.Status] 54 | 55 | return ns, isTerminal, err 56 | }, 57 | Timeout: timeout, 58 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 59 | }) 60 | if err != nil { 61 | return nil, errors.Wrap(err, "waiting for namespace failed") 62 | } 63 | return namespace.(*Namespace), nil 64 | } 65 | -------------------------------------------------------------------------------- /api/registry/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/registry/v1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepNamespace(scwClient *scw.Client, region scw.Region) error { 12 | registryAPI := registry.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the registry namespaces in (%s)", region) 14 | listNamespaces, err := registryAPI.ListNamespaces( 15 | ®istry.ListNamespacesRequest{Region: region}, scw.WithAllPages()) 16 | if err != nil { 17 | return fmt.Errorf("error listing namespaces in (%s) in sweeper: %s", region, err) 18 | } 19 | 20 | for _, ns := range listNamespaces.Namespaces { 21 | _, err := registryAPI.DeleteNamespace(®istry.DeleteNamespaceRequest{ 22 | NamespaceID: ns.ID, 23 | Region: region, 24 | }) 25 | if err != nil { 26 | return fmt.Errorf("error deleting namespace in sweeper: %s", err) 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func SweepAllLocalities(scwClient *scw.Client) error { 34 | for _, region := range (®istry.API{}).Regions() { 35 | err := SweepNamespace(scwClient, region) 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /api/registry/v1/tag_utils.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | // WaitForTagRequest is used by WaitForTag method 12 | type WaitForTagRequest struct { 13 | TagID string 14 | Region scw.Region 15 | Timeout *time.Duration 16 | RetryInterval *time.Duration 17 | } 18 | 19 | // WaitForTag wait for the tag to be in a "terminal state" before returning. 20 | // This function can be used to wait for a tag to be ready for example. 21 | func (s *API) WaitForTag(req *WaitForTagRequest, opts ...scw.RequestOption) (*Tag, error) { 22 | timeout := defaultTimeout 23 | if req.Timeout != nil { 24 | timeout = *req.Timeout 25 | } 26 | retryInterval := defaultRetryInterval 27 | if req.RetryInterval != nil { 28 | retryInterval = *req.RetryInterval 29 | } 30 | 31 | terminalStatus := map[TagStatus]struct{}{ 32 | TagStatusReady: {}, 33 | TagStatusLocked: {}, 34 | TagStatusError: {}, 35 | TagStatusUnknown: {}, 36 | } 37 | 38 | tag, err := async.WaitSync(&async.WaitSyncConfig{ 39 | Get: func() (interface{}, bool, error) { 40 | t, err := s.GetTag(&GetTagRequest{ 41 | Region: req.Region, 42 | TagID: req.TagID, 43 | }, opts...) 44 | if err != nil { 45 | return nil, false, err 46 | } 47 | 48 | _, isTerminal := terminalStatus[t.Status] 49 | 50 | return t, isTerminal, err 51 | }, 52 | Timeout: timeout, 53 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 54 | }) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "waiting for tag failed") 57 | } 58 | return tag.(*Tag), nil 59 | } 60 | -------------------------------------------------------------------------------- /api/secret/v1beta1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | secretSDK "github.com/scaleway/scaleway-sdk-go/api/secret/v1beta1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepSecret(scwClient *scw.Client, region scw.Region) error { 12 | secretAPI := secretSDK.NewAPI(scwClient) 13 | 14 | logger.Warningf("sweeper: deleting the secrets in (%s)", region) 15 | 16 | listSecrets, err := secretAPI.ListSecrets(&secretSDK.ListSecretsRequest{Region: region}, scw.WithAllPages()) 17 | if err != nil { 18 | return fmt.Errorf("error listing secrets in (%s) in sweeper: %s", region, err) 19 | } 20 | 21 | for _, se := range listSecrets.Secrets { 22 | err := secretAPI.DeleteSecret(&secretSDK.DeleteSecretRequest{ 23 | SecretID: se.ID, 24 | Region: region, 25 | }) 26 | if err != nil { 27 | return fmt.Errorf("error deleting secret in sweeper: %s", err) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func SweepAllLocalities(scwClient *scw.Client) error { 35 | for _, region := range (&secretSDK.API{}).Regions() { 36 | err := SweepSecret(scwClient, region) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /api/serverless_sqldb/v1alpha1/serverless_sqldb_utils.go: -------------------------------------------------------------------------------- 1 | package serverless_sqldb 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 15 * time.Second 13 | defaultTimeout = 15 * time.Minute 14 | ) 15 | 16 | // WaitForDatabaseRequest is used by WaitForDatabase method. 17 | type WaitForDatabaseRequest struct { 18 | DatabaseID string 19 | Region scw.Region 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForDatabase waits for the database to be in a "terminal state" before returning. 25 | // This function can be used to wait for a database to be ready for example. 26 | func (s *API) WaitForDatabase(req *WaitForDatabaseRequest, opts ...scw.RequestOption) (*Database, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[DatabaseStatus]struct{}{ 37 | DatabaseStatusReady: {}, 38 | DatabaseStatusError: {}, 39 | DatabaseStatusLocked: {}, 40 | } 41 | 42 | database, err := async.WaitSync(&async.WaitSyncConfig{ 43 | Get: func() (interface{}, bool, error) { 44 | res, err := s.GetDatabase(&GetDatabaseRequest{ 45 | DatabaseID: req.DatabaseID, 46 | Region: req.Region, 47 | }, opts...) 48 | if err != nil { 49 | return nil, false, err 50 | } 51 | _, isTerminal := terminalStatus[res.Status] 52 | 53 | return res, isTerminal, nil 54 | }, 55 | Timeout: timeout, 56 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 57 | }) 58 | if err != nil { 59 | return nil, errors.Wrap(err, "waiting for database failed") 60 | } 61 | return database.(*Database), nil 62 | } 63 | 64 | // WaitForDatabaseBackupRequest is used by WaitForDatabase method. 65 | type WaitForDatabaseBackupRequest struct { 66 | BackupID string 67 | Region scw.Region 68 | Timeout *time.Duration 69 | RetryInterval *time.Duration 70 | } 71 | 72 | // WaitForDatabaseBackup waits for the backup to be in a "terminal state" before returning. 73 | // This function can be used to wait for a backup to be ready for example. 74 | func (s *API) WaitForDatabaseBackup(req *WaitForDatabaseBackupRequest, opts ...scw.RequestOption) (*DatabaseBackup, error) { 75 | timeout := defaultTimeout 76 | if req.Timeout != nil { 77 | timeout = *req.Timeout 78 | } 79 | retryInterval := defaultRetryInterval 80 | if req.RetryInterval != nil { 81 | retryInterval = *req.RetryInterval 82 | } 83 | 84 | terminalStatus := map[DatabaseBackupStatus]struct{}{ 85 | DatabaseBackupStatusReady: {}, 86 | DatabaseBackupStatusError: {}, 87 | DatabaseBackupStatusLocked: {}, 88 | } 89 | 90 | backup, err := async.WaitSync(&async.WaitSyncConfig{ 91 | Get: func() (interface{}, bool, error) { 92 | res, err := s.GetDatabaseBackup(&GetDatabaseBackupRequest{ 93 | BackupID: req.BackupID, 94 | Region: req.Region, 95 | }, opts...) 96 | if err != nil { 97 | return nil, false, err 98 | } 99 | _, isTerminal := terminalStatus[res.Status] 100 | 101 | return res, isTerminal, nil 102 | }, 103 | Timeout: timeout, 104 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 105 | }) 106 | if err != nil { 107 | return nil, errors.Wrap(err, "waiting for database backup failed") 108 | } 109 | return backup.(*DatabaseBackup), nil 110 | } 111 | -------------------------------------------------------------------------------- /api/serverless_sqldb/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdbSDK "github.com/scaleway/scaleway-sdk-go/api/serverless_sqldb/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepDatabase(scwClient *scw.Client, region scw.Region) error { 12 | sdbAPI := sdbSDK.NewAPI(scwClient) 13 | logger.Warningf("sweeper: destroying the serverless sql database in (%s)", region) 14 | listServerlessSQLDBDatabases, err := sdbAPI.ListDatabases( 15 | &sdbSDK.ListDatabasesRequest{ 16 | Region: region, 17 | }, scw.WithAllPages()) 18 | if err != nil { 19 | return fmt.Errorf("error listing database in (%s) in sweeper: %s", region, err) 20 | } 21 | 22 | for _, database := range listServerlessSQLDBDatabases.Databases { 23 | _, err := sdbAPI.DeleteDatabase(&sdbSDK.DeleteDatabaseRequest{ 24 | DatabaseID: database.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting database in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepAllLocalities(scwClient *scw.Client) error { 36 | for _, region := range (&sdbSDK.API{}).Regions() { 37 | err := SweepDatabase(scwClient, region) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /api/std/std_sdk.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated. DO NOT EDIT. 2 | // If you have any remark or suggestion do not hesitate to open an issue. 3 | 4 | // Package std provides methods and message types of the std API. 5 | package std 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | "time" 16 | 17 | "github.com/scaleway/scaleway-sdk-go/marshaler" 18 | "github.com/scaleway/scaleway-sdk-go/namegenerator" 19 | "github.com/scaleway/scaleway-sdk-go/parameter" 20 | "github.com/scaleway/scaleway-sdk-go/scw" 21 | ) 22 | 23 | // always import dependencies 24 | var ( 25 | _ fmt.Stringer 26 | _ json.Unmarshaler 27 | _ url.URL 28 | _ net.IP 29 | _ http.Header 30 | _ bytes.Reader 31 | _ time.Time 32 | _ = strings.Join 33 | 34 | _ scw.ScalewayRequest 35 | _ marshaler.Duration 36 | _ scw.File 37 | _ = parameter.AddToQuery 38 | _ = namegenerator.GetRandomName 39 | ) 40 | 41 | type LanguageCode string 42 | 43 | const ( 44 | LanguageCodeUnknownLanguageCode = LanguageCode("unknown_language_code") 45 | LanguageCodeEnUS = LanguageCode("en_US") 46 | LanguageCodeFrFR = LanguageCode("fr_FR") 47 | LanguageCodeDeDE = LanguageCode("de_DE") 48 | ) 49 | 50 | func (enum LanguageCode) String() string { 51 | if enum == "" { 52 | // return default value if empty 53 | return string(LanguageCodeUnknownLanguageCode) 54 | } 55 | return string(enum) 56 | } 57 | 58 | func (enum LanguageCode) Values() []LanguageCode { 59 | return []LanguageCode{ 60 | "unknown_language_code", 61 | "en_US", 62 | "fr_FR", 63 | "de_DE", 64 | } 65 | } 66 | 67 | func (enum LanguageCode) MarshalJSON() ([]byte, error) { 68 | return []byte(fmt.Sprintf(`"%s"`, enum)), nil 69 | } 70 | 71 | func (enum *LanguageCode) UnmarshalJSON(data []byte) error { 72 | tmp := "" 73 | 74 | if err := json.Unmarshal(data, &tmp); err != nil { 75 | return err 76 | } 77 | 78 | *enum = LanguageCode(LanguageCode(tmp).String()) 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /api/tem/v1alpha1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | temSDK "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepDomain(scwClient *scw.Client, region scw.Region, skippedDomain string) error { 12 | temAPI := temSDK.NewAPI(scwClient) 13 | logger.Warningf("sweeper: revoking the tem domains in (%s)", region) 14 | 15 | listDomains, err := temAPI.ListDomains(&temSDK.ListDomainsRequest{Region: region}, scw.WithAllPages()) 16 | if err != nil { 17 | return fmt.Errorf("error listing domains in (%s) in sweeper: %s", region, err) 18 | } 19 | 20 | for _, ns := range listDomains.Domains { 21 | if ns.Name == skippedDomain { 22 | logger.Debugf("sweeper: skipping deletion of domain %s", ns.Name) 23 | continue 24 | } 25 | _, err := temAPI.RevokeDomain(&temSDK.RevokeDomainRequest{ 26 | DomainID: ns.ID, 27 | Region: region, 28 | }) 29 | if err != nil { 30 | return fmt.Errorf("error revoking domain in sweeper: %s", err) 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /api/tem/v1alpha1/tem_utils.go: -------------------------------------------------------------------------------- 1 | package tem 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultTimeout = 5 * time.Minute 13 | defaultRetryInterval = 15 * time.Second 14 | 15 | SMTPHost = "smtp.tem.scw.cloud" 16 | SMTPPortUnsecure = 25 17 | SMTPPort = 587 18 | SMTPPortAlternative = 2587 19 | SMTPSPort = 465 20 | SMTPSPortAlternative = 2465 21 | 22 | MXBlackhole = "blackhole.scw-tem.cloud." 23 | ) 24 | 25 | // WaitForDomainRequest is used by WaitForDomain method 26 | type WaitForDomainRequest struct { 27 | DomainID string 28 | Region scw.Region 29 | Timeout *time.Duration 30 | RetryInterval *time.Duration 31 | } 32 | 33 | // WaitForDomain wait for the domain to be in a "terminal state" before returning. 34 | // This function can be used to wait for a domain to be checked for example. 35 | func (s *API) WaitForDomain(req *WaitForDomainRequest, opts ...scw.RequestOption) (*Domain, error) { 36 | timeout := defaultTimeout 37 | if req.Timeout != nil { 38 | timeout = *req.Timeout 39 | } 40 | retryInterval := defaultRetryInterval 41 | if req.RetryInterval != nil { 42 | retryInterval = *req.RetryInterval 43 | } 44 | 45 | terminalStatus := map[DomainStatus]struct{}{ 46 | DomainStatusChecked: {}, 47 | DomainStatusUnchecked: {}, 48 | DomainStatusInvalid: {}, 49 | DomainStatusLocked: {}, 50 | DomainStatusRevoked: {}, 51 | DomainStatusUnknown: {}, 52 | } 53 | 54 | domain, err := async.WaitSync(&async.WaitSyncConfig{ 55 | Get: func() (interface{}, bool, error) { 56 | img, err := s.GetDomain(&GetDomainRequest{ 57 | Region: req.Region, 58 | DomainID: req.DomainID, 59 | }, opts...) 60 | if err != nil { 61 | return nil, false, err 62 | } 63 | 64 | _, isTerminal := terminalStatus[img.Status] 65 | 66 | return img, isTerminal, err 67 | }, 68 | Timeout: timeout, 69 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 70 | }) 71 | if err != nil { 72 | return nil, errors.Wrap(err, "waiting for domain failed") 73 | } 74 | return domain.(*Domain), nil 75 | } 76 | -------------------------------------------------------------------------------- /api/vpc/v2/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | vpcSDK "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" 7 | "github.com/scaleway/scaleway-sdk-go/logger" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func SweepVPC(scwClient *scw.Client, region scw.Region) error { 12 | vpcAPI := vpcSDK.NewAPI(scwClient) 13 | 14 | listVPCs, err := vpcAPI.ListVPCs(&vpcSDK.ListVPCsRequest{Region: region}, scw.WithAllPages()) 15 | if err != nil { 16 | return fmt.Errorf("error listing secrets in (%s) in sweeper: %s", region, err) 17 | } 18 | 19 | for _, v := range listVPCs.Vpcs { 20 | if v.IsDefault { 21 | continue 22 | } 23 | err := vpcAPI.DeleteVPC(&vpcSDK.DeleteVPCRequest{ 24 | VpcID: v.ID, 25 | Region: region, 26 | }) 27 | if err != nil { 28 | return fmt.Errorf("error deleting VPC in sweeper: %s", err) 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func SweepPrivateNetwork(scwClient *scw.Client, region scw.Region) error { 36 | vpcAPI := vpcSDK.NewAPI(scwClient) 37 | 38 | logger.Debugf("sweeper: destroying the private network in (%s)", region) 39 | 40 | listPNResponse, err := vpcAPI.ListPrivateNetworks(&vpcSDK.ListPrivateNetworksRequest{ 41 | Region: region, 42 | }, scw.WithAllPages()) 43 | if err != nil { 44 | return fmt.Errorf("error listing private network in sweeper: %s", err) 45 | } 46 | 47 | for _, pn := range listPNResponse.PrivateNetworks { 48 | err := vpcAPI.DeletePrivateNetwork(&vpcSDK.DeletePrivateNetworkRequest{ 49 | Region: region, 50 | PrivateNetworkID: pn.ID, 51 | }) 52 | if err != nil { 53 | return fmt.Errorf("error deleting private network in sweeper: %s", err) 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func SweepRoute(scwClient *scw.Client, region scw.Region) error { 61 | vpcAPI := vpcSDK.NewAPI(scwClient) 62 | vpcRouteAPI := vpcSDK.NewRoutesWithNexthopAPI(scwClient) 63 | 64 | logger.Warningf("sweeper: destroying the route in (%s)", region) 65 | 66 | listRoutesResponse, err := vpcRouteAPI.ListRoutesWithNexthop(&vpcSDK.RoutesWithNexthopAPIListRoutesWithNexthopRequest{ 67 | Region: region, 68 | }, scw.WithAllPages()) 69 | if err != nil { 70 | return fmt.Errorf("error listing route in sweeper: %s", err) 71 | } 72 | 73 | for _, routeWithNexthop := range listRoutesResponse.Routes { 74 | if routeWithNexthop.Route != nil { 75 | err := vpcAPI.DeleteRoute(&vpcSDK.DeleteRouteRequest{ 76 | Region: region, 77 | RouteID: routeWithNexthop.Route.ID, 78 | }) 79 | if err != nil { 80 | return fmt.Errorf("error deleting route in sweeper: %s", err) 81 | } 82 | } else { 83 | return fmt.Errorf("route is nil in RouteWithNexthop: %v", routeWithNexthop) 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func SweepAllLocalities(scwClient *scw.Client) error { 91 | for _, region := range (&vpcSDK.API{}).Regions() { 92 | err := SweepVPC(scwClient, region) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | err = SweepPrivateNetwork(scwClient, region) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | err = SweepRoute(scwClient, region) 103 | if err != nil { 104 | return err 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /api/vpcgw/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | vpcgwSDK "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | func SweepVPCPublicGateway(scwClient *scw.Client, zone scw.Zone) error { 11 | api := vpcgwSDK.NewAPI(scwClient) 12 | 13 | listGatewayResponse, err := api.ListGateways(&vpcgwSDK.ListGatewaysRequest{ //nolint:staticcheck 14 | Zone: zone, 15 | }, scw.WithAllPages()) 16 | if err != nil { 17 | return fmt.Errorf("error listing public gateway in sweeper: %w", err) 18 | } 19 | 20 | for _, gateway := range listGatewayResponse.Gateways { 21 | err := api.DeleteGateway(&vpcgwSDK.DeleteGatewayRequest{ //nolint:staticcheck 22 | Zone: zone, 23 | GatewayID: gateway.ID, 24 | }) 25 | if err != nil { 26 | return fmt.Errorf("error deleting public gateway in sweeper: %w", err) 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func SweepGatewayNetworks(scwClient *scw.Client, zone scw.Zone) error { 33 | api := vpcgwSDK.NewAPI(scwClient) 34 | 35 | listPNResponse, err := api.ListGatewayNetworks(&vpcgwSDK.ListGatewayNetworksRequest{ //nolint:staticcheck 36 | Zone: zone, 37 | }, scw.WithAllPages()) 38 | if err != nil { 39 | return fmt.Errorf("error listing gateway network in sweeper: %s", err) 40 | } 41 | 42 | for _, gn := range listPNResponse.GatewayNetworks { 43 | err := api.DeleteGatewayNetwork(&vpcgwSDK.DeleteGatewayNetworkRequest{ //nolint:staticcheck 44 | GatewayNetworkID: gn.GatewayID, 45 | Zone: zone, 46 | // Cleanup the dhcp resource related. DON'T CALL THE SWEEPER DHCP 47 | CleanupDHCP: true, 48 | }) 49 | if err != nil { 50 | return fmt.Errorf("error deleting gateway network in sweeper: %s", err) 51 | } 52 | } 53 | return nil 54 | } 55 | 56 | func SweepVPCPublicGatewayIP(scwClient *scw.Client, zone scw.Zone) error { 57 | api := vpcgwSDK.NewAPI(scwClient) 58 | 59 | listIPResponse, err := api.ListIPs(&vpcgwSDK.ListIPsRequest{ //nolint:staticcheck 60 | Zone: zone, 61 | }, scw.WithAllPages()) 62 | if err != nil { 63 | return fmt.Errorf("error listing public gateway ip in sweeper: %s", err) 64 | } 65 | 66 | for _, ip := range listIPResponse.IPs { 67 | err := api.DeleteIP(&vpcgwSDK.DeleteIPRequest{ //nolint:staticcheck 68 | Zone: zone, 69 | IPID: ip.ID, 70 | }) 71 | if err != nil { 72 | return fmt.Errorf("error deleting public gateway ip in sweeper: %s", err) 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | func SweepVPCPublicGatewayDHCP(scwClient *scw.Client, zone scw.Zone) error { 79 | api := vpcgwSDK.NewAPI(scwClient) 80 | 81 | listDHCPsResponse, err := api.ListDHCPs(&vpcgwSDK.ListDHCPsRequest{ //nolint:staticcheck 82 | Zone: zone, 83 | }, scw.WithAllPages()) 84 | if err != nil { 85 | return fmt.Errorf("error listing public gateway dhcps in sweeper: %w", err) 86 | } 87 | 88 | for _, dhcp := range listDHCPsResponse.Dhcps { 89 | err := api.DeleteDHCP(&vpcgwSDK.DeleteDHCPRequest{ //nolint:staticcheck 90 | Zone: zone, 91 | DHCPID: dhcp.ID, 92 | }) 93 | if err != nil { 94 | return fmt.Errorf("error deleting public gateway dhcp in sweeper: %w", err) 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func SweepAllLocalities(scwClient *scw.Client) error { 102 | for _, zone := range (&vpcgwSDK.API{}).Zones() { 103 | err := SweepVPCPublicGateway(scwClient, zone) 104 | if err != nil { 105 | return err 106 | } 107 | err = SweepGatewayNetworks(scwClient, zone) 108 | if err != nil { 109 | return err 110 | } 111 | err = SweepVPCPublicGatewayIP(scwClient, zone) 112 | if err != nil { 113 | return err 114 | } 115 | err = SweepVPCPublicGatewayDHCP(scwClient, zone) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /api/vpcgw/v2/vpcgw_utils.go: -------------------------------------------------------------------------------- 1 | package vpcgw 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultTimeout = 5 * time.Minute 13 | defaultRetryInterval = 15 * time.Second 14 | ) 15 | 16 | // WaitForGatewayRequest is used by WaitForGateway method 17 | type WaitForGatewayRequest struct { 18 | GatewayID string 19 | Zone scw.Zone 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForGateway waits for the gateway to be in a "terminal state" before returning. 25 | // This function can be used to wait for a gateway to be ready for example. 26 | func (s *API) WaitForGateway(req *WaitForGatewayRequest, opts ...scw.RequestOption) (*Gateway, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[GatewayStatus]struct{}{ 37 | GatewayStatusUnknownStatus: {}, 38 | GatewayStatusStopped: {}, 39 | GatewayStatusRunning: {}, 40 | GatewayStatusFailed: {}, 41 | GatewayStatusLocked: {}, 42 | } 43 | 44 | gateway, err := async.WaitSync(&async.WaitSyncConfig{ 45 | Get: func() (interface{}, bool, error) { 46 | ns, err := s.GetGateway(&GetGatewayRequest{ 47 | Zone: req.Zone, 48 | GatewayID: req.GatewayID, 49 | }, opts...) 50 | if err != nil { 51 | return nil, false, err 52 | } 53 | 54 | _, isTerminal := terminalStatus[ns.Status] 55 | 56 | return ns, isTerminal, err 57 | }, 58 | Timeout: timeout, 59 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 60 | }) 61 | if err != nil { 62 | return nil, errors.Wrap(err, "waiting for gateway failed") 63 | } 64 | 65 | return gateway.(*Gateway), nil 66 | } 67 | 68 | // WaitForGatewayNetworkRequest is used by WaitForGatewayNetwork method 69 | type WaitForGatewayNetworkRequest struct { 70 | GatewayNetworkID string 71 | Zone scw.Zone 72 | Timeout *time.Duration 73 | RetryInterval *time.Duration 74 | } 75 | 76 | // WaitForGatewayNetwork waits for the gateway network to be in a "terminal state" before returning. 77 | // This function can be used to wait for a gateway network to be ready for example. 78 | func (s *API) WaitForGatewayNetwork(req *WaitForGatewayNetworkRequest, opts ...scw.RequestOption) (*GatewayNetwork, error) { 79 | timeout := defaultTimeout 80 | if req.Timeout != nil { 81 | timeout = *req.Timeout 82 | } 83 | retryInterval := defaultRetryInterval 84 | if req.RetryInterval != nil { 85 | retryInterval = *req.RetryInterval 86 | } 87 | 88 | terminalStatus := map[GatewayNetworkStatus]struct{}{ 89 | GatewayNetworkStatusReady: {}, 90 | GatewayNetworkStatusUnknownStatus: {}, 91 | } 92 | 93 | gatewayNetwork, err := async.WaitSync(&async.WaitSyncConfig{ 94 | Get: func() (interface{}, bool, error) { 95 | ns, err := s.GetGatewayNetwork(&GetGatewayNetworkRequest{ 96 | Zone: req.Zone, 97 | GatewayNetworkID: req.GatewayNetworkID, 98 | }, opts...) 99 | if err != nil { 100 | return nil, false, err 101 | } 102 | 103 | _, isTerminal := terminalStatus[ns.Status] 104 | 105 | return ns, isTerminal, err 106 | }, 107 | Timeout: timeout, 108 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 109 | }) 110 | if err != nil { 111 | return nil, errors.Wrap(err, "waiting for gateway network failed") 112 | } 113 | 114 | return gatewayNetwork.(*GatewayNetwork), nil 115 | } 116 | -------------------------------------------------------------------------------- /api/webhosting/v1/sweepers/sweepers.go: -------------------------------------------------------------------------------- 1 | package sweepers 2 | 3 | import ( 4 | "fmt" 5 | 6 | webhostingSDK "github.com/scaleway/scaleway-sdk-go/api/webhosting/v1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | ) 9 | 10 | func SweepWebHosting(scwClient *scw.Client, region scw.Region) error { 11 | webHostingAPI := webhostingSDK.NewHostingAPI(scwClient) 12 | 13 | listHostings, err := webHostingAPI.ListHostings(&webhostingSDK.HostingAPIListHostingsRequest{Region: region}, scw.WithAllPages()) 14 | if err != nil { 15 | return fmt.Errorf("error listing hostings in (%s) in sweeper: %s", region, err) 16 | } 17 | 18 | for _, hosting := range listHostings.Hostings { 19 | _, err := webHostingAPI.DeleteHosting(&webhostingSDK.HostingAPIDeleteHostingRequest{ 20 | HostingID: hosting.ID, 21 | Region: region, 22 | }) 23 | if err != nil { 24 | return fmt.Errorf("error deleting hosting in sweeper: %s", err) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func SweepAllLocalities(scwClient *scw.Client) error { 32 | for _, region := range (&webhostingSDK.HostingAPI{}).Regions() { 33 | err := SweepWebHosting(scwClient, region) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /api/webhosting/v1/webhosting_utils.go: -------------------------------------------------------------------------------- 1 | package webhosting 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/errors" 7 | "github.com/scaleway/scaleway-sdk-go/internal/async" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | const ( 12 | defaultRetryInterval = 5 * time.Second 13 | defaultTimeout = 5 * time.Minute 14 | ) 15 | 16 | // WaitForHostingRequest is used by WaitForHosting method. 17 | type WaitForHostingRequest struct { 18 | HostingID string 19 | Region scw.Region 20 | Timeout *time.Duration 21 | RetryInterval *time.Duration 22 | } 23 | 24 | // WaitForHosting waits for a hosting to be in a "terminal" state before returning. 25 | // Terminal states are defined as: HostingStatusReady, HostingStatusError, HostingStatusUnknownStatus, and HostingStatusLocked. 26 | func (s *HostingAPI) WaitForHosting(req *WaitForHostingRequest, opts ...scw.RequestOption) (*Hosting, error) { 27 | timeout := defaultTimeout 28 | if req.Timeout != nil { 29 | timeout = *req.Timeout 30 | } 31 | retryInterval := defaultRetryInterval 32 | if req.RetryInterval != nil { 33 | retryInterval = *req.RetryInterval 34 | } 35 | 36 | terminalStatus := map[HostingStatus]struct{}{ 37 | HostingStatusReady: {}, 38 | HostingStatusError: {}, 39 | HostingStatusUnknownStatus: {}, 40 | HostingStatusLocked: {}, 41 | } 42 | 43 | res, err := async.WaitSync(&async.WaitSyncConfig{ 44 | Get: func() (interface{}, bool, error) { 45 | hosting, err := s.GetHosting(&HostingAPIGetHostingRequest{ 46 | HostingID: req.HostingID, 47 | Region: req.Region, 48 | }, opts...) 49 | if err != nil { 50 | return nil, false, err 51 | } 52 | 53 | _, isTerminal := terminalStatus[hosting.Status] 54 | return hosting, isTerminal, nil 55 | }, 56 | Timeout: timeout, 57 | IntervalStrategy: async.LinearIntervalStrategy(retryInterval), 58 | }) 59 | if err != nil { 60 | return nil, errors.Wrap(err, "waiting for hosting failed") 61 | } 62 | 63 | return res.(*Hosting), nil 64 | } 65 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package scalewaysdkgo is the Scaleway API SDK for Go. 2 | // 3 | // In order to use the available APIs, create a `Client`. Once created, it can be used to instantiate an API. 4 | // To use the `instance` API, for example, instantiate it (with the client object) `instance.NewApi(client)`. 5 | // On this instance API, all the available API functions can be called. 6 | package scalewaysdkgo 7 | -------------------------------------------------------------------------------- /docs/CONTINUOUS_CODE_DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # Continuous code deployment 2 | 3 | Part of this repo is automatically generated from our [protocol buffer](https://en.wikipedia.org/wiki/Protocol_Buffers) monorepo. 4 | This enables us to keep Scaleway toolings up to date with the latest version of our APIs ([developer website](https://www.scaleway.com/en/developers/), soon-to-be CLI, ...). 5 | 6 | ## Generated files 7 | 8 | Generated files and folders are located in [scaleway-sdk-go/api](../api). 9 | They always start with the following line: 10 | 11 | ```c 12 | // This file was automatically generated. DO NOT EDIT. 13 | ``` 14 | 15 | ## Continuous deployment process 16 | 17 | TODO: explains the continuous deployment process. 18 | 19 | ## Synchronization frequency 20 | 21 | The continuous code deployment process can occur at anytime of the day, sometime many times a day. 22 | Expect it to happen regularly. 23 | 24 | ## Any question? 25 | 26 | If you have any question or request about the continuous code deployment process feel free to [reach us](../README.md#reach-us). 27 | -------------------------------------------------------------------------------- /docs/static_files/sdk-artwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleway/scaleway-sdk-go/2c91f5b7ce9b79ac20eb3b2a21bf377de141d312/docs/static_files/sdk-artwork.png -------------------------------------------------------------------------------- /errors/error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | // Error is a base error that implement scw.SdkError 6 | type Error struct { 7 | Str string 8 | Err error 9 | } 10 | 11 | // Error implement standard xerror.Wrapper interface 12 | func (e *Error) Unwrap() error { 13 | return e.Err 14 | } 15 | 16 | // Error implement standard error interface 17 | func (e *Error) Error() string { 18 | str := "scaleway-sdk-go: " + e.Str 19 | if e.Err != nil { 20 | str += ": " + e.Err.Error() 21 | } 22 | return str 23 | } 24 | 25 | // IsScwSdkError implement SdkError interface 26 | func (e *Error) IsScwSdkError() {} 27 | 28 | // New creates a new error with that same interface as fmt.Errorf 29 | func New(format string, args ...interface{}) *Error { 30 | return &Error{ 31 | Str: fmt.Sprintf(format, args...), 32 | } 33 | } 34 | 35 | // Wrap an error with additional information 36 | func Wrap(err error, format string, args ...interface{}) *Error { 37 | return &Error{ 38 | Err: err, 39 | Str: fmt.Sprintf(format, args...), 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/scaleway/scaleway-sdk-go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/dnaeon/go-vcr v1.2.0 9 | golang.org/x/text v0.26.0 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= 2 | github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 3 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 4 | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= 5 | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= 6 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 8 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 9 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 10 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 11 | -------------------------------------------------------------------------------- /internal/async/wait.go: -------------------------------------------------------------------------------- 1 | package async 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | var ( 9 | defaultInterval = time.Second 10 | defaultTimeout = time.Minute * 5 11 | ) 12 | 13 | type IntervalStrategy func() <-chan time.Time 14 | 15 | // WaitSyncConfig defines the waiting options. 16 | type WaitSyncConfig struct { 17 | // This method will be called from another goroutine. 18 | Get func() (value interface{}, isTerminal bool, err error) 19 | IntervalStrategy IntervalStrategy 20 | Timeout time.Duration 21 | } 22 | 23 | // LinearIntervalStrategy defines a linear interval duration. 24 | func LinearIntervalStrategy(interval time.Duration) IntervalStrategy { 25 | return func() <-chan time.Time { 26 | return time.After(interval) 27 | } 28 | } 29 | 30 | // FibonacciIntervalStrategy defines an interval duration who follow the Fibonacci sequence. 31 | func FibonacciIntervalStrategy(base time.Duration, factor float32) IntervalStrategy { 32 | var x, y float32 = 0, 1 33 | 34 | return func() <-chan time.Time { 35 | x, y = y, x+(y*factor) 36 | return time.After(time.Duration(x) * base) 37 | } 38 | } 39 | 40 | // WaitSync waits and returns when a given stop condition is true or if an error occurs. 41 | func WaitSync(config *WaitSyncConfig) (terminalValue interface{}, err error) { 42 | // initialize configuration 43 | if config.IntervalStrategy == nil { 44 | config.IntervalStrategy = LinearIntervalStrategy(defaultInterval) 45 | } 46 | 47 | if config.Timeout == 0 { 48 | config.Timeout = defaultTimeout 49 | } 50 | 51 | resultValue := make(chan interface{}) 52 | resultErr := make(chan error) 53 | timeout := make(chan bool) 54 | 55 | go func() { 56 | for { 57 | // get the payload 58 | value, stopCondition, err := config.Get() 59 | // send the payload 60 | if err != nil { 61 | resultErr <- err 62 | return 63 | } 64 | if stopCondition { 65 | resultValue <- value 66 | return 67 | } 68 | 69 | // waiting for an interval before next get() call or a timeout 70 | select { 71 | case <-timeout: 72 | return 73 | case <-config.IntervalStrategy(): 74 | // sleep 75 | } 76 | } 77 | }() 78 | 79 | // waiting for a result or a timeout 80 | select { 81 | case val := <-resultValue: 82 | return val, nil 83 | case err := <-resultErr: 84 | return nil, err 85 | case <-time.After(config.Timeout): 86 | timeout <- true 87 | return nil, fmt.Errorf("timeout after %v", config.Timeout) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internal/async/wait_test.go: -------------------------------------------------------------------------------- 1 | package async 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 9 | ) 10 | 11 | const flakiness = 500 * time.Millisecond 12 | 13 | type value struct { 14 | doneIterations int 15 | totalDuration time.Duration 16 | } 17 | 18 | func getMock(iterations int, sleepTime time.Duration) func() (interface{}, bool, error) { 19 | cpt := iterations 20 | var startTime time.Time 21 | 22 | return func() (interface{}, bool, error) { 23 | if cpt == iterations { 24 | startTime = time.Now() 25 | } 26 | cpt-- 27 | 28 | // fake working time 29 | time.Sleep(sleepTime) 30 | 31 | v := &value{ 32 | doneIterations: iterations - cpt, 33 | totalDuration: time.Since(startTime), 34 | } 35 | return v, cpt == 0, nil 36 | } 37 | } 38 | 39 | func TestWaitSync(t *testing.T) { 40 | t.Parallel() 41 | testsCases := []struct { 42 | name string 43 | config *WaitSyncConfig 44 | expValue interface{} 45 | expErr error 46 | }{ 47 | { 48 | name: "With default timeout and interval", 49 | config: &WaitSyncConfig{ 50 | Get: getMock(2, 0), 51 | }, 52 | expValue: &value{ 53 | doneIterations: 2, 54 | totalDuration: time.Second, 55 | }, 56 | }, 57 | { 58 | name: "With useless timeout", 59 | config: &WaitSyncConfig{ 60 | Get: getMock(2, time.Second), 61 | Timeout: 4 * time.Second, 62 | }, 63 | expValue: &value{ 64 | doneIterations: 2, 65 | totalDuration: 3 * time.Second, 66 | }, 67 | }, 68 | { 69 | name: "Should timeout", 70 | config: &WaitSyncConfig{ 71 | Get: getMock(2, 2*time.Second), 72 | Timeout: time.Second, 73 | }, 74 | expValue: nil, 75 | expErr: errors.New("timeout after 1s"), 76 | }, 77 | { 78 | name: "With interval", 79 | config: &WaitSyncConfig{ 80 | Get: getMock(2, 0), 81 | IntervalStrategy: LinearIntervalStrategy(2 * time.Second), 82 | }, 83 | expValue: &value{ 84 | doneIterations: 2, 85 | totalDuration: 2 * time.Second, 86 | }, 87 | }, 88 | { 89 | name: "With fibonacci interval", 90 | config: &WaitSyncConfig{ 91 | Get: getMock(5, 0), 92 | IntervalStrategy: FibonacciIntervalStrategy(time.Second, 1), 93 | }, 94 | expValue: &value{ 95 | doneIterations: 5, 96 | totalDuration: 7 * time.Second, 97 | }, 98 | }, 99 | { 100 | name: "Should timeout with interval", 101 | config: &WaitSyncConfig{ 102 | Get: getMock(2, time.Second), 103 | Timeout: 2 * time.Second, 104 | IntervalStrategy: LinearIntervalStrategy(2 * time.Second), 105 | }, 106 | expValue: nil, 107 | expErr: errors.New("timeout after 2s"), 108 | }, 109 | } 110 | for _, c := range testsCases { 111 | c := c // do not remove me 112 | t.Run(c.name, func(t *testing.T) { 113 | t.Parallel() 114 | 115 | terminalValue, err := WaitSync(c.config) 116 | 117 | testhelpers.Equals(t, c.expErr, err) 118 | 119 | if c.expValue != nil { 120 | exp := c.expValue.(*value) 121 | acc := terminalValue.(*value) 122 | testhelpers.Equals(t, exp.doneIterations, acc.doneIterations) 123 | 124 | ok := exp.totalDuration > acc.totalDuration-flakiness && exp.totalDuration < acc.totalDuration+flakiness 125 | testhelpers.Assert(t, ok, "totalDuration don't match the target: (acc: %v, exp: %v)", acc.totalDuration, exp.totalDuration) 126 | } 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /internal/auth/access_key.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | type AccessKeyOnly struct { 6 | // auth config may contain an access key without being authenticated 7 | AccessKey string 8 | } 9 | 10 | // NewNoAuth return an auth with no authentication method 11 | func NewAccessKeyOnly(accessKey string) *AccessKeyOnly { 12 | return &AccessKeyOnly{accessKey} 13 | } 14 | 15 | func (t *AccessKeyOnly) Headers() http.Header { 16 | return http.Header{} 17 | } 18 | 19 | func (t *AccessKeyOnly) AnonymizedHeaders() http.Header { 20 | return http.Header{} 21 | } 22 | -------------------------------------------------------------------------------- /internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | // Auth implement methods required for authentication. 6 | // Valid authentication are currently a token or no auth. 7 | type Auth interface { 8 | // Headers returns headers that must be add to the http request 9 | Headers() http.Header 10 | 11 | // AnonymizedHeaders returns an anonymised version of Headers() 12 | // This method could be use for logging purpose. 13 | AnonymizedHeaders() http.Header 14 | } 15 | 16 | type headerAnonymizer func(header http.Header) http.Header 17 | 18 | var headerAnonymizers = []headerAnonymizer{ 19 | AnonymizeTokenHeaders, 20 | AnonymizeJWTHeaders, 21 | } 22 | 23 | func AnonymizeHeaders(headers http.Header) http.Header { 24 | for _, anonymizer := range headerAnonymizers { 25 | headers = anonymizer(headers) 26 | } 27 | 28 | return headers 29 | } 30 | -------------------------------------------------------------------------------- /internal/auth/jwt.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | // JWT is the session token used in browser. 9 | type JWT struct { 10 | Token string 11 | } 12 | 13 | // XSessionTokenHeader is Scaleway auth header for browser 14 | const XSessionTokenHeader = "X-Session-Token" // #nosec G101 15 | 16 | // NewJWT create a token authentication from a jwt 17 | func NewJWT(token string) *JWT { 18 | return &JWT{Token: token} 19 | } 20 | 21 | // Headers returns headers that must be added to the http request 22 | func (j *JWT) Headers() http.Header { 23 | headers := http.Header{} 24 | headers.Set(XSessionTokenHeader, j.Token) 25 | return headers 26 | } 27 | 28 | func AnonymizeJWTHeaders(headers http.Header) http.Header { 29 | token := headers.Get(XSessionTokenHeader) 30 | 31 | if token != "" { 32 | headers.Set(XSessionTokenHeader, HideJWT(token)) 33 | } 34 | 35 | return headers 36 | } 37 | 38 | // AnonymizedHeaders returns an anonymized version of Headers() 39 | // This method could be used for logging purpose. 40 | func (j *JWT) AnonymizedHeaders() http.Header { 41 | return AnonymizeJWTHeaders(j.Headers()) 42 | } 43 | 44 | func HideJWT(token string) string { 45 | if len(token) == 0 { 46 | return "" 47 | } 48 | // token should be (header).(payload).(signature) 49 | lastDot := strings.LastIndex(token, ".") 50 | if lastDot != -1 { 51 | token = token[:lastDot] 52 | } 53 | 54 | return token 55 | } 56 | -------------------------------------------------------------------------------- /internal/auth/no_auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | type NoAuth struct{} 6 | 7 | // NewNoAuth return an auth with no authentication method 8 | func NewNoAuth() *NoAuth { 9 | return &NoAuth{} 10 | } 11 | 12 | func (t *NoAuth) Headers() http.Header { 13 | return http.Header{} 14 | } 15 | 16 | func (t *NoAuth) AnonymizedHeaders() http.Header { 17 | return http.Header{} 18 | } 19 | -------------------------------------------------------------------------------- /internal/auth/token.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "net/http" 4 | 5 | // Token is the pair accessKey + secretKey. 6 | // This type is public because it's an internal package. 7 | type Token struct { 8 | AccessKey string 9 | SecretKey string 10 | } 11 | 12 | // XAuthTokenHeader is Scaleway standard auth header 13 | const XAuthTokenHeader = "X-Auth-Token" // #nosec G101 14 | 15 | // NewToken create a token authentication from an 16 | // access key and a secret key 17 | func NewToken(accessKey, secretKey string) *Token { 18 | return &Token{AccessKey: accessKey, SecretKey: secretKey} 19 | } 20 | 21 | // Headers returns headers that must be add to the http request 22 | func (t *Token) Headers() http.Header { 23 | headers := http.Header{} 24 | headers.Set(XAuthTokenHeader, t.SecretKey) 25 | return headers 26 | } 27 | 28 | func AnonymizeTokenHeaders(headers http.Header) http.Header { 29 | key := headers.Get(XAuthTokenHeader) 30 | if key != "" { 31 | headers.Set(XAuthTokenHeader, HideSecretKey(key)) 32 | } 33 | return headers 34 | } 35 | 36 | // AnonymizedHeaders returns an anonymized version of Headers() 37 | // This method could be use for logging purpose. 38 | func (t *Token) AnonymizedHeaders() http.Header { 39 | return AnonymizeTokenHeaders(t.Headers()) 40 | } 41 | 42 | func HideSecretKey(k string) string { 43 | switch { 44 | case len(k) == 0: 45 | return "" 46 | case len(k) > 8: 47 | return k[0:8] + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 48 | default: 49 | return "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/auth/token_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 8 | ) 9 | 10 | func TestToken_Headers(t *testing.T) { 11 | const ( 12 | accessKey = "ACCESS_KEY" 13 | secretKey = "SECRET_KEY" 14 | ) 15 | auth := NewToken(accessKey, secretKey) 16 | testhelpers.Equals(t, http.Header{ 17 | "X-Auth-Token": []string{secretKey}, 18 | }, auth.Headers()) 19 | } 20 | -------------------------------------------------------------------------------- /internal/e2e/errors_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/api/test/v1" 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 8 | "github.com/scaleway/scaleway-sdk-go/scw" 9 | ) 10 | 11 | func TestStandardErrors(t *testing.T) { 12 | t.Skip("Skipping test while api-test not yet deployed") 13 | client, _, _, err := newE2EClient(true) 14 | testhelpers.AssertNoError(t, err) 15 | 16 | t.Run("not found", func(t *testing.T) { 17 | _, err = client.GetHuman(&test.GetHumanRequest{ 18 | HumanID: "b3ba839a-dcf2-4b0a-ac81-fc32370052a0", 19 | }) 20 | testhelpers.Equals(t, &scw.ResourceNotFoundError{ 21 | Resource: "human", 22 | ResourceID: "b3ba839a-dcf2-4b0a-ac81-fc32370052a0", 23 | RawBody: []byte(`{"message":"resource is not found","resource":"human","resource_id":"b3ba839a-dcf2-4b0a-ac81-fc32370052a0","type":"not_found"}`), 24 | }, err) 25 | }) 26 | 27 | t.Run("invalid argument", func(t *testing.T) { 28 | _, err = client.CreateHuman(&test.CreateHumanRequest{ 29 | AltitudeInMeter: -7000000, 30 | }) 31 | testhelpers.Equals(t, &scw.InvalidArgumentsError{ 32 | Details: []scw.InvalidArgumentsErrorDetail{ 33 | { 34 | ArgumentName: "altitude_in_meter", 35 | Reason: "constraint", 36 | HelpMessage: "lowest altitude on earth is -6371km", 37 | }, 38 | }, 39 | RawBody: []byte(`{"details":[{"argument_name":"altitude_in_meter","help_message":"lowest altitude on earth is -6371km","reason":"constraint"}],"message":"invalid argument(s)","type":"invalid_arguments"}`), 40 | }, err) 41 | }) 42 | 43 | t.Run("quotas exceeded", func(t *testing.T) { 44 | var humans []*test.Human 45 | 46 | for i := 0; i < 10; i++ { 47 | human, err := client.CreateHuman(&test.CreateHumanRequest{}) 48 | testhelpers.AssertNoError(t, err) 49 | humans = append(humans, human) 50 | } 51 | 52 | _, err = client.CreateHuman(&test.CreateHumanRequest{}) 53 | testhelpers.Equals(t, &scw.QuotasExceededError{ 54 | Details: []scw.QuotasExceededErrorDetail{ 55 | { 56 | Resource: "human", 57 | Quota: 10, 58 | Current: 10, 59 | }, 60 | }, 61 | RawBody: []byte(`{"details":[{"current":10,"quota":10,"resource":"human"}],"message":"quota(s) exceeded for this resource","type":"quotas_exceeded"}`), 62 | }, err) 63 | 64 | for _, human := range humans { 65 | _, err := client.DeleteHuman(&test.DeleteHumanRequest{HumanID: human.ID}) 66 | testhelpers.AssertNoError(t, err) 67 | } 68 | }) 69 | 70 | t.Run("transient state", func(t *testing.T) { 71 | human, err := client.CreateHuman(&test.CreateHumanRequest{}) 72 | testhelpers.AssertNoError(t, err) 73 | defer func() { 74 | _, _ = client.DeleteHuman(&test.DeleteHumanRequest{HumanID: human.ID}) 75 | }() 76 | 77 | _, err = client.RunHuman(&test.RunHumanRequest{HumanID: human.ID}) 78 | testhelpers.AssertNoError(t, err) 79 | 80 | _, err = client.UpdateHuman(&test.UpdateHumanRequest{HumanID: human.ID}) 81 | testhelpers.Equals(t, &scw.TransientStateError{ 82 | Resource: "human", 83 | ResourceID: human.ID, 84 | CurrentState: "running", 85 | RawBody: []byte(`{"current_state":"running","message":"resource is in a transient state","resource":"human","resource_id":"` + human.ID + `","type":"transient_state"}`), 86 | }, err) 87 | }) 88 | 89 | t.Run("out of stock", func(t *testing.T) { 90 | _, err = client.CreateHuman(&test.CreateHumanRequest{ 91 | ShoeSize: 60, 92 | }) 93 | testhelpers.Equals(t, &scw.OutOfStockError{ 94 | Resource: "ShoeSize60", 95 | RawBody: []byte(`{"message":"resource is out of stock","resource":"ShoeSize60","type":"out_of_stock"}`), 96 | }, err) 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /internal/generic/fields.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import "reflect" 4 | 5 | // HasField returns true if given struct has a field with given name 6 | // Also allow a slice, it will use the underlying type 7 | func HasField(i interface{}, fieldName string) bool { 8 | value := reflect.Indirect(reflect.ValueOf(i)) 9 | typ := value.Type() 10 | 11 | if value.Kind() == reflect.Slice { 12 | typ = indirectType(typ.Elem()) 13 | } 14 | 15 | _, fieldExists := typ.FieldByName(fieldName) 16 | return fieldExists 17 | } 18 | -------------------------------------------------------------------------------- /internal/generic/fields_test.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import "testing" 4 | 5 | func TestHasField(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | i interface{} 9 | fieldName string 10 | want bool 11 | }{ 12 | { 13 | "native type", 14 | struct { 15 | Zone string 16 | }{}, 17 | "Zone", 18 | true, 19 | }, 20 | { 21 | "ptr type", 22 | struct { 23 | Zone *string 24 | }{}, 25 | "Zone", 26 | true, 27 | }, 28 | { 29 | "invalid case", 30 | struct { 31 | Zone *string 32 | }{}, 33 | "zone", 34 | false, 35 | }, 36 | { 37 | "slice", 38 | []struct { 39 | Zone string 40 | }{}, 41 | "Zone", 42 | true, 43 | }, 44 | { 45 | "slice invalid case", 46 | []struct { 47 | Zone string 48 | }{}, 49 | "zone", 50 | false, 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | if got := HasField(tt.i, tt.fieldName); got != tt.want { 56 | t.Errorf("HasField() = %v, want %v", got, tt.want) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/generic/ptr.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import "reflect" 4 | 5 | func indirectType(typ reflect.Type) reflect.Type { 6 | if typ.Kind() == reflect.Ptr { 7 | return typ.Elem() 8 | } 9 | 10 | return typ 11 | } 12 | -------------------------------------------------------------------------------- /internal/generic/sort.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | // SortSliceByField sorts given slice of struct by passing the specified field to given compare function 9 | // given slice must be a slice of Ptr 10 | func SortSliceByField(list interface{}, field string, compare func(interface{}, interface{}) bool) { 11 | listValue := reflect.ValueOf(list) 12 | sort.SliceStable(list, func(i, j int) bool { 13 | field1 := listValue.Index(i).Elem().FieldByName(field).Interface() 14 | field2 := listValue.Index(j).Elem().FieldByName(field).Interface() 15 | return compare(field1, field2) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /internal/generic/sort_test.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 7 | ) 8 | 9 | func Test_SortSliceByField(t *testing.T) { 10 | type Elem struct { 11 | Field string 12 | } 13 | elems := []*Elem{ 14 | {"2"}, 15 | {"1"}, 16 | {"3"}, 17 | } 18 | SortSliceByField(elems, "Field", func(i interface{}, i2 interface{}) bool { 19 | return i.(string) < i2.(string) 20 | }) 21 | testhelpers.Assert(t, elems[0].Field == "1", "slice is not sorted") 22 | testhelpers.Assert(t, elems[1].Field == "2", "slice is not sorted") 23 | testhelpers.Assert(t, elems[2].Field == "3", "slice is not sorted") 24 | } 25 | -------------------------------------------------------------------------------- /internal/testhelpers/httprecorder/recorder.go: -------------------------------------------------------------------------------- 1 | package httprecorder 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "strings" 8 | 9 | "github.com/dnaeon/go-vcr/cassette" 10 | "github.com/dnaeon/go-vcr/recorder" 11 | "github.com/scaleway/scaleway-sdk-go/errors" 12 | "github.com/scaleway/scaleway-sdk-go/scw" 13 | ) 14 | 15 | // IsUpdatingCassette returns true if we are updating cassettes. 16 | func IsUpdatingCassette() bool { 17 | return os.Getenv("SDK_UPDATE_CASSETTES") == "true" 18 | } 19 | 20 | // CreateRecordedScwClient creates a new scw.Client that records all HTTP requests in a cassette. 21 | // This cassette is then replayed whenever tests are executed again. This means that once the 22 | // requests are recorded in the cassette, no more real HTTP request must be made to run the tests. 23 | // 24 | // It is important to call add a `defer recorder.Stop()` so the given cassette files are correctly 25 | // closed and saved after the requests. 26 | // 27 | // To update the cassette files, add `SDK_UPDATE_CASSETTES=true` to the environment variables. 28 | // When updating cassettes, make sure your Scaleway credentials are set in your config or in the 29 | // variables `SCW_ACCESS_KEY` and `SCW_SECRET_KEY`. 30 | func CreateRecordedScwClient(cassetteName string) (*scw.Client, *recorder.Recorder, error) { 31 | UpdateCassette := IsUpdatingCassette() 32 | 33 | var activeProfile *scw.Profile 34 | 35 | recorderMode := recorder.ModeReplaying 36 | if UpdateCassette { 37 | recorderMode = recorder.ModeRecording 38 | config, err := scw.LoadConfig() 39 | if err != nil { 40 | return nil, nil, err 41 | } 42 | activeProfile, err = config.GetActiveProfile() 43 | if err != nil { 44 | return nil, nil, err 45 | } 46 | } 47 | 48 | // Setup recorder and scw client 49 | r, err := recorder.NewAsMode("testdata/"+cassetteName, recorderMode, nil) 50 | if err != nil { 51 | return nil, nil, err 52 | } 53 | 54 | // Add a filter which removes Authorization headers from all requests: 55 | r.AddFilter(func(i *cassette.Interaction) error { 56 | delete(i.Request.Headers, "x-auth-token") 57 | delete(i.Request.Headers, "X-Auth-Token") 58 | 59 | if UpdateCassette { 60 | secretKey := *activeProfile.SecretKey 61 | if i != nil && strings.Contains(fmt.Sprintf("%v", *i), secretKey) { 62 | panic(errors.New("found secret key in cassette")) 63 | } 64 | } 65 | 66 | return nil 67 | }) 68 | 69 | // Create new http.Client where transport is the recorder 70 | httpClient := &http.Client{Transport: r} 71 | 72 | var client *scw.Client 73 | 74 | if UpdateCassette { 75 | // When updating the recoreded test requests, we need the access key and secret key. 76 | client, err = scw.NewClient( 77 | scw.WithHTTPClient(httpClient), 78 | scw.WithProfile(activeProfile), 79 | scw.WithEnv(), 80 | scw.WithDefaultRegion(scw.RegionFrPar), 81 | scw.WithDefaultZone(scw.ZoneFrPar1), 82 | ) 83 | if err != nil { 84 | return nil, nil, err 85 | } 86 | } else { 87 | // No need for auth when using cassette 88 | client, err = scw.NewClient( 89 | scw.WithHTTPClient(httpClient), 90 | scw.WithDefaultRegion(scw.RegionFrPar), 91 | scw.WithDefaultZone(scw.ZoneFrPar1), 92 | ) 93 | if err != nil { 94 | return nil, nil, err 95 | } 96 | } 97 | 98 | return client, r, nil 99 | } 100 | -------------------------------------------------------------------------------- /internal/testhelpers/test_helpers.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "reflect" 7 | "runtime" 8 | "testing" 9 | ) 10 | 11 | // Assert fails the test if the condition is false. 12 | func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 13 | tb.Helper() 14 | if !condition { 15 | _, file, line, _ := runtime.Caller(1) 16 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) 17 | tb.FailNow() 18 | } 19 | } 20 | 21 | // AssertNoError fails the test if an err is not nil. 22 | func AssertNoError(tb testing.TB, err error) { 23 | tb.Helper() 24 | if err != nil { 25 | _, file, line, _ := runtime.Caller(1) 26 | fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) 27 | tb.FailNow() 28 | } 29 | } 30 | 31 | // Equals fails the test if exp is not equal to act. 32 | func Equals(tb testing.TB, exp, act interface{}) { 33 | tb.Helper() 34 | if !reflect.DeepEqual(exp, act) { 35 | _, file, line, _ := runtime.Caller(1) 36 | fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) 37 | tb.FailNow() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/testhelpers/test_resources.go: -------------------------------------------------------------------------------- 1 | package testhelpers 2 | 3 | import "strings" 4 | 5 | // IsTestResource returns true if given resource identifier is from terraform test 6 | // identifier should be resource name but some resource don't have names 7 | // return true if identifier match regex "tf[-_]test" 8 | // common used prefixes are "tf_tests", "tf_test", "tf-tests", "tf-test" 9 | func IsTestResource(identifier string) bool { 10 | return len(identifier) >= len("tf_test") && 11 | strings.HasPrefix(identifier, "tf") && 12 | (identifier[2] == '_' || identifier[2] == '-') && 13 | identifier[3:7] == "test" 14 | } 15 | -------------------------------------------------------------------------------- /logger/default_logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | var ( 12 | DefaultLogger = newLogger(os.Stderr, LogLevelWarning) 13 | logger Logger = DefaultLogger 14 | ) 15 | 16 | // loggerT is the default logger used by scaleway-sdk-go. 17 | type loggerT struct { 18 | m [4]*log.Logger 19 | v LogLevel 20 | } 21 | 22 | // Init create a new default logger. 23 | // Not mutex-protected, should be called before any scaleway-sdk-go functions. 24 | func (g *loggerT) Init(w io.Writer, level LogLevel) { 25 | g.m = newLogger(w, level).m 26 | g.v = level 27 | } 28 | 29 | // Debugf logs to the DEBUG log. Arguments are handled in the manner of fmt.Printf. 30 | func Debugf(format string, args ...interface{}) { logger.Debugf(format, args...) } 31 | 32 | func (g *loggerT) Debugf(format string, args ...interface{}) { 33 | g.m[LogLevelDebug].Printf(format, args...) 34 | } 35 | 36 | // Infof logs to the INFO log. Arguments are handled in the manner of fmt.Printf. 37 | func Infof(format string, args ...interface{}) { logger.Infof(format, args...) } 38 | 39 | func (g *loggerT) Infof(format string, args ...interface{}) { 40 | g.m[LogLevelInfo].Printf(format, args...) 41 | } 42 | 43 | // Warningf logs to the WARNING log. Arguments are handled in the manner of fmt.Printf. 44 | func Warningf(format string, args ...interface{}) { logger.Warningf(format, args...) } 45 | 46 | func (g *loggerT) Warningf(format string, args ...interface{}) { 47 | g.m[LogLevelWarning].Printf(format, args...) 48 | } 49 | 50 | // Errorf logs to the ERROR log. Arguments are handled in the manner of fmt.Printf. 51 | func Errorf(format string, args ...interface{}) { logger.Errorf(format, args...) } 52 | 53 | func (g *loggerT) Errorf(format string, args ...interface{}) { 54 | g.m[LogLevelError].Printf(format, args...) 55 | } 56 | 57 | // ShouldLog reports whether verbosity level l is at least the requested verbose level. 58 | func ShouldLog(level LogLevel) bool { return logger.ShouldLog(level) } 59 | 60 | func (g *loggerT) ShouldLog(level LogLevel) bool { 61 | return level >= g.v 62 | } 63 | 64 | func isEnabled(envKey string) bool { 65 | env, exist := os.LookupEnv(envKey) 66 | if !exist { 67 | return false 68 | } 69 | 70 | value, err := strconv.ParseBool(env) 71 | if err != nil { 72 | fmt.Fprintf(os.Stderr, "ERROR: environment variable %s has invalid boolean value\n", envKey) 73 | } 74 | 75 | return value 76 | } 77 | 78 | // newLogger creates a logger to be used as default logger. 79 | // All logs are written to w. 80 | func newLogger(w io.Writer, level LogLevel) *loggerT { 81 | errorW := io.Discard 82 | warningW := io.Discard 83 | infoW := io.Discard 84 | debugW := io.Discard 85 | if isEnabled(DebugEnv) { 86 | level = LogLevelDebug 87 | } 88 | switch level { 89 | case LogLevelDebug: 90 | debugW = w 91 | case LogLevelInfo: 92 | infoW = w 93 | case LogLevelWarning: 94 | warningW = w 95 | case LogLevelError: 96 | errorW = w 97 | } 98 | 99 | // Error logs will be written to errorW, warningW, infoW and debugW. 100 | // Warning logs will be written to warningW, infoW and debugW. 101 | // Info logs will be written to infoW and debugW. 102 | // Debug logs will be written to debugW. 103 | var m [4]*log.Logger 104 | 105 | m[LogLevelError] = log.New(io.MultiWriter(debugW, infoW, warningW, errorW), 106 | severityName[LogLevelError]+": ", log.LstdFlags) 107 | 108 | m[LogLevelWarning] = log.New(io.MultiWriter(debugW, infoW, warningW), 109 | severityName[LogLevelWarning]+": ", log.LstdFlags) 110 | 111 | m[LogLevelInfo] = log.New(io.MultiWriter(debugW, infoW), 112 | severityName[LogLevelInfo]+": ", log.LstdFlags) 113 | 114 | m[LogLevelDebug] = log.New(debugW, 115 | severityName[LogLevelDebug]+": ", log.LstdFlags) 116 | 117 | return &loggerT{m: m, v: level} 118 | } 119 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "os" 4 | 5 | type LogLevel int 6 | 7 | const DebugEnv = "SCW_DEBUG" 8 | 9 | const ( 10 | // LogLevelDebug indicates Debug severity. 11 | LogLevelDebug LogLevel = iota 12 | // LogLevelInfo indicates Info severity. 13 | LogLevelInfo 14 | // LogLevelWarning indicates Warning severity. 15 | LogLevelWarning 16 | // LogLevelError indicates Error severity. 17 | LogLevelError 18 | ) 19 | 20 | // severityName contains the string representation of each severity. 21 | var severityName = []string{ 22 | LogLevelDebug: "DEBUG", 23 | LogLevelInfo: "INFO", 24 | LogLevelWarning: "WARNING", 25 | LogLevelError: "ERROR", 26 | } 27 | 28 | // Logger does underlying logging work for scaleway-sdk-go. 29 | type Logger interface { 30 | // Debugf logs to DEBUG log. Arguments are handled in the manner of fmt.Printf. 31 | Debugf(format string, args ...interface{}) 32 | // Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf. 33 | Infof(format string, args ...interface{}) 34 | // Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf. 35 | Warningf(format string, args ...interface{}) 36 | // Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf. 37 | Errorf(format string, args ...interface{}) 38 | // ShouldLog reports whether verbosity level l is at least the requested verbose level. 39 | ShouldLog(level LogLevel) bool 40 | } 41 | 42 | // SetLogger sets logger that is used in by the SDK. 43 | // Not mutex-protected, should be called before any scaleway-sdk-go functions. 44 | func SetLogger(l Logger) { 45 | logger = l 46 | } 47 | 48 | // EnableDebugMode enable LogLevelDebug on the default logger. 49 | // If a custom logger was provided with SetLogger this method has no effect. 50 | func EnableDebugMode() { 51 | DefaultLogger.Init(os.Stderr, LogLevelDebug) 52 | } 53 | -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 10 | ) 11 | 12 | var ( 13 | expectedErrorf = "ERROR: cd" 14 | expectedWarningf = "WARNING: ij" 15 | expectedInfof = "INFO: op" 16 | expectedDebugf = "DEBUG: uv" 17 | ) 18 | 19 | func TestDebug(t *testing.T) { 20 | buf := &bytes.Buffer{} 21 | logThings(newLogger(buf, LogLevelDebug)) 22 | testThings(t, []string{ 23 | expectedErrorf, 24 | expectedWarningf, 25 | expectedInfof, 26 | expectedDebugf, 27 | }, buf.String()) 28 | } 29 | 30 | func TestInfo(t *testing.T) { 31 | buf := &bytes.Buffer{} 32 | logThings(newLogger(buf, LogLevelInfo)) 33 | testThings(t, []string{ 34 | expectedErrorf, 35 | expectedWarningf, 36 | expectedInfof, 37 | }, buf.String()) 38 | } 39 | 40 | func TestWarning(t *testing.T) { 41 | buf := &bytes.Buffer{} 42 | logThings(newLogger(buf, LogLevelWarning)) 43 | testThings(t, []string{ 44 | expectedErrorf, 45 | expectedWarningf, 46 | }, buf.String()) 47 | } 48 | 49 | func TestError(t *testing.T) { 50 | buf := &bytes.Buffer{} 51 | logThings(newLogger(buf, LogLevelError)) 52 | testThings(t, []string{ 53 | expectedErrorf, 54 | }, buf.String()) 55 | } 56 | 57 | func TestEnableDebugMode(t *testing.T) { 58 | _defaultLogger := DefaultLogger 59 | 60 | DefaultLogger = newLogger(os.Stderr, LogLevelWarning) 61 | EnableDebugMode() 62 | testhelpers.Equals(t, true, DefaultLogger.ShouldLog(LogLevelDebug)) 63 | 64 | DefaultLogger = _defaultLogger 65 | } 66 | 67 | func testThings(t *testing.T, expectedEvents []string, actualOutput string) { 68 | t.Helper() 69 | lines := strings.Split(actualOutput, "\n") 70 | for i, line := range lines[:len(lines)-1] { // last line is always empty 71 | tmp := strings.Split(line, " ") 72 | actualMessage := strings.Join(append([]string{tmp[0]}, tmp[3:]...), " ") // date and hour is not kept 73 | testhelpers.Equals(t, expectedEvents[i], actualMessage) 74 | } 75 | } 76 | 77 | func logThings(log Logger) { 78 | log.Errorf("c%s", "d") 79 | log.Warningf("i%s", "j") 80 | log.Infof("o%s", "p") 81 | log.Debugf("u%s", "v") 82 | } 83 | -------------------------------------------------------------------------------- /namegenerator/name_generator_test.go: -------------------------------------------------------------------------------- 1 | // Source: github.com/docker/docker/pkg/namesgenerator 2 | 3 | package namegenerator 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestNameFormat(t *testing.T) { 11 | name := GetRandomName() 12 | if strings.Count(name, "-") != 1 { 13 | t.Fatalf("Generated name does not contain exactly 1 hyphen") 14 | } 15 | if strings.ContainsAny(name, "0123456789") { 16 | t.Fatalf("Generated name contains numbers!") 17 | } 18 | } 19 | 20 | func TestNameFormatWithPrefix(t *testing.T) { 21 | name := GetRandomName("scw") 22 | if strings.Count(name, "-") != 2 { 23 | t.Fatalf("Generated name does not contain exactly 2 hyphens") 24 | } 25 | if !strings.HasPrefix(name, "scw-") { 26 | t.Fatalf("Generated name must begin with \"tf-scw-\"") 27 | } 28 | if strings.ContainsAny(name, "0123456789") { 29 | t.Fatalf("Generated name contains numbers!") 30 | } 31 | } 32 | 33 | func TestNameFormatWithPrefixes(t *testing.T) { 34 | name := GetRandomName("tf", "scw") 35 | if strings.Count(name, "-") != 3 { 36 | t.Fatalf("Generated name does not contain exactly 3 hyphens") 37 | } 38 | if !strings.HasPrefix(name, "tf-scw-") { 39 | t.Fatalf("Generated name must begin with \"tf-scw-\"") 40 | } 41 | if strings.ContainsAny(name, "0123456789") { 42 | t.Fatalf("Generated name contains numbers!") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /parameter/query.go: -------------------------------------------------------------------------------- 1 | package parameter 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "reflect" 8 | "time" 9 | 10 | "github.com/scaleway/scaleway-sdk-go/scw" 11 | ) 12 | 13 | // AddToQuery add a key/value pair to an URL query 14 | func AddToQuery(query url.Values, key string, value interface{}) { 15 | elemValue := reflect.ValueOf(value) 16 | 17 | if elemValue.Kind() == reflect.Invalid || elemValue.Kind() == reflect.Ptr && elemValue.IsNil() { 18 | return 19 | } 20 | 21 | for elemValue.Kind() == reflect.Ptr { 22 | elemValue = reflect.ValueOf(value).Elem() 23 | } 24 | 25 | elemType := elemValue.Type() 26 | switch { 27 | case elemType == reflect.TypeOf(net.IP{}): 28 | query.Add(key, value.(*net.IP).String()) 29 | case elemType == reflect.TypeOf(net.IPNet{}): 30 | query.Add(key, value.(*net.IPNet).String()) 31 | case elemType == reflect.TypeOf(scw.IPNet{}): 32 | query.Add(key, value.(*scw.IPNet).String()) 33 | case elemType.Kind() == reflect.Slice: 34 | for i := 0; i < elemValue.Len(); i++ { 35 | query.Add(key, fmt.Sprint(elemValue.Index(i).Interface())) 36 | } 37 | case elemType == reflect.TypeOf(time.Time{}): 38 | query.Add(key, value.(*time.Time).Format(time.RFC3339)) 39 | default: 40 | query.Add(key, fmt.Sprint(elemValue.Interface())) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scaleway GO SDK - Scripts 2 | 3 | This directory contains useful scripts to work on scaleway-go-sdk. 4 | 5 | ### check_for_tokens.sh \*\* 6 | 7 | Checks that no token are present in cassette file 8 | 9 | ``` 10 | Usage: ./script/check_for_tokens.sh 11 | ``` 12 | 13 | ### release.sh \*\* 14 | 15 | This script will trigger the release process. 16 | For more information on the release process you can refer to ./release/release.js file 17 | 18 | ``` 19 | Usage: ./script/release.sh 20 | ``` 21 | -------------------------------------------------------------------------------- /scripts/check_for_tokens.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | files=$(find . -type f -print | grep testdata); 4 | 5 | for file in $files 6 | do 7 | echo "checking $file"; 8 | if grep -i --quiet "x-auth-token" $file 9 | then 10 | echo "found x-auth-token in file $file"; 11 | exit 1; 12 | fi 13 | done 14 | exit 0; 15 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" 5 | 6 | ## 7 | # Colorize output 8 | ## 9 | function color() { 10 | case $1 in 11 | yellow) echo -e -n "\033[33m" ;; 12 | green) echo -e -n "\033[32m" ;; 13 | red) echo -e -n "\033[0;31m" ;; 14 | esac 15 | echo "$2" 16 | echo -e -n "\033[0m" 17 | } 18 | 19 | ## 20 | # Print Usage 21 | ## 22 | function usage() { 23 | color yellow "Usage:" 24 | echo " $SCRIPT_DIR [OPTIONS]" 25 | echo "" 26 | 27 | color yellow "Options:" 28 | 29 | color green " -w, --write" 30 | echo -e "\tFix found issues (if it's supported by the linter)." 31 | 32 | color green " --list" 33 | echo -e "\tList current linters configuration." 34 | 35 | color green " -h, --help" 36 | echo -e "\tDisplay this help." 37 | 38 | echo "" 39 | exit "$1"; 40 | } 41 | 42 | OPT_CMD="run" 43 | OPT_FLAGS="" 44 | OPT_DIRS="$ROOT_DIR/..." 45 | 46 | ## 47 | # Parse arguments 48 | ## 49 | while [[ $# -gt 0 ]] 50 | do 51 | case "$1" in 52 | -h|--help) 53 | usage 0 ;; 54 | 55 | -w|--write) 56 | OPT_CMD="run" 57 | OPT_FLAGS+=" --fix" ;; 58 | 59 | --list) 60 | OPT_DIRS="" 61 | OPT_CMD="linters" ;; 62 | 63 | -v|--verbose) 64 | OPT_FLAGS+=" -v" ;; 65 | 66 | *) 67 | color red "Unkown argument '$1'" 68 | echo 69 | usage 1 ;; 70 | esac 71 | shift 72 | done 73 | 74 | ## 75 | # Check golangci-lint command existence 76 | ## 77 | if [ ! -x "$(command -v golangci-lint)" ]; 78 | then 79 | echo "golangci-lint is not installed" 80 | echo "On macOS, you can run: brew install golangci/tap/golangci-lint" 81 | echo "On other systems, refer to installation instructions: https://github.com/golangci/golangci-lint#install" 82 | exit 1 83 | fi 84 | 85 | ## 86 | # Execute golangci-lint command 87 | ## 88 | golangci-lint $OPT_CMD $OPT_FLAGS $OPT_DIRS 89 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd scripts/release || (echo "Please run this script from repo root" && exit 1) 4 | 5 | yarn install --frozen-lock-file 6 | yarn run release 7 | -------------------------------------------------------------------------------- /scw/env_test.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 8 | ) 9 | 10 | // TestLoadConfig tests config getters return correct values 11 | func TestLoadEnvProfile(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | env map[string]string 15 | 16 | expectedAccessKey *string 17 | expectedSecretKey *string 18 | expectedAPIURL *string 19 | expectedInsecure *bool 20 | expectedDefaultOrganizationID *string 21 | expectedDefaultProjectID *string 22 | expectedDefaultRegion *string 23 | expectedDefaultZone *string 24 | }{ 25 | // up-to-date env variables 26 | { 27 | name: "No config with env variables", 28 | env: map[string]string{ 29 | ScwAccessKeyEnv: v2ValidAccessKey, 30 | ScwSecretKeyEnv: v2ValidSecretKey, 31 | ScwAPIURLEnv: v2ValidAPIURL, 32 | ScwInsecureEnv: "false", 33 | ScwDefaultOrganizationIDEnv: v2ValidDefaultOrganizationID, 34 | ScwDefaultProjectIDEnv: v2ValidDefaultProjectID, 35 | ScwDefaultRegionEnv: v2ValidDefaultRegion, 36 | ScwDefaultZoneEnv: v2ValidDefaultZone, 37 | }, 38 | expectedAccessKey: s(v2ValidAccessKey), 39 | expectedSecretKey: s(v2ValidSecretKey), 40 | expectedAPIURL: s(v2ValidAPIURL), 41 | expectedInsecure: b(false), 42 | expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), 43 | expectedDefaultProjectID: s(v2ValidDefaultProjectID), 44 | expectedDefaultRegion: s(v2ValidDefaultRegion), 45 | expectedDefaultZone: s(v2ValidDefaultZone), 46 | }, 47 | { 48 | name: "No config with terraform legacy env variables", 49 | env: map[string]string{ 50 | terraformAccessKeyEnv: v2ValidAccessKey, 51 | terraformSecretKeyEnv: v2ValidSecretKey, 52 | terraformOrganizationEnv: v2ValidDefaultOrganizationID, 53 | terraformRegionEnv: v2ValidDefaultRegion, 54 | }, 55 | expectedAccessKey: s(v2ValidAccessKey), 56 | expectedSecretKey: s(v2ValidSecretKey), 57 | expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), 58 | expectedDefaultProjectID: s(v2ValidDefaultProjectID), 59 | expectedDefaultRegion: s(v2ValidDefaultRegion), 60 | }, 61 | { 62 | name: "No config with CLI legacy env variables", 63 | env: map[string]string{ 64 | cliSecretKeyEnv: v2ValidSecretKey2, 65 | cliOrganizationEnv: v2ValidDefaultOrganizationID2, 66 | cliRegionEnv: v2ValidDefaultRegion2, 67 | cliTLSVerifyEnv: "false", 68 | }, 69 | expectedSecretKey: s(v2ValidSecretKey2), 70 | expectedInsecure: b(true), 71 | expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID2), 72 | expectedDefaultProjectID: s(v2ValidDefaultProjectID2), 73 | expectedDefaultRegion: s(v2ValidDefaultRegion2), 74 | }, 75 | } 76 | 77 | // create home dir 78 | dir := initEnv(t) 79 | 80 | // delete home dir and reset env variables 81 | defer resetEnv(t, os.Environ(), dir) 82 | for _, test := range tests { 83 | t.Run(test.name, func(t *testing.T) { 84 | // set up env and config file(s) 85 | setEnv(t, test.env, nil, dir) 86 | 87 | // remove config file(s) 88 | defer cleanEnv(t, nil, dir) 89 | 90 | // load config 91 | p := LoadEnvProfile() 92 | 93 | // assert getters 94 | testhelpers.Equals(t, test.expectedAccessKey, p.AccessKey) 95 | testhelpers.Equals(t, test.expectedSecretKey, p.SecretKey) 96 | testhelpers.Equals(t, test.expectedAPIURL, p.APIURL) 97 | testhelpers.Equals(t, test.expectedDefaultOrganizationID, p.DefaultOrganizationID) 98 | testhelpers.Equals(t, test.expectedDefaultRegion, p.DefaultRegion) 99 | testhelpers.Equals(t, test.expectedDefaultZone, p.DefaultZone) 100 | testhelpers.Equals(t, test.expectedInsecure, p.Insecure) 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /scw/locality_test.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/scaleway/scaleway-sdk-go/errors" 8 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 9 | ) 10 | 11 | func TestParseZone(t *testing.T) { 12 | tests := []struct { 13 | input string 14 | err error 15 | expected Zone 16 | }{ 17 | { 18 | input: "fr-par-1", 19 | expected: ZoneFrPar1, 20 | }, 21 | { 22 | input: "pl-waw-1", 23 | expected: ZonePlWaw1, 24 | }, 25 | { 26 | input: "pl-waw-2", 27 | expected: ZonePlWaw2, 28 | }, 29 | { 30 | input: "pl-waw-3", 31 | expected: ZonePlWaw3, 32 | }, 33 | { 34 | input: "nl-ams-2", 35 | expected: ZoneNlAms2, 36 | }, 37 | { 38 | input: "nl-ams-3", 39 | expected: ZoneNlAms3, 40 | }, 41 | { 42 | input: "par1", 43 | expected: ZoneFrPar1, 44 | }, 45 | { 46 | input: "ams1", 47 | expected: ZoneNlAms1, 48 | }, 49 | { 50 | input: "xx-xxx-1", 51 | expected: "xx-xxx-1", 52 | }, 53 | { 54 | input: "fr-par", 55 | expected: "", 56 | err: errors.New("bad zone format, available zones are: fr-par-1, fr-par-2, fr-par-3, nl-ams-1, nl-ams-2, nl-ams-3, pl-waw-1, pl-waw-2, pl-waw-3"), 57 | }, 58 | { 59 | input: "fr-par-n", 60 | expected: "", 61 | err: errors.New("bad zone format, available zones are: fr-par-1, fr-par-2, fr-par-3, nl-ams-1, nl-ams-2, nl-ams-3, pl-waw-1, pl-waw-2, pl-waw-3"), 62 | }, 63 | { 64 | input: "fr-par-0", 65 | expected: "", 66 | err: errors.New("bad zone format, available zones are: fr-par-1, fr-par-2, fr-par-3, nl-ams-1, nl-ams-2, nl-ams-3, pl-waw-1, pl-waw-2, pl-waw-3"), 67 | }, 68 | } 69 | 70 | for _, test := range tests { 71 | t.Run(test.input, func(t *testing.T) { 72 | z, err := ParseZone(test.input) 73 | testhelpers.Equals(t, test.err, err) 74 | testhelpers.Equals(t, test.expected, z) 75 | }) 76 | } 77 | } 78 | 79 | func TestZoneJSONUnmarshall(t *testing.T) { 80 | t.Run("test with zone", func(t *testing.T) { 81 | input := `{"Test": "par1"}` 82 | value := struct{ Test Zone }{} 83 | 84 | err := json.Unmarshal([]byte(input), &value) 85 | testhelpers.AssertNoError(t, err) 86 | 87 | testhelpers.Equals(t, ZoneFrPar1, value.Test) 88 | }) 89 | 90 | t.Run("test with region", func(t *testing.T) { 91 | input := `{"Test": "par1"}` 92 | value := struct{ Test Region }{} 93 | 94 | err := json.Unmarshal([]byte(input), &value) 95 | testhelpers.AssertNoError(t, err) 96 | 97 | testhelpers.Equals(t, RegionFrPar, value.Test) 98 | }) 99 | } 100 | 101 | func TestParseRegion(t *testing.T) { 102 | tests := []struct { 103 | input string 104 | err error 105 | expected Region 106 | }{ 107 | { 108 | input: "fr-par", 109 | expected: RegionFrPar, 110 | }, 111 | { 112 | input: "par1", 113 | expected: RegionFrPar, 114 | }, 115 | { 116 | input: "ams1", 117 | expected: RegionNlAms, 118 | }, 119 | { 120 | input: "pl-waw", 121 | expected: RegionPlWaw, 122 | }, 123 | { 124 | input: "xx-xxx", 125 | expected: "xx-xxx", 126 | }, 127 | { 128 | input: "fr-par-1", 129 | expected: "", 130 | err: errors.New("bad region format, available regions are: fr-par, nl-ams, pl-waw"), 131 | }, 132 | { 133 | input: "fr-pa1", 134 | expected: "", 135 | err: errors.New("bad region format, available regions are: fr-par, nl-ams, pl-waw"), 136 | }, 137 | } 138 | 139 | for _, test := range tests { 140 | t.Run(test.input, func(t *testing.T) { 141 | r, err := ParseRegion(test.input) 142 | testhelpers.Equals(t, test.err, err) 143 | testhelpers.Equals(t, test.expected, r) 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /scw/path.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | const ( 10 | // XDG wiki: https://wiki.archlinux.org/index.php/XDG_Base_Directory 11 | xdgConfigDirEnv = "XDG_CONFIG_HOME" 12 | xdgCacheDirEnv = "XDG_CACHE_HOME" 13 | 14 | unixHomeDirEnv = "HOME" 15 | windowsHomeDirEnv = "USERPROFILE" 16 | 17 | defaultConfigFileName = "config.yaml" 18 | ) 19 | 20 | // ErrNoHomeDir errors when no user directory is found 21 | var ErrNoHomeDir = errors.New("user home directory not found") 22 | 23 | // GetCacheDirectory returns the default cache directory. 24 | // Cache directory is based on the following priority order: 25 | // - $SCW_CACHE_DIR 26 | // - $XDG_CACHE_HOME/scw 27 | // - $HOME/.cache/scw 28 | // - $USERPROFILE/.cache/scw 29 | func GetCacheDirectory() string { 30 | cacheDir := "" 31 | switch { 32 | case os.Getenv(ScwCacheDirEnv) != "": 33 | cacheDir = os.Getenv(ScwCacheDirEnv) 34 | case os.Getenv(xdgCacheDirEnv) != "": 35 | cacheDir = filepath.Join(os.Getenv(xdgCacheDirEnv), "scw") 36 | case os.Getenv(unixHomeDirEnv) != "": 37 | cacheDir = filepath.Join(os.Getenv(unixHomeDirEnv), ".cache", "scw") 38 | case os.Getenv(windowsHomeDirEnv) != "": 39 | cacheDir = filepath.Join(os.Getenv(windowsHomeDirEnv), ".cache", "scw") 40 | default: 41 | // TODO: fallback on local folder? 42 | } 43 | 44 | // Clean the cache directory path when exiting the function 45 | return filepath.Clean(cacheDir) 46 | } 47 | 48 | // GetConfigPath returns the default path. 49 | // Default path is based on the following priority order: 50 | // - $SCW_CONFIG_PATH 51 | // - $XDG_CONFIG_HOME/scw/config.yaml 52 | // - $HOME/.config/scw/config.yaml 53 | // - $USERPROFILE/.config/scw/config.yaml 54 | func GetConfigPath() string { 55 | configPath := os.Getenv(ScwConfigPathEnv) 56 | if configPath == "" { 57 | configPath, _ = getConfigV2FilePath() 58 | } 59 | return filepath.Clean(configPath) 60 | } 61 | 62 | // getConfigV2FilePath returns the path to the v2 config file 63 | func getConfigV2FilePath() (string, bool) { 64 | configDir, err := GetScwConfigDir() 65 | if err != nil { 66 | return "", false 67 | } 68 | return filepath.Clean(filepath.Join(configDir, defaultConfigFileName)), true 69 | } 70 | 71 | // GetScwConfigDir returns the path to scw config folder 72 | func GetScwConfigDir() (string, error) { 73 | if xdgPath := os.Getenv(xdgConfigDirEnv); xdgPath != "" { 74 | return filepath.Join(xdgPath, "scw"), nil 75 | } 76 | 77 | homeDir, err := os.UserHomeDir() 78 | if err != nil { 79 | return "", err 80 | } 81 | return filepath.Join(homeDir, ".config", "scw"), nil 82 | } 83 | -------------------------------------------------------------------------------- /scw/request.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/scaleway/scaleway-sdk-go/errors" 12 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 13 | ) 14 | 15 | // ScalewayRequest contains all the contents related to performing a request on the Scaleway API. 16 | type ScalewayRequest struct { 17 | Method string 18 | Path string 19 | Headers http.Header 20 | Query url.Values 21 | Body io.Reader 22 | 23 | // request options 24 | ctx context.Context 25 | auth auth.Auth 26 | allPages bool 27 | zones []Zone 28 | regions []Region 29 | } 30 | 31 | // getURL constructs a URL based on the base url and the client. 32 | func (req *ScalewayRequest) getURL(baseURL string) (*url.URL, error) { 33 | url, err := url.Parse(baseURL + req.Path) 34 | if err != nil { 35 | return nil, errors.New("invalid url %s: %s", baseURL+req.Path, err) 36 | } 37 | url.RawQuery = req.Query.Encode() 38 | 39 | return url, nil 40 | } 41 | 42 | // SetBody json marshal the given body and write the json content type 43 | // to the request. It also catches when body is a file. 44 | func (req *ScalewayRequest) SetBody(body interface{}) error { 45 | var contentType string 46 | var content io.Reader 47 | 48 | switch b := body.(type) { 49 | case *File: 50 | contentType = b.ContentType 51 | content = b.Content 52 | case io.Reader: 53 | contentType = "text/plain" 54 | content = b 55 | default: 56 | buf, err := json.Marshal(body) 57 | if err != nil { 58 | return err 59 | } 60 | contentType = "application/json" 61 | content = bytes.NewReader(buf) 62 | } 63 | 64 | if req.Headers == nil { 65 | req.Headers = http.Header{} 66 | } 67 | 68 | req.Headers.Set("Content-Type", contentType) 69 | req.Body = content 70 | 71 | return nil 72 | } 73 | 74 | func (req *ScalewayRequest) apply(opts []RequestOption) { 75 | for _, opt := range opts { 76 | opt(req) 77 | } 78 | } 79 | 80 | func (req *ScalewayRequest) validate() error { 81 | // nothing so far 82 | return nil 83 | } 84 | 85 | func (req *ScalewayRequest) clone() *ScalewayRequest { 86 | clonedReq := &ScalewayRequest{ 87 | Method: req.Method, 88 | Path: req.Path, 89 | Headers: req.Headers.Clone(), 90 | ctx: req.ctx, 91 | auth: req.auth, 92 | allPages: req.allPages, 93 | zones: req.zones, 94 | } 95 | if req.Query != nil { 96 | clonedReq.Query = url.Values(http.Header(req.Query).Clone()) 97 | } 98 | return clonedReq 99 | } 100 | -------------------------------------------------------------------------------- /scw/request_header.go: -------------------------------------------------------------------------------- 1 | //go:build !wasm || !js 2 | 3 | package scw 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 9 | ) 10 | 11 | // getAllHeaders constructs a http.Header object and aggregates all headers into the object. 12 | func (req *ScalewayRequest) getAllHeaders(token auth.Auth, userAgent string, anonymized bool) http.Header { 13 | var allHeaders http.Header 14 | if anonymized { 15 | allHeaders = token.AnonymizedHeaders() 16 | } else { 17 | allHeaders = token.Headers() 18 | } 19 | 20 | allHeaders.Set("User-Agent", userAgent) 21 | if req.Body != nil { 22 | allHeaders.Set("Content-Type", "application/json") 23 | } 24 | for key, value := range req.Headers { 25 | allHeaders.Del(key) 26 | for _, v := range value { 27 | allHeaders.Add(key, v) 28 | } 29 | } 30 | 31 | return allHeaders 32 | } 33 | -------------------------------------------------------------------------------- /scw/request_header_wasm.go: -------------------------------------------------------------------------------- 1 | //go:build wasm && js 2 | 3 | package scw 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 9 | ) 10 | 11 | // getAllHeaders constructs a http.Header object and aggregates all headers into the object. 12 | func (req *ScalewayRequest) getAllHeaders(token auth.Auth, userAgent string, anonymized bool) http.Header { 13 | var allHeaders http.Header 14 | if anonymized { 15 | allHeaders = token.AnonymizedHeaders() 16 | } else { 17 | allHeaders = token.Headers() 18 | } 19 | 20 | allHeaders.Set("X-User-Agent", userAgent) 21 | if req.Body != nil { 22 | allHeaders.Set("Content-Type", "application/json") 23 | } 24 | for key, value := range req.Headers { 25 | allHeaders.Del(key) 26 | for _, v := range value { 27 | allHeaders.Add(key, v) 28 | } 29 | } 30 | 31 | return allHeaders 32 | } 33 | -------------------------------------------------------------------------------- /scw/request_option.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 7 | ) 8 | 9 | // RequestOption is a function that applies options to a ScalewayRequest. 10 | type RequestOption func(*ScalewayRequest) 11 | 12 | // WithContext request option sets the context of a ScalewayRequest 13 | func WithContext(ctx context.Context) RequestOption { 14 | return func(s *ScalewayRequest) { 15 | s.ctx = ctx 16 | } 17 | } 18 | 19 | // WithAllPages aggregate all pages in the response of a List request. 20 | // Will error when pagination is not supported on the request. 21 | func WithAllPages() RequestOption { 22 | return func(s *ScalewayRequest) { 23 | s.allPages = true 24 | } 25 | } 26 | 27 | // WithAuthRequest overwrites the client access key and secret key used in the request. 28 | func WithAuthRequest(accessKey, secretKey string) RequestOption { 29 | return func(s *ScalewayRequest) { 30 | s.auth = auth.NewToken(accessKey, secretKey) 31 | } 32 | } 33 | 34 | // WithZones aggregate results from requested zones in the response of a List request. 35 | // response rows are sorted by zone using order of given zones 36 | // Will error when pagination is not supported on the request. 37 | func WithZones(zones ...Zone) RequestOption { 38 | return func(s *ScalewayRequest) { 39 | s.zones = append(s.zones, zones...) 40 | } 41 | } 42 | 43 | // WithRegions aggregate results from requested regions in the response of a List request. 44 | // response rows are sorted by region using order of given regions 45 | // Will error when pagination is not supported on the request. 46 | func WithRegions(regions ...Region) RequestOption { 47 | return func(s *ScalewayRequest) { 48 | s.regions = append(s.regions, regions...) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scw/request_test.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "testing" 9 | "time" 10 | 11 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 12 | "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" 13 | ) 14 | 15 | const ( 16 | testBaseURL = "http://example.com" 17 | testPath = "/some/path/" 18 | testKey = "some_key" 19 | testValue = "some_value" 20 | testBody = "some body" 21 | testUserAgent = "user/agent" 22 | 23 | testHeaderKey = "Some-Header-Key" 24 | testHeaderVal = "some_header_val" 25 | testTokenKey = "some_secret_key" 26 | ) 27 | 28 | func TestGetURL(t *testing.T) { 29 | req := ScalewayRequest{ 30 | Path: testPath, 31 | Query: url.Values{ 32 | testKey: []string{testValue}, 33 | }, 34 | } 35 | 36 | newURL, err := req.getURL(testBaseURL) 37 | testhelpers.AssertNoError(t, err) 38 | 39 | expectedURL := fmt.Sprintf("%s%s?%s=%s", testBaseURL, testPath, testKey, testValue) 40 | 41 | testhelpers.Equals(t, expectedURL, newURL.String()) 42 | } 43 | 44 | func TestGetHeadersWithoutBody(t *testing.T) { 45 | req := ScalewayRequest{ 46 | Headers: http.Header{ 47 | testHeaderKey: []string{testHeaderVal}, 48 | }, 49 | } 50 | token := auth.NewToken(testAccessKey, testTokenKey) 51 | 52 | expectedHeaders := http.Header{ 53 | testHeaderKey: []string{testHeaderVal}, 54 | "X-Auth-Token": []string{testTokenKey}, 55 | "User-Agent": []string{testUserAgent}, 56 | } 57 | 58 | allHeaders := req.getAllHeaders(token, testUserAgent, false) 59 | 60 | testhelpers.Equals(t, expectedHeaders, allHeaders) 61 | } 62 | 63 | func TestGetHeadersWithBody(t *testing.T) { 64 | req := ScalewayRequest{ 65 | Headers: http.Header{ 66 | testHeaderKey: []string{testHeaderVal}, 67 | }, 68 | Body: bytes.NewReader([]byte(testBody)), 69 | } 70 | token := auth.NewToken(testSecretKey, testTokenKey) 71 | 72 | expectedHeaders := http.Header{ 73 | testHeaderKey: []string{testHeaderVal}, 74 | "X-Auth-Token": []string{testTokenKey}, 75 | "Content-Type": []string{"application/json"}, 76 | "User-Agent": []string{testUserAgent}, 77 | } 78 | 79 | allHeaders := req.getAllHeaders(token, testUserAgent, false) 80 | 81 | testhelpers.Equals(t, expectedHeaders, allHeaders) 82 | } 83 | 84 | func TestSetBody(t *testing.T) { 85 | body := struct { 86 | Region Region `json:"-"` 87 | ID string `json:"-"` 88 | Name string `json:"name,omitempty"` 89 | Slice []string `json:"slice,omitempty"` 90 | Flag bool `json:"flag,omitempty"` 91 | Timeout *time.Duration `json:"timeout,omitempty"` 92 | }{ 93 | Region: RegionNlAms, 94 | ID: "plop", 95 | Name: "plop", 96 | Slice: []string{"plop", "plop"}, 97 | Flag: true, 98 | Timeout: TimeDurationPtr(time.Second), 99 | } 100 | 101 | req := ScalewayRequest{ 102 | Headers: http.Header{}, 103 | } 104 | 105 | testhelpers.AssertNoError(t, req.SetBody(body)) 106 | 107 | r, isBytesReader := req.Body.(*bytes.Reader) 108 | 109 | testhelpers.Assert(t, isBytesReader, "req.Body should be bytes Reader") 110 | 111 | b := make([]byte, r.Len()) 112 | _, err := r.Read(b) 113 | testhelpers.AssertNoError(t, err) 114 | 115 | testhelpers.Equals(t, []string{"application/json"}, req.Headers["Content-Type"]) 116 | testhelpers.Equals(t, `{"name":"plop","slice":["plop","plop"],"flag":true,"timeout":1000000000}`, string(b)) 117 | } 118 | 119 | func TestSetFileBody(t *testing.T) { 120 | body := &File{ 121 | Content: bytes.NewReader([]byte(testBody)), 122 | ContentType: "plain/text", 123 | } 124 | 125 | req := ScalewayRequest{ 126 | Headers: http.Header{}, 127 | } 128 | 129 | testhelpers.AssertNoError(t, req.SetBody(body)) 130 | 131 | r, isBytesReader := req.Body.(*bytes.Reader) 132 | 133 | testhelpers.Assert(t, isBytesReader, "req.Body should be bytes Reader") 134 | 135 | b := make([]byte, r.Len()) 136 | _, err := r.Read(b) 137 | testhelpers.AssertNoError(t, err) 138 | 139 | testhelpers.Equals(t, []string{"plain/text"}, req.Headers["Content-Type"]) 140 | testhelpers.Equals(t, `some body`, string(b)) 141 | } 142 | -------------------------------------------------------------------------------- /scw/transport.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "math/rand" 5 | "net/http" 6 | "net/http/httputil" 7 | "sync/atomic" 8 | 9 | "github.com/scaleway/scaleway-sdk-go/internal/auth" 10 | "github.com/scaleway/scaleway-sdk-go/logger" 11 | ) 12 | 13 | type requestLoggerTransport struct { 14 | rt http.RoundTripper 15 | // requestNumber auto increments on each do(). 16 | // This allows easy distinguishing of concurrently performed requests in log. 17 | requestNumber uint32 18 | } 19 | 20 | func (l *requestLoggerTransport) RoundTrip(request *http.Request) (*http.Response, error) { 21 | currentRequestNumber := atomic.AddUint32(&l.requestNumber, 1) 22 | // Keep original headers (before anonymization) 23 | originalHeaders := request.Header 24 | 25 | // Get anonymized headers 26 | request.Header = auth.AnonymizeHeaders(request.Header.Clone()) 27 | 28 | // Add a pseudo random request identifier, this can be used to identify request and response in large logs 29 | requestIdentifier := rand.Uint32() 30 | 31 | dump, err := httputil.DumpRequestOut(request, true) 32 | if err != nil { 33 | logger.Warningf("cannot dump outgoing request: %s", err) 34 | } else { 35 | var logString string 36 | logString += "\n---------- Scaleway SDK REQUEST %d (%x) : ----------\n" 37 | logString += "%s\n" 38 | logString += "---------------------------------------------------------\n" 39 | 40 | logger.Debugf(logString, currentRequestNumber, requestIdentifier, dump) 41 | } 42 | 43 | // Restore original headers before sending the request 44 | request.Header = originalHeaders 45 | response, requestError := l.rt.RoundTrip(request) 46 | if requestError != nil { 47 | _, isSdkError := requestError.(SdkError) 48 | if !isSdkError { 49 | return response, requestError 50 | } 51 | } 52 | 53 | dump, err = httputil.DumpResponse(response, true) 54 | if err != nil { 55 | logger.Warningf("cannot dump ingoing response: %s", err) 56 | } else { 57 | var logString string 58 | logString += "\n---------- Scaleway SDK RESPONSE %d (%x) : ----------\n" 59 | logString += "%s\n" 60 | logString += "----------------------------------------------------------\n" 61 | 62 | logger.Debugf(logString, currentRequestNumber, requestIdentifier, dump) 63 | } 64 | 65 | return response, requestError 66 | } 67 | -------------------------------------------------------------------------------- /scw/version.go: -------------------------------------------------------------------------------- 1 | package scw 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "runtime/debug" 7 | ) 8 | 9 | // TODO: versioning process 10 | const ( 11 | defaultVersion = "v1.0.0-beta.7+dev" 12 | path = "github.com/scaleway/scaleway-sdk-go" 13 | ) 14 | 15 | var cachedVersion = (*string)(nil) 16 | 17 | func getVersion() string { 18 | if cachedVersion == nil { 19 | debugVersion := "" 20 | b, ok := debug.ReadBuildInfo() 21 | if ok { 22 | for _, dep := range b.Deps { 23 | if dep.Path == path { 24 | debugVersion = dep.Version 25 | } 26 | } 27 | } 28 | 29 | cachedVersion = &debugVersion 30 | } 31 | 32 | if *cachedVersion != "" { 33 | return *cachedVersion 34 | } 35 | 36 | return defaultVersion 37 | } 38 | 39 | var userAgent = fmt.Sprintf("scaleway-sdk-go/%s (%s; %s; %s)", getVersion(), runtime.Version(), runtime.GOOS, runtime.GOARCH) 40 | -------------------------------------------------------------------------------- /sdk_compilation_test.go: -------------------------------------------------------------------------------- 1 | package scalewaysdkgo 2 | 3 | // This test file makes sure that all the auto-generated code compiles. 4 | 5 | import ( 6 | _ "github.com/scaleway/scaleway-sdk-go/api/account/v3" 7 | _ "github.com/scaleway/scaleway-sdk-go/api/applesilicon/v1alpha1" 8 | _ "github.com/scaleway/scaleway-sdk-go/api/baremetal/v1" 9 | _ "github.com/scaleway/scaleway-sdk-go/api/billing/v2beta1" 10 | _ "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" 11 | _ "github.com/scaleway/scaleway-sdk-go/api/cockpit/v1" 12 | _ "github.com/scaleway/scaleway-sdk-go/api/container/v1beta1" 13 | _ "github.com/scaleway/scaleway-sdk-go/api/dedibox/v1" 14 | _ "github.com/scaleway/scaleway-sdk-go/api/documentdb/v1beta1" 15 | _ "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" 16 | _ "github.com/scaleway/scaleway-sdk-go/api/edge_services/v1beta1" 17 | _ "github.com/scaleway/scaleway-sdk-go/api/flexibleip/v1alpha1" 18 | _ "github.com/scaleway/scaleway-sdk-go/api/function/v1beta1" 19 | _ "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" 20 | _ "github.com/scaleway/scaleway-sdk-go/api/inference/v1beta1" 21 | _ "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 22 | _ "github.com/scaleway/scaleway-sdk-go/api/iot/v1" 23 | _ "github.com/scaleway/scaleway-sdk-go/api/ipam/v1" 24 | _ "github.com/scaleway/scaleway-sdk-go/api/ipam/v1alpha1" 25 | _ "github.com/scaleway/scaleway-sdk-go/api/k8s/v1" 26 | _ "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1" 27 | _ "github.com/scaleway/scaleway-sdk-go/api/lb/v1" 28 | _ "github.com/scaleway/scaleway-sdk-go/api/marketplace/v2" 29 | _ "github.com/scaleway/scaleway-sdk-go/api/mnq/v1beta1" 30 | _ "github.com/scaleway/scaleway-sdk-go/api/mongodb/v1alpha1" 31 | _ "github.com/scaleway/scaleway-sdk-go/api/qaas/v1alpha1" 32 | _ "github.com/scaleway/scaleway-sdk-go/api/rdb/v1" 33 | _ "github.com/scaleway/scaleway-sdk-go/api/redis/v1" 34 | _ "github.com/scaleway/scaleway-sdk-go/api/registry/v1" 35 | _ "github.com/scaleway/scaleway-sdk-go/api/secret/v1beta1" 36 | _ "github.com/scaleway/scaleway-sdk-go/api/serverless_sqldb/v1alpha1" 37 | _ "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1" 38 | _ "github.com/scaleway/scaleway-sdk-go/api/test/v1" 39 | _ "github.com/scaleway/scaleway-sdk-go/api/vpc/v2" 40 | _ "github.com/scaleway/scaleway-sdk-go/api/vpcgw/v1" 41 | _ "github.com/scaleway/scaleway-sdk-go/api/webhosting/v1" 42 | ) 43 | -------------------------------------------------------------------------------- /strcase/bash_arg.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "strings" 5 | 6 | "golang.org/x/text/cases" 7 | "golang.org/x/text/language" 8 | ) 9 | 10 | var customBashNames = map[string]string{ 11 | "aclid": "acl-id", 12 | "ipid": "ip-id", 13 | "lbid": "lb-id", 14 | "dhcpid": "dhcp-id", 15 | } 16 | 17 | // ToBashArg returns the Bash public name of the given string. 18 | func ToBashArg(s string) string { 19 | s = ToPublicGoName(s) 20 | if customBashName, exists := customBashNames[strings.ToLower(s)]; exists { 21 | return customBashName 22 | } 23 | 24 | caser := cases.Title(language.English) 25 | for _, initialism := range customInitialisms { 26 | // catch this kind of pattern: ExampleIDs ==> ExampleIds ==> example-ids 27 | s = strings.ReplaceAll(s, initialism[0], caser.String(strings.ToLower(initialism[0]))) 28 | } 29 | return toKebab(s) 30 | } 31 | 32 | // toKebab converts a string to kebab-case. 33 | func toKebab(s string) string { 34 | return toDelimited(s, '-') 35 | } 36 | 37 | // toDelimited converts a string to delimited lowercase. 38 | func toDelimited(s string, del uint8) string { 39 | s = strings.Trim(s, " ") 40 | n := "" 41 | for i, v := range s { 42 | // treat acronyms as words, eg for JSONData -> JSON is a whole word 43 | nextCaseIsChanged := false 44 | if i+1 < len(s) { 45 | next := s[i+1] 46 | if (isUpperLetter(v) && isLowerLetter(int32(next))) || (isLowerLetter(v) && isUpperLetter(int32(next))) { 47 | nextCaseIsChanged = true 48 | } 49 | } 50 | 51 | switch { 52 | case i > 0 && n[len(n)-1] != del && nextCaseIsChanged: 53 | if isUpperLetter(v) { 54 | n += string(del) + string(v) 55 | } else if isLowerLetter(v) { 56 | n += string(v) + string(del) 57 | } 58 | case v == ' ' || v == '-' || v == '_': 59 | n += string(del) 60 | default: 61 | n += string(v) 62 | } 63 | } 64 | n = strings.ToLower(n) 65 | return n 66 | } 67 | 68 | func isUpperLetter(c int32) bool { 69 | return c >= 'A' && c <= 'Z' 70 | } 71 | 72 | func isLowerLetter(c int32) bool { 73 | return c >= 'a' && c <= 'z' 74 | } 75 | -------------------------------------------------------------------------------- /strcase/bash_arg_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToKebabCase(t *testing.T) { 8 | cases := []struct { 9 | in string 10 | want string 11 | }{ 12 | {"testCase", "test-case"}, 13 | {"TestCase", "test-case"}, 14 | {"Test Case", "test-case"}, 15 | {" Test Case", "test-case"}, 16 | {"Test Case ", "test-case"}, 17 | {" Test Case ", "test-case"}, 18 | {"test", "test"}, 19 | {"test_case", "test-case"}, 20 | {"Test", "test"}, 21 | {"", ""}, 22 | {"ManyManyWords", "many-many-words"}, 23 | {"manyManyWords", "many-many-words"}, 24 | {"AnyKind of_string", "any-kind-of-string"}, 25 | {"numbers2and55with000", "numbers2and55with000"}, 26 | {"JSONData", "json-data"}, 27 | {"userID", "user-id"}, 28 | {"AAAbbb", "aa-abbb"}, 29 | } 30 | for _, c := range cases { 31 | t.Run(c.in, func(t *testing.T) { 32 | got := toKebab(c.in) 33 | if got != c.want { 34 | t.Errorf("toKebab(%q) == %q, want %q", c.in, got, c.want) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /strcase/camel.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import "strings" 4 | 5 | func ToCamel(s string) string { 6 | if s == "" { 7 | return s 8 | } 9 | if r := rune(s[0]); r >= 'A' && r <= 'Z' { 10 | s = strings.ToLower(string(r)) + s[1:] 11 | } 12 | return toPascalInitCase(s, false) 13 | } 14 | -------------------------------------------------------------------------------- /strcase/camel_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToCamel(t *testing.T) { 8 | cases := [][]string{ 9 | {"foo-bar", "fooBar"}, 10 | {"TestCase", "testCase"}, 11 | {"", ""}, 12 | {"AnyKind of_string", "anyKindOfString"}, 13 | } 14 | for _, i := range cases { 15 | in := i[0] 16 | out := i[1] 17 | result := ToCamel(in) 18 | if result != out { 19 | t.Error("'" + result + "' != '" + out + "'") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /strcase/goname_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import "testing" 4 | 5 | func TestLowerCaseFirstLetterOrAcronyms(t *testing.T) { 6 | cases := []struct { 7 | in string 8 | want string 9 | }{ 10 | {"", ""}, 11 | {"t", "t"}, 12 | {"Test Case", "test Case"}, 13 | {"test Case", "test Case"}, 14 | {"TEST CASE", "tEST CASE"}, 15 | {"tEST CASE", "tEST CASE"}, 16 | {"#EST CASE", "#EST CASE"}, 17 | {"APITest", "apiTest"}, 18 | {"AVATATest", "aVATATest"}, 19 | {"TestStuff", "testStuff"}, 20 | } 21 | for _, c := range cases { 22 | result := lowerCaseFirstLetterOrAcronyms(c.in) 23 | if result != c.want { 24 | t.Errorf("lowerCaseFirstLetterOrAcronyms(%q) == %q, want %q", c.in, result, c.want) 25 | } 26 | } 27 | } 28 | 29 | func TestTitleFirstWord(t *testing.T) { 30 | cases := [][]string{ 31 | {"", ""}, 32 | {"t", "T"}, 33 | {"Test Case", "Test Case"}, 34 | {"test Case", "Test Case"}, 35 | {"test case", "Test case"}, 36 | {"TEST CASE", "TEST CASE"}, 37 | {"tEST CASE", "TEST CASE"}, 38 | {"#EST CASE", "#EST CASE"}, 39 | } 40 | for _, i := range cases { 41 | in := i[0] 42 | out := i[1] 43 | result := TitleFirstWord(in) 44 | if result != out { 45 | t.Error("'" + result + "' != '" + out + "'") 46 | } 47 | } 48 | } 49 | 50 | func Test_UntitleFirstWord(t *testing.T) { 51 | cases := [][]string{ 52 | {"", ""}, 53 | {"T", "t"}, 54 | {"UUID", "UUID"}, 55 | {"UUI", "uUI"}, 56 | {"TEST CASE", "tEST CASE"}, 57 | } 58 | for _, i := range cases { 59 | in := i[0] 60 | out := i[1] 61 | result := UntitleFirstWord(in) 62 | if result != out { 63 | t.Error("'" + result + "' != '" + out + "'") 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /strcase/kebab.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import "strings" 4 | 5 | // Converts a string to kebab-case 6 | func ToKebab(s string) string { 7 | return strings.ReplaceAll(ToSnake(s), "_", "-") 8 | } 9 | 10 | // Converts a string to kebab-case 11 | func ToSpace(s string) string { 12 | return strings.ReplaceAll(ToSnake(s), "_", " ") 13 | } 14 | -------------------------------------------------------------------------------- /strcase/kebab_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToKebab(t *testing.T) { 8 | cases := [][]string{ 9 | {"testCase", "test-case"}, 10 | {"TestCase", "test-case"}, 11 | {"Test Case", "test-case"}, 12 | {" Test Case", "test-case"}, 13 | {"Test Case ", "test-case"}, 14 | {" Test Case ", "test-case"}, 15 | {"test", "test"}, 16 | {"test_case", "test-case"}, 17 | {"Test", "test"}, 18 | {"", ""}, 19 | {"ManyManyWords", "many-many-words"}, 20 | {"manyManyWords", "many-many-words"}, 21 | {"AnyKind of_string", "any-kind-of-string"}, 22 | {"numbers2and55with000", "numbers2and55with000"}, 23 | {"JSONData", "json-data"}, 24 | {"userID", "user-id"}, 25 | {"AAAbbb", "aa-abbb"}, 26 | } 27 | for _, i := range cases { 28 | in := i[0] 29 | out := i[1] 30 | result := ToKebab(in) 31 | if result != out { 32 | t.Error("'" + result + "' != '" + out + "'") 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /strcase/number.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var ( 8 | numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`) 9 | numberReplacement = []byte(`$1 $2 $3`) 10 | ) 11 | 12 | func addWordBoundariesToNumbers(s string) string { 13 | b := []byte(s) 14 | b = numberSequence.ReplaceAll(b, numberReplacement) 15 | return string(b) 16 | } 17 | -------------------------------------------------------------------------------- /strcase/pascal.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import "strings" 4 | 5 | // Converts a string to CamelCase 6 | func toPascalInitCase(s string, initCase bool) string { 7 | s = addWordBoundariesToNumbers(s) 8 | s = strings.Trim(s, " ") 9 | n := "" 10 | capNext := initCase 11 | for _, v := range s { 12 | if v >= 'A' && v <= 'Z' { 13 | n += string(v) 14 | } 15 | if v >= '0' && v <= '9' { 16 | n += string(v) 17 | } 18 | if v >= 'a' && v <= 'z' { 19 | if capNext { 20 | n += strings.ToUpper(string(v)) 21 | } else { 22 | n += string(v) 23 | } 24 | } 25 | if v == '_' || v == ' ' || v == '-' { 26 | capNext = true 27 | } else { 28 | capNext = false 29 | } 30 | } 31 | return n 32 | } 33 | 34 | func ToPascal(s string) string { 35 | return toPascalInitCase(s, true) 36 | } 37 | -------------------------------------------------------------------------------- /strcase/pascal_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import "testing" 4 | 5 | func TestToPascal(t *testing.T) { 6 | cases := [][]string{ 7 | {"test_case", "TestCase"}, 8 | {"test", "Test"}, 9 | {"TestCase", "TestCase"}, 10 | {" test case ", "TestCase"}, 11 | {"", ""}, 12 | {"many_many_words", "ManyManyWords"}, 13 | {"AnyKind of_string", "AnyKindOfString"}, 14 | {"odd-fix", "OddFix"}, 15 | {"numbers2And55with000", "Numbers2And55With000"}, 16 | } 17 | for _, i := range cases { 18 | in := i[0] 19 | out := i[1] 20 | result := ToPascal(in) 21 | if result != out { 22 | t.Error("'" + result + "' != '" + out + "'") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /strcase/snake.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Converts a string to snake_case 8 | func ToSnake(s string) string { 9 | s = strings.Trim(s, " ") 10 | n := "" 11 | for i, v := range s { 12 | // treat acronyms as words, eg for JSONData -> JSON is a whole word 13 | nextCaseIsChanged := false 14 | if i+1 < len(s) { 15 | next := s[i+1] 16 | if (isUpperLetter(v) && isLowerLetter(int32(next))) || (isLowerLetter(v) && isUpperLetter(int32(next))) { 17 | nextCaseIsChanged = true 18 | } 19 | } 20 | 21 | switch { 22 | case i > 0 && n[len(n)-1] != '_' && nextCaseIsChanged: 23 | if isUpperLetter(v) { 24 | n += "_" + string(v) 25 | } else if isLowerLetter(v) { 26 | n += string(v) + "_" 27 | } 28 | case v == ' ' || v == '-': 29 | n += "_" 30 | default: 31 | n += string(v) 32 | } 33 | } 34 | n = strings.ToLower(n) 35 | return n 36 | } 37 | -------------------------------------------------------------------------------- /strcase/snake_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToSnake(t *testing.T) { 8 | cases := [][2]string{ 9 | {"testCase", "test_case"}, 10 | {"TestCase", "test_case"}, 11 | {"Test Case", "test_case"}, 12 | {" Test Case", "test_case"}, 13 | {"Test Case ", "test_case"}, 14 | {" Test Case ", "test_case"}, 15 | {"test", "test"}, 16 | {"test_case", "test_case"}, 17 | {"Test", "test"}, 18 | {"", ""}, 19 | {"ManyManyWords", "many_many_words"}, 20 | {"manyManyWords", "many_many_words"}, 21 | {"AnyKind of_string", "any_kind_of_string"}, 22 | {"numbers2and55with000", "numbers2and55with000"}, 23 | {"ip-v6", "ip_v6"}, 24 | {"ipV6", "ip_v6"}, 25 | {"IPV6", "ipv6"}, 26 | {"ipv6", "ipv6"}, 27 | {"JSONData", "json_data"}, 28 | {"userID", "user_id"}, 29 | {"AAAbbb", "aa_abbb"}, 30 | } 31 | for _, i := range cases { 32 | in := i[0] 33 | out := i[1] 34 | result := ToSnake(in) 35 | if result != out { 36 | t.Error("'" + result + "' != '" + out + "'") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /strcase/strcase_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAllStrCases(t *testing.T) { 8 | tests := []struct { 9 | name, publicGoName, privateGoName, bashArgName string 10 | }{ 11 | {"foo_bar", "FooBar", "fooBar", "foo-bar"}, 12 | {"foo_bar_baz", "FooBarBaz", "fooBarBaz", "foo-bar-baz"}, 13 | {"Foo_bar", "FooBar", "fooBar", "foo-bar"}, 14 | {"foo_WiFi", "FooWiFi", "fooWiFi", "foo-wi-fi"}, 15 | {"id", "ID", "id", "id"}, 16 | {"Id", "ID", "id", "id"}, 17 | {"foo_id", "FooID", "fooID", "foo-id"}, 18 | {"fooId", "FooID", "fooID", "foo-id"}, 19 | {"fooUid", "FooUID", "fooUID", "foo-uid"}, 20 | {"idFoo", "IDFoo", "idFoo", "id-foo"}, 21 | {"uidFoo", "UIDFoo", "uidFoo", "uid-foo"}, 22 | {"midIdDle", "MidIDDle", "midIDDle", "mid-id-dle"}, 23 | {"APIProxy", "APIProxy", "apiProxy", "api-proxy"}, 24 | {"ApiProxy", "APIProxy", "apiProxy", "api-proxy"}, 25 | {"apiProxy", "APIProxy", "apiProxy", "api-proxy"}, 26 | {"_Leading", "_Leading", "_Leading", "-leading"}, 27 | {"___Leading", "_Leading", "_Leading", "-leading"}, 28 | {"trailing_", "Trailing", "trailing", "trailing"}, 29 | {"trailing___", "Trailing", "trailing", "trailing"}, 30 | {"a_b", "AB", "aB", "ab"}, 31 | {"a__b", "AB", "aB", "ab"}, 32 | {"a___b", "AB", "aB", "ab"}, 33 | {"Rpc1150", "RPC1150", "rpc1150", "rpc1150"}, 34 | {"case3_1", "Case3_1", "case3_1", "case3-1"}, 35 | {"case3__1", "Case3_1", "case3_1", "case3-1"}, 36 | {"IEEE802_16bit", "IEEE802_16bit", "iEEE802_16bit", "ieee802-16bit"}, 37 | {"IEEE802_16Bit", "IEEE802_16Bit", "iEEE802_16Bit", "ieee802-16-bit"}, 38 | {"IPv4", "IPv4", "ipv4", "ipv4"}, 39 | {"Ipv4", "IPv4", "ipv4", "ipv4"}, 40 | {"iPV4", "IPV4", "iPV4", "ipv4"}, 41 | {"RepeatedIpv4", "RepeatedIPv4", "repeatedIPv4", "repeated-ipv4"}, 42 | {"eSport", "ESport", "eSport", "e-sport"}, 43 | {"stopped in place", "StoppedInPlace", "stoppedInPlace", "stopped-in-place"}, 44 | {"l_ssd", "LSSD", "lSSD", "lssd"}, 45 | {"ids", "IDs", "ids", "ids"}, 46 | {"my_resource_ids", "MyResourceIDs", "myResourceIDs", "my-resource-ids"}, 47 | {"acids", "Acids", "acids", "acids"}, 48 | {"secret-key", "SecretKey", "secretKey", "secret-key"}, 49 | {"ip-id", "IPID", "ipID", "ip-id"}, 50 | {"lb-id", "LBID", "lbID", "lb-id"}, 51 | {"acl-id", "ACLID", "aclID", "acl-id"}, 52 | {"dhcp-id", "DHCPID", "dhcpID", "dhcp-id"}, 53 | } 54 | for _, test := range tests { 55 | got := ToPublicGoName(test.name) 56 | if got != test.publicGoName { 57 | t.Errorf("ToPublicGoName(%q) == %q, want %q", test.name, got, test.publicGoName) 58 | } 59 | got = ToPrivateGoName(test.name) 60 | if got != test.privateGoName { 61 | t.Errorf("ToPrivateGoName(%q) == %q, want %q", test.name, got, test.privateGoName) 62 | } 63 | got = ToBashArg(test.name) 64 | if got != test.bashArgName { 65 | t.Errorf("ToBashArg(%q) == %q, want %q", test.name, got, test.bashArgName) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /validation/is.go: -------------------------------------------------------------------------------- 1 | // Package validation provides format validation functions. 2 | package validation 3 | 4 | import ( 5 | "net/url" 6 | "regexp" 7 | ) 8 | 9 | var ( 10 | isUUIDRegexp = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") 11 | isRegionRegex = regexp.MustCompile("^[a-z]{2}-[a-z]{3}$") 12 | isZoneRegex = regexp.MustCompile("^[a-z]{2}-[a-z]{3}-[1-9]$") 13 | isAccessKey = regexp.MustCompile("^SCW[A-Z0-9]{17}$") 14 | isEmailRegexp = regexp.MustCompile("^.+@.+$") 15 | ) 16 | 17 | // IsUUID returns true if the given string has a valid UUID format. 18 | func IsUUID(s string) bool { 19 | return isUUIDRegexp.MatchString(s) 20 | } 21 | 22 | // IsAccessKey returns true if the given string has a valid Scaleway access key format. 23 | func IsAccessKey(s string) bool { 24 | return isAccessKey.MatchString(s) 25 | } 26 | 27 | // IsSecretKey returns true if the given string has a valid Scaleway secret key format. 28 | func IsSecretKey(s string) bool { 29 | return IsUUID(s) 30 | } 31 | 32 | // IsOrganizationID returns true if the given string has a valid Scaleway organization ID format. 33 | func IsOrganizationID(s string) bool { 34 | return IsUUID(s) 35 | } 36 | 37 | // IsProjectID returns true if the given string has a valid Scaleway project ID format. 38 | func IsProjectID(s string) bool { 39 | return IsUUID(s) 40 | } 41 | 42 | // IsRegion returns true if the given string has a valid region format. 43 | func IsRegion(s string) bool { 44 | return isRegionRegex.MatchString(s) 45 | } 46 | 47 | // IsZone returns true if the given string has a valid zone format. 48 | func IsZone(s string) bool { 49 | return isZoneRegex.MatchString(s) 50 | } 51 | 52 | // IsURL returns true if the given string has a valid URL format. 53 | func IsURL(s string) bool { 54 | if s == "" { 55 | return false 56 | } 57 | 58 | _, err := url.Parse(s) 59 | return err == nil 60 | } 61 | 62 | // IsEmail returns true if the given string has an email format. 63 | func IsEmail(v string) bool { 64 | return isEmailRegexp.MatchString(v) 65 | } 66 | --------------------------------------------------------------------------------