├── .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 |
5 |
6 |
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 |
--------------------------------------------------------------------------------