From fcd0d22b0d70ff6b4c61174dcc0c1a4aea72b329 Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Sun, 16 Aug 2026 20:27:48 +1000 Subject: [PATCH] Initial BinaryLane v2 API client --- .editorconfig | 30 + .gitattributes | 16 + .github/PULL_REQUEST_TEMPLATE.md | 12 + .github/dependabot.yml | 23 + .github/workflows/ci.yml | 91 + .github/workflows/codeql.yml | 56 + .github/workflows/contract-monitor.yml | 92 + .github/workflows/publish.yml | 186 + .gitignore | 31 + CHANGELOG.md | 28 + CONTRIBUTING.md | 71 + Directory.Build.props | 13 + Directory.Packages.props | 25 + LICENSE | 21 + NuGet.config | 9 + README.md | 94 + SECURITY.md | 25 + assets/package-icon.svg | 10 + docs/actions-and-polling.md | 48 + docs/api-coverage.md | 33 + docs/configuration.md | 72 + docs/errors.md | 40 + docs/getting-started.md | 53 + docs/pagination.md | 31 + docs/releasing.md | 63 + docs/upstream-contract.md | 48 + eng/assert-release-version.sh | 40 + eng/extract-release-notes.sh | 39 + eng/normalize-openapi.sh | 54 + eng/openapi/README.md | 21 + eng/openapi/binarylane-v2.openapi.yaml | 9470 +++++++++++++++++ eng/openapi/contract.json | 6 + eng/openapi/normalization.json | 64 + eng/pack.sh | 14 + eng/read-package-version.sh | 28 + eng/refresh-openapi-contract.sh | 57 + eng/test-demo-package.sh | 65 + eng/verify-openapi-contract.sh | 66 + .../BinaryLane.Api.Demo.csproj | 19 + examples/BinaryLane.Api.Demo/Program.cs | 167 + examples/BinaryLane.Api.Demo/README.md | 28 + examples/BinaryLane.Api.Demo/appsettings.json | 5 + .../BinaryLane.Api.Demo/packages.lock.json | 320 + global.json | 7 + .../IBinaryLaneTokenProvider.cs | 11 + .../OptionsBinaryLaneTokenProvider.cs | 30 + .../StaticBinaryLaneTokenProvider.cs | 26 + src/BinaryLane.Api/BinaryLane.Api.csproj | 48 + src/BinaryLane.Api/BinaryLaneClient.cs | 117 + .../Configuration/BinaryLaneOptions.cs | 36 + .../BinaryLaneOptionsValidator.cs | 24 + .../ServiceCollectionExtensions.cs | 81 + .../Errors/BinaryLaneApiException.cs | 43 + .../Errors/BinaryLaneApiProblem.cs | 17 + .../Errors/BinaryLaneForbiddenException.cs | 14 + .../Errors/BinaryLaneNotFoundException.cs | 14 + .../Errors/BinaryLaneUnauthorizedException.cs | 14 + .../Errors/BinaryLaneValidationException.cs | 14 + .../Http/BinaryLaneHttpExecutor.cs | 492 + .../Http/BinaryLaneJsonSerializerOptions.cs | 24 + src/BinaryLane.Api/Http/BinaryLaneRequest.cs | 44 + src/BinaryLane.Api/Http/BinaryLaneResponse.cs | 34 + .../Http/IBinaryLaneApiExecutor.cs | 57 + src/BinaryLane.Api/IBinaryLaneClient.cs | 59 + src/BinaryLane.Api/Models/BinaryLaneDto.cs | 31 + src/BinaryLane.Api/Models/BinaryLaneValues.cs | 275 + src/BinaryLane.Api/Models/CoreModels.cs | 615 ++ src/BinaryLane.Api/Models/IsExternalInit.cs | 9 + src/BinaryLane.Api/Models/RequestModels.cs | 390 + src/BinaryLane.Api/Models/ResourceModels.cs | 653 ++ src/BinaryLane.Api/Models/ResponseModels.cs | 472 + .../Models/ServerActionJsonConverter.cs | 209 + .../Models/ServerActionModels.cs | 521 + src/BinaryLane.Api/Pagination/Page.cs | 44 + src/BinaryLane.Api/Pagination/PageRequest.cs | 33 + .../Resources/ActionSubmission.cs | 28 + .../Resources/ActionWaitOptions.cs | 26 + .../Resources/BinaryLaneResourceBase.cs | 293 + .../Resources/ResourceClients.Part1.cs | 337 + .../Resources/ResourceClients.Part2.cs | 286 + .../Resources/ResourceInterfaces.cs | 186 + src/BinaryLane.Api/icon.png | Bin 0 -> 12048 bytes src/BinaryLane.Api/packages.lock.json | 348 + tests/BinaryLane.Api.Tests/AssemblyInfo.cs | 3 + .../BinaryLane.Api.Tests.csproj | 19 + .../BinaryLaneHttpExecutorTests.cs | 375 + .../ClientResourceTests.cs | 157 + .../DependencyInjectionTests.cs | 85 + .../HttpClientTestHandler.cs | 17 + .../ModelSerializationTests.cs | 149 + tests/BinaryLane.Api.Tests/packages.lock.json | 240 + 91 files changed, 18691 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/contract-monitor.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 LICENSE create mode 100644 NuGet.config create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 assets/package-icon.svg create mode 100644 docs/actions-and-polling.md create mode 100644 docs/api-coverage.md create mode 100644 docs/configuration.md create mode 100644 docs/errors.md create mode 100644 docs/getting-started.md create mode 100644 docs/pagination.md create mode 100644 docs/releasing.md create mode 100644 docs/upstream-contract.md create mode 100755 eng/assert-release-version.sh create mode 100755 eng/extract-release-notes.sh create mode 100755 eng/normalize-openapi.sh create mode 100644 eng/openapi/README.md create mode 100644 eng/openapi/binarylane-v2.openapi.yaml create mode 100644 eng/openapi/contract.json create mode 100644 eng/openapi/normalization.json create mode 100755 eng/pack.sh create mode 100755 eng/read-package-version.sh create mode 100755 eng/refresh-openapi-contract.sh create mode 100755 eng/test-demo-package.sh create mode 100755 eng/verify-openapi-contract.sh create mode 100644 examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj create mode 100644 examples/BinaryLane.Api.Demo/Program.cs create mode 100644 examples/BinaryLane.Api.Demo/README.md create mode 100644 examples/BinaryLane.Api.Demo/appsettings.json create mode 100644 examples/BinaryLane.Api.Demo/packages.lock.json create mode 100644 global.json create mode 100644 src/BinaryLane.Api/Authentication/IBinaryLaneTokenProvider.cs create mode 100644 src/BinaryLane.Api/Authentication/OptionsBinaryLaneTokenProvider.cs create mode 100644 src/BinaryLane.Api/Authentication/StaticBinaryLaneTokenProvider.cs create mode 100644 src/BinaryLane.Api/BinaryLane.Api.csproj create mode 100644 src/BinaryLane.Api/BinaryLaneClient.cs create mode 100644 src/BinaryLane.Api/Configuration/BinaryLaneOptions.cs create mode 100644 src/BinaryLane.Api/Configuration/BinaryLaneOptionsValidator.cs create mode 100644 src/BinaryLane.Api/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneApiException.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneApiProblem.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneForbiddenException.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneNotFoundException.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneUnauthorizedException.cs create mode 100644 src/BinaryLane.Api/Errors/BinaryLaneValidationException.cs create mode 100644 src/BinaryLane.Api/Http/BinaryLaneHttpExecutor.cs create mode 100644 src/BinaryLane.Api/Http/BinaryLaneJsonSerializerOptions.cs create mode 100644 src/BinaryLane.Api/Http/BinaryLaneRequest.cs create mode 100644 src/BinaryLane.Api/Http/BinaryLaneResponse.cs create mode 100644 src/BinaryLane.Api/Http/IBinaryLaneApiExecutor.cs create mode 100644 src/BinaryLane.Api/IBinaryLaneClient.cs create mode 100644 src/BinaryLane.Api/Models/BinaryLaneDto.cs create mode 100644 src/BinaryLane.Api/Models/BinaryLaneValues.cs create mode 100644 src/BinaryLane.Api/Models/CoreModels.cs create mode 100644 src/BinaryLane.Api/Models/IsExternalInit.cs create mode 100644 src/BinaryLane.Api/Models/RequestModels.cs create mode 100644 src/BinaryLane.Api/Models/ResourceModels.cs create mode 100644 src/BinaryLane.Api/Models/ResponseModels.cs create mode 100644 src/BinaryLane.Api/Models/ServerActionJsonConverter.cs create mode 100644 src/BinaryLane.Api/Models/ServerActionModels.cs create mode 100644 src/BinaryLane.Api/Pagination/Page.cs create mode 100644 src/BinaryLane.Api/Pagination/PageRequest.cs create mode 100644 src/BinaryLane.Api/Resources/ActionSubmission.cs create mode 100644 src/BinaryLane.Api/Resources/ActionWaitOptions.cs create mode 100644 src/BinaryLane.Api/Resources/BinaryLaneResourceBase.cs create mode 100644 src/BinaryLane.Api/Resources/ResourceClients.Part1.cs create mode 100644 src/BinaryLane.Api/Resources/ResourceClients.Part2.cs create mode 100644 src/BinaryLane.Api/Resources/ResourceInterfaces.cs create mode 100644 src/BinaryLane.Api/icon.png create mode 100644 src/BinaryLane.Api/packages.lock.json create mode 100644 tests/BinaryLane.Api.Tests/AssemblyInfo.cs create mode 100644 tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj create mode 100644 tests/BinaryLane.Api.Tests/BinaryLaneHttpExecutorTests.cs create mode 100644 tests/BinaryLane.Api.Tests/ClientResourceTests.cs create mode 100644 tests/BinaryLane.Api.Tests/DependencyInjectionTests.cs create mode 100644 tests/BinaryLane.Api.Tests/HttpClientTestHandler.cs create mode 100644 tests/BinaryLane.Api.Tests/ModelSerializationTests.cs create mode 100644 tests/BinaryLane.Api.Tests/packages.lock.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..508acf7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{cs,csx}] +indent_size = 4 +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:suggestion +csharp_style_var_elsewhere = false:suggestion +csharp_prefer_braces = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion + +[*.{yml,yaml,json}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..af2ba85 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +* text=auto eol=lf + +*.cs text diff=csharp +*.csproj text +*.props text +*.targets text +*.sln text +*.yml text +*.yaml text +*.json text +*.md text +*.sh text eol=lf + +*.png binary +*.nupkg binary +*.snupkg binary diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b41da2a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +## Summary + + + +## Validation + +- [ ] Unit tests added or updated +- [ ] Demo builds locally +- [ ] Documentation and XML comments updated where needed +- [ ] API coverage and OpenAPI snapshot reviewed where applicable +- [ ] Changelog updated for a user-visible change +- [ ] No credentials, personal data, or sensitive infrastructure details added diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..32f830c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: nuget + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + groups: + dotnet: + patterns: + - "*" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..264cf53 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + NUGET_XMLDOC_MODE: skip + +jobs: + build-test-pack: + name: Build, test, and pack + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out source + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Set up .NET + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 + with: + dotnet-version: 8.0.x + global-json-file: global.json + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Restore locked dependencies + run: | + dotnet restore src/BinaryLane.Api/BinaryLane.Api.csproj --locked-mode + dotnet restore tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --locked-mode + dotnet restore examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --locked-mode + + - name: Build + run: | + dotnet build src/BinaryLane.Api/BinaryLane.Api.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + dotnet build tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + dotnet build examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + + - name: Test + run: >- + dotnet test tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --configuration Release --no-build + -m:1 + --logger "trx;LogFileName=test-results.trx" + --results-directory TestResults + + - name: Verify formatting + run: | + dotnet format src/BinaryLane.Api/BinaryLane.Api.csproj --verify-no-changes --no-restore + dotnet format tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --verify-no-changes --no-restore + dotnet format examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --verify-no-changes --no-restore + + - name: Pack + run: >- + dotnet pack src/BinaryLane.Api/BinaryLane.Api.csproj + --configuration Release --no-build --output artifacts + -p:BuildInParallel=false + + - name: Compile the demo against the packed package + run: ./eng/test-demo-package.sh artifacts + + - name: Upload NuGet packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: nuget-packages + path: artifacts/ + if-no-files-found: error + retention-days: 14 + + - name: Upload test results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: test-results + path: TestResults/ + if-no-files-found: ignore + retention-days: 14 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..a1d221a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,56 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: '31 4 * * 4' + workflow_dispatch: + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze C# + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out source + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Set up .NET + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 + with: + dotnet-version: 8.0.x + global-json-file: global.json + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Initialize CodeQL + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 + with: + languages: csharp + build-mode: manual + + - name: Restore locked dependencies + run: | + dotnet restore src/BinaryLane.Api/BinaryLane.Api.csproj --locked-mode + dotnet restore tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --locked-mode + dotnet restore examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --locked-mode + + - name: Build for analysis + run: | + dotnet build src/BinaryLane.Api/BinaryLane.Api.csproj --configuration Release --no-restore -p:BuildInParallel=false + dotnet build tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --configuration Release --no-restore -p:BuildInParallel=false + dotnet build examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --configuration Release --no-restore -p:BuildInParallel=false + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 diff --git a/.github/workflows/contract-monitor.yml b/.github/workflows/contract-monitor.yml new file mode 100644 index 0000000..1bba9cf --- /dev/null +++ b/.github/workflows/contract-monitor.yml @@ -0,0 +1,92 @@ +name: Monitor upstream OpenAPI contract + +on: + schedule: + - cron: '17 3 * * 1' + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: contract-monitor + cancel-in-progress: false + +jobs: + check: + name: Check BinaryLane OpenAPI contract + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out source + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Compare live contract with the reviewed snapshot + id: contract + shell: bash + run: | + set +e + ./eng/verify-openapi-contract.sh > contract-check.txt 2>&1 + status=$? + set -e + + cat contract-check.txt + case "$status" in + 0) + echo "changed=false" >> "$GITHUB_OUTPUT" + ;; + 1) + echo "changed=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "The contract check could not complete (exit $status)." >&2 + exit "$status" + ;; + esac + + - name: Upload contract-check report + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: contract-check + path: contract-check.txt + if-no-files-found: ignore + retention-days: 14 + + - name: Open one review issue for contract drift + if: steps.contract.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + existing_count="$(gh issue list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --search 'in:title "BinaryLane OpenAPI contract changed"' \ + --json number \ + --jq 'length')" + + if [[ "$existing_count" -gt 0 ]]; then + echo "An upstream-contract review issue is already open." + exit 0 + fi + + { + echo "BinaryLane's public OpenAPI contract no longer matches the reviewed snapshot." + echo + echo "Review the upstream change before updating generated code or SDK behavior." + echo + echo "```text" + cat contract-check.txt + echo "```" + echo + echo "Suggested next step: run `./eng/refresh-openapi-contract.sh`, review the diff," + echo "then update models, fixtures, API coverage, docs, and tests in one pull request." + } > issue-body.md + + gh issue create \ + --repo "$GITHUB_REPOSITORY" \ + --title "BinaryLane OpenAPI contract changed" \ + --body-file issue-body.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e22114c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,186 @@ +name: Publish + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + NUGET_XMLDOC_MODE: skip + +jobs: + build: + name: Validate, test, and pack + if: github.repository == 'alexhopeoconnor/binarylane-dotnet' + runs-on: ubuntu-latest + timeout-minutes: 20 + outputs: + package_version: ${{ steps.package_version.outputs.value }} + + steps: + - name: Check out tagged source + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + + - name: Set up .NET + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 + with: + dotnet-version: 8.0.x + global-json-file: global.json + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Verify the tagged commit is reachable from main + run: | + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then + echo "Release tags must point to a commit reachable from main." >&2 + exit 1 + fi + + - name: Assert tag, package version, and changelog agree + run: ./eng/assert-release-version.sh "${{ github.ref_name }}" + + - name: Read package version + id: package_version + run: echo "value=$(./eng/read-package-version.sh)" >> "$GITHUB_OUTPUT" + + - name: Restore locked dependencies + run: | + dotnet restore src/BinaryLane.Api/BinaryLane.Api.csproj --locked-mode + dotnet restore tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --locked-mode + dotnet restore examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --locked-mode + + - name: Build + run: | + dotnet build src/BinaryLane.Api/BinaryLane.Api.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + dotnet build tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + dotnet build examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --configuration Release --no-restore /p:ContinuousIntegrationBuild=true -p:BuildInParallel=false + + - name: Test + run: dotnet test tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --configuration Release --no-build -m:1 + + - name: Pack + run: >- + dotnet pack src/BinaryLane.Api/BinaryLane.Api.csproj + --configuration Release --no-build --output artifacts + -p:BuildInParallel=false + + - name: Compile the demo against the packed package + run: ./eng/test-demo-package.sh artifacts + + - name: Upload NuGet packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: nuget-packages + path: artifacts/ + if-no-files-found: error + retention-days: 14 + + publish: + name: Publish to NuGet.org + needs: build + if: github.repository == 'alexhopeoconnor/binarylane-dotnet' + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: release + permissions: + contents: read + id-token: write + + steps: + - name: Download NuGet packages + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: nuget-packages + path: artifacts + + - name: Check trusted-publishing configuration + run: | + if [[ -z "${{ secrets.NUGET_USER }}" ]]; then + echo "Set the protected release-environment secret NUGET_USER to the NuGet.org username." >&2 + exit 1 + fi + + - name: Authenticate to NuGet.org through GitHub OIDC + id: nuget_login + uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1 + with: + user: ${{ secrets.NUGET_USER }} + + - name: Publish package + env: + NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }} + run: >- + dotnet nuget push "artifacts/*.nupkg" + --source https://api.nuget.org/v3/index.json + --api-key "$NUGET_API_KEY" + --skip-duplicate + + - name: Publish symbols + env: + NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }} + shell: bash + run: | + shopt -s nullglob + for package in artifacts/*.snupkg; do + dotnet nuget push "$package" \ + --source https://api.nuget.org/v3/index.json \ + --api-key "$NUGET_API_KEY" \ + --skip-duplicate + done + + release: + name: Create GitHub Release + needs: + - build + - publish + if: github.repository == 'alexhopeoconnor/binarylane-dotnet' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + + steps: + - name: Check out tagged source + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Download NuGet packages + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: nuget-packages + path: artifacts + + - name: Extract release notes + run: ./eng/extract-release-notes.sh "${{ needs.build.outputs.package_version }}" > release-notes.md + + - name: Create or update GitHub Release + env: + GH_TOKEN: ${{ github.token }} + PACKAGE_VERSION: ${{ needs.build.outputs.package_version }} + TAG_NAME: ${{ github.ref_name }} + shell: bash + run: | + if gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then + gh release upload "$TAG_NAME" artifacts/* eng/openapi/contract.json --clobber --repo "$GITHUB_REPOSITORY" + gh release edit "$TAG_NAME" \ + --title "BinaryLane.Api $PACKAGE_VERSION" \ + --notes-file release-notes.md \ + --repo "$GITHUB_REPOSITORY" + else + gh release create "$TAG_NAME" artifacts/* eng/openapi/contract.json \ + --title "BinaryLane.Api $PACKAGE_VERSION" \ + --notes-file release-notes.md \ + --verify-tag \ + --repo "$GITHUB_REPOSITORY" + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6efe806 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Build output +[Bb]in/ +[Oo]bj/ +[Aa]rtifacts/ +[Tt]est[Rr]esult*/ +coverage/ +*.coverage +*.coveragexml + +# IDE and user-specific files +.vs/ +.vscode/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# Local configuration and credentials +.env +.env.* +!.env.example +appsettings.Development.json +secrets.json + +# Operating-system files +.DS_Store +Thumbs.db + +# Contract-monitor working files +eng/openapi/*.downloaded.yaml +eng/openapi/*.normalized.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3b746b1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project are documented here. This project follows +[Semantic Versioning](https://semver.org/). The SDK version describes the +public .NET API; it is deliberately independent from BinaryLane's preview API +version. + +## [Unreleased] + +### Added + +- Nothing yet. + +## [0.1.0-beta.1] - 2026-08-16 + +### Added + +- Initial preview release of the unofficial community .NET client for the + BinaryLane v2 API. +- Typed HTTP client setup, pluggable bearer-token authentication, pagination, + API error mapping, and a maintained read-only demo application. +- HTTPS-only bearer-token transport, protected request authority and + authorization headers, and a 16 MiB successful-response buffer limit. +- OpenAPI contract snapshot and change-monitoring automation for BinaryLane + API reference version `0.39.1`. + +[Unreleased]: https://github.com/alexhopeoconnor/binarylane-dotnet/compare/v0.1.0-beta.1...HEAD +[0.1.0-beta.1]: https://github.com/alexhopeoconnor/binarylane-dotnet/releases/tag/v0.1.0-beta.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5ad5c9f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +Thanks for improving the SDK. Keep changes focused, tested, documented, and +backwards compatible unless a breaking change is explicitly planned. + +## Local setup + +Install the SDK pinned in `global.json`, then run: + +```bash +dotnet restore src/BinaryLane.Api/BinaryLane.Api.csproj --locked-mode +dotnet restore tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --locked-mode +dotnet restore examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --locked-mode +dotnet build src/BinaryLane.Api/BinaryLane.Api.csproj -c Release --no-restore +dotnet build tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj -c Release --no-restore +dotnet build examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj -c Release --no-restore +dotnet test tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj -c Release --no-build +dotnet format src/BinaryLane.Api/BinaryLane.Api.csproj --verify-no-changes --no-restore +dotnet format tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj --verify-no-changes --no-restore +dotnet format examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj --verify-no-changes --no-restore +``` + +The tracked demo application lives in +`examples/BinaryLane.Api.Demo`. Configure a personal API token with user +secrets; never put it in a file, test fixture, issue, pull request, or commit. + +```bash +dotnet user-secrets set \ + "BinaryLane:ApiToken" "your-token" \ + --project examples/BinaryLane.Api.Demo +``` + +## Pull requests + +- Keep each pull request narrowly scoped and explain the user-visible effect. +- Add or update unit tests for every changed HTTP request, response, error, or + public API contract. +- Use scrubbed fixtures only. Never record a real bearer token, password, + user-data payload, IP address, server name, or account email unless it is + demonstrably non-sensitive and intended for publication. +- Update XML documentation, the README, and the appropriate page in `docs/` + when a public behavior changes. +- Update `docs/api-coverage.md` when support for a documented BinaryLane route + changes. +- Add a changelog entry under **Unreleased** for a user-visible change. + +## API design rules + +- Keep public APIs in `BinaryLane.Api.V2.*`. +- Prefer focused resource interfaces over a monolithic service interface. +- Accept a `CancellationToken` on every asynchronous operation. +- Keep provider fields forward compatible: do not turn preview response values + into closed C# enums unless the API contract guarantees them. +- Do not add automatic retries for state-changing requests. BinaryLane does not + document idempotency keys, so retries can provision or mutate resources twice. +- Avoid breaking changes in a minor or patch SDK version. Follow + [Semantic Versioning](https://semver.org/). + +## Upstream OpenAPI changes + +The provider's OpenAPI document is a preview contract and can change without a +version increment. See [the contract workflow](docs/upstream-contract.md) +before updating `eng/openapi/`. Review any change by hand; the raw contract +contains virtual `#ActionName` documentation paths that must not become real +HTTP endpoints in the SDK. + +## Releases + +Only maintainers may release packages. See [the release guide](docs/releasing.md). +The release workflow uses NuGet trusted publishing through GitHub OIDC; no +long-lived NuGet API key belongs in this repository or its GitHub secrets. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..aec06b5 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + enable + enable + latest + latest-recommended + true + true + true + true + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..71fae4e --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,25 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5b052ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Alex Hope-O'Connor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..ca61912 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9edf84 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# BinaryLane.Api + +[![CI](https://github.com/alexhopeoconnor/binarylane-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/alexhopeoconnor/binarylane-dotnet/actions/workflows/ci.yml) +[![NuGet](https://img.shields.io/nuget/vpre/BinaryLane.Api.svg)](https://www.nuget.org/packages/BinaryLane.Api) + +`BinaryLane.Api` is an unofficial .NET client for the BinaryLane v2 API. It +provides typed clients for account, billing, servers, images, DNS, load +balancers, VPCs, and other v2 resources. + +See the [BinaryLane API reference](https://api.binarylane.com.au/reference/) for +provider-specific API behaviour. + +BinaryLane describes its API as a developer preview. Pin the package version +used by production applications and review release notes before upgrading. + +> This project is not affiliated with, endorsed by, or supported by BinaryLane. +> For account or infrastructure support, use +> [BinaryLane Support](https://support.binarylane.com.au/support/home). + +## Install + +```bash +dotnet add package BinaryLane.Api --prerelease +``` + +Remove `--prerelease` once a stable package version is available. + +## Quick start + +Keep the API token in a secret store, user secrets, or an environment variable. +Do not commit it to configuration or source control. + +```csharp +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddBinaryLaneApi(options => +{ + options.ApiToken = builder.Configuration["BinaryLane:ApiToken"] + ?? Environment.GetEnvironmentVariable("BINARYLANE_API_TOKEN") + ?? throw new InvalidOperationException("A BinaryLane API token is required."); +}); + +var app = builder.Build(); + +app.MapGet("/account", async (IBinaryLaneClient binaryLane, CancellationToken cancellationToken) => + await binaryLane.Account.GetAsync(cancellationToken)); + +app.Run(); +``` + +Use the resource clients from `IBinaryLaneClient`: + +```csharp +static async Task ListServersAsync( + IBinaryLaneClient binaryLane, + CancellationToken cancellationToken) +{ + await foreach (var server in binaryLane.Servers.ListAllAsync(cancellationToken: cancellationToken)) + { + Console.WriteLine($"{server.Id}: {server.Name}"); + } +} +``` + +List methods support `CancellationToken`. Mutating calls are sent once; add a +retry policy only when the operation is known to be safe to repeat. + +## Documentation + +| Guide | Use it for | +| --- | --- | +| [Getting started](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/docs/getting-started.md) | Your first request and user-secrets setup. | +| [Configuration](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/docs/configuration.md) | Tokens, timeouts, direct construction, and HTTP configuration. | +| [Pagination](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/docs/pagination.md) | Working with pages or async enumeration. | +| [Actions](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/docs/actions-and-polling.md) | Submitting and optionally waiting for server actions. | +| [Error handling](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/docs/errors.md) | Handling API failures safely. | +| [Demo](https://github.com/alexhopeoconnor/binarylane-dotnet/tree/main/examples/BinaryLane.Api.Demo) | Running the included read-only console app. | + +## Compatibility + +The package targets `net8.0` and `netstandard2.0`; .NET 10 applications use +the `net8.0` asset. Package versions follow [Semantic Versioning](https://semver.org/). + +## Contributing and security + +See [CONTRIBUTING.md](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/CONTRIBUTING.md) for development guidance and +[SECURITY.md](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/SECURITY.md) for private vulnerability reporting. + +## License + +Distributed under the [MIT License](https://github.com/alexhopeoconnor/binarylane-dotnet/blob/main/LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f6f6984 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security policy + +## Supported versions + +Security fixes are made for the latest released package version. While the SDK +is pre-1.0, upgrade to the latest beta before reporting a suspected issue. + +## Reporting a vulnerability + +Please report vulnerabilities privately through +[GitHub Security Advisories](https://github.com/alexhopeoconnor/binarylane-dotnet/security/advisories/new). +If private reporting is unavailable, open a minimal public issue that asks for +a private contact channel and does not disclose exploit details. + +Do not include any of the following in a report, issue, pull request, log, or +sample: + +- BinaryLane bearer tokens or authorization headers; +- account email addresses, invoices, or billing data; +- passwords, password reset payloads, SSH private keys, or `user_data`; +- private IP addresses, server names, DNS records, or raw response bodies that + are not essential to demonstrate the problem. + +Maintainers aim to acknowledge a report within seven days and will coordinate +a fix and disclosure timeline with the reporter. diff --git a/assets/package-icon.svg b/assets/package-icon.svg new file mode 100644 index 0000000..8b52ee1 --- /dev/null +++ b/assets/package-icon.svg @@ -0,0 +1,10 @@ + + Abstract network route icon + + + + + + + + diff --git a/docs/actions-and-polling.md b/docs/actions-and-polling.md new file mode 100644 index 0000000..a51ff45 --- /dev/null +++ b/docs/actions-and-polling.md @@ -0,0 +1,48 @@ +# Server actions + +Use a typed `ServerAction` to submit a server action. For example, this powers +on a server: + +```csharp +using BinaryLane.Api.V2.Models; + +var submission = await client.Servers.SubmitActionAsync( + serverId, + new PowerOnServerAction(), + cancellationToken); +``` + +Common action types include `PowerOnServerAction`, `PowerOffServerAction`, +`RebootServerAction`, `ResizeServerAction`, and `RebuildServerAction`. + +## Waiting for completion + +BinaryLane can accept an action before it has finished. When the response +contains an action, use its identifier to wait for the final status: + +```csharp +if (submission.Action is { } action) +{ + var completed = await client.Actions.WaitForCompletionAsync( + action.Id, + cancellationToken: cancellationToken); +} +``` + +Pass `ActionWaitOptions` when you need a different timeout or polling interval. +The default is a 15-minute timeout with a two-second polling interval. + +## Status values + +`BinaryLaneAction.Status` and `BinaryLaneAction.Type` are strings. Compare a +documented value with `BinaryLaneValues`: + +```csharp +if (action.Status == BinaryLaneValues.ActionStatus.Completed) +{ + // Continue with the next step. +} +``` + +If an action request has an uncertain outcome, retrieve the action or server +state before submitting it again. diff --git a/docs/api-coverage.md b/docs/api-coverage.md new file mode 100644 index 0000000..f816ba9 --- /dev/null +++ b/docs/api-coverage.md @@ -0,0 +1,33 @@ +# Maintainer guide: API coverage + +This is a maintainer reference. Package consumers can start with the +[README](../README.md). + +The SDK's scope is the BinaryLane v2 OpenAPI contract committed at +`eng/openapi/binarylane-v2.openapi.yaml`. The raw contract currently declares +version `0.39.1`; it is a developer-preview contract and may change without a +version change. + +The raw document contains virtual paths such as +`/v2/servers/{server_id}/actions#PowerOn`. These are API-reference aliases for +action payload variants, not real HTTP routes. Coverage and generated-code +checks must use the normalized contract and count only the real +`POST /v2/servers/{server_id}/actions` route. + +| API area | Public resource boundary | +| --- | --- | +| Account | `IAccountApi` | +| Balances, invoices, and unpaid invoices | `IBillingApi` | +| Actions | `IActionsApi` | +| Servers and server subresources | `IServersApi` | +| Images | `IImagesApi` | +| SSH keys | `ISshKeysApi` | +| DNS domains, nameservers, and records | `IDomainsApi` | +| Load balancers and forwarding rules | `ILoadBalancersApi` | +| VPCs and members | `IVpcsApi` | +| Regions, sizes, and software catalogues | `IRegionsApi`, `ISizesApi`, `ISoftwareApi` | +| Reverse names | `IReverseNamesApi` | +| Data usage | `IDataUsageApi` | +| Sample sets | `ISampleSetsApi` | + +Keep this table and the related tests up to date when API support changes. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..73c2aa1 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,72 @@ +# Configuration + +## Standard registration + +In an ASP.NET Core or generic-host application, `AddBinaryLaneApi` configures +`IBinaryLaneClient` and the individual resource interfaces. + +```csharp +builder.Services.AddBinaryLaneApi(options => +{ + options.ApiToken = builder.Configuration["BinaryLane:ApiToken"]; + options.RequestTimeoutSeconds = 100; +}); +``` + +| Setting | Default | Description | +| --- | --- | --- | +| `BaseUrl` | `https://api.binarylane.com.au/` | API base address. Leave unchanged for BinaryLane's public API. | +| `ApiToken` | none | Bearer token used by the default provider. | +| `RequestTimeoutSeconds` | `100` | Timeout for an individual HTTP request. | + +The base URL must be an HTTPS URL without credentials, a query string, or a +fragment. Options are validated when the application starts. + +## Rotating tokens + +Register `IBinaryLaneTokenProvider` before calling `AddBinaryLaneApi` when the +token comes from a vault or changes during the lifetime of the application. +`VaultTokenProvider` below is your implementation of that interface. + +```csharp +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.DependencyInjection; + +builder.Services.AddSingleton(); +builder.Services.AddBinaryLaneApi(options => options.RequestTimeoutSeconds = 100); +``` + +The provider is called for every request. Do not log the returned token. + +## Use without dependency injection + +```csharp +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.Authentication; + +using var httpClient = new HttpClient +{ + BaseAddress = new Uri("https://api.binarylane.com.au/"), +}; + +var token = Environment.GetEnvironmentVariable("BINARYLANE_API_TOKEN") + ?? throw new InvalidOperationException("Set BINARYLANE_API_TOKEN."); + +var client = new BinaryLaneClient( + httpClient, + new StaticBinaryLaneTokenProvider(token)); +``` + +## HTTP configuration + +`AddBinaryLaneApi` returns an `IHttpClientBuilder`, so you can configure proxy, +telemetry, or other application-specific handlers. + +```csharp +builder.Services.AddBinaryLaneApi(options => options.ApiToken = token) + .ConfigureHttpClient(client => client.DefaultRequestHeaders.Add("X-App", "my-service")); +``` + +Only add automatic retries when the request is safe to repeat. In particular, +POST, PUT, PATCH, and DELETE requests may have already been applied when a +network failure is reported. diff --git a/docs/errors.md b/docs/errors.md new file mode 100644 index 0000000..01b4d6c --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,40 @@ +# Error handling + +All API failures derive from `BinaryLaneApiException`. + +| Exception | When it is used | +| --- | --- | +| `BinaryLaneUnauthorizedException` | The token is missing, invalid, or expired. | +| `BinaryLaneForbiddenException` | The token does not have access to the resource. | +| `BinaryLaneNotFoundException` | The requested resource does not exist or is not visible to the token. | +| `BinaryLaneValidationException` | BinaryLane rejected the request payload. | +| `BinaryLaneApiException` | Any other non-success HTTP response. | + +```csharp +using BinaryLane.Api.V2.Errors; + +try +{ + var server = await client.Servers.GetAsync(serverId, cancellationToken); +} +catch (BinaryLaneNotFoundException) +{ + // Handle a missing server. +} +catch (BinaryLaneValidationException) +{ + // Show an appropriate validation message to the caller. +} +catch (BinaryLaneApiException exception) +{ + // Use exception.StatusCode to choose an application-specific response. +} +``` + +Each exception provides `StatusCode`, `RequestUri`, `Headers`, and, when +available, `Problem` and `ResponseBody`. Provider detail text can contain user +data. Do not write `Problem`, `Headers`, or `ResponseBody` to application logs +without appropriate redaction. + +Successful JSON responses are limited to 16 MiB. A larger response throws +`HttpRequestException` before the client buffers it in memory. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..ea3516d --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,53 @@ +# Getting started + +`BinaryLane.Api` connects to BinaryLane's bearer-token v2 API at +`https://api.binarylane.com.au/v2/`. + +## 1. Install the package + +```bash +dotnet add package BinaryLane.Api --prerelease +``` + +## 2. Store the token safely + +For local development, initialise user secrets for your application and add +the token: + +```bash +dotnet user-secrets init +dotnet user-secrets set "BinaryLane:ApiToken" "your-token" +``` + +For hosted applications, use the platform's secret store or supply +`BINARYLANE_API_TOKEN` at runtime. + +## 3. Register the client + +```csharp +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.DependencyInjection; +using BinaryLane.Api.V2.Models; + +builder.Services.AddBinaryLaneApi(options => +{ + options.ApiToken = builder.Configuration["BinaryLane:ApiToken"] + ?? Environment.GetEnvironmentVariable("BINARYLANE_API_TOKEN") + ?? throw new InvalidOperationException("A BinaryLane API token is required."); +}); +``` + +## 4. Make a request + +Inject `IBinaryLaneClient`, then select the resource you need: + +```csharp +public sealed class ServerReader(IBinaryLaneClient binaryLane) +{ + public IAsyncEnumerable ListAsync(CancellationToken cancellationToken) => + binaryLane.Servers.ListAllAsync(cancellationToken: cancellationToken); +} +``` + +See [Configuration](configuration.md) for custom token providers and +[Pagination](pagination.md) for page-by-page access. diff --git a/docs/pagination.md b/docs/pagination.md new file mode 100644 index 0000000..068ef41 --- /dev/null +++ b/docs/pagination.md @@ -0,0 +1,31 @@ +# Pagination + +List endpoints accept one-based `page` and `per_page` values. `per_page` can be +between 1 and 200. + +Use `ListAsync` when you need a specific page or the provider's page links: + +```csharp +using BinaryLane.Api.V2.Pagination; + +var page = await client.Servers.ListAsync( + new PageRequest { Page = 1, PerPage = 50 }, + cancellationToken); + +foreach (var server in page.Items) +{ + await ProcessAsync(server, cancellationToken); +} +``` + +Use `ListAllAsync` to process every page as it is needed: + +```csharp +await foreach (var server in client.Servers.ListAllAsync(cancellationToken: cancellationToken)) +{ + await ProcessAsync(server, cancellationToken); +} +``` + +Pass a cancellation token from the calling request, worker, or command so a +stopped operation does not continue fetching pages. diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..2599b83 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,63 @@ +# Maintainer guide: releases + +This page is for package maintainers. + +This repository publishes packages with NuGet trusted publishing and GitHub +OpenID Connect (OIDC). It must not use a long-lived NuGet API key. + +## One-time setup + +1. On NuGet.org, create or verify the trusted-publishing policy: + - publisher: `GitHubActions`; + - GitHub owner: `alexhopeoconnor`; + - repository: `binarylane-dotnet`; + - workflow: `publish.yml`; + - environment: `release`. +2. In GitHub, create a protected environment named `release` and require a + maintainer approval. +3. Create a protected `release` environment secret named `NUGET_USER` with + the NuGet.org username `alex.hope.oconnor`. It is an identifier, not an + API key. +4. Protect `main` with CI and restrict creation of `v*` tags. + +The first successful OIDC publication must occur before NuGet's policy +activation window expires. After success, NuGet permanently activates the +policy for this exact repository/workflow/environment identity. + +## Release checklist + +1. Review BinaryLane contract changes and update the committed snapshot if + necessary. +2. Update code, tests, docs, API coverage, and `CHANGELOG.md`. +3. Set the package `` to the intended SemVer release. +4. Open and merge the release pull request after CI succeeds. +5. Tag the exact merge commit. The tag must equal the package version with a + leading `v`. + +```bash +git tag -a v0.1.0-beta.1 -m "BinaryLane.Api 0.1.0-beta.1" +git push origin v0.1.0-beta.1 +``` + +6. Approve the protected `release` environment in GitHub Actions. +7. Verify the package page, rendered README, icon, license, repository link, + symbols, package ownership, and GitHub Release. + +## What the release workflow does + +`publish.yml` validates the tag, package version, and changelog heading; it +then restores, builds, tests, packs, and compiles the maintained demo against +the resulting local `.nupkg`. Only then does `NuGet/login@v1` exchange the +GitHub OIDC identity for a short-lived one-time publish key. The key is used +only in memory by the job. + +The GitHub Release job runs after NuGet publishing and receives no OIDC +permission. It attaches the package files and uses the matching changelog +section for release notes. + +## Recovery + +NuGet packages cannot be overwritten. If a release has a packaging defect, +unlist it if appropriate, publish a new version, and document the correction +in the changelog. Do not retag a version or attempt to reuse a published +package version. diff --git a/docs/upstream-contract.md b/docs/upstream-contract.md new file mode 100644 index 0000000..62fe376 --- /dev/null +++ b/docs/upstream-contract.md @@ -0,0 +1,48 @@ +# Maintainer guide: upstream API contract + +This page describes SDK maintenance. Package consumers can start with the +[README](../README.md). + +The source of truth for BinaryLane's API is its published OpenAPI document: + + + +The committed raw snapshot is intentionally reviewable: + +```text +eng/openapi/binarylane-v2.openapi.yaml +eng/openapi/contract.json +``` + +`contract.json` records the source URL, provider-declared version, SHA-256, +and retrieval time. It describes an upstream artifact; it does not set the +NuGet package version. + +## Snapshot purpose + +BinaryLane's preview API can change without a version change. The committed +snapshot makes those changes visible during SDK maintenance. + +## Normalization + +The raw document includes virtual `#ActionName` paths for individual server +action variants. They exist to improve the provider's reference UI but are not +HTTP paths. `eng/normalize-openapi.sh` strips them when producing a normalized +document for code generation or coverage checks. Do not issue requests to +those virtual paths. + +## Monitoring and updating + +The scheduled `contract-monitor.yml` workflow downloads the live document and +runs `eng/verify-openapi-contract.sh`. If the SHA-256 or provider-declared +version differs, it opens one issue rather than silently updating the SDK. + +To intentionally refresh the snapshot after review: + +```bash +./eng/refresh-openapi-contract.sh +git diff -- eng/openapi docs/api-coverage.md +``` + +Then update affected models, coverage documentation, changelog, and tests in +the same pull request. Review the change before merging it. diff --git a/eng/assert-release-version.sh b/eng/assert-release-version.sh new file mode 100755 index 0000000..98a8509 --- /dev/null +++ b/eng/assert-release-version.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Enforces the release identity before any package is published. The version is +# intentionally read from the package project so one source of truth controls +# both the .nupkg and its corresponding Git tag. + +if [[ $# -ne 1 ]]; then + printf 'Usage: %s v\n' "${0##*/}" >&2 + exit 2 +fi + +tag_name="$1" +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +changelog_file="$repository_root/CHANGELOG.md" + +if [[ ! -f "$changelog_file" ]]; then + printf 'Expected changelog does not exist.\n' >&2 + exit 2 +fi + +package_version="$("$script_dir/read-package-version.sh")" + +expected_tag="v${package_version}" +if [[ "$tag_name" != "$expected_tag" ]]; then + printf 'Tag %s does not match package version %s (expected %s).\n' \ + "$tag_name" "$package_version" "$expected_tag" >&2 + exit 1 +fi + +if ! grep -Fqx "## [$package_version]" "$changelog_file" && \ + ! grep -Fq "## [$package_version] -" "$changelog_file" && \ + ! grep -Fqx "## $package_version" "$changelog_file" && \ + ! grep -Fq "## $package_version -" "$changelog_file"; then + printf 'CHANGELOG.md needs a level-two heading for %s.\n' "$package_version" >&2 + exit 1 +fi + +printf 'Release version is consistent: %s.\n' "$package_version" diff --git a/eng/extract-release-notes.sh b/eng/extract-release-notes.sh new file mode 100755 index 0000000..18ab405 --- /dev/null +++ b/eng/extract-release-notes.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + printf 'Usage: %s \n' "${0##*/}" >&2 + exit 2 +fi + +package_version="$1" +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +changelog_file="$repository_root/CHANGELOG.md" + +notes="$(awk -v bracket_heading="## [$package_version]" -v bare_heading="## $package_version" ' + $0 == bracket_heading || index($0, bracket_heading " -") == 1 || + $0 == bare_heading || index($0, bare_heading " -") == 1 { + capture = 1 + next + } + + capture && /^## / { + exit + } + + capture && /^\[/ { + exit + } + + capture { + print + } +' "$changelog_file")" + +if [[ -z "${notes//[[:space:]]/}" ]]; then + printf 'No release notes found for %s in CHANGELOG.md.\n' "$package_version" >&2 + exit 1 +fi + +printf '%s\n' "$notes" diff --git a/eng/normalize-openapi.sh b/eng/normalize-openapi.sh new file mode 100755 index 0000000..7b9fbb8 --- /dev/null +++ b/eng/normalize-openapi.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Normalize BinaryLane's raw OpenAPI document for code-generation or coverage +# tooling. The reference includes virtual `#ActionName` paths for individual +# server action payloads. They are documentation aliases, not HTTP endpoints. + +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +default_input="$script_dir/openapi/binarylane-v2.openapi.yaml" +input_file="${1:-$default_input}" +output_file="${2:-}" + +if [[ ! -f "$input_file" ]]; then + printf 'OpenAPI input does not exist: %s\n' "$input_file" >&2 + exit 2 +fi + +normalize() { + awk ' + /^paths:$/ { + in_paths = 1 + } + + in_paths && /^components:$/ { + in_paths = 0 + skip_virtual_path = 0 + } + + in_paths && /^ ['"'"'][^'"'"']*#[^'"'"']*['"'"']:[[:space:]]*$/ { + skip_virtual_path = 1 + removed += 1 + next + } + + in_paths && /^ [^[:space:]][^:]*:[[:space:]]*$/ { + skip_virtual_path = 0 + } + + !skip_virtual_path { + print + } + + END { + printf "Removed %d virtual action path(s).\n", removed > "/dev/stderr" + } + ' "$input_file" +} + +if [[ -n "$output_file" ]]; then + mkdir -p "$(dirname -- "$output_file")" + normalize > "$output_file" +else + normalize +fi diff --git a/eng/openapi/README.md b/eng/openapi/README.md new file mode 100644 index 0000000..88024b4 --- /dev/null +++ b/eng/openapi/README.md @@ -0,0 +1,21 @@ +# BinaryLane API contract + +`binarylane-v2.openapi.yaml` is an unmodified snapshot of BinaryLane's published +OpenAPI document. Its provenance and digest are recorded in `contract.json`. + +The provider currently describes the API as a developer preview, so an upstream +version number alone must not be used as a compatibility guarantee. The contract +monitor compares the document digest as well as its declared version. + +## Normalization rule + +The document has 52 routable paths and 42 additional paths such as +`/v2/servers/{server_id}/actions#PowerOn`. The latter are documentation aliases +for payload variants of `POST /v2/servers/{server_id}/actions`; URI fragments do +not reach an HTTP server. They must be removed before an OpenAPI generator is +asked to create a route surface. `normalization.json` records the exact rule and +each affected path. + +Public SDK DTOs deliberately model all provider enum values as strings. This is +forward-compatible with a preview API that may add a value before a new SDK +release is available. diff --git a/eng/openapi/binarylane-v2.openapi.yaml b/eng/openapi/binarylane-v2.openapi.yaml new file mode 100644 index 0000000..72e816f --- /dev/null +++ b/eng/openapi/binarylane-v2.openapi.yaml @@ -0,0 +1,9470 @@ +openapi: 3.0.4 +info: + title: BinaryLane API + description: "# Introduction\n\nThis API is used by BinaryLane's 'mPanel' website, so you'll find everything you need here to automate your use of BinaryLane or develop tools for others. You could even build your own version of 'mPanel' if you wanted!\n\n\n## Getting Started Guide\n\nYou can find a detailed guide to using the API to manage your servers here.\n\n\n# API Tools\n\nWe have created a CLI tool to interact with the API. You can find the repository here.\n\n\n# Units\n\nStorage, memory and data-transfer values use the familiar `GB`, `MB` and `TB` labels (matching our control panel and website) rather than the IEC `GiB`/`MiB` forms. The exact byte conventions are:\n\n| Label | Used for | Definition |\n| --- | --- | --- |\n| `MB` | Memory, storage | Binary megabyte: 1 MB = 1024² bytes |\n| `GB` | Disk, storage, data transfer | Binary gigabyte: 1 GB = 1024³ bytes |\n| `TB` | Data transfer | 1 TB = 1000 GB, where each GB = 1024³ bytes (so 1 TB = 1000 × 1024³ bytes) |\n\n\n# Developer Preview Notes\n\n**Warning: this specification is currently in active development.** Until this warning is removed breaking changes are possible without the version changing.\nHowever, this API is mature and we have a fully functioning new version of mPanel that uses it, so breaking changes will be very limited.\n\nWe welcome all feedback from API users at the support email address above.\n\n\n# Version Information\n\nThis section will contain a link to our changelog once the developer preview period is complete." + contact: + name: BinaryLane Support + url: https://support.binarylane.com.au/support/home + email: support@binarylane.com.au + version: 0.39.1 + x-logo: + url: https://www.binarylane.com.au/res/images/binarylane/logo.png + backgroundColor: '#000000' + altText: BinaryLane Logo + href: https://www.binarylane.com.au +servers: + - url: https://api.binarylane.com.au +paths: + /v2/account: + get: + tags: + - Accounts + summary: Fetch Information About the Current Account + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AccountResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/account\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: account get + '/v2/actions/{action_id}': + get: + tags: + - Actions + summary: Fetch an Existing Action + parameters: + - name: action_id + in: path + description: The ID of the action to fetch. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/actions/${action_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: action get + /v2/actions: + get: + tags: + - Actions + summary: List All Actions + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionsResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/actions?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: action list + '/v2/actions/{action_id}/proceed': + post: + tags: + - Actions + summary: Respond to a UserInteractionRequired Action + parameters: + - name: action_id + in: path + description: The ID of the action for which this is a response. + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ProceedRequest' + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/actions/${action_id}/proceed\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: action proceed + /v2/customers/my/balance: + get: + tags: + - Customers + summary: Fetch Current Balance Information + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/BalanceResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/customers/my/balance\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: account balance + '/v2/customers/my/invoices/{invoice_id}': + get: + tags: + - Customers + summary: Fetch an Invoice + parameters: + - name: invoice_id + in: path + description: The ID of the invoice to fetch. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/InvoiceResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/customers/my/invoices/${invoice_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: account invoice get + /v2/customers/my/invoices: + get: + tags: + - Customers + summary: Fetch Invoices + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/InvoicesResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/customers/my/invoices?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: account invoice list + /v2/customers/my/unpaid-payment-failed-invoices: + get: + tags: + - Customers + summary: Fetch Unpaid Failed Invoices + description: Any invoices that are unpaid and have a failed payment attempt may block the ability to renew existing services or add new services. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/UnpaidFailedInvoicesResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/customers/my/unpaid-payment-failed-invoices\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: account invoice overdue + '/v2/data_usages/{server_id}/current': + get: + tags: + - DataUsages + summary: Fetch the Current Data Usage (Transfer) for a Server + parameters: + - name: server_id + in: path + description: The target server id. + required: true + schema: + type: integer + description: The target server id. + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DataUsageResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/data_usages/${server_id}/current\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server data-usage get + /v2/data_usages/current: + get: + tags: + - DataUsages + summary: Fetch all Current Data Usage (Transfer) for All Servers + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DataUsagesResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/data_usages/current?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server data-usage list + /v2/domains/nameservers: + get: + tags: + - Domains + summary: List All Public Nameservers + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/LocalNameserversResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/domains/nameservers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain nameservers list + /v2/domains/refresh_nameserver_cache: + post: + tags: + - Domains + summary: Refresh Cached Nameserver Domain Records + description: The nameservers for domains are cached. If you have recently altered the nameservers for a managed domain you may need to refresh the cached domain records. + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DomainRefreshRequest' + responses: + '204': + description: No Content + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/domains/refresh_nameserver_cache\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: domain refresh-nameserver-cache + /v2/domains: + get: + tags: + - Domains + summary: List All Domains + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainsResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/domains?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain list + post: + tags: + - Domains + summary: Create a New Domain + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DomainRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/domains\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: domain create + '/v2/domains/{domain_name}': + get: + tags: + - Domains + summary: Fetch an Existing Domain + parameters: + - name: domain_name + in: path + description: The name or domain ID of the domain to fetch. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/domains/${domain_name}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain get + delete: + tags: + - Domains + summary: Delete an Existing Domain + parameters: + - name: domain_name + in: path + description: The name or domain ID of the domain to delete. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/domains/${domain_name}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain delete + '/v2/domains/{domain_name}/records': + get: + tags: + - Domains + summary: List All Domain Records for a Domain + parameters: + - name: domain_name + in: path + description: The domain name or domain ID for for which records should be listed. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: type + in: query + description: "\n| Value | Description |\n| ----- | ----------- |\n| A | Map an IPv4 address to a hostname. |\n| AAAA | Map an IPv6 address to a hostname. |\n| CAA | Restrict which certificate authorities are permitted to issue certificates for a domain. |\n| CNAME | Define an alias for your canonical hostname. |\n| MX | Define the mail exchanges that handle mail for the domain. |\n| NS | Define the nameservers that manage the domain. |\n| SOA | The Start of Authority record for the zone. |\n| SRV | Specify a server by hostname and port to handle a service or services. |\n| TXT | Define a string of text that is associated with a hostname. |\n\n" + schema: + allOf: + - $ref: '#/components/schemas/DomainRecordType' + - name: name + in: query + description: Only return records for this subdomain name. + schema: + type: string + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainRecordsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/domains/${domain_name}/records?type=${type}&name=${name}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain record list + post: + tags: + - Domains + summary: Create a New Domain Record + parameters: + - name: domain_name + in: path + description: The domain name or domain ID for for which the record should be created. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DomainRecordRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainRecordResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/domains/${domain_name}/records\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: domain record create + '/v2/domains/{domain_name}/records/{record_id}': + get: + tags: + - Domains + summary: Fetch an Existing Domain Record + parameters: + - name: domain_name + in: path + description: The domain name or domain ID for for which the record should be fetched. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: record_id + in: path + description: The ID of the record to fetch. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainRecordResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/domains/${domain_name}/records/${record_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain record get + put: + tags: + - Domains + summary: Update an Existing Domain Record + parameters: + - name: domain_name + in: path + description: The domain name or domain ID for for which the record should be updated. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: record_id + in: path + description: The ID of the record to update. + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/UpdateDomainRecordRequest' + description: 'Any values not provided will be retained. Provide empty strings to clear existing string values, nulls to retain the existing values.' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/DomainRecordResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PUT \"https://api.binarylane.com.au/v2/domains/${domain_name}/records/${record_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: domain record update + delete: + tags: + - Domains + summary: Delete an Existing Domain Record + parameters: + - name: domain_name + in: path + description: The domain name or domain ID for which the record should be deleted. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: record_id + in: path + description: The ID of the record to delete. + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/domains/${domain_name}/records/${record_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: domain record delete + /v2/images: + get: + tags: + - Images + summary: List All Images + parameters: + - name: type + in: query + description: Queries for distribution will include images that have pre-installed applications. + schema: + allOf: + - $ref: '#/components/schemas/ImageQueryType' + - name: private + in: query + description: Provide 'true' to only list private images. 'false' has no effect. + schema: + type: boolean + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ImagesResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/images?type=${type}&private=${private}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: image list + '/v2/images/{image_id_or_slug}': + get: + tags: + - Images + summary: Fetch an Existing Image + parameters: + - name: image_id_or_slug + in: path + description: The ID or Slug (if an operating system) of the image to retrieve. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ImageResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/images/${image_id_or_slug}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: image get + '/v2/images/{image_id}': + put: + tags: + - Images + summary: Update an Existing Image + description: Only updating of user created backups is currently supported. + parameters: + - name: image_id + in: path + description: The ID of the image to update. + required: true + schema: + type: integer + format: int64 + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ImageRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ImageResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PUT \"https://api.binarylane.com.au/v2/images/${image_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: image update + '/v2/images/{image_id}/download': + get: + tags: + - Images + summary: Download an Existing Image + description: Only user created backup images are currently available for download. + parameters: + - name: image_id + in: path + description: The ID of the image to download. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ImageDownloadResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/images/${image_id}/download\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: image download + '/v2/account/keys/{key_id}': + get: + tags: + - Keys + summary: Fetch an Existing SSH Key + description: The key_id may be either the Id or the key fingerprint. + parameters: + - name: key_id + in: path + description: The ID or fingerprint of the SSH Key to fetch. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SshKeyResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/account/keys/${key_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: ssh-key get + put: + tags: + - Keys + summary: Update an Existing SSH Key + description: The key_id may be either the Id or the key fingerprint. + parameters: + - name: key_id + in: path + description: The ID or fingerprint of the SSH Key to update. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/UpdateSshKeyRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SshKeyResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PUT \"https://api.binarylane.com.au/v2/account/keys/${key_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: ssh-key update + delete: + tags: + - Keys + summary: Delete an Existing SSH Key + description: The key_id may be either the Id or the key fingerprint. + parameters: + - name: key_id + in: path + description: The ID or fingerprint of the SSH Key to delete. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/account/keys/${key_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: ssh-key delete + /v2/account/keys: + get: + tags: + - Keys + summary: List All SSH Keys + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SshKeysResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/account/keys?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: ssh-key list + post: + tags: + - Keys + summary: Add a New SSH Key + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/SshKeyRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SshKeyResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/account/keys\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: ssh-key create + '/v2/load_balancers/{load_balancer_id}': + get: + tags: + - LoadBalancers + summary: Fetch an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer to fetch. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/LoadBalancerResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: load-balancer get + put: + tags: + - LoadBalancers + summary: Update an Existing Load Balancer + description: Any existing settings or servers that are not included will revert to default values. + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer to update. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/UpdateLoadBalancerRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateLoadBalancerResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PUT \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer update + delete: + tags: + - LoadBalancers + summary: Cancel an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer to cancel. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: load-balancer delete + /v2/load_balancers: + get: + tags: + - LoadBalancers + summary: List all Load Balancers + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/LoadBalancersResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/load_balancers?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: load-balancer list + post: + tags: + - LoadBalancers + summary: Create a New Load Balancer + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/CreateLoadBalancerRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/CreateLoadBalancerResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/load_balancers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer create + /v2/load_balancers/availability: + get: + tags: + - LoadBalancers + summary: Fetch Load Balancer Availability and Pricing + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/LoadBalancerAvailabilityResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/load_balancers/availability\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: load-balancer availability + '/v2/load_balancers/{load_balancer_id}/servers': + post: + tags: + - LoadBalancers + summary: Add Servers to an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer to which servers should be added. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ServerIdsRequest' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/servers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer server create + delete: + tags: + - LoadBalancers + summary: Remove Servers from an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer for which servers should be removed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ServerIdsRequest' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/servers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer server delete + '/v2/load_balancers/{load_balancer_id}/forwarding_rules': + post: + tags: + - LoadBalancers + summary: Add Forwarding Rules to an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer to which forwarding rules should be added. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ForwardingRulesRequest' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/forwarding_rules\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer rule create + delete: + tags: + - LoadBalancers + summary: Remove Forwarding Rules from an Existing Load Balancer + parameters: + - name: load_balancer_id + in: path + description: The ID of the load balancer for which forwarding rules should be removed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: load-balancer list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ForwardingRulesRequest' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/load_balancers/${load_balancer_id}/forwarding_rules\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: load-balancer rule delete + /v2/regions: + get: + tags: + - Regions + summary: List all Regions + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/RegionsResponse' + x-codeSamples: + - lang: Curl + source: 'curl -X GET "https://api.binarylane.com.au/v2/regions?page=${page}&per_page=${per_page}"' + x-cli-command: region list + /v2/reverse_names/ipv6: + get: + tags: + - ReverseNames + summary: Fetch all Existing IPv6 Name Server Records + description: "IPv6 addresses within the allocated IPv6 floating range do not have PTR records under our default configuration.\nYou may provide resolution by delegating PTR lookups to your own nameservers.\nThese nameservers - and the floating range itself - are shared by all your servers; PTR lookups for addresses within your routed range will also be delegated." + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ReverseNameServersResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/reverse_names/ipv6?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server ipv6-ptr-ns list + post: + tags: + - ReverseNames + summary: Create New or Update Existing Global IPv6 Name Server Records + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ReverseNameserversRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/reverse_names/ipv6\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server ipv6-ptr-ns update + '/v2/samplesets/{server_id}/latest': + get: + tags: + - SampleSets + summary: Fetch the Latest Performance and Usage Data Sample Set for a Server + description: Returns the latest performance and usage sample set. + parameters: + - name: server_id + in: path + description: The target server id. + required: true + schema: + type: integer + description: The target server id. + format: int64 + x-cli-entity-lookup: server list + - name: data_interval + in: query + description: "\n| Value | Description |\n| ----- | ----------- |\n| five-minute | 5 Minutes |\n| half-hour | 30 Minutes |\n| four-hour | 4 Hours |\n| day | 1 Day |\n| week | 7 Days |\n| month | 1 Month |\n\n" + schema: + allOf: + - $ref: '#/components/schemas/DataInterval' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SampleSetResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/samplesets/${server_id}/latest?data_interval=${data_interval}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server metrics get + '/v2/samplesets/{server_id}': + get: + tags: + - SampleSets + summary: Fetch all of the Performance and Usage Data Sample Sets for a Server + parameters: + - name: server_id + in: path + description: The target server id. + required: true + schema: + type: integer + description: The target server id. + format: int64 + x-cli-entity-lookup: server list + - name: data_interval + in: query + description: "\n| Value | Description |\n| ----- | ----------- |\n| five-minute | 5 Minutes |\n| half-hour | 30 Minutes |\n| four-hour | 4 Hours |\n| day | 1 Day |\n| week | 7 Days |\n| month | 1 Month |\n\n" + schema: + allOf: + - $ref: '#/components/schemas/DataInterval' + - name: start + in: query + description: 'The start of the window of samples to retrieve, ISO8601 format (eg 2022-12-30T22:50:00Z). Defaults to 1 week before end for intervals larger than 5 minutes, or 1 day for 5 minute intervals.' + schema: + type: string + format: date-time + - name: end + in: query + description: 'The start of the window of samples to retrieve, ISO8601 format (eg 2022-12-30T22:50:00Z). Defaults to 1 week or 1 day after start date depending on the selected data interval (or the current time if start is not provided). Can''t be more than 1 year from start.' + schema: + type: string + format: date-time + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SampleSetsResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/samplesets/${server_id}?data_interval=${data_interval}&start=${start}&end=${end}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server metrics list + '/v2/servers/{server_id}/actions': + get: + tags: + - ServerActions + summary: List All Actions for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which actions should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/actions?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server action list + post: + tags: + - ServerActions + summary: Perform an Action on a Server + description: "Review the ServerAction schema for a list of available actions.\nReview the description in each individual schema for each type of server action for further information.\nFor actions that 'query' something (e.g. \"Ping\") fetch the completed action from the action endpoint for the response." + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ServerAction' + oneOf: + - $ref: '#/components/schemas/AddDisk' + - $ref: '#/components/schemas/AttachBackup' + - $ref: '#/components/schemas/ChangeAdvancedFeatures' + - $ref: '#/components/schemas/ChangeAdvancedFirewallRules' + - $ref: '#/components/schemas/ChangeBackupSchedule' + - $ref: '#/components/schemas/ChangeIpv6' + - $ref: '#/components/schemas/ChangeIpv6ReverseNameservers' + - $ref: '#/components/schemas/ChangeKernel' + - $ref: '#/components/schemas/ChangeManageOffsiteBackupCopies' + - $ref: '#/components/schemas/ChangeNetwork' + - $ref: '#/components/schemas/ChangeOffsiteBackupLocation' + - $ref: '#/components/schemas/ChangePartner' + - $ref: '#/components/schemas/ChangePortBlocking' + - $ref: '#/components/schemas/ChangeRegion' + - $ref: '#/components/schemas/ChangeReverseName' + - $ref: '#/components/schemas/ChangeSeparatePrivateNetworkInterface' + - $ref: '#/components/schemas/ChangeSourceAndDestinationCheck' + - $ref: '#/components/schemas/ChangeThresholdAlerts' + - $ref: '#/components/schemas/ChangeVpcIpv4' + - $ref: '#/components/schemas/CloneUsingBackup' + - $ref: '#/components/schemas/DeleteDisk' + - $ref: '#/components/schemas/DetachBackup' + - $ref: '#/components/schemas/DisableBackups' + - $ref: '#/components/schemas/DisableSelinux' + - $ref: '#/components/schemas/EnableBackups' + - $ref: '#/components/schemas/EnableIpv6' + - $ref: '#/components/schemas/IsRunning' + - $ref: '#/components/schemas/PasswordReset' + - $ref: '#/components/schemas/Ping' + - $ref: '#/components/schemas/PowerCycle' + - $ref: '#/components/schemas/PowerOff' + - $ref: '#/components/schemas/PowerOn' + - $ref: '#/components/schemas/Reboot' + - $ref: '#/components/schemas/Rebuild' + - $ref: '#/components/schemas/Rename' + - $ref: '#/components/schemas/Resize' + - $ref: '#/components/schemas/ResizeDisk' + - $ref: '#/components/schemas/Restore' + - $ref: '#/components/schemas/Shutdown' + - $ref: '#/components/schemas/TakeBackup' + - $ref: '#/components/schemas/Uncancel' + - $ref: '#/components/schemas/Uptime' + discriminator: + propertyName: type + mapping: + uptime: '#/components/schemas/Uptime' + uncancel: '#/components/schemas/Uncancel' + take_backup: '#/components/schemas/TakeBackup' + shutdown: '#/components/schemas/Shutdown' + restore: '#/components/schemas/Restore' + resize_disk: '#/components/schemas/ResizeDisk' + resize: '#/components/schemas/Resize' + rename: '#/components/schemas/Rename' + rebuild: '#/components/schemas/Rebuild' + reboot: '#/components/schemas/Reboot' + power_on: '#/components/schemas/PowerOn' + power_off: '#/components/schemas/PowerOff' + power_cycle: '#/components/schemas/PowerCycle' + ping: '#/components/schemas/Ping' + password_reset: '#/components/schemas/PasswordReset' + is_running: '#/components/schemas/IsRunning' + enable_ipv6: '#/components/schemas/EnableIpv6' + enable_backups: '#/components/schemas/EnableBackups' + disable_selinux: '#/components/schemas/DisableSelinux' + disable_backups: '#/components/schemas/DisableBackups' + detach_backup: '#/components/schemas/DetachBackup' + delete_disk: '#/components/schemas/DeleteDisk' + clone_using_backup: '#/components/schemas/CloneUsingBackup' + change_vpc_ipv4: '#/components/schemas/ChangeVpcIpv4' + change_threshold_alerts: '#/components/schemas/ChangeThresholdAlerts' + change_source_and_destination_check: '#/components/schemas/ChangeSourceAndDestinationCheck' + change_separate_private_network_interface: '#/components/schemas/ChangeSeparatePrivateNetworkInterface' + change_reverse_name: '#/components/schemas/ChangeReverseName' + change_region: '#/components/schemas/ChangeRegion' + change_port_blocking: '#/components/schemas/ChangePortBlocking' + change_partner: '#/components/schemas/ChangePartner' + change_offsite_backup_location: '#/components/schemas/ChangeOffsiteBackupLocation' + change_network: '#/components/schemas/ChangeNetwork' + change_manage_offsite_backup_copies: '#/components/schemas/ChangeManageOffsiteBackupCopies' + change_kernel: '#/components/schemas/ChangeKernel' + change_ipv6_reverse_nameservers: '#/components/schemas/ChangeIpv6ReverseNameservers' + change_ipv6: '#/components/schemas/ChangeIpv6' + change_backup_schedule: '#/components/schemas/ChangeBackupSchedule' + change_advanced_firewall_rules: '#/components/schemas/ChangeAdvancedFirewallRules' + change_advanced_features: '#/components/schemas/ChangeAdvancedFeatures' + attach_backup: '#/components/schemas/AttachBackup' + add_disk: '#/components/schemas/AddDisk' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + '/v2/servers/{server_id}': + get: + tags: + - Servers + summary: Fetch an Existing Server + parameters: + - name: server_id + in: path + description: The ID of the server to fetch. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ServerResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server get + delete: + tags: + - Servers + summary: Cancel an Existing Server + parameters: + - name: server_id + in: path + description: The ID of the server to be cancelled. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: reason + in: query + description: The reason for cancelling the server. + schema: + maxLength: 250 + minLength: 0 + type: string + responses: + '204': + description: No Content + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/servers/${server_id}?reason=${reason}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server delete + /v2/servers: + get: + tags: + - Servers + summary: List All Servers + parameters: + - name: hostname + in: query + description: Providing a hostname restricts the results to the server that has this hostname (case insensitive). If this parameter is provided at most 1 server will be returned. + schema: + type: string + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ServersResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers?hostname=${hostname}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server list + post: + tags: + - Servers + summary: Create a New Server + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/CreateServerRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/CreateServerResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server create + '/v2/servers/{server_id}/actions/{action_id}': + get: + tags: + - Servers + summary: Fetch an Action for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which the action should be fetched. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: action_id + in: path + description: The ID of the action to fetch. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/actions/${action_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server action get + '/v2/servers/{server_id}/advanced_firewall_rules': + get: + tags: + - Servers + summary: Fetch All Advanced Firewall Rules for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which firewall rules should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AdvancedFirewallRulesResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/advanced_firewall_rules\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server firewall list + '/v2/servers/{server_id}/available_advanced_features': + get: + tags: + - Servers + summary: Fetch the Currently Available Advanced Features for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which advanced features should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/AvailableAdvancedServerFeaturesResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/available_advanced_features\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server feature list + '/v2/servers/{server_id}/backups': + get: + tags: + - Servers + summary: List All Backups for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which backups should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/BackupsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/backups?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server backup list + post: + tags: + - Servers + summary: Upload a Backup for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which the backup is to be uploaded. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/UploadImageRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/backups\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server backup upload + '/v2/servers/{server_id}/kernels': + get: + tags: + - Servers + summary: List all Available Kernels for a Server + description: Only servers based on one of our Linux distribution images may change kernels. + parameters: + - name: server_id + in: path + description: The ID of the server for which kernels should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/KernelsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/kernels?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server kernel list + '/v2/servers/{server_id}/snapshots': + get: + tags: + - Servers + summary: List all Snapshots for a Server + description: Server snapshots are not implemented. This will always return 0 results. + parameters: + - name: server_id + in: path + description: The ID of the server for which snapshots should be listed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SnapshotsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/snapshots?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server snapshot list + '/v2/servers/{server_id}/threshold_alerts': + get: + tags: + - Servers + summary: Fetch the Currently Set Threshold Alerts for a Server + description: Includes the current values for the most recent threshold alert period. + parameters: + - name: server_id + in: path + description: The ID of the server for which threshold alerts should be fetched. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ThresholdAlertsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/threshold_alerts\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server alert get + /v2/servers/threshold_alerts: + get: + tags: + - Servers + summary: List any Servers that have a Current Exceeded Threshold Alert + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/CurrentServerAlertsResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/threshold_alerts\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server alert list + '/v2/servers/{server_id}/software': + get: + tags: + - Servers + summary: List all Enabled Software for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which software should be fetched. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/LicensedSoftwaresResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/software?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server software + '/v2/servers/{server_id}/user_data': + get: + tags: + - Servers + summary: Fetch the Currently Set UserData for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which userdata should be fetched. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/UserData' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/user_data\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server user-data + '/v2/servers/{server_id}/console': + get: + tags: + - Servers + summary: Fetch the Console URLs for a Server + parameters: + - name: server_id + in: path + description: The ID of the server for which console URLs will be fetched. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ConsoleResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/servers/${server_id}/console\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: server console + /v2/sizes: + get: + tags: + - Sizes + summary: List All Available Sizes + parameters: + - name: server_id + in: query + description: If supplied only sizes available for a resize the specified server will be returned. This parameter is only available when authenticated. + schema: + type: integer + format: int64 + - name: image + in: query + description: If null or not provided regions that support the size are included in the returned objects regardless of operating system. If this is provided it must be the id or slug of an operating system image and will cause only valid regions for the size and operating system to be included in the returned objects. + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SizesResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/sizes?server_id=${server_id}&image=${image}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: size list + '/v2/software/{software_id}': + get: + tags: + - Software + summary: Fetch Existing Software + parameters: + - name: software_id + in: path + description: The ID of the software to fetch. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SoftwareResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/software/${software_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: software get + /v2/software: + get: + tags: + - Software + summary: List All Available Software + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SoftwaresResponse' + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/software?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: software list + '/v2/software/operating_system/{operating_system_id_or_slug}': + get: + tags: + - Software + summary: List All Available Software for an Existing Operating System + description: "If any software for the operating system has a non null value for the group property, one (and only one) of the software from each group must be selected.\nThere may be a software in each group with a licence_step_count value of -1 that may be selected to indicate the software from that group is not required." + parameters: + - name: operating_system_id_or_slug + in: path + description: The ID or slug of the operating system for which available software should be listed. + required: true + schema: + oneOf: + - type: integer + - type: string + example: 5 + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/SoftwaresResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/software/operating_system/${operating_system_id_or_slug}?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: software operating-system + '/v2/vpcs/{vpc_id}': + get: + tags: + - Vpcs + summary: Fetch an Existing VPC + parameters: + - name: vpc_id + in: path + description: The target vpc id. + required: true + schema: + type: integer + description: The target vpc id. + format: int64 + x-cli-entity-lookup: vpc list + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/vpcs/${vpc_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: vpc get + put: + tags: + - Vpcs + summary: Update an Existing VPC + description: 'Anything not included in this will be removed, especially route entries.' + parameters: + - name: vpc_id + in: path + description: The target vpc id. + required: true + schema: + type: integer + description: The target vpc id. + format: int64 + x-cli-entity-lookup: vpc list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/UpdateVpcRequest' + description: Any properties that are not included will be cleared. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PUT \"https://api.binarylane.com.au/v2/vpcs/${vpc_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: vpc update + patch: + tags: + - Vpcs + summary: Update an Existing VPC + description: Anything not included in this will be un-altered. + parameters: + - name: vpc_id + in: path + description: The target vpc id. + required: true + schema: + type: integer + description: The target vpc id. + format: int64 + x-cli-entity-lookup: vpc list + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PatchVpcRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X PATCH \"https://api.binarylane.com.au/v2/vpcs/${vpc_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: vpc patch + delete: + tags: + - Vpcs + summary: Delete an Existing VPC + parameters: + - name: vpc_id + in: path + description: The target vpc id. + required: true + schema: + type: integer + description: The target vpc id. + format: int64 + x-cli-entity-lookup: vpc list + responses: + '204': + description: No Content + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X DELETE \"https://api.binarylane.com.au/v2/vpcs/${vpc_id}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: vpc delete + /v2/vpcs: + get: + tags: + - Vpcs + summary: List All Existing VPCs + parameters: + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcsResponse' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/vpcs?page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: vpc list + post: + tags: + - Vpcs + summary: Create a New VPC + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/CreateVpcRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/vpcs\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: vpc create + '/v2/vpcs/{vpc_id}/members': + get: + tags: + - Vpcs + summary: List All Members of an Existing VPC + description: Only resource type 'server' is currently supported. + parameters: + - name: vpc_id + in: path + description: The target vpc id. + required: true + schema: + type: integer + description: The target vpc id. + format: int64 + x-cli-entity-lookup: vpc list + - name: resource_type + in: query + description: "\n| Value | Description |\n| ----- | ----------- |\n| server | Server |\n| load-balancer | Load Balancer |\n| ssh-key | SSH Key |\n| vpc | Virtual Private Network |\n| image | Backup or Operating System Image |\n| registered-domain-name | Registered Domain Name |\n\n" + schema: + allOf: + - $ref: '#/components/schemas/ResourceType' + - name: page + in: query + description: The selected page. Page numbering starts at 1 + schema: + maximum: 2147483647 + minimum: 1 + type: integer + description: The selected page. Page numbering starts at 1 + format: int32 + default: 1 + - name: per_page + in: query + description: The number of results to show per page. + schema: + maximum: 200 + minimum: 1 + type: integer + description: The number of results to show per page. + format: int32 + default: 20 + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/VpcMembersResponse' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X GET \"https://api.binarylane.com.au/v2/vpcs/${vpc_id}/members?resource_type=${resource_type}&page=${page}&per_page=${per_page}\" \\\n-H \"Authorization: Bearer ${APITOKEN}\"" + x-cli-command: vpc members + '/v2/servers/{server_id}/actions#ChangeThresholdAlerts': + post: + tags: + - ServerActions + summary: Set or Update the Threshold Alerts for a Server + description: This is used to set threshold alerts (also known as resource alerts) for the server. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeThresholdAlerts' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-threshold-alerts + '/v2/servers/{server_id}/actions#IsRunning': + post: + tags: + - ServerActions + summary: Check if a Server is Running + description: 'This is used to check if the server is running, but not the state of the server. For example, the server may be running but be unresponsive, rebooting etc.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/IsRunning' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action is-running + '/v2/servers/{server_id}/actions#Ping': + post: + tags: + - ServerActions + summary: Attempt to Ping a Server + description: 'This is used to determine if the server responds to a ping. The ping attempt is normally made to the public interface, so it may be blocked by your firewall settings etc.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Ping' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action ping + '/v2/servers/{server_id}/actions#Uncancel': + post: + tags: + - ServerActions + summary: Revert the Cancellation of a Server + description: '**NB: this is only available before the server is suspended and we make no guarantees that un-cancellation is possible.**' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Uncancel' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action uncancel + '/v2/servers/{server_id}/actions#Uptime': + post: + tags: + - ServerActions + summary: Check the Uptime of a Server + description: This is used to determine how long the server has been continuously running. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Uptime' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action uptime + '/v2/servers/{server_id}/actions#AttachBackup': + post: + tags: + - ServerActions + summary: Attach a Backup to a Server + description: This is most often used to restore an individual file from a backup. It may also be used to boot the server from an ISO image. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AttachBackup' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action attach-backup + '/v2/servers/{server_id}/actions#ChangeBackupSchedule': + post: + tags: + - ServerActions + summary: Change the Backup Schedule of a Server + description: 'This changes when the backups are taken, not the frequency or retention of backups.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeBackupSchedule' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-backup-schedule + '/v2/servers/{server_id}/actions#ChangeManageOffsiteBackupCopies': + post: + tags: + - ServerActions + summary: Change the Management of Offsite Backup Copies + description: This is used to alter whether old offsite backups will be removed when the replacement upload is complete if offsite backups have been selected and a custom offsite location is used. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeManageOffsiteBackupCopies' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-manage-offsite-backup-copies + '/v2/servers/{server_id}/actions#ChangeOffsiteBackupLocation': + post: + tags: + - ServerActions + summary: Change the Offsite Backup Location of a Server + description: 'If offsite backups have been selected this is used to change the offsite backup location, either to a valid Amazon S3 bucket or our internal offsite backup location.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeOffsiteBackupLocation' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-offsite-backup-location + '/v2/servers/{server_id}/actions#CloneUsingBackup': + post: + tags: + - ServerActions + summary: Restore a Backup of a Server to a Different Existing Server + description: "This is used to clone a new server from the backup of an existing server. \nThe action is 'performed' on the source server. The target server must have finished the initial building process: this will fail if the target server is does not have the status 'available'. \n**NB: This is a destructive operation on the target server and no further confirmation will be requested.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CloneUsingBackup' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action clone-using-backup + '/v2/servers/{server_id}/actions#DetachBackup': + post: + tags: + - ServerActions + summary: Detach Any Attached Backup from a Server + description: This is used to detach a backup that was attached with the Attach Backup action. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DetachBackup' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action detach-backup + '/v2/servers/{server_id}/actions#DisableBackups': + post: + tags: + - ServerActions + summary: Disable Backups for an Existing Server + description: "This will disable backups for a server only if it has the backup configuration that is created by the EnabledBackups action, i.e. two daily backups. \nThis is not a 'pause' for backups, this will change the server's options to remove the backups. \nFor greater control over the backup options use Options property on the Resize action. \n**NB: This is a destructive operation (previous backups will no longer be available) and no further confirmation will be requested.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DisableBackups' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action disable-backups + '/v2/servers/{server_id}/actions#EnableBackups': + post: + tags: + - ServerActions + summary: Enable Two Daily Backups for an Existing Server + description: "This will enable two daily backups if the server currently has no backups. This is not an 'un-pause' for backups. It will change the server's options to enable two daily backups. \nFor greater control over the backup options use Options property on the Resize action. \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EnableBackups' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action enable-backups + '/v2/servers/{server_id}/actions#Restore': + post: + tags: + - ServerActions + summary: Restore a Backup to a Server + description: "This will remove the server's existing disks and restore the selected backup. \n**NB: This is a destructive operation and no further confirmation will be requested.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Restore' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action restore + '/v2/servers/{server_id}/actions#TakeBackup': + post: + tags: + - ServerActions + summary: Take a Backup of a Server + description: This creates an immediate backup of the server. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TakeBackup' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action take-backup + '/v2/servers/{server_id}/actions#PasswordReset': + post: + tags: + - ServerActions + summary: Reset the Password of a Server + description: This will reset the password of the specified or default remote user if possible (e.g. on most Unix derived operating systems) or clear the administrator password on Windows operating systems. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordReset' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action password-reset + '/v2/servers/{server_id}/actions#PowerCycle': + post: + tags: + - ServerActions + summary: Power a Server Off and then On + description: "This is a 'hard' reboot, equivalent to turning the server's power switch off and on. \nThis should be used only if the server is unresponsive when the 'Reboot' action is attempted. \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerCycle' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action power-cycle + '/v2/servers/{server_id}/actions#PowerOff': + post: + tags: + - ServerActions + summary: Power a Server Off + description: 'This is a ''hard'' power off, equivalent to turning the server''s power switch off.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerOff' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action power-off + '/v2/servers/{server_id}/actions#PowerOn': + post: + tags: + - ServerActions + summary: Power a Server On + description: This may be used if the server has previous been powered off either from inside the server's operating system or via an action. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PowerOn' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action power-on + '/v2/servers/{server_id}/actions#Reboot': + post: + tags: + - ServerActions + summary: Request a Server Perform a Reboot + description: 'This is a ''soft'' reboot, equivalent to tapping the power button on the server or selecting ''restart'' in the operating system to trigger an orderly shutdown, waiting for that shutdown, then starting the server.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Reboot' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action reboot + '/v2/servers/{server_id}/actions#Shutdown': + post: + tags: + - ServerActions + summary: Request a Server Perform a Shutdown + description: 'This is a ''soft'' shutdown, equivalent to tapping the power button on the server or selecting ''shutdown'' in the operating system to trigger an orderly shutdown, then waiting for that shutdown.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Shutdown' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action shutdown + '/v2/servers/{server_id}/actions#ChangeAdvancedFeatures': + post: + tags: + - ServerActions + summary: Change the Advanced Features of a Server + description: 'This is used to change the virtual processor model, emulated video card and other virtualization options.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeAdvancedFeatures' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-advanced-features + '/v2/servers/{server_id}/actions#ChangeAdvancedFirewallRules': + post: + tags: + - ServerActions + summary: Change the Advanced Firewall Rules for a Server + description: This is used to configure the external firewall. Please see our documentation for more information. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeAdvancedFirewallRules' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-advanced-firewall-rules + '/v2/servers/{server_id}/actions#ChangeKernel': + post: + tags: + - ServerActions + summary: Change the Kernel of a Server + description: 'This is most often used to boot using our provided Finnix recovery CD. Please review the ''servers/{server_id}/kernels'' endpoint for more information.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeKernel' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-kernel + '/v2/servers/{server_id}/actions#ChangePartner': + post: + tags: + - ServerActions + summary: 'Add, Update or Remove a Partner Server for a Server' + description: Making servers 'partners' prevents them from being co-located on the same hardware. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePartner' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-partner + '/v2/servers/{server_id}/actions#ChangeRegion': + post: + tags: + - ServerActions + summary: Change the Region of a Server + description: "This is used to move a server to a different region. \nPlease check our knowledge base article for the current requirements for servers to be able to move between regions. \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeRegion' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-region + '/v2/servers/{server_id}/actions#DisableSelinux': + post: + tags: + - ServerActions + summary: Disable SE Linux for a Server + description: If you have upgraded to CentOS 7.6 and rebooted your server (or an automated update has done so on your behalf) then SELinux may be preventing your system from working correctly. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DisableSelinux' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action disable-selinux + '/v2/servers/{server_id}/actions#Rebuild': + post: + tags: + - ServerActions + summary: Rebuild an Existing Server + description: "This will restore the server to the base image provided. \n**NB: This is a destructive operation and no further confirmation will be requested.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Rebuild' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action rebuild + '/v2/servers/{server_id}/actions#Resize': + post: + tags: + - ServerActions + summary: Update the Size and Related Options for a Server + description: "This is used to change the base size (also known as 'change plan') or many of the additional options that are available. \n**NB: This *may* be a destructive operation (e.g. if a new base image is provided the server will be rebuilt, or if the weekly backups are reduced to 0 all weekly backups will be removed) and no further confirmation will be requested.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Resize' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action resize + '/v2/servers/{server_id}/actions#AddDisk': + post: + tags: + - ServerActions + summary: Create an Additional Disk for a Server + description: "This is used to add an additional disk in unallocated storage space. \nThis does not alter the total disk space available to your server: to add additional disk space for your server use the 'Resize' action. \n**This action may require the server to be rebooted.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AddDisk' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action add-disk + '/v2/servers/{server_id}/actions#ResizeDisk': + post: + tags: + - ServerActions + summary: Alter the Size of an Existing Disk for a Server + description: "This is used to alter the size of an existing disk, either to use or create unallocated storage space. \nThis does not alter the total disk space available to your server: to add or remove disk space for your server use the 'Resize' action. \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ResizeDisk' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action resize-disk + '/v2/servers/{server_id}/actions#DeleteDisk': + post: + tags: + - ServerActions + summary: Delete an Additional Disk for a Server + description: "This is used to delete a disk added using the 'AddDisk' action. \n**NB: This is a destructive operation and no further confirmation will be requested.** \n**This action may require the server to be rebooted.** \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteDisk' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action delete-disk + '/v2/servers/{server_id}/actions#ChangeIpv6ReverseNameservers': + post: + tags: + - ServerActions + summary: Update the IPv6 Reverse Name Servers for a Server + description: This is used to alter the IPv6 reverse name servers for this server. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeIpv6ReverseNameservers' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-ipv6-reverse-nameservers + '/v2/servers/{server_id}/actions#ChangeReverseName': + post: + tags: + - ServerActions + summary: Change the Reverse Name for an IPv4 Address on a Server + description: This is used to change the reverse name for an IPv4 on this server. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeReverseName' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-reverse-name + '/v2/servers/{server_id}/actions#Rename': + post: + tags: + - ServerActions + summary: Rename a Server + description: This is used to change the name of the server in our system. The name in the server's operating system will not be changed. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Rename' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action rename + '/v2/servers/{server_id}/actions#ChangeIpv6': + post: + tags: + - ServerActions + summary: Enable or Disable IPv6 for a Server + description: This is used to enable or disable IPv6 networking support for this server. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeIpv6' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-ipv6 + '/v2/servers/{server_id}/actions#ChangePortBlocking': + post: + tags: + - ServerActions + summary: Change the Port Blocking for a Server + description: "Port blocking is a security feature. In the majority of cases when a server is hacked, the attacker will then use the server to either send spam or perform brute-force authentication attacks against other servers on the internet. \nThis is used to enable or disable blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389). \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePortBlocking' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-port-blocking + '/v2/servers/{server_id}/actions#EnableIpv6': + post: + tags: + - ServerActions + summary: Enable IPv6 for a Server + description: "This is used to enable IPv6 networking support for this server. \nThis action is provided for compatibility reasons and as the same effect as enabling IPv6 via the \"ChangeIpv6\" action. \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EnableIpv6' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action enable-ipv6 + '/v2/servers/{server_id}/actions#ChangeNetwork': + post: + tags: + - ServerActions + summary: Move a Server to an Existing Network + description: 'This is used to move a server either to an already defined virtual private cloud, or the public network for the server''s region.' + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeNetwork' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-network + '/v2/servers/{server_id}/actions#ChangeSeparatePrivateNetworkInterface': + post: + tags: + - ServerActions + summary: Enable or Disable a Separate Private Network Interface for a Server in a VPC + description: This is used to enable or disable a separate second network interface for VPC network traffic (if the server is in a VPC). + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeSeparatePrivateNetworkInterface' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-separate-private-network-interface + '/v2/servers/{server_id}/actions#ChangeSourceAndDestinationCheck': + post: + tags: + - ServerActions + summary: Enable or Disable Network Source and Destination Checks for a Server in a VPC + description: "This is used to enable or disable source and destination checks for network packets. \nSource/Destination Check is a feature that controls what packets are allowed to be sent and received by your Cloud Server. When enabled (which it is by default), your server will only be able to send or receive packets that are directly addressed to one of the IP addresses associated with the Cloud Server. Generally, this is desirable behaviour because it prevents IP conflicts and other hard-to-diagnose networking faults due to incorrect network configuration. \nWhen Source/Destination Check is disabled, your Cloud Server will be able to send and receive packets addressed to any server whatsoever. This is typically used when you want to use your Cloud Server as a VPN endpoint, a NAT server to provide internet access, or IP forwarding. \n \n" + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeSourceAndDestinationCheck' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-source-and-destination-check + '/v2/servers/{server_id}/actions#ChangeVpcIpv4': + post: + tags: + - ServerActions + summary: Change the IPv4 Address for a Server in a VPC + description: This is used to change the private IPv4 address for a server in a VPC. + parameters: + - name: server_id + in: path + description: The ID of the server on which the action should be performed. + required: true + schema: + type: integer + format: int64 + x-cli-entity-lookup: server list + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeVpcIpv4' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ActionResponse' + '202': + description: Accepted + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationProblemDetails' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + x-codeSamples: + - lang: Curl + source: "curl -X POST \"https://api.binarylane.com.au/v2/servers/${server_id}/actions\" \\\n-H \"Authorization: Bearer ${APITOKEN}\" \\\n-H \"Content-Type: application/json\" -d \"${PAYLOAD}\"" + x-cli-command: server action change-vpc-ipv4 +components: + schemas: + Account: + required: + - additional_ipv4_limit + - configured_payment_methods + - email + - email_verified + - status + - tax_code + - two_factor_authentication_enabled + type: object + properties: + email: + type: string + description: The email address registered for this account. + email_verified: + type: boolean + description: Whether this account has been verified. Unverified accounts are subject to some restrictions. + two_factor_authentication_enabled: + type: boolean + description: Whether this account has enabled app-based two factor authentication. + status: + allOf: + - $ref: '#/components/schemas/AccountStatus' + description: "The status of this account.\n\n| Value | Description |\n| ----- | ----------- |\n| incomplete | An account that exists but is not ready for use. The most common reason for this is a lack of payment information. |\n| active | An account in the normal state. |\n| warning | An account that is under review. If you are unsure why your account has this status please urgently contact support. |\n| locked | An account that is no longer permitted to access the service. |\n\n" + tax_code: + allOf: + - $ref: '#/components/schemas/TaxCode' + description: The tax code that currently applies to transactions for this account. + configured_payment_methods: + type: array + items: + $ref: '#/components/schemas/PaymentMethod' + description: The payment methods that are configured (available) for this account. + additional_ipv4_limit: + type: integer + description: The maximum additional IPv4 addresses this account may assign across all servers. You may contact support to request this limit be increased. + format: int32 + AccountResponse: + required: + - account + type: object + properties: + account: + allOf: + - $ref: '#/components/schemas/Account' + AccountStatus: + enum: + - incomplete + - active + - warning + - locked + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| incomplete | An account that exists but is not ready for use. The most common reason for this is a lack of payment information. |\n| active | An account in the normal state. |\n| warning | An account that is under review. If you are unsure why your account has this status please urgently contact support. |\n| locked | An account that is no longer permitted to access the service. |\n\n" + x-enum-descriptions: + - An account that exists but is not ready for use. The most common reason for this is a lack of payment information. + - An account in the normal state. + - An account that is under review. If you are unsure why your account has this status please urgently contact support. + - An account that is no longer permitted to access the service. + Action: + required: + - id + - progress + - reason + - started_at + - status + - title + - type + type: object + properties: + id: + type: integer + description: The ID of this action. + format: int64 + x-cli-format: 1 + status: + allOf: + - $ref: '#/components/schemas/ActionStatus' + x-cli-format: 6 + type: + type: string + description: The type of this action. + x-cli-format: 2 + started_at: + type: string + description: The timestamp in ISO8601 format of when processing of this action started. + format: date-time + x-cli-format: 3 + completed_at: + type: string + description: The timestamp in ISO8601 format of when processing of this action completed. If this value is null the action is currently in progress. + format: date-time + nullable: true + x-cli-format: 4 + resource_type: + allOf: + - $ref: '#/components/schemas/ResourceType' + description: "The resource type (if any) associated with this action.\n\n| Value | Description |\n| ----- | ----------- |\n| server | Server |\n| load-balancer | Load Balancer |\n| ssh-key | SSH Key |\n| vpc | Virtual Private Network |\n| image | Backup or Operating System Image |\n| registered-domain-name | Registered Domain Name |\n\n" + nullable: true + resource_id: + type: integer + description: The resource id of the resource (if any) associated with this action. + format: int64 + nullable: true + x-cli-format: 5 + region: + allOf: + - $ref: '#/components/schemas/Region' + description: The region (if any) of the resource associated with this action. + nullable: true + region_slug: + type: string + description: The region slug (if any) of the resource associated with this action. + nullable: true + title: + type: string + description: A short display name for the action. + reason: + type: string + description: A user-friendly explanation of what is happening. + progress: + allOf: + - $ref: '#/components/schemas/ActionProgress' + description: Information about the current progress of the action. Some actions are divided into 'steps' and this may also contain information about the current and completed steps. + result_data: + type: string + description: 'Returned information from a completed action. For example: a successful completed ''uptime'' action will have the uptime value in this field.' + nullable: true + x-cli-format: 7 + blocking_invoice_id: + type: integer + description: If this Action is currently blocked by an invoice that requires payment this property will be set. + format: int64 + nullable: true + user_interaction_required: + allOf: + - $ref: '#/components/schemas/UserInteractionRequired' + description: If this is not null the action is waiting on a response from the user. + nullable: true + ActionLink: + required: + - href + - id + - rel + type: object + properties: + id: + type: integer + format: int64 + rel: + type: string + href: + type: string + ActionProgress: + required: + - completed_steps + - percent_complete + type: object + properties: + current_step_detail: + type: string + description: 'Detail about the progress of the current step of the action. For example, when creating an offsite backup this may be populated with the current upload speed and completion ETA of the upload step.' + nullable: true + percent_complete: + type: integer + description: An estimation of the overall completion of the action. + format: int32 + current_step: + type: string + description: An description of the current action step. + nullable: true + completed_steps: + type: array + items: + type: string + description: A list of the completed action steps. + ActionResponse: + required: + - action + type: object + properties: + action: + allOf: + - $ref: '#/components/schemas/Action' + ActionStatus: + enum: + - in-progress + - completed + - errored + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| in-progress | This action is currently in progress. |\n| completed | This action has successfully completed. |\n| errored | An error was encountered while processing the action. |\n\n" + x-enum-descriptions: + - This action is currently in progress. + - This action has successfully completed. + - An error was encountered while processing the action. + ActionsLinks: + required: + - actions + type: object + properties: + actions: + type: array + items: + $ref: '#/components/schemas/ActionLink' + ActionsResponse: + required: + - actions + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + actions: + type: array + items: + $ref: '#/components/schemas/Action' + AddDisk: + required: + - size_gigabytes + - type + type: object + properties: + type: + minLength: 1 + enum: + - add_disk + type: string + size_gigabytes: + maximum: 2147483647 + minimum: 1 + type: integer + description: The size of the new disk in GB. The server must have at least this much unallocated storage space. + format: int32 + description: + maxLength: 255 + minLength: 0 + type: string + description: An optional description for the disk. If this is null a default description will be added. Submit an empty string to prevent the default description being added. + nullable: true + description: Create an Additional Disk for a Server + AdvancedFeature: + enum: + - emulated-hyperv + - emulated-devices + - nested-virt + - driver-disk + - unset-uuid + - local-rtc + - emulated-tpm + - cloud-init + - qemu-guest-agent + - uefi-boot + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| emulated-hyperv | Enable HyperV (a hypervisor produced by Microsoft) support. Enabled by default on Windows servers, generally of no value for non-Windows servers. |\n| emulated-devices | When emulated devices is enabled, the KVM specific \"VirtIO\" disk drive and network devices are removed, and replaced with emulated versions of physical hardware: an old IDE HDD and an Intel E1000 network card. Emulated devices are much slower than the VirtIO devices, and so this option should not be enabled unless absolutely necessary. |\n| nested-virt | When this option is enabled the functionality necessary to run your own KVM servers within your server is enabled. Note that all the networking limits - one MAC address per VPS, restricted to specific IPs - still apply to public cloud so this is feature is generally only useful in combination with Virtual Private Cloud. |\n| driver-disk | When this option is enabled a copy of the KVM driver disc for Windows (\"virtio-win.iso\") will be attached to your server as a virtual CD. This option can also be used in combination with your own attached backup when installing Windows. |\n| unset-uuid | When this option is NOT enabled a 128-bit unique identifier is exposed to your server through the virtual BIOS. Each server receives a different UUID. Some propriety licensed software utilise this identifier to \"tie\" the license to a specific server. |\n| local-rtc | When a server is booted the virtual BIOS receives the current date and time from the host node. The BIOS does not have an explicit timezone, so the timezone used is implicit and must be understood by the operating system. Most operating systems other than Windows expect the time to be UTC since it allows the operating system to control the timezone used when displaying the time. Our Windows installations have also been customized to use UTC, but when using your own installation of Windows this should be set to the host node's local timezone. |\n| emulated-tpm | When enabled this provides an emulated TPM v1.2 device to your Cloud Server. Warning: the TPM state is not backed up. |\n| cloud-init | (Read-Only) When this option is enabled the Cloud Server will be provided a datasource for the cloud-init service. |\n| qemu-guest-agent | (Read-Only) When this option is enabled the server will allow QEMU Guest Agent to perform password reset without rebooting. |\n| uefi-boot | (Read-Only) When this option is enabled the Cloud Server will use UEFI instead of legacy PC BIOS. |\n\n" + x-enum-descriptions: + - 'Enable HyperV (a hypervisor produced by Microsoft) support. Enabled by default on Windows servers, generally of no value for non-Windows servers.' + - 'When emulated devices is enabled, the KVM specific "VirtIO" disk drive and network devices are removed, and replaced with emulated versions of physical hardware: an old IDE HDD and an Intel E1000 network card. Emulated devices are much slower than the VirtIO devices, and so this option should not be enabled unless absolutely necessary.' + - 'When this option is enabled the functionality necessary to run your own KVM servers within your server is enabled. Note that all the networking limits - one MAC address per VPS, restricted to specific IPs - still apply to public cloud so this is feature is generally only useful in combination with Virtual Private Cloud.' + - When this option is enabled a copy of the KVM driver disc for Windows ("virtio-win.iso") will be attached to your server as a virtual CD. This option can also be used in combination with your own attached backup when installing Windows. + - When this option is NOT enabled a 128-bit unique identifier is exposed to your server through the virtual BIOS. Each server receives a different UUID. Some propriety licensed software utilise this identifier to "tie" the license to a specific server. + - 'When a server is booted the virtual BIOS receives the current date and time from the host node. The BIOS does not have an explicit timezone, so the timezone used is implicit and must be understood by the operating system. Most operating systems other than Windows expect the time to be UTC since it allows the operating system to control the timezone used when displaying the time. Our Windows installations have also been customized to use UTC, but when using your own installation of Windows this should be set to the host node''s local timezone.' + - 'When enabled this provides an emulated TPM v1.2 device to your Cloud Server. Warning: the TPM state is not backed up.' + - (Read-Only) When this option is enabled the Cloud Server will be provided a datasource for the cloud-init service. + - (Read-Only) When this option is enabled the server will allow QEMU Guest Agent to perform password reset without rebooting. + - (Read-Only) When this option is enabled the Cloud Server will use UEFI instead of legacy PC BIOS. + AdvancedFirewallRule: + required: + - action + - destination_addresses + - protocol + - source_addresses + type: object + properties: + source_addresses: + type: array + items: + type: string + description: The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation. + x-cli-format: 1 + destination_addresses: + type: array + items: + type: string + description: The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation. + x-cli-format: 2 + destination_ports: + type: array + items: + type: string + description: The destination ports to match for this rule. Leave null or empty to match on all ports. + nullable: true + x-cli-format: 4 + protocol: + allOf: + - $ref: '#/components/schemas/AdvancedFirewallRuleProtocol' + description: "The protocol to match for this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| all | This rule will match any protocol. |\n| icmp | This rule will match ICMP traffic only. |\n| tcp | This rule will match TCP traffic only. |\n| udp | This rule will match UDP traffic only. |\n\n" + x-cli-format: 3 + action: + allOf: + - $ref: '#/components/schemas/AdvancedFirewallRuleAction' + description: "The action to take when there is a match on this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| drop | Traffic matching this rule will be dropped. |\n| accept | Traffic matching this rule will be accepted. |\n\n" + x-cli-format: 5 + description: + type: string + description: 'A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".' + nullable: true + x-cli-format: 6 + AdvancedFirewallRuleAction: + enum: + - drop + - accept + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| drop | Traffic matching this rule will be dropped. |\n| accept | Traffic matching this rule will be accepted. |\n\n" + x-enum-descriptions: + - Traffic matching this rule will be dropped. + - Traffic matching this rule will be accepted. + AdvancedFirewallRuleProtocol: + enum: + - all + - icmp + - tcp + - udp + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| all | This rule will match any protocol. |\n| icmp | This rule will match ICMP traffic only. |\n| tcp | This rule will match TCP traffic only. |\n| udp | This rule will match UDP traffic only. |\n\n" + x-enum-descriptions: + - This rule will match any protocol. + - This rule will match ICMP traffic only. + - This rule will match TCP traffic only. + - This rule will match UDP traffic only. + AdvancedFirewallRuleRequest: + required: + - action + - destination_addresses + - protocol + - source_addresses + type: object + properties: + source_addresses: + minItems: 1 + type: array + items: + type: string + description: The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation. + destination_addresses: + minItems: 1 + type: array + items: + type: string + description: The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation. + destination_ports: + type: array + items: + type: string + description: The destination ports to match for this rule. Leave null or empty to match on all ports. + nullable: true + protocol: + allOf: + - $ref: '#/components/schemas/AdvancedFirewallRuleProtocol' + description: "The protocol to match for this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| all | This rule will match any protocol. |\n| icmp | This rule will match ICMP traffic only. |\n| tcp | This rule will match TCP traffic only. |\n| udp | This rule will match UDP traffic only. |\n\n" + action: + allOf: + - $ref: '#/components/schemas/AdvancedFirewallRuleAction' + description: "The action to take when there is a match on this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| drop | Traffic matching this rule will be dropped. |\n| accept | Traffic matching this rule will be accepted. |\n\n" + description: + maxLength: 250 + minLength: 0 + type: string + description: 'A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".' + nullable: true + AdvancedFirewallRulesResponse: + required: + - firewall_rules + type: object + properties: + firewall_rules: + type: array + items: + $ref: '#/components/schemas/AdvancedFirewallRule' + description: 'The advanced firewall rules for the selected server. Please note that the order of the rules is critical as the first matching rule is applied, and if no rules match the traffic is permitted.' + AdvancedServerFeatures: + required: + - enabled_advanced_features + - video_device + type: object + properties: + processor_model: + type: integer + description: "The ID of the processor model (and therefore CPU flags) available for this server.\nA null value indicates automatic selection of the best processor model supported by the host node.\nThis does not change the physical CPU, only the CPU flags available to the operating system." + format: int64 + nullable: true + machine_type: + allOf: + - $ref: '#/components/schemas/VmMachineType' + description: "The machine_type (corresponding to a KVM version) used for this server.\nA null value indicates automatic selection of the best KVM machine type supported by the host node.\n\n| Value | Description |\n| ----- | ----------- |\n| pc_i440fx_1point5 | PC i440FX 1.5 |\n| pc_i440fx_2point11 | PC i440FX 2.11 |\n| pc_i440fx_4point1 | PC i440FX 4.1 |\n| pc_i440fx_4point2 | PC i440FX 4.2 |\n| pc_i440fx_5point0 | PC i440FX 5.0 |\n| pc_i440fx_5point1 | PC i440FX 5.1 |\n| pc_i440fx_7point2 | PC i440FX 7.2 |\n| pc_i440fx_7point2point1 | PC i440FX 7.2.1 |\n| pc_i440fx_8point2 | PC i440FX 8.2 |\n\n" + nullable: true + video_device: + allOf: + - $ref: '#/components/schemas/VideoDevice' + description: "Video Device\n\n| Value | Description |\n| ----- | ----------- |\n| cirrus-logic | Cirrus Logic GD5446 |\n| standard | Standard VGA with VESA 2.0 extensions |\n| virtio | Virtio VGA (800x600) |\n| virtio-wide | Virtio VGA (1600x900) |\n\n" + enabled_advanced_features: + type: array + items: + $ref: '#/components/schemas/AdvancedFeature' + description: A list of the currently enabled advanced features for this server. + AttachBackup: + required: + - image + - type + type: object + properties: + type: + minLength: 1 + enum: + - attach_backup + type: string + image: + type: integer + description: Only attaching backup images is currently supported. + format: int64 + description: Attach a Backup to a Server + AttachedBackup: + required: + - disk_identifiers + - id + type: object + properties: + id: + type: integer + description: The ID of the backup image. + format: int64 + disk_identifiers: + type: array + items: + type: string + description: A list of the operating specific disk identifiers for the attached backup disks. + attached_at: + type: string + description: The date and time in ISO8601 format when this image was attached to the server. + format: date-time + nullable: true + attachment_expires: + type: string + description: The date and time in ISO8601 format when the backup will be automatically detached unless it is manually detached earlier. + format: date-time + nullable: true + AvailableAdvancedServerFeatures: + required: + - advanced_features + - machine_types + - processor_models + type: object + properties: + processor_models: + type: array + items: + $ref: '#/components/schemas/ProcessorModel' + description: A list of the processor models available for this server. + machine_types: + type: array + items: + $ref: '#/components/schemas/VmMachineType' + description: A list of the machine types available for this server. + advanced_features: + type: array + items: + $ref: '#/components/schemas/AdvancedFeature' + description: A list of the advanced features available for this server. + AvailableAdvancedServerFeaturesResponse: + required: + - available_advanced_server_features + type: object + properties: + available_advanced_server_features: + allOf: + - $ref: '#/components/schemas/AvailableAdvancedServerFeatures' + BackupDisk: + required: + - id + - min_disk_size + - size_gigabytes + type: object + properties: + id: + type: integer + description: The ID of this backup disk. + format: int64 + size_gigabytes: + type: number + description: This is the compressed size of the disk image in GB. + format: double + min_disk_size: + type: integer + description: This is the minimum disk size in GB required to restore this disk image. + format: int32 + description: + type: string + description: A description of this disk. + nullable: true + BackupInfo: + required: + - backup_disks + - iso + - locked + - offsite + - server_id + - type + type: object + properties: + type: + allOf: + - $ref: '#/components/schemas/BackupSlot' + server_id: + type: integer + description: The server ID that was used to create this backup. + format: int64 + offsite: + type: boolean + description: 'If this is true, an attempt to create an offsite copy was made. This does not mean that the offsite copy attempt was successful or that the copy still exists.' + locked: + type: boolean + description: If this is true the backup is locked and cannot be replaced. + iso: + type: boolean + description: If this is true the backup is an ISO image and cannot be restored or downloaded. ISO images may only be attached for use as a boot disk or an additional disk. + backup_disks: + type: array + items: + $ref: '#/components/schemas/BackupDisk' + description: A list of the individual disks that make up this backup. + BackupReplacementStrategy: + enum: + - none + - specified + - oldest + - newest + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| none | Do not replace any existing backup: use a free slot of the provided backup type. If there are no free slots an error will occur. |\n| specified | Replace the specific backup id provided. |\n| oldest | Use any free slots of the provided backup type, and if there are no free slots replace the oldest unlocked and un-attached backup of the provided backup type. |\n| newest | Use any free slots of the provided backup type, and if there are no free slots replace the newest unlocked and un-attached backup of the provided backup type. |\n\n" + x-enum-descriptions: + - 'Do not replace any existing backup: use a free slot of the provided backup type. If there are no free slots an error will occur.' + - Replace the specific backup id provided. + - 'Use any free slots of the provided backup type, and if there are no free slots replace the oldest unlocked and un-attached backup of the provided backup type.' + - 'Use any free slots of the provided backup type, and if there are no free slots replace the newest unlocked and un-attached backup of the provided backup type.' + BackupSettings: + required: + - backup_day_of_month + - backup_day_of_week + - backup_hour_of_day + type: object + properties: + backup_hour_of_day: + type: integer + description: The hour of the day that backups will be scheduled. This is an approximate value. + format: int32 + backup_day_of_week: + type: integer + description: If weekly backups are enabled the day of the week that the weekly backup will occur. Sunday is day 0. + format: int32 + backup_day_of_month: + type: integer + description: If monthly backups are enabled the day of the month the monthly backup will occur. + format: int32 + offsite_backup_settings: + allOf: + - $ref: '#/components/schemas/OffsiteBackupSettings' + description: If offsite backups are enabled this details how they are stored and managed. + nullable: true + BackupSlot: + enum: + - daily + - weekly + - monthly + - temporary + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| daily | A backup which is scheduled to be taken each day. |\n| weekly | A backup which is scheduled to be taken each week. |\n| monthly | A backup which is scheduled to be taken each month. |\n| temporary | A backup which is created on demand and only retained for a maximum of seven days. |\n\n" + x-enum-descriptions: + - A backup which is scheduled to be taken each day. + - A backup which is scheduled to be taken each week. + - A backup which is scheduled to be taken each month. + - A backup which is created on demand and only retained for a maximum of seven days. + BackupWindow: + required: + - end + - start + type: object + properties: + start: + type: string + description: The approximate earliest start date and time in ISO8601 format of the next scheduled backup. + format: date-time + end: + type: string + description: "The expected latest date and time in ISO8601 format of the next scheduled backup.\nThis is not a guarantee that the backup will have started or completed by this date and time." + format: date-time + BackupsResponse: + required: + - backups + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + backups: + type: array + items: + $ref: '#/components/schemas/Image' + Balance: + required: + - available_credit + - charges + - unbilled_total + type: object + properties: + unbilled_total: + type: number + description: The total of any un-billed charges in AU$. + format: double + available_credit: + type: number + description: Available credit in AU$. + format: double + charges: + type: array + items: + $ref: '#/components/schemas/ChargeInformation' + description: A list of all of the individual charges that contribute to the un-billed total. + generated_at: + type: string + description: The timestamp of the most recent charge. + format: date-time + nullable: true + BalanceResponse: + required: + - balance + type: object + properties: + balance: + allOf: + - $ref: '#/components/schemas/Balance' + ChangeAdvancedFeatures: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_advanced_features + type: string + enabled_advanced_features: + type: array + items: + $ref: '#/components/schemas/AdvancedFeature' + description: 'Do not provide or set to null to keep existing advanced features. Provide an empty array to disable all advanced features, otherwise provide an array with selected advanced features. If provided, any currently enabled advanced features that aren''t included will be disabled.' + nullable: true + processor_model: + type: integer + description: Do not provide or set to null to keep existing processor model. + format: int64 + nullable: true + automatic_processor_model: + type: boolean + description: Set to true to use best available processor model. If this is provided the processor_model property must not be provided. + nullable: true + machine_type: + allOf: + - $ref: '#/components/schemas/VmMachineType' + description: "Do not provide or set to null to keep existing machine type.\n\n| Value | Description |\n| ----- | ----------- |\n| pc_i440fx_1point5 | PC i440FX 1.5 |\n| pc_i440fx_2point11 | PC i440FX 2.11 |\n| pc_i440fx_4point1 | PC i440FX 4.1 |\n| pc_i440fx_4point2 | PC i440FX 4.2 |\n| pc_i440fx_5point0 | PC i440FX 5.0 |\n| pc_i440fx_5point1 | PC i440FX 5.1 |\n| pc_i440fx_7point2 | PC i440FX 7.2 |\n| pc_i440fx_7point2point1 | PC i440FX 7.2.1 |\n| pc_i440fx_8point2 | PC i440FX 8.2 |\n\n" + nullable: true + automatic_machine_type: + type: boolean + description: Set to true to use best available machine type. If this is provided the machine_type property must not be provided. + nullable: true + video_device: + allOf: + - $ref: '#/components/schemas/VideoDevice' + description: "Do not provide or set to null to keep existing video device.\n\n| Value | Description |\n| ----- | ----------- |\n| cirrus-logic | Cirrus Logic GD5446 |\n| standard | Standard VGA with VESA 2.0 extensions |\n| virtio | Virtio VGA (800x600) |\n| virtio-wide | Virtio VGA (1600x900) |\n\n" + nullable: true + description: Change the Advanced Features of a Server + ChangeAdvancedFirewallRules: + required: + - firewall_rules + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_advanced_firewall_rules + type: string + firewall_rules: + type: array + items: + $ref: '#/components/schemas/AdvancedFirewallRuleRequest' + description: 'A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules.' + description: Change the Advanced Firewall Rules for a Server + ChangeBackupSchedule: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_backup_schedule + type: string + backup_hour_of_day: + maximum: 23 + minimum: 0 + type: integer + description: Do not provide a value to keep the current setting. + format: int32 + nullable: true + backup_day_of_week: + maximum: 6 + minimum: 0 + type: integer + description: 'Sunday is 0, Monday is 1 etc. Do not provide a value to keep the current setting.' + format: int32 + nullable: true + backup_day_of_month: + maximum: 28 + minimum: 1 + type: integer + description: Do not provide a value to keep the current setting. + format: int32 + nullable: true + description: Change the Backup Schedule of a Server + ChangeImage: + type: object + properties: + image: + oneOf: + - type: integer + - type: string + description: The slug or ID of the selected image. What type of image is permitted here varies based on the server action. + nullable: true + example: 5 + options: + allOf: + - $ref: '#/components/schemas/ImageOptions' + description: Additional options for the server configuration after the image has been changed. + nullable: true + ChangeIpv6: + required: + - enabled + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_ipv6 + type: string + enabled: + type: boolean + description: The desired enabled status of IPv6. + description: Enable or Disable IPv6 for a Server + ChangeIpv6ReverseNameservers: + required: + - ipv6_reverse_nameservers + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_ipv6_reverse_nameservers + type: string + ipv6_reverse_nameservers: + type: array + items: + type: string + description: A list of all IPv6 reverse name servers for this server. Any existing reverse name servers that are omitted from the list will be removed from the server. + description: Update the IPv6 Reverse Name Servers for a Server + ChangeKernel: + required: + - kernel + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_kernel + type: string + kernel: + type: integer + description: The ID of the kernel to use. + format: int64 + description: Change the Kernel of a Server + ChangeLicenses: + required: + - licenses + type: object + properties: + licenses: + type: array + items: + $ref: '#/components/schemas/License' + description: The desired set of licenses. + ChangeManageOffsiteBackupCopies: + required: + - manage_offsite_backup_copies + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_manage_offsite_backup_copies + type: string + manage_offsite_backup_copies: + type: boolean + description: 'This only has effect if a custom offsite location is being used: the internal offsite backup location always manages copies. If this is true old offsite backups will be removed once the replacement upload is complete. If this is false backups must be removed from the Amazon S3 bucket manually. Amazon will charge your S3 account at their standard rate for every backup stored.' + description: Change the Management of Offsite Backup Copies + ChangeNetwork: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_network + type: string + vpc_id: + type: integer + description: If this is null the server will be moved into the public network for the server's region. + format: int64 + nullable: true + description: Move a Server to an Existing Network + ChangeOffsiteBackupLocation: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_offsite_backup_location + type: string + offsite_backup_location: + type: string + description: 'Do not provide or set to null to use the internal offsite backup location, otherwise this must be a valid Amazon S3 bucket address. If this is provided Amazon will charge your S3 account at their standard rate for every backup stored.' + nullable: true + description: Change the Offsite Backup Location of a Server + ChangePartner: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_partner + type: string + partner_server_id: + type: integer + description: Leave this null to remove the server partnership. The partner server must be in the same region as the target server. + format: int64 + nullable: true + description: 'Add, Update or Remove a Partner Server for a Server' + ChangePortBlocking: + required: + - enabled + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_port_blocking + type: string + enabled: + type: boolean + description: The desired enabled status for port blocking. + description: Change the Port Blocking for a Server + ChangeRegion: + required: + - region + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_region + type: string + region: + minLength: 1 + type: string + description: The slug of the selected region. + description: Change the Region of a Server + ChangeReverseName: + required: + - ipv4_address + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_reverse_name + type: string + ipv4_address: + minLength: 1 + type: string + description: The IPv4 address to set or clear the reverse name for. + reverse_name: + type: string + description: Leave this null to clear the custom reverse name. + nullable: true + description: Change the Reverse Name for an IPv4 Address on a Server + ChangeSeparatePrivateNetworkInterface: + required: + - enabled + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_separate_private_network_interface + type: string + enabled: + type: boolean + description: The desired enabled status of the separate second network interface. + description: Enable or Disable a Separate Private Network Interface for a Server in a VPC + ChangeSizeOptionsRequest: + type: object + properties: + daily_backups: + maximum: 14 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + weekly_backups: + maximum: 13 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + monthly_backups: + maximum: 12 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + offsite_backups: + type: boolean + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + nullable: true + ipv4_addresses: + maximum: 2147483647 + minimum: 0 + type: integer + description: 'The total count of IPv4 addresses for this server. If specified this is the absolute value, not just the additional IPv4 addresses above what is included in the size. Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. Must not exceed the size.ipv4_addresses_max value.' + format: int32 + nullable: true + memory: + type: integer + description: "The total memory in MB for this server.\nIf specified this is the absolute value, not just the additional memory above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values:\n- must be a multiple of 128\n- > 2048MB must be a multiple of 1024\n- > 16384MB must be a multiple of 2048\n- > 24576MB must be a multiple of 4096" + format: int32 + nullable: true + disk: + type: integer + description: "The total storage in GB for this server.\nIf specified this is the absolute value, not just the additional storage above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values for sizes that do not provide a value for options.restricted_storage_values_gb:\n- must be a multiple of 5\n- > 60GB must be a multiple of 10\n- > 200GB must be a multiple of 100" + format: int32 + nullable: true + transfer: + type: number + description: "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1000):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000" + format: double + nullable: true + ipv4_addresses_to_remove: + type: array + items: + type: string + description: If you are reducing the number of IPv4 addresses you must specify which addresses to remove. If you specify more IPv4 addresses to remove than the number of IPv4 addresses being removed the extra IPv4 addresses will be re-provisioned with new addresses. + nullable: true + ChangeSourceAndDestinationCheck: + required: + - enabled + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_source_and_destination_check + type: string + enabled: + type: boolean + description: The desired enabled status of the source and destination checks for network packets. + description: Enable or Disable Network Source and Destination Checks for a Server in a VPC + ChangeThresholdAlerts: + required: + - threshold_alerts + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_threshold_alerts + type: string + threshold_alerts: + type: array + items: + $ref: '#/components/schemas/ThresholdAlertRequest' + description: Any alert type not listed will not be updated. + description: Set or Update the Threshold Alerts for a Server + ChangeVpcIpv4: + required: + - current_ipv4_address + - new_ipv4_address + - type + type: object + properties: + type: + minLength: 1 + enum: + - change_vpc_ipv4 + type: string + current_ipv4_address: + minLength: 1 + type: string + description: The existing Ipv4 address for the private VPC network adapter you wish to change. + new_ipv4_address: + minLength: 1 + type: string + description: The new Ipv4 address for the private VPC network adapter. + description: Change the IPv4 Address for a Server in a VPC + ChargeInformation: + required: + - created + - description + - ongoing + - total + type: object + properties: + created: + type: string + description: The time when the charge was created. + format: date-time + description: + type: string + description: A summary of the charge. + total: + type: number + description: The cost in AU$. + format: double + ongoing: + type: boolean + description: If this is true the charge is for an ongoing service. If this is false the charge is complete and awaiting invoicing. + CloneUsingBackup: + required: + - image_id + - target_server_id + - type + type: object + properties: + type: + minLength: 1 + enum: + - clone_using_backup + type: string + image_id: + type: integer + description: The ID of the image to clone. Only backup type images are currently supported. This must be a backup of the server ID in the action endpoint URL. + format: int64 + target_server_id: + type: integer + description: The target server ID. This server's current disks will be wiped and replaced with the selected backup image. + format: int64 + name: + type: string + description: The new hostname for the target server. If this is not supplied the target server's existing hostname will be used. + nullable: true + description: Restore a Backup of a Server to a Different Existing Server + Console: + required: + - browser + - expiry + - height + - iframe + - width + type: object + properties: + iframe: + type: string + description: The URL for the embedded version of the console. + browser: + type: string + description: The URL for the full screen and full featured version of the console. + width: + type: integer + description: Rescue console native width. + format: int32 + height: + type: integer + description: Rescue console native height. + format: int32 + expiry: + type: string + description: The expiry time of the provided URLs. + format: date-time + ConsoleResponse: + required: + - console + type: object + properties: + console: + allOf: + - $ref: '#/components/schemas/Console' + CreateLoadBalancerRequest: + required: + - name + type: object + properties: + name: + minLength: 1 + type: string + description: The hostname of the load balancer. + forwarding_rules: + type: array + items: + $ref: '#/components/schemas/ForwardingRuleRequest' + description: The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. + nullable: true + health_check: + allOf: + - $ref: '#/components/schemas/HealthCheckRequest' + description: The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. + nullable: true + server_ids: + type: array + items: + type: integer + format: int64 + description: A list of server IDs to assign to this load balancer. + nullable: true + region: + type: string + description: Leave null to create an anycast load balancer. + nullable: true + CreateLoadBalancerResponse: + required: + - links + - load_balancer + type: object + properties: + load_balancer: + allOf: + - $ref: '#/components/schemas/LoadBalancer' + links: + allOf: + - $ref: '#/components/schemas/ActionsLinks' + CreateServerRequest: + required: + - image + - region + - size + type: object + properties: + name: + type: string + description: 'The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.' + nullable: true + backups: + type: boolean + description: If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect. + nullable: true + ipv6: + type: boolean + description: If true this will enable IPv6 for this server. + nullable: true + size: + minLength: 1 + type: string + description: The slug of the selected size. + image: + oneOf: + - type: integer + - type: string + description: The slug or id of the selected operating system. + example: 5 + region: + minLength: 1 + type: string + description: The slug of the selected region. + vpc_id: + type: integer + description: Leave null to use default (public) network for the selected region. + format: int64 + nullable: true + vpc_ipv4_address: + type: string + description: If provided this will be the Ipv4 address for the server's private VPC network adapter. If this is null an unused Ipv4 address will be assigned. This field is only valid when VpcId is provided. + nullable: true + ssh_keys: + type: array + items: + oneOf: + - type: integer + - type: string + example: 5 + description: This may be either the SSH keys Ids or fingerprints. If this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH keys). Submit an empty array to disable deployment of default keys. + nullable: true + options: + allOf: + - $ref: '#/components/schemas/SizeOptionsRequest' + description: This may be left null to accept all of the defaults for the selected size. + nullable: true + licenses: + type: array + items: + $ref: '#/components/schemas/License' + description: The desired set of licenses. + nullable: true + user_data: + maxLength: 65536 + type: string + description: 'If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.' + nullable: true + port_blocking: + type: boolean + description: 'Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to verified accounts.' + nullable: true + separate_private_network_interface: + type: boolean + description: If true this will enable a separate private network interface for the server. This is only available for servers in a VPC. + nullable: true + password: + type: string + description: If this is provided the default remote user account's password will be set to this value. If this is null a random password will be generated and emailed to the account email address. + nullable: true + CreateServerResponse: + required: + - links + - server + type: object + properties: + server: + allOf: + - $ref: '#/components/schemas/Server' + links: + allOf: + - $ref: '#/components/schemas/ActionsLinks' + CreateVpcRequest: + required: + - name + type: object + properties: + name: + maxLength: 250 + minLength: 0 + type: string + description: A name to help identify this VPC. + route_entries: + type: array + items: + $ref: '#/components/schemas/RouteEntryRequest' + description: The route entries that control how network traffic is directed through the VPC environment. + nullable: true + ip_range: + type: string + description: 'A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.' + nullable: true + CurrentServerAlertsResponse: + required: + - server_ids + type: object + properties: + server_ids: + type: array + items: + type: integer + format: int64 + DataInterval: + enum: + - five-minute + - half-hour + - four-hour + - day + - week + - month + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| five-minute | 5 Minutes |\n| half-hour | 30 Minutes |\n| four-hour | 4 Hours |\n| day | 1 Day |\n| week | 7 Days |\n| month | 1 Month |\n\n" + x-enum-descriptions: + - 5 Minutes + - 30 Minutes + - 4 Hours + - 1 Day + - 7 Days + - 1 Month + DataUsage: + required: + - current_transfer_usage_gigabytes + - expires + - server_id + - transfer_gigabytes + - transfer_period_end + type: object + properties: + server_id: + type: integer + description: The ID of the server that this data transfer usage refers to. + format: int64 + expires: + type: string + description: The date and time in ISO8601 format that the current billing period expires. + format: date-time + transfer_gigabytes: + type: integer + description: The included data transfer for this server in this period in GB. + format: int64 + current_transfer_usage_gigabytes: + type: number + description: "The used data transfer for this server in this period in GB.\nIf you have more than one server, please see our data pooling policy: this value may include excess data transfer used by other servers or may have 'offloaded' excess data transfer to other servers with spare capacity." + format: double + transfer_period_end: + type: string + description: The date and time in ISO8601 format that the data transfer limit period ended (if it is completed) or when it will end (if this is the current period). + format: date-time + DataUsageResponse: + required: + - data_usage + type: object + properties: + data_usage: + allOf: + - $ref: '#/components/schemas/DataUsage' + DataUsagesResponse: + required: + - data_usages + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + data_usages: + type: array + items: + $ref: '#/components/schemas/DataUsage' + DeleteDisk: + required: + - disk_id + - type + type: object + properties: + type: + minLength: 1 + enum: + - delete_disk + type: string + disk_id: + type: integer + description: The ID of the existing disk. See server.disks for a list of IDs. + format: int64 + description: Delete an Additional Disk for a Server + DetachBackup: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - detach_backup + type: string + description: Detach Any Attached Backup from a Server + DisableBackups: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - disable_backups + type: string + description: Disable Backups for an Existing Server + DisableSelinux: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - disable_selinux + type: string + description: Disable SE Linux for a Server + Disk: + required: + - id + - primary + - size_gigabytes + type: object + properties: + id: + type: integer + description: The ID of this disk. + format: int64 + size_gigabytes: + type: number + description: The size of the disk in GB. + format: double + description: + type: string + description: A description of this disk. + nullable: true + primary: + type: boolean + description: A primary disk is treated differently from other disks. + DistributionFeature: + enum: + - ssh + - remote-desktop + - user-data + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| ssh | Initial install supports SSH connections and SSH keys. |\n| remote-desktop | Initial install supports Remote Desktop (RDP) connections. |\n| user-data | Initial install supports cloud-init user-data. |\n\n" + x-enum-descriptions: + - Initial install supports SSH connections and SSH keys. + - Initial install supports Remote Desktop (RDP) connections. + - Initial install supports cloud-init user-data. + DistributionInfo: + required: + - features + - image_id + - password_recovery + type: object + properties: + image_id: + type: integer + description: 'If this is a backup image, the operating system ID of the server at the time the backup was created.' + format: int64 + password_recovery: + allOf: + - $ref: '#/components/schemas/PasswordRecoveryType' + description: "Supported methods of password recovery.\n\n| Value | Description |\n| ----- | ----------- |\n| manual | Password must be reset manually using the recovery console and rescue disk. |\n| offline-clear | Password can be cleared for the admin/root user only. New password needs to be provided on login via the console (Requires restart). |\n| offline-change | Password can be reset and new credentials sent (Requires restart). |\n| online-change | Password may be reset without requiring a reboot via installed QEMU Guest Agent. |\n\n" + remote_access_user: + type: string + description: User name to use when connecting via remote access (RDP or SSH). + nullable: true + features: + type: array + items: + $ref: '#/components/schemas/DistributionFeature' + description: Features supported by this distribution. + DistributionSurcharges: + type: object + properties: + surcharge_base_cost: + type: number + description: The additional cost for using this operating system as the base image for a size. + format: double + nullable: true + surcharge_per_memory_megabyte: + type: number + description: The additional cost per MB for using this operating system as the base image for a size. + format: double + nullable: true + surcharge_per_memory_max_megabytes: + type: integer + description: The maximum memory in MB that counts towards the surcharge_per_memory_mb. Any memory above this cap does not attract the surcharge. + format: int32 + nullable: true + surcharge_per_vcpu: + type: number + description: The additional cost per vcpu for using this operating system as the base image for a size. + format: double + nullable: true + surcharge_min_vcpu: + type: integer + description: The minimum vcpu count for surcharge calculations. + format: int32 + nullable: true + Domain: + required: + - current_nameservers + - id + - name + - zone_file + type: object + properties: + id: + type: integer + description: The ID of this domain. + format: int64 + name: + type: string + description: The name of the domain. + current_nameservers: + type: array + items: + type: string + description: The current authoritative name servers for this domain. + ttl: + type: integer + description: The time to live for records in this domain in seconds. If the DNS records for this domain are not managed locally this will be what the TTL would be if the authority was delegated to us. + format: int32 + nullable: true + zone_file: + type: string + description: The zone file for the selected domain. If the DNS records for this domain are not managed locally this is what the zone file would be if the authority was delegated to us. The serial is will always be 0 rather than the correct value. + DomainRecord: + required: + - id + - name + - ttl + - type + type: object + properties: + id: + type: integer + description: The ID of this domain record. + format: int64 + x-cli-format: 1 + type: + allOf: + - $ref: '#/components/schemas/DomainRecordType' + description: "A general data field that has different functions depending on the record type.\n\n| Value | Description |\n| ----- | ----------- |\n| A | Map an IPv4 address to a hostname. |\n| AAAA | Map an IPv6 address to a hostname. |\n| CAA | Restrict which certificate authorities are permitted to issue certificates for a domain. |\n| CNAME | Define an alias for your canonical hostname. |\n| MX | Define the mail exchanges that handle mail for the domain. |\n| NS | Define the nameservers that manage the domain. |\n| SOA | The Start of Authority record for the zone. |\n| SRV | Specify a server by hostname and port to handle a service or services. |\n| TXT | Define a string of text that is associated with a hostname. |\n\n" + x-cli-format: 3 + name: + type: string + description: 'The subdomain, alias, or service defined by the record.' + x-cli-format: 2 + data: + type: string + description: Variable data depending on record type. + nullable: true + x-cli-format: 4 + priority: + type: integer + description: A priority value that is only relevant for SRV and MX records. + format: int32 + nullable: true + port: + type: integer + description: A port value that is only relevant for SRV records. + format: int32 + nullable: true + ttl: + type: integer + description: This value is the time to live for the record in seconds. + format: int32 + weight: + type: integer + description: The weight value that is only relevant for SRV records. + format: int32 + nullable: true + flags: + type: integer + description: An unsigned integer between 0-255 that is only relevant for CAA records. + format: int32 + nullable: true + tag: + type: string + description: A parameter tag that is only relevant for CAA records. + nullable: true + DomainRecordRequest: + required: + - data + - name + - type + type: object + properties: + type: + allOf: + - $ref: '#/components/schemas/DomainRecordType' + description: "The type of the DNS record.\n\n| Value | Description |\n| ----- | ----------- |\n| A | Map an IPv4 address to a hostname. |\n| AAAA | Map an IPv6 address to a hostname. |\n| CAA | Restrict which certificate authorities are permitted to issue certificates for a domain. |\n| CNAME | Define an alias for your canonical hostname. |\n| MX | Define the mail exchanges that handle mail for the domain. |\n| NS | Define the nameservers that manage the domain. |\n| SOA | The Start of Authority record for the zone. |\n| SRV | Specify a server by hostname and port to handle a service or services. |\n| TXT | Define a string of text that is associated with a hostname. |\n\n" + name: + minLength: 1 + type: string + description: 'The subdomain for this record. Use @ for records on the domain itself, and * to create a wildcard record.' + data: + minLength: 1 + type: string + description: A general data field that has different functions depending on the record type. + priority: + maximum: 65535 + minimum: 0 + type: integer + description: A priority value that is only relevant for SRV and MX records. + format: int32 + nullable: true + port: + maximum: 65535 + minimum: 0 + type: integer + description: A port value that is only relevant for SRV records. + format: int32 + nullable: true + ttl: + type: integer + description: 'This value is the time to live for the record, in seconds. The default and only supported value is 3600. Leave null to accept this default.' + format: int32 + nullable: true + weight: + maximum: 65535 + minimum: 0 + type: integer + description: The weight value that is only relevant for SRV records. + format: int32 + nullable: true + flags: + maximum: 255 + minimum: 0 + type: integer + description: An unsigned integer between 0-255 that is only relevant for CAA records. + format: int32 + nullable: true + tag: + maxLength: 15 + minLength: 1 + pattern: '[a-z0-9]+' + type: string + description: A parameter tag that is only relevant for CAA records. + nullable: true + DomainRecordResponse: + required: + - domain_record + type: object + properties: + domain_record: + allOf: + - $ref: '#/components/schemas/DomainRecord' + DomainRecordType: + enum: + - A + - AAAA + - CAA + - CNAME + - MX + - NS + - SOA + - SRV + - TXT + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| A | Map an IPv4 address to a hostname. |\n| AAAA | Map an IPv6 address to a hostname. |\n| CAA | Restrict which certificate authorities are permitted to issue certificates for a domain. |\n| CNAME | Define an alias for your canonical hostname. |\n| MX | Define the mail exchanges that handle mail for the domain. |\n| NS | Define the nameservers that manage the domain. |\n| SOA | The Start of Authority record for the zone. |\n| SRV | Specify a server by hostname and port to handle a service or services. |\n| TXT | Define a string of text that is associated with a hostname. |\n\n" + x-enum-descriptions: + - Map an IPv4 address to a hostname. + - Map an IPv6 address to a hostname. + - Restrict which certificate authorities are permitted to issue certificates for a domain. + - Define an alias for your canonical hostname. + - Define the mail exchanges that handle mail for the domain. + - Define the nameservers that manage the domain. + - The Start of Authority record for the zone. + - Specify a server by hostname and port to handle a service or services. + - Define a string of text that is associated with a hostname. + DomainRecordsResponse: + required: + - domain_records + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + domain_records: + type: array + items: + $ref: '#/components/schemas/DomainRecord' + DomainRefreshRequest: + required: + - domain_names + type: object + properties: + domain_names: + type: array + items: + type: string + description: The domain names to refresh. + DomainRequest: + required: + - name + type: object + properties: + name: + minLength: 1 + type: string + description: The domain name to add to the DNS management system. + ip_address: + type: string + description: An optional IPv4 address that will be used to create an A record for the root domain. + nullable: true + DomainResponse: + required: + - domain + type: object + properties: + domain: + allOf: + - $ref: '#/components/schemas/Domain' + DomainsResponse: + required: + - domains + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + domains: + type: array + items: + $ref: '#/components/schemas/Domain' + EnableBackups: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - enable_backups + type: string + description: Enable Two Daily Backups for an Existing Server + EnableIpv6: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - enable_ipv6 + type: string + description: Enable IPv6 for a Server + ForwardingRule: + required: + - entry_protocol + type: object + properties: + entry_protocol: + allOf: + - $ref: '#/components/schemas/LoadBalancerRuleProtocol' + description: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n" + ForwardingRuleRequest: + required: + - entry_protocol + type: object + properties: + entry_protocol: + allOf: + - $ref: '#/components/schemas/LoadBalancerRuleProtocol' + description: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n" + ForwardingRulesRequest: + required: + - forwarding_rules + type: object + properties: + forwarding_rules: + type: array + items: + $ref: '#/components/schemas/ForwardingRuleRequest' + description: The rules that control which traffic the load balancer will forward to servers in the pool. + HealthCheck: + required: + - path + - protocol + type: object + properties: + protocol: + allOf: + - $ref: '#/components/schemas/HealthCheckProtocol' + description: "The protocol used for the health check.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The health check will be performed via HTTP. |\n| https | The health check will be performed via HTTPS. |\n| both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |\n\n" + path: + type: string + description: The path to the health check endpoint. + HealthCheckProtocol: + enum: + - http + - https + - both + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| http | The health check will be performed via HTTP. |\n| https | The health check will be performed via HTTPS. |\n| both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |\n\n" + x-enum-descriptions: + - The health check will be performed via HTTP. + - The health check will be performed via HTTPS. + - The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. + HealthCheckRequest: + type: object + properties: + protocol: + allOf: + - $ref: '#/components/schemas/HealthCheckProtocol' + description: "Leave null to accept the default HTTP protocol.\n\n| Value | Description |\n| ----- | ----------- |\n| http | The health check will be performed via HTTP. |\n| https | The health check will be performed via HTTPS. |\n| both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |\n\n" + nullable: true + path: + minLength: 1 + pattern: '/[A-Za-z0-9/.?=&+%_-]*' + type: string + description: Leave null to accept the default '/' path. + nullable: true + Host: + required: + - display_name + type: object + properties: + display_name: + type: string + description: The name for this host. If this is a dedicated host this property will be empty. + uptime_ms: + type: integer + description: The current uptime in milliseconds of this host. + format: int64 + nullable: true + status_page: + type: string + description: This is the URL of the status page of the host. This will normally only be set if the host is under maintenance. + nullable: true + Image: + required: + - distribution_info + - id + - min_disk_size + - name + - public + - regions + - size_gigabytes + - status + - type + type: object + properties: + id: + type: integer + description: The ID of this image. + format: int64 + x-cli-format: 1 + name: + type: string + description: 'If this is an operating system image, this is the name of the operating system version. If this is a backup image, this is the label of the backup if it exists, otherwise it is the UTC timestamp of the creation of the image.' + x-cli-format: 4 + type: + allOf: + - $ref: '#/components/schemas/ImageType' + distribution: + type: string + description: 'If this is an operating system image, this is the name of the distribution. If this is a backup image, this is the name of the distribution the server is using.' + nullable: true + x-cli-format: 3 + full_name: + type: string + description: 'If this is an operating system image, this is the name and version of the distribution. If this is a backup image, this is the server hostname and label of the backup if it exists, otherwise it is the server hostname and UTC timestamp of the creation of the image.' + nullable: true + slug: + type: string + description: If this is an operating system image this is a slug which may be used as an alternative to the ID as a reference. + nullable: true + x-cli-format: 2 + public: + type: boolean + description: A public image is available to all users. A private image is available only to the account that created the image. + regions: + type: array + items: + type: string + description: The slugs of the regions where the image is available for use. + created_at: + type: string + description: If this is a backup image this is the date and time in ISO8601 format when the image was created. + format: date-time + nullable: true + min_disk_size: + type: integer + description: For a distribution image this is the minimum disk size in GB required to install the operating system. For a backup image this is the minimum total disk size in GB required to restore the backup. + format: int32 + size_gigabytes: + type: number + description: For a distribution image this is the disk size used in GB by the operating system on initial install. For a backup image this is the size of the compressed backup image in GB. + format: double + description: + type: string + description: A description that may provide further details or warnings about the image. + nullable: true + status: + allOf: + - $ref: '#/components/schemas/ImageStatus' + error_message: + type: string + description: If the image creation failed this may provide further information. + nullable: true + min_memory_megabytes: + type: integer + description: This is minimum memory in MB necessary to support this operating system (or the base operating system for a backup image). + format: int32 + nullable: true + distribution_surcharges: + allOf: + - $ref: '#/components/schemas/DistributionSurcharges' + description: If this is not null the use of this image may incur surcharges above the base cost of the server. All costs are in AU$. + nullable: true + distribution_info: + allOf: + - $ref: '#/components/schemas/DistributionInfo' + description: This object may provide further information about the distribution. + backup_info: + allOf: + - $ref: '#/components/schemas/BackupInfo' + description: 'If this image is a backup, this object will provide further information.' + nullable: true + ImageDiskDownload: + required: + - compressed_url + - id + - raw_url + type: object + properties: + id: + type: integer + description: The ID of this backup disk. + format: int64 + compressed_url: + type: string + description: The URL of the compressed disk image. It is always preferable to download the compressed disk image if at all possible. + raw_url: + type: string + description: The URL of the raw (uncompressed) disk image. + ImageDownload: + required: + - disks + - expiry + - id + type: object + properties: + id: + type: integer + description: The ID of the image this download object refers to. + format: int64 + expiry: + type: string + description: The date and time in ISO8601 format that this download URL will expire. + format: date-time + disks: + type: array + items: + $ref: '#/components/schemas/ImageDiskDownload' + description: A list of objects containing the download URLs for each disk in the image. + ImageDownloadResponse: + required: + - link + type: object + properties: + link: + allOf: + - $ref: '#/components/schemas/ImageDownload' + ImageOptions: + type: object + properties: + name: + type: string + description: The hostname for the server. Leave null to accept the auto-generated permalink. + nullable: true + ssh_keys: + type: array + items: + oneOf: + - type: integer + - type: string + example: 5 + description: "This may be either the existing SSH Keys IDs or fingerprints.\nIf this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH Keys).\nSubmit an empty array to disable deployment of default keys." + nullable: true + user_data: + maxLength: 65536 + type: string + description: 'If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.' + nullable: true + password: + type: string + description: If this is provided the default remote user account's password will be set to this value. If this is null a random password will be generated and emailed to the account email address. + nullable: true + ImageQueryType: + enum: + - distribution + - backup + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| distribution | Base operating system images. |\n| backup | A backup image of a server. |\n\n" + x-enum-descriptions: + - Base operating system images. + - A backup image of a server. + ImageRequest: + type: object + properties: + name: + maxLength: 250 + minLength: 0 + type: string + description: 'Optional: a new display name for this image. Do not provide to leave the display name unchanged, submit an empty string to clear the display name.' + nullable: true + locked: + type: boolean + description: 'Optional: you may choose to lock an individual backup in which case we will not update that backup until you unlock it. Do not provide to leave the locked status unchanged. You may not lock or unlock a temporary backup or a backup that is attached to a server.' + nullable: true + ImageResponse: + required: + - image + type: object + properties: + image: + allOf: + - $ref: '#/components/schemas/Image' + ImageStatus: + enum: + - NEW + - available + - pending + - deleted + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| NEW | The image is new. |\n| available | The image is available for use. |\n| pending | The image is pending and is not yet available for use. |\n| deleted | The image has been deleted and is no longer available for use. |\n\n" + x-enum-descriptions: + - The image is new. + - The image is available for use. + - The image is pending and is not yet available for use. + - The image has been deleted and is no longer available for use. + ImageType: + enum: + - custom + - snapshot + - backup + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| custom | An image uploaded by a user. |\n| snapshot | A snapshot. Snapshot creation is not currently supported so only distribution images will have this value. |\n| backup | A backup of a server. |\n\n" + x-enum-descriptions: + - An image uploaded by a user. + - A snapshot. Snapshot creation is not currently supported so only distribution images will have this value. + - A backup of a server. + ImagesResponse: + required: + - images + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + images: + type: array + items: + $ref: '#/components/schemas/Image' + Invoice: + required: + - amount + - created + - date_due + - date_overdue + - invoice_id + - invoice_items + - invoice_number + - paid + - refunded + - tax + - tax_code + type: object + properties: + invoice_id: + type: integer + description: The ID of the invoice. + format: int64 + reference: + type: string + description: 'The reference for this invoice. If this invoice is for a single service this may identify the service, otherwise it will be the account reference.' + nullable: true + invoice_number: + type: string + description: The invoice number for this invoice. + amount: + type: number + description: The amount of the invoice in AU$. + format: double + tax_code: + allOf: + - $ref: '#/components/schemas/TaxCode' + description: The tax code that was applied to transactions on this invoice. + tax: + type: number + description: The amount of tax (if any) that was charged on the transactions on this invoice. + format: double + created: + type: string + description: The date in ISO8601 format this invoice was created. + format: date-time + date_due: + type: string + description: The date in ISO8601 format this invoice is due for payment. + format: date-time + date_overdue: + type: string + description: The date in ISO8601 format this invoice is considered overdue. + format: date-time + paid: + type: boolean + description: If this is true the invoice has been paid. + refunded: + type: boolean + description: If this is true the payment for this invoice has been refunded. + payment_failure_count: + type: integer + description: If this is included it indicates the number of failed attempts at processing payment for this invoice that have occurred. + format: int32 + nullable: true + invoice_items: + type: array + items: + $ref: '#/components/schemas/InvoiceLineItem' + description: The individual items that make up invoice. + invoice_download_url: + type: string + description: The download URL for the PDF version of the invoice. This URL expires 24 hours after it is generated. + nullable: true + invoice_view_url: + type: string + description: The URL for the HTML rendered version of the invoice. This URL expires 24 hours after it is generated. + nullable: true + InvoiceLineItem: + required: + - amount + - amount_includes_tax + - name + type: object + properties: + name: + type: string + description: A description of the item. + amount: + type: number + description: The charge in AU$ for this item. A negative value indicates a discount or credit. + format: double + amount_includes_tax: + type: boolean + description: "If this is true the line item amount includes (if applicable) whatever tax was applied (see invoice.tax_code for details) and the total of the line items on this invoice will match the invoice.amount.\nIf this is false the line item amount does not include any applicable tax and the total of the line items on this invoice will be the invoice.amount less invoice.tax." + InvoiceResponse: + required: + - invoice + type: object + properties: + invoice: + allOf: + - $ref: '#/components/schemas/Invoice' + InvoicesResponse: + required: + - invoices + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + invoices: + type: array + items: + $ref: '#/components/schemas/Invoice' + IsRunning: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - is_running + type: string + description: Check if a Server is Running + Kernel: + required: + - id + type: object + properties: + id: + type: integer + description: The ID of this kernel. + format: int64 + x-cli-format: 1 + name: + type: string + description: This name of this kernel. + nullable: true + x-cli-format: 2 + version: + type: string + description: The version (if any) of this kernel. + nullable: true + x-cli-format: 3 + KernelsResponse: + required: + - kernels + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + kernels: + type: array + items: + $ref: '#/components/schemas/Kernel' + License: + required: + - count + - software_id + type: object + properties: + software_id: + type: integer + description: The ID of the software to license. + format: int64 + count: + type: integer + description: The number of licences. + format: int32 + LicensedSoftware: + required: + - incompatible + - licence_count + - software + type: object + properties: + software: + allOf: + - $ref: '#/components/schemas/Software' + description: The currently licensed software. + licence_count: + type: integer + description: The current licence count for the software. + format: int32 + incompatible: + type: boolean + description: "Software that is incompatible with the server will be automatically removed at the next plan change.\nServers may have incompatible software due to changes made by support. Software is not incompatible merely because it is disabled;\ndisabled software may be retained by servers that already have it, incompatible software will be removed." + LicensedSoftwaresResponse: + required: + - licensed_software + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + licensed_software: + type: array + items: + $ref: '#/components/schemas/LicensedSoftware' + Links: + required: + - pages + type: object + properties: + pages: + allOf: + - $ref: '#/components/schemas/Pages' + description: 'Provides links to first, last, next and previous pages if more than a single page of results exists.' + LoadBalancer: + required: + - created_at + - forwarding_rules + - health_check + - id + - ip + - name + - server_ids + - status + type: object + properties: + id: + type: integer + description: The ID of the load balancer. + format: int64 + x-cli-format: 1 + name: + type: string + description: The hostname of the load balancer. + x-cli-format: 2 + ip: + type: string + description: The IPv4 address of the load balancer. + x-cli-format: 4 + status: + allOf: + - $ref: '#/components/schemas/LoadBalancerStatus' + description: "The current status of the load balancer.\n\n| Value | Description |\n| ----- | ----------- |\n| new | The load balancer is currently being built and is not ready to accept connections. |\n| active | The load balancer is available. |\n| errored | The load balancer is in an errored state. |\n\n" + created_at: + type: string + description: The date and time in ISO8601 format of the creation of the load balancer. + format: date-time + forwarding_rules: + type: array + items: + $ref: '#/components/schemas/ForwardingRule' + description: The rules that control which traffic the load balancer will forward to servers in the pool. + health_check: + allOf: + - $ref: '#/components/schemas/HealthCheck' + description: The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. + region: + allOf: + - $ref: '#/components/schemas/Region' + description: The region the load balancer is located in. If this value is null the load balancer is an 'AnyCast' load balancer. + nullable: true + x-cli-format: 3 + server_ids: + type: array + items: + type: integer + format: int64 + description: The server IDs of the servers that are currently in the load balancer pool (regardless of their current 'health'). + x-cli-entity-ref: name + LoadBalancerAvailabilityOption: + required: + - anycast + - price_hourly + - price_monthly + type: object + properties: + regions: + type: array + items: + type: string + description: The slugs of regions where this load balancer option is available. If this is an Anycast load balancer option this will be null. + nullable: true + anycast: + type: boolean + description: If true this is an Anycast load balancer option. + price_monthly: + type: number + description: Monthly Price in AU$. + format: double + price_hourly: + type: number + description: Hourly price in AU$. + format: double + LoadBalancerAvailabilityResponse: + required: + - load_balancer_availability_options + type: object + properties: + load_balancer_availability_options: + type: array + items: + $ref: '#/components/schemas/LoadBalancerAvailabilityOption' + LoadBalancerResponse: + required: + - load_balancer + type: object + properties: + load_balancer: + allOf: + - $ref: '#/components/schemas/LoadBalancer' + LoadBalancerRuleProtocol: + enum: + - http + - https + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| http | The load balancer will forward HTTP traffic that matches this rule. |\n| https | The load balancer will forward HTTPS traffic that matches this rule. |\n\n" + x-enum-descriptions: + - The load balancer will forward HTTP traffic that matches this rule. + - The load balancer will forward HTTPS traffic that matches this rule. + LoadBalancerStatus: + enum: + - new + - active + - errored + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| new | The load balancer is currently being built and is not ready to accept connections. |\n| active | The load balancer is available. |\n| errored | The load balancer is in an errored state. |\n\n" + x-enum-descriptions: + - The load balancer is currently being built and is not ready to accept connections. + - The load balancer is available. + - The load balancer is in an errored state. + LoadBalancersResponse: + required: + - load_balancers + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + load_balancers: + type: array + items: + $ref: '#/components/schemas/LoadBalancer' + x-cli-entity-list: load_balancers + LocalNameserversResponse: + required: + - local_nameservers + type: object + properties: + local_nameservers: + type: array + items: + type: string + Meta: + required: + - total + type: object + properties: + total: + type: integer + description: The total number of items available. + format: int32 + description: 'Contains metadata about the response, currently this includes the total number of items.' + Network: + required: + - ip_address + - type + type: object + properties: + ip_address: + type: string + description: The IP address for this network. + netmask: + oneOf: + - type: integer + - type: string + description: The netmask for this network. + nullable: true + example: 5 + gateway: + type: string + description: The gateway for this network. + nullable: true + type: + allOf: + - $ref: '#/components/schemas/NetworkType' + reverse_name: + type: string + description: The reverse name (if any) for this network. + nullable: true + nat_target: + type: string + description: If this is not null this property is the private IP address which receives packets from this network. + nullable: true + NetworkType: + enum: + - private + - public + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| private | A private (non internet accessible) network. |\n| public | A public (internet accessible) network. |\n\n" + x-enum-descriptions: + - A private (non internet accessible) network. + - A public (internet accessible) network. + Networks: + required: + - mac_address + - port_blocking + - recent_ddos + - v4 + - v6 + type: object + properties: + v4: + type: array + items: + $ref: '#/components/schemas/Network' + description: A list of the IPv4 networks for this server. + v6: + type: array + items: + $ref: '#/components/schemas/Network' + description: A list of the IPv6 networks for this server. + port_blocking: + type: boolean + description: Whether the default port blocking is enabled for this server. + separate_private_network_interface: + type: boolean + description: Whether a separate private network interface is provided for the server's VPC traffic. + nullable: true + source_and_destination_check: + type: boolean + description: 'If enabled, this server is only able to send and receive data packets directly addressed to an IP address assigned to this server.' + nullable: true + recent_ddos: + type: boolean + description: 'If this is true this server has been the target of a recent DDOS attack. An email will have been sent to your email address when the DDOS was detected (and if it has ended, when it ended) with more details.' + ipv6_reverse_nameservers: + type: array + items: + type: string + description: Any configured IPv6 reverse nameservers for this server. Please see our documentation for how this interacts with IPv6 nameserver settings at the account level. + nullable: true + mac_address: + type: string + description: 'The MAC address of this server''s primary network interface. Customers may need this for ARP entries, DHCP reservations on a VPC, or MAC-based licensing.' + OffsiteBackupFrequencyCost: + required: + - daily_per_gigabyte + - monthly_per_gigabyte + - weekly_per_gigabyte + type: object + properties: + daily_per_gigabyte: + type: number + description: 'The additional cost per GB per month for enabling daily offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.' + format: double + weekly_per_gigabyte: + type: number + description: 'The additional cost per GB per month for enabling weekly offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.' + format: double + monthly_per_gigabyte: + type: number + description: 'The additional cost per GB per month for enabling monthly offsite backups. Only the highest value of the daily, weekly and monthly is applied. See the API support document for how to calculate the final cost of backups based on the options selected.' + format: double + description: All costs are in AU$. + OffsiteBackupSettings: + required: + - use_custom_backup_location + type: object + properties: + use_custom_backup_location: + type: boolean + description: If this is true a custom backup location will be used. If false our internally managed offsite backup location be used. + offsite_backup_location: + type: string + description: 'If a custom backup location is used, this is the provided location.' + nullable: true + manage_offsite_copies: + type: boolean + description: "This only has effect if a custom offsite location is being used: the internal offsite backup location always manages copies.\nIf this is true old offsite backups will be removed once the replacement upload is complete.\nIf this is false backups must be removed from the Amazon S3 bucket manually. Amazon will charge your S3 account at their standard rate for every backup stored." + nullable: true + Pages: + type: object + properties: + last: + type: string + description: A link to the last page of items if this is not the last page. + nullable: true + next: + type: string + description: A link to the next page of items if this is not the last page. + nullable: true + prev: + type: string + description: A link to the previous page of items if this is not the first page. + nullable: true + first: + type: string + description: A link to the first page of items if this is not the first page. + nullable: true + description: 'Provides links to first, last, next and previous pages if more than a single page of results exists.' + PasswordRecoveryType: + enum: + - manual + - offline-clear + - offline-change + - online-change + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| manual | Password must be reset manually using the recovery console and rescue disk. |\n| offline-clear | Password can be cleared for the admin/root user only. New password needs to be provided on login via the console (Requires restart). |\n| offline-change | Password can be reset and new credentials sent (Requires restart). |\n| online-change | Password may be reset without requiring a reboot via installed QEMU Guest Agent. |\n\n" + x-enum-descriptions: + - Password must be reset manually using the recovery console and rescue disk. + - Password can be cleared for the admin/root user only. New password needs to be provided on login via the console (Requires restart). + - Password can be reset and new credentials sent (Requires restart). + - Password may be reset without requiring a reboot via installed QEMU Guest Agent. + PasswordReset: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - password_reset + type: string + username: + type: string + description: "The username of the user to change the password.\nOnly valid if the server supports password change actions (check server.password_change_supported via the servers endpoint).\nIf omitted and the server supports password change actions this will default to the username of the remote user that was configured when the server was created (normally 'root')." + nullable: true + password: + type: string + description: "If this is provided the specified or default remote user's account password will be set to this value.\nOnly valid if the server supports password change actions (check server.password_change_supported via the servers endpoint).\nIf omitted and the server supports password change actions a random password will be generated and emailed to the account email address." + nullable: true + description: Reset the Password of a Server + PatchVpcRequest: + type: object + properties: + name: + maxLength: 250 + minLength: 1 + type: string + description: '>A name to help identify this VPC. Submit null to leave unaltered.' + nullable: true + route_entries: + type: array + items: + $ref: '#/components/schemas/RouteEntryRequest' + description: 'Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save.' + nullable: true + PaymentMethod: + enum: + - credit-card + - paypal + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| credit-card | A validated credit card. |\n| paypal | PayPal. |\n\n" + x-enum-descriptions: + - A validated credit card. + - PayPal. + Period: + required: + - data_interval + - end + - start + type: object + properties: + start: + type: string + description: The date and time of the start of the period in ISO8601 format. + format: date-time + end: + type: string + description: The date and time of the end of the period in ISO8601 format. + format: date-time + data_interval: + allOf: + - $ref: '#/components/schemas/DataInterval' + description: "The duration between data points. This is not the collection interval.\n\n| Value | Description |\n| ----- | ----------- |\n| five-minute | 5 Minutes |\n| half-hour | 30 Minutes |\n| four-hour | 4 Hours |\n| day | 1 Day |\n| week | 7 Days |\n| month | 1 Month |\n\n" + Ping: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - ping + type: string + description: Attempt to Ping a Server + PowerCycle: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - power_cycle + type: string + description: Power a Server Off and then On + PowerOff: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - power_off + type: string + description: Power a Server Off + PowerOn: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - power_on + type: string + description: Power a Server On + ProblemDetails: + required: + - title + type: object + properties: + type: + type: string + nullable: true + title: + type: string + nullable: true + status: + type: integer + format: int32 + nullable: true + detail: + type: string + nullable: true + instance: + type: string + nullable: true + additionalProperties: { } + ProceedRequest: + required: + - proceed + type: object + properties: + proceed: + type: boolean + description: Please see the documentation for each type of interaction for the effect of providing 'true' or 'false' here. + ProcessorModel: + required: + - id + - name + type: object + properties: + id: + type: integer + description: The ID of this processor model. + format: int64 + name: + type: string + description: The name of this processor model. + description: + type: string + description: A description of this processor model. + nullable: true + Reboot: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - reboot + type: string + description: Request a Server Perform a Reboot + Rebuild: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - rebuild + type: string + image: + oneOf: + - type: integer + - type: string + description: The Operating System ID or slug or Backup image ID to use as a base for the rebuild. + nullable: true + example: 5 + options: + allOf: + - $ref: '#/components/schemas/ImageOptions' + description: Additional options. Leaving this entirely null or any of the properties included null will use the defaults from the existing server. + nullable: true + description: Rebuild an Existing Server + Region: + required: + - available + - features + - name + - name_servers + - sizes + - slug + type: object + properties: + slug: + type: string + description: The unique slug for this region. + x-cli-format: 1 + name: + type: string + description: The name of this region. + x-cli-format: 2 + sizes: + type: array + items: + type: string + description: The slugs of the sizes available in this region. + available: + type: boolean + description: Whether this region is available for the allocation of new resources. + features: + type: array + items: + type: string + description: A list of features available for resources in this region. + name_servers: + type: array + items: + type: string + description: A list of nameservers available for resources in this region. + RegionsResponse: + required: + - meta + - regions + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + regions: + type: array + items: + $ref: '#/components/schemas/Region' + Rename: + required: + - name + - type + type: object + properties: + type: + minLength: 1 + enum: + - rename + type: string + name: + minLength: 1 + type: string + description: 'The new hostname of your server, such as vps01.yourcompany.com.' + description: Rename a Server + Resize: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - resize + type: string + size: + type: string + description: The slug of the selected size. Do not provide to keep the current size. + nullable: true + options: + allOf: + - $ref: '#/components/schemas/ChangeSizeOptionsRequest' + description: If this is null and the server has no selected size options the defaults for the size will be used. If this is null and the server has currently selected size options those will be preserved. If this is provided any option fields that are not included will be removed from the existing server. + nullable: true + change_image: + allOf: + - $ref: '#/components/schemas/ChangeImage' + description: This may be left null to keep the current base image for the server. If this is provided the server disks will be destroyed and the server will be rebuilt from the selected image. + nullable: true + change_licenses: + allOf: + - $ref: '#/components/schemas/ChangeLicenses' + description: 'This may be left null to keep the current licenses for the server. If this is provided any licenses that are not included will be removed. Any licences that are invalid will be removed regardless, see the documentation for `servers/{server_id}/software` for more information.' + nullable: true + pre_action_backup: + allOf: + - $ref: '#/components/schemas/TakeBackup' + description: 'Specify this to create a backup before any actions are taken, or leave null to skip.' + nullable: true + description: Update the Size and Related Options for a Server + ResizeDisk: + required: + - disk_id + - size_gigabytes + - type + type: object + properties: + type: + minLength: 1 + enum: + - resize_disk + type: string + disk_id: + type: integer + description: The ID of the existing disk. See server.disks for a list of IDs. + format: int64 + size_gigabytes: + maximum: 2147483647 + minimum: 1 + type: integer + description: The new size of the disk in GB. If increasing the size of the disk the server must have sufficient unallocated storage space. + format: int32 + description: Alter the Size of an Existing Disk for a Server + ResourceType: + enum: + - server + - load-balancer + - ssh-key + - vpc + - image + - registered-domain-name + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| server | Server |\n| load-balancer | Load Balancer |\n| ssh-key | SSH Key |\n| vpc | Virtual Private Network |\n| image | Backup or Operating System Image |\n| registered-domain-name | Registered Domain Name |\n\n" + x-enum-descriptions: + - Server + - Load Balancer + - SSH Key + - Virtual Private Network + - Backup or Operating System Image + - Registered Domain Name + Restore: + required: + - image + - type + type: object + properties: + type: + minLength: 1 + enum: + - restore + type: string + image: + oneOf: + - type: integer + - type: string + description: The ID of the specific backup to use. Snapshots are not currently supported. + example: 5 + description: Restore a Backup to a Server + ReverseNameServersResponse: + required: + - meta + - reverse_nameservers + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + reverse_nameservers: + type: array + items: + type: string + ReverseNameserversRequest: + required: + - reverse_nameservers + type: object + properties: + reverse_nameservers: + type: array + items: + type: string + description: A list of IPv6 reverse name servers for all IPv6 enabled servers. Any existing reverse name servers that are omitted from the list will be removed. + RouteEntry: + required: + - destination + - router + type: object + properties: + router: + type: string + description: The server that will receive traffic sent to the destination property in this VPC. + destination: + type: string + description: The destination address for this route entry. This may be in CIDR format. + description: + type: string + description: An optional description for the route. + nullable: true + RouteEntryRequest: + required: + - destination + - router + type: object + properties: + router: + minLength: 1 + type: string + description: The server that will receive traffic sent to the destination property in this VPC. + destination: + minLength: 1 + type: string + description: The destination address for this route entry. This may be in CIDR format. + description: + maxLength: 250 + type: string + description: An optional description for the route. + nullable: true + Sample: + required: + - cpu_usage_detailed + - cpu_usage_percent + - memory_usage_bytes + - network_incoming_kbps + - network_outgoing_kbps + - storage_read_kbps + - storage_read_requests_per_second + - storage_usage_megabytes + - storage_write_kbps + - storage_write_requests_per_second + type: object + properties: + cpu_usage_percent: + type: number + description: The usage percentage of all CPU; 100% is the maximum possible even with multiple processors. + format: double + cpu_usage_detailed: + type: array + items: + type: number + format: double + description: The usage percentage of each virtual CPU. + memory_usage_bytes: + type: number + description: The virtual memory used in bytes. + format: double + network_incoming_kbps: + type: number + description: 'The incoming network data rate in KB per second (binary: 1 KB = 1024 bytes).' + format: double + network_outgoing_kbps: + type: number + description: 'The outgoing network data rate in KB per second (binary: 1 KB = 1024 bytes).' + format: double + storage_usage_megabytes: + type: number + description: The total storage used in MB. + format: double + storage_read_kbps: + type: number + description: 'The storage read rate in KB per second (binary: 1 KB = 1024 bytes).' + format: double + storage_write_kbps: + type: number + description: 'The storage write rate in KB per second (binary: 1 KB = 1024 bytes).' + format: double + storage_read_requests_per_second: + type: number + description: The storage read requests per second. + format: double + storage_write_requests_per_second: + type: number + description: The storage write requests per second. + format: double + SampleSet: + required: + - average + - maximum_memory_megabytes + - maximum_storage_gigabytes + - period + - server_id + type: object + properties: + server_id: + type: integer + description: The ID of the server that this sample set refers to. + format: int64 + period: + allOf: + - $ref: '#/components/schemas/Period' + description: The period when this sample set was collected. + average: + allOf: + - $ref: '#/components/schemas/Sample' + description: The average values of the samples collected during this period. + maximum_memory_megabytes: + type: number + description: The maximum memory used in MB at any point during this collection period. + format: double + maximum_storage_gigabytes: + type: number + description: The maximum storage used in GB at any point during this collection period. + format: double + SampleSetResponse: + type: object + properties: + sample_set: + allOf: + - $ref: '#/components/schemas/SampleSet' + nullable: true + SampleSetsResponse: + required: + - meta + - sample_sets + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + sample_sets: + type: array + items: + $ref: '#/components/schemas/SampleSet' + SelectedSizeOptions: + required: + - daily_backups + - disk + - ipv4_addresses + - memory + - monthly_backups + - offsite_backups + - transfer + - weekly_backups + type: object + properties: + daily_backups: + type: integer + description: 'The number of retained daily backups. e.g. if this is ''2'' we will store two daily backups, so each daily backup will be retained for two days before being overwritten.' + format: int32 + weekly_backups: + type: integer + description: 'The number of retained weekly backups. e.g. if this is ''1'' we will store one weekly backup, so that weekly backup will be retained for one week before being overwritten.' + format: int32 + monthly_backups: + type: integer + description: 'The number of retained monthly backups. e.g. if this is ''3'' we will store three monthly backups, so each monthly backup will be retained for three months before being overwritten.' + format: int32 + offsite_backups: + type: boolean + description: 'If this is true any daily, weekly or monthly backups will be duplicated to an off-site location.' + ipv4_addresses: + type: integer + description: The total count of IPv4 addresses for this server. + format: int32 + memory: + type: integer + description: The total memory in MB for this server. + format: int32 + disk: + type: integer + description: The total storage in GB for this server. + format: int32 + transfer: + type: number + description: The total transfer per month in TB for this server. + format: double + Server: + required: + - advanced_features + - backup_ids + - backup_settings + - created_at + - disk + - disks + - failover_ips + - features + - host + - id + - image + - memory + - name + - networks + - password_change_supported + - region + - size + - size_slug + - status + - vcpus + type: object + properties: + id: + type: integer + description: The ID of this server. + format: int64 + x-cli-format: 1 + name: + type: string + description: The hostname of this server. + x-cli-format: 2 + memory: + type: integer + description: The memory in MB of this server. + format: int32 + x-cli-format: 5 + vcpus: + type: integer + description: The number of virtual CPUs of this server. + format: int32 + x-cli-format: 4 + disk: + type: integer + description: The total disk in GB of this server. + format: int32 + x-cli-format: 6 + vpc_id: + type: integer + description: The VPC ID that this server is allocated to. If this value is null the server is in the default (public) network for the region. + format: int64 + nullable: true + created_at: + type: string + description: The date and time in ISO8601 format of this server's initial creation. + format: date-time + status: + allOf: + - $ref: '#/components/schemas/ServerStatus' + backup_ids: + type: array + items: + type: integer + format: int64 + description: A list of the currently existing backup image IDs for this server (if any). + features: + type: array + items: + type: string + description: A list of the currently enabled features on this server. + region: + allOf: + - $ref: '#/components/schemas/Region' + description: The region this server is allocated to. + x-cli-format: 7 + image: + allOf: + - $ref: '#/components/schemas/Image' + description: The base image used to create this server. + x-cli-format: 3 + size: + allOf: + - $ref: '#/components/schemas/Size' + description: The currently selected size for this server. + size_slug: + type: string + description: The slug of the currently selected size for this server. + selected_size_options: + allOf: + - $ref: '#/components/schemas/SelectedSizeOptions' + description: An object that details the selected options for the current size. + nullable: true + networks: + allOf: + - $ref: '#/components/schemas/Networks' + description: A list of the networks of the server. + x-cli-format: 8 + kernel: + allOf: + - $ref: '#/components/schemas/Kernel' + description: The currently selected kernel for the server. + nullable: true + next_backup_window: + allOf: + - $ref: '#/components/schemas/BackupWindow' + description: 'The details of the next scheduled backup, if any.' + nullable: true + disks: + type: array + items: + $ref: '#/components/schemas/Disk' + description: A list of the disks that are currently attached to the server. + backup_settings: + allOf: + - $ref: '#/components/schemas/BackupSettings' + description: Detailed backup settings for the server. + cancelled_at: + type: string + description: 'If the server has been cancelled, this is the date and time in ISO8601 format of that cancellation.' + format: date-time + nullable: true + failover_ips: + type: array + items: + type: string + description: A list of any assigned failover IP addresses for this server. + host: + allOf: + - $ref: '#/components/schemas/Host' + description: Summary information about the host of this server. + partner_id: + type: integer + description: 'The server ID of the partner of this server, if one has been assigned.' + format: int64 + nullable: true + password_change_supported: + type: boolean + description: If this is true the password_reset server action can be called to change a user's password. If this is false the password_reset server action will merely clear the root/administrator password allowing the password to be changed via the web console. + permalink: + type: string + description: A randomly generated two-word identifier assigned to servers in regions that support this feature. + nullable: true + attached_backup: + allOf: + - $ref: '#/components/schemas/AttachedBackup' + description: An object that provides details of any backup image currently attached to the server.. + nullable: true + advanced_features: + allOf: + - $ref: '#/components/schemas/AdvancedServerFeatures' + description: 'The currently enabled advanced features, machine type and processor flags.' + is_under_maintenance: + type: boolean + description: 'If true, this server is currently under maintenance and most actions will not be available. If this is null the status was not checked.' + nullable: true + x-cli-entity-ref: name + ServerAction: + required: + - type + type: object + oneOf: + - $ref: '#/components/schemas/AddDisk' + - $ref: '#/components/schemas/AttachBackup' + - $ref: '#/components/schemas/ChangeAdvancedFeatures' + - $ref: '#/components/schemas/ChangeAdvancedFirewallRules' + - $ref: '#/components/schemas/ChangeBackupSchedule' + - $ref: '#/components/schemas/ChangeIpv6' + - $ref: '#/components/schemas/ChangeIpv6ReverseNameservers' + - $ref: '#/components/schemas/ChangeKernel' + - $ref: '#/components/schemas/ChangeManageOffsiteBackupCopies' + - $ref: '#/components/schemas/ChangeNetwork' + - $ref: '#/components/schemas/ChangeOffsiteBackupLocation' + - $ref: '#/components/schemas/ChangePartner' + - $ref: '#/components/schemas/ChangePortBlocking' + - $ref: '#/components/schemas/ChangeRegion' + - $ref: '#/components/schemas/ChangeReverseName' + - $ref: '#/components/schemas/ChangeSeparatePrivateNetworkInterface' + - $ref: '#/components/schemas/ChangeSourceAndDestinationCheck' + - $ref: '#/components/schemas/ChangeThresholdAlerts' + - $ref: '#/components/schemas/ChangeVpcIpv4' + - $ref: '#/components/schemas/CloneUsingBackup' + - $ref: '#/components/schemas/DeleteDisk' + - $ref: '#/components/schemas/DetachBackup' + - $ref: '#/components/schemas/DisableBackups' + - $ref: '#/components/schemas/DisableSelinux' + - $ref: '#/components/schemas/EnableBackups' + - $ref: '#/components/schemas/EnableIpv6' + - $ref: '#/components/schemas/IsRunning' + - $ref: '#/components/schemas/PasswordReset' + - $ref: '#/components/schemas/Ping' + - $ref: '#/components/schemas/PowerCycle' + - $ref: '#/components/schemas/PowerOff' + - $ref: '#/components/schemas/PowerOn' + - $ref: '#/components/schemas/Reboot' + - $ref: '#/components/schemas/Rebuild' + - $ref: '#/components/schemas/Rename' + - $ref: '#/components/schemas/Resize' + - $ref: '#/components/schemas/ResizeDisk' + - $ref: '#/components/schemas/Restore' + - $ref: '#/components/schemas/Shutdown' + - $ref: '#/components/schemas/TakeBackup' + - $ref: '#/components/schemas/Uncancel' + - $ref: '#/components/schemas/Uptime' + properties: + type: + minLength: 1 + type: string + discriminator: + propertyName: type + mapping: + add_disk: '#/components/schemas/AddDisk' + attach_backup: '#/components/schemas/AttachBackup' + change_advanced_features: '#/components/schemas/ChangeAdvancedFeatures' + change_advanced_firewall_rules: '#/components/schemas/ChangeAdvancedFirewallRules' + change_backup_schedule: '#/components/schemas/ChangeBackupSchedule' + change_ipv6: '#/components/schemas/ChangeIpv6' + change_ipv6_reverse_nameservers: '#/components/schemas/ChangeIpv6ReverseNameservers' + change_kernel: '#/components/schemas/ChangeKernel' + change_manage_offsite_backup_copies: '#/components/schemas/ChangeManageOffsiteBackupCopies' + change_network: '#/components/schemas/ChangeNetwork' + change_offsite_backup_location: '#/components/schemas/ChangeOffsiteBackupLocation' + change_partner: '#/components/schemas/ChangePartner' + change_port_blocking: '#/components/schemas/ChangePortBlocking' + change_region: '#/components/schemas/ChangeRegion' + change_reverse_name: '#/components/schemas/ChangeReverseName' + change_separate_private_network_interface: '#/components/schemas/ChangeSeparatePrivateNetworkInterface' + change_source_and_destination_check: '#/components/schemas/ChangeSourceAndDestinationCheck' + change_threshold_alerts: '#/components/schemas/ChangeThresholdAlerts' + change_vpc_ipv4: '#/components/schemas/ChangeVpcIpv4' + clone_using_backup: '#/components/schemas/CloneUsingBackup' + delete_disk: '#/components/schemas/DeleteDisk' + detach_backup: '#/components/schemas/DetachBackup' + disable_backups: '#/components/schemas/DisableBackups' + disable_selinux: '#/components/schemas/DisableSelinux' + enable_backups: '#/components/schemas/EnableBackups' + enable_ipv6: '#/components/schemas/EnableIpv6' + is_running: '#/components/schemas/IsRunning' + password_reset: '#/components/schemas/PasswordReset' + ping: '#/components/schemas/Ping' + power_cycle: '#/components/schemas/PowerCycle' + power_off: '#/components/schemas/PowerOff' + power_on: '#/components/schemas/PowerOn' + reboot: '#/components/schemas/Reboot' + rebuild: '#/components/schemas/Rebuild' + rename: '#/components/schemas/Rename' + resize: '#/components/schemas/Resize' + resize_disk: '#/components/schemas/ResizeDisk' + restore: '#/components/schemas/Restore' + shutdown: '#/components/schemas/Shutdown' + take_backup: '#/components/schemas/TakeBackup' + uncancel: '#/components/schemas/Uncancel' + uptime: '#/components/schemas/Uptime' + ServerIdsRequest: + required: + - server_ids + type: object + properties: + server_ids: + type: array + items: + type: integer + format: int64 + description: A list of server IDs. + ServerResponse: + required: + - server + type: object + properties: + server: + allOf: + - $ref: '#/components/schemas/Server' + ServerStatus: + enum: + - new + - active + - archive + - off + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| new | The server is currently in the process of building and is not yet available for use. |\n| active | The server is available for use. |\n| archive | The server is powered off due to cancellation or non payment. |\n| off | The server has been powered off, but may be powered back on. |\n\n" + x-enum-descriptions: + - The server is currently in the process of building and is not yet available for use. + - The server is available for use. + - The server is powered off due to cancellation or non payment. + - 'The server has been powered off, but may be powered back on.' + ServersResponse: + required: + - meta + - servers + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + servers: + type: array + items: + $ref: '#/components/schemas/Server' + x-cli-entity-list: servers + Shutdown: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - shutdown + type: string + description: Request a Server Perform a Shutdown + Size: + required: + - available + - disk + - excess_transfer_cost_per_gigabyte + - memory + - options + - price_hourly + - price_monthly + - regions + - size_type + - slug + - transfer + - vcpu_units + - vcpus + type: object + properties: + slug: + type: string + description: The slug of this size. + x-cli-format: 1 + description: + type: string + description: A description of this size. + nullable: true + cpu_description: + type: string + description: A description of the CPU provided in this size. + nullable: true + storage_description: + type: string + description: A description of the storage provided in this size. + nullable: true + size_type: + allOf: + - $ref: '#/components/schemas/SizeType' + description: 'The type of this size, generally used to differentiate sizes optimized for different usages.' + x-cli-format: 2 + available: + type: boolean + description: If this is false the size is not available for new servers. + regions: + type: array + items: + type: string + description: "A list of region slugs where this size is available regardless of stock.\nIf this a response to a query that included a selected operating system this response will only include regions where that operating system is available on this size,\notherwise not all regions listed will support all operating systems on this size." + regions_out_of_stock: + type: array + items: + type: string + description: A list of region slugs where the size is normally available but is currently not available due to lack of stock. + nullable: true + price_monthly: + type: number + description: Monthly Price in AU$. + format: double + x-cli-format: 8 + price_hourly: + type: number + description: Hourly price in AU$. + format: double + disk: + type: integer + description: The included storage for this size in GB. + format: int32 + x-cli-format: 6 + memory: + type: integer + description: The included memory for this size in MB. + format: int32 + x-cli-format: 5 + transfer: + type: number + description: The included data transfer for this size in TB. + format: double + x-cli-format: 7 + excess_transfer_cost_per_gigabyte: + type: number + description: The excess charged for any transfer above the included data transfer in AU$ per GB. + format: double + vcpus: + type: integer + description: The count of virtual CPUs for this size. See vcpu_units for a description of how each virtual CPU maps to the underlying hardware. + format: int32 + x-cli-format: 3 + vcpu_units: + type: string + description: 'This is the unit that the vcpus field counts, e.g. "core" or "thread".' + x-cli-format: 4 + options: + allOf: + - $ref: '#/components/schemas/SizeOptions' + description: Available add-ons (optional features not included in the base price) for the size. All costs are in AU$ per month (pro-rated). + SizeOptions: + required: + - backups_cost_per_backup_per_gigabyte + - daily_backups + - discount_for_no_public_ipv4 + - disk_cost_per_additional_gigabyte + - disk_max + - disk_min + - ipv4_addresses_cost_per_address + - ipv4_addresses_max + - memory_cost_per_additional_megabyte + - memory_max + - monthly_backups + - offsite_backup_frequency_cost + - offsite_backups_cost_per_gigabyte + - transfer_cost_per_additional_gigabyte + - transfer_max + - weekly_backups + type: object + properties: + disk_min: + type: integer + description: The minimum storage in GB permitted on this size. + format: int32 + disk_max: + type: integer + description: The maximum storage in GB permitted on this size. + format: int32 + disk_cost_per_additional_gigabyte: + type: number + description: The additional cost per GB per month for additional storage space. + format: double + restricted_disk_values: + type: array + items: + type: integer + format: int32 + description: 'If this is null the normal valid values in the documentation for SizeOptionsRequest are used, otherwise only these values (in GB) are permitted.' + nullable: true + memory_max: + type: integer + description: The maximum memory in MB permitted on this size. + format: int32 + memory_cost_per_additional_megabyte: + type: number + description: The additional cost per MB per month for additional memory. + format: double + transfer_max: + type: number + description: The maximum transfer in TB permitted for this size. If this is the same as Size.Transfer no additional transfer is supported. + format: double + transfer_cost_per_additional_gigabyte: + type: number + description: The additional cost per GB per month for additional included transfer. + format: double + ipv4_addresses_max: + type: integer + description: The maximum number of IPv4 addresses permitted on this size. + format: int32 + ipv4_addresses_cost_per_address: + type: number + description: The additional cost per public IPv4 address per month for additional IPv4 addresses. + format: double + discount_for_no_public_ipv4: + type: number + description: This is the discount (if any) that is applied if no public IPv4 addresses are selected. + format: double + daily_backups: + type: integer + description: The number of daily backups included in the base size cost. + format: int32 + weekly_backups: + type: integer + description: The number of weekly backups included in the base size cost. + format: int32 + monthly_backups: + type: integer + description: The number of monthly backups included in the base size cost. + format: int32 + backups_cost_per_backup_per_gigabyte: + type: number + description: The cost per GB of storage of each selected backup. See the API support document for how to calculate the final cost of backups based on the options selected. + format: double + offsite_backups_cost_per_gigabyte: + type: number + description: The additional cost per GB of storage for enabling offsite backups. See the API support document for how to calculate the final cost of backups based on the options selected. + format: double + offsite_backup_frequency_cost: + allOf: + - $ref: '#/components/schemas/OffsiteBackupFrequencyCost' + description: The additional cost per GB of storage for enabling offsite backups based on highest frequency of backups currently enabled. All costs are in AU$. + description: Available add-ons (optional features not included in the base price) for the size. All costs are per month (pro-rated). + SizeOptionsRequest: + type: object + properties: + daily_backups: + maximum: 14 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + weekly_backups: + maximum: 13 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + monthly_backups: + maximum: 12 + minimum: 0 + type: integer + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + format: int32 + nullable: true + offsite_backups: + type: boolean + description: Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. + nullable: true + ipv4_addresses: + maximum: 2147483647 + minimum: 0 + type: integer + description: 'The total count of IPv4 addresses for this server. If specified this is the absolute value, not just the additional IPv4 addresses above what is included in the size. Leave null to accept the default for the size if this is a new server or to keep the current value if this is a resize of an existing server. Must not exceed the size.ipv4_addresses_max value.' + format: int32 + nullable: true + memory: + type: integer + description: "The total memory in MB for this server.\nIf specified this is the absolute value, not just the additional memory above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values:\n- must be a multiple of 128\n- > 2048MB must be a multiple of 1024\n- > 16384MB must be a multiple of 2048\n- > 24576MB must be a multiple of 4096" + format: int32 + nullable: true + disk: + type: integer + description: "The total storage in GB for this server.\nIf specified this is the absolute value, not just the additional storage above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values for sizes that do not provide a value for options.restricted_storage_values_gb:\n- must be a multiple of 5\n- > 60GB must be a multiple of 10\n- > 200GB must be a multiple of 100" + format: int32 + nullable: true + transfer: + type: number + description: "The total transfer per month in TB for this server.\nIf specified this is the absolute value, not just the additional transfer above what is included in the size.\nLeave null to accept the default for the size if this is a new server or a resize to a different base size, or to keep the current value if this a resize with the same base size but different options.\n \nValid values (when converted to GB by multiplying the value provided by 1000):\n- must be a multiple of 5GB\n- > 30GB must be a multiple of 10\n- > 200GB must be a multiple of 100\n- > 2000GB must be a multiple of 1000" + format: double + nullable: true + SizeType: + required: + - name + - slug + type: object + properties: + slug: + type: string + description: The slug of this size type. + name: + type: string + description: The name of this size type. + description: + type: string + description: 'The description of this size type, generally used to indicate the intended use case for sizes of this type.' + nullable: true + SizesResponse: + required: + - meta + - sizes + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + sizes: + type: array + items: + $ref: '#/components/schemas/Size' + SnapshotsResponse: + required: + - meta + - snapshots + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + snapshots: + type: array + items: + $ref: '#/components/schemas/Image' + Software: + required: + - cost_per_licence_per_month + - description + - enabled + - id + - licence_step_count + - maximum_licence_count + - minimum_licence_count + - name + - supported_operating_systems + type: object + properties: + id: + type: integer + description: The ID of this software. + format: int64 + x-cli-format: 1 + enabled: + type: boolean + description: Software that is not enabled is not available to be added to servers but may be retained by servers that currently use it. + name: + type: string + description: The name of this software. + x-cli-format: 2 + description: + type: string + description: The description of this software. + x-cli-format: 3 + cost_per_licence_per_month: + type: number + description: The cost for each licence of this software per month in AU$. + format: double + x-cli-format: 4 + minimum_licence_count: + type: integer + description: The minimum licences permitted for this software. + format: int32 + maximum_licence_count: + type: integer + description: The maximum licences permitted for this software. + format: int32 + licence_step_count: + type: integer + description: Licences must be purchased in multiples of this value. + format: int32 + group: + type: string + description: Software in the same group may not be licensed together. + nullable: true + supported_operating_systems: + type: array + items: + type: string + description: A list of slugs of operating system images that support this software. + SoftwareResponse: + required: + - software + type: object + properties: + software: + allOf: + - $ref: '#/components/schemas/Software' + SoftwaresResponse: + required: + - meta + - software + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + software: + type: array + items: + $ref: '#/components/schemas/Software' + SshKey: + required: + - default + - fingerprint + - id + - public_key + type: object + properties: + id: + type: integer + description: The ID of this SSH key. + format: int64 + x-cli-format: 1 + fingerprint: + type: string + description: The fingerprint of this SSH key. + x-cli-format: 4 + public_key: + type: string + description: The public key of this SSH key. + name: + type: string + description: The name of this SSH key. This is used only to aid in identification. + nullable: true + x-cli-format: 2 + default: + type: boolean + description: If an SSH key is marked as default it will be deployed to all newly created servers that support SSH keys unless expressly overridden in the creation request. + x-cli-format: 3 + SshKeyRequest: + required: + - name + - public_key + type: object + properties: + public_key: + minLength: 1 + type: string + description: The public key in OpenSSH "authorized_keys" format. + name: + minLength: 1 + type: string + description: A name to help you identify the key. + default: + type: boolean + description: 'Optional: If true this will be added to all new server installations (if we support SSH Key injection for the server''s operating system).' + nullable: true + SshKeyResponse: + required: + - ssh_key + type: object + properties: + ssh_key: + allOf: + - $ref: '#/components/schemas/SshKey' + SshKeysResponse: + required: + - meta + - ssh_keys + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + ssh_keys: + type: array + items: + $ref: '#/components/schemas/SshKey' + TakeBackup: + required: + - replacement_strategy + - type + type: object + properties: + type: + minLength: 1 + enum: + - take_backup + type: string + backup_type: + allOf: + - $ref: '#/components/schemas/BackupSlot' + description: "If replacement_strategy is anything other than 'specified', this must be provided.\n\n| Value | Description |\n| ----- | ----------- |\n| daily | A backup which is scheduled to be taken each day. |\n| weekly | A backup which is scheduled to be taken each week. |\n| monthly | A backup which is scheduled to be taken each month. |\n| temporary | A backup which is created on demand and only retained for a maximum of seven days. |\n\n" + nullable: true + replacement_strategy: + allOf: + - $ref: '#/components/schemas/BackupReplacementStrategy' + description: "The strategy for selecting which backup to replace (if any).\n\n| Value | Description |\n| ----- | ----------- |\n| none | Do not replace any existing backup: use a free slot of the provided backup type. If there are no free slots an error will occur. |\n| specified | Replace the specific backup id provided. |\n| oldest | Use any free slots of the provided backup type, and if there are no free slots replace the oldest unlocked and un-attached backup of the provided backup type. |\n| newest | Use any free slots of the provided backup type, and if there are no free slots replace the newest unlocked and un-attached backup of the provided backup type. |\n\n" + backup_id_to_replace: + type: integer + description: If replacement_strategy is 'specified' this property must be set to an existing backup. + format: int64 + nullable: true + label: + maxLength: 250 + type: string + description: An optional label to identify the backup. + nullable: true + description: Take a Backup of a Server + TaxCode: + required: + - name + - type + type: object + properties: + name: + type: string + description: The name of this tax code. + type: + allOf: + - $ref: '#/components/schemas/TaxCodeType' + description: "The type of tax code.\n\n| Value | Description |\n| ----- | ----------- |\n| none | No tax is applied to any transaction. |\n| scalar | A fixed fraction of the value of all transactions is added as tax. |\n\n" + fixed_percent: + type: number + description: 'If this is set then this tax is added to all applicable transactions. This is a percentage value where 100 = 100%. For example: if the type is ''scalar'' and the value of this is ''10'' then 10% of the value of all transactions will be added as tax.' + format: double + nullable: true + TaxCodeType: + enum: + - none + - scalar + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| none | No tax is applied to any transaction. |\n| scalar | A fixed fraction of the value of all transactions is added as tax. |\n\n" + x-enum-descriptions: + - No tax is applied to any transaction. + - A fixed fraction of the value of all transactions is added as tax. + ThresholdAlert: + required: + - alert_type + - description + - enabled + - name + - unit + - value + type: object + properties: + alert_type: + allOf: + - $ref: '#/components/schemas/ThresholdAlertType' + name: + type: string + description: The display name of this threshold alert. + unit: + type: string + description: The unit suffix for this alert's value (e.g. `"%"` or `"requests/second"`). + description: + type: string + description: A human-readable description of what this threshold alert measures. + enabled: + type: boolean + description: If a threshold alert is not enabled it will not generate warnings for the user. + value: + type: integer + description: The threshold value of the alert. Refer to the documentation for each threshold alert type for what this value measures in the context of the alert type. + format: int32 + current_value: + type: integer + description: The last measured value for this alert type over the threshold alert period. Refer to the documentation for each threshold alert type for what this value measures in the context of the alert type. If there is no measured value in the threshold alert period this will be null. + format: int32 + nullable: true + last_raised: + type: string + description: The date and time (if any) in ISO8601 format of the last time this alert was raised. An alert may not be raised again until it has been cleared. + format: date-time + nullable: true + last_cleared: + type: string + description: The date and time (if any) in ISO8601 format of the last time this alert was cleared. An alert may not be raised again until a minimum duration has passed since it was last cleared. + format: date-time + nullable: true + ThresholdAlertRequest: + required: + - alert_type + type: object + properties: + alert_type: + allOf: + - $ref: '#/components/schemas/ThresholdAlertType' + enabled: + type: boolean + description: Do not provide or leave null to keep existing status. + nullable: true + value: + type: integer + description: Do not provide or leave null to keep existing value. + format: int32 + nullable: true + ThresholdAlertType: + enum: + - cpu + - storage-requests + - network-incoming + - network-outgoing + - data-transfer-used + - storage-used + - memory-used + - locked-backup-slots + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| cpu | The alert is based off the average percentage of all CPU; 100% is the maximum possible even with multiple processors. A high average will prevent the server from responding quickly. |\n| storage-requests | The alert is based off The average number of requests (combined read and write) received by the storage subsystem. A high number of requests often indicates swap usage (due to memory exhaustion) and is associated with poor performance. |\n| network-incoming | The alert is based off the amount of data going into the server (from the internet and the LAN). A sudden increase may indicate the server is the victim of a DOS attack. |\n| network-outgoing | The alert is based off the amount of data coming out of the server (to the internet and the LAN). A sudden increase may indicate the server has been hacked and is being used for spam delivery. |\n| data-transfer-used | The alert is based off the percentage of your monthly data transfer limit. |\n| storage-used | The alert is based off the disk space consumed as a percentage of your total disk space. If the server runs out of disk space programs may fail to execute or be unable to create new files, or the server may become unresponsive. |\n| memory-used | The alert is based off the virtual memory consumed as a percentage of your physical memory. Virtual memory includes the swap file so the percentage may exceed 100% indicating that the server has run out of physical memory and is relying on swap space, which will generally cause poor performance. |\n| locked-backup-slots | The alert is based off the percentage of scheduled backup slots (daily, weekly, monthly) occupied by locked backups. When all slots are locked, automated backups cannot proceed. |\n\n" + x-enum-descriptions: + - The alert is based off the average percentage of all CPU; 100% is the maximum possible even with multiple processors. A high average will prevent the server from responding quickly. + - The alert is based off The average number of requests (combined read and write) received by the storage subsystem. A high number of requests often indicates swap usage (due to memory exhaustion) and is associated with poor performance. + - The alert is based off the amount of data going into the server (from the internet and the LAN). A sudden increase may indicate the server is the victim of a DOS attack. + - The alert is based off the amount of data coming out of the server (to the internet and the LAN). A sudden increase may indicate the server has been hacked and is being used for spam delivery. + - The alert is based off the percentage of your monthly data transfer limit. + - 'The alert is based off the disk space consumed as a percentage of your total disk space. If the server runs out of disk space programs may fail to execute or be unable to create new files, or the server may become unresponsive.' + - 'The alert is based off the virtual memory consumed as a percentage of your physical memory. Virtual memory includes the swap file so the percentage may exceed 100% indicating that the server has run out of physical memory and is relying on swap space, which will generally cause poor performance.' + - 'The alert is based off the percentage of scheduled backup slots (daily, weekly, monthly) occupied by locked backups. When all slots are locked, automated backups cannot proceed.' + ThresholdAlertsResponse: + required: + - threshold_alerts + type: object + properties: + threshold_alerts: + type: array + items: + $ref: '#/components/schemas/ThresholdAlert' + Uncancel: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - uncancel + type: string + description: Revert the Cancellation of a Server + UnpaidFailedInvoicesResponse: + required: + - invoices + type: object + properties: + invoices: + type: array + items: + $ref: '#/components/schemas/Invoice' + UpdateDomainRecordRequest: + type: object + properties: + type: + allOf: + - $ref: '#/components/schemas/DomainRecordType' + description: "The type of the DNS record.\n\n| Value | Description |\n| ----- | ----------- |\n| A | Map an IPv4 address to a hostname. |\n| AAAA | Map an IPv6 address to a hostname. |\n| CAA | Restrict which certificate authorities are permitted to issue certificates for a domain. |\n| CNAME | Define an alias for your canonical hostname. |\n| MX | Define the mail exchanges that handle mail for the domain. |\n| NS | Define the nameservers that manage the domain. |\n| SOA | The Start of Authority record for the zone. |\n| SRV | Specify a server by hostname and port to handle a service or services. |\n| TXT | Define a string of text that is associated with a hostname. |\n\n" + nullable: true + name: + type: string + description: 'The subdomain for this record. Use @ for records on the domain itself, and * to create a wildcard record.' + nullable: true + data: + type: string + description: A general data field that has different functions depending on the record type. + nullable: true + priority: + maximum: 65535 + minimum: 0 + type: integer + description: A priority value that is only relevant for SRV and MX records. + format: int32 + nullable: true + port: + maximum: 65535 + minimum: 0 + type: integer + description: A port value that is only relevant for SRV records. + format: int32 + nullable: true + ttl: + type: integer + description: 'This value is the time to live for the record, in seconds.' + format: int32 + nullable: true + weight: + maximum: 65535 + minimum: 0 + type: integer + description: The weight value that is only relevant for SRV records. + format: int32 + nullable: true + flags: + maximum: 255 + minimum: 0 + type: integer + description: An unsigned integer between 0-255 that is only relevant for CAA records. + format: int32 + nullable: true + tag: + maxLength: 15 + minLength: 1 + pattern: '[a-z0-9]+' + type: string + description: A parameter tag that is only relevant for CAA records. + nullable: true + description: 'Any values not provided will be retained. Provide empty strings to clear existing string values, nulls to retain the existing values.' + UpdateLoadBalancerRequest: + required: + - name + type: object + properties: + name: + minLength: 1 + type: string + description: The hostname of the load balancer. + forwarding_rules: + type: array + items: + $ref: '#/components/schemas/ForwardingRuleRequest' + description: The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. + nullable: true + health_check: + allOf: + - $ref: '#/components/schemas/HealthCheckRequest' + description: The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. + nullable: true + server_ids: + type: array + items: + type: integer + format: int64 + description: A list of server IDs to assign to this load balancer. + nullable: true + UpdateLoadBalancerResponse: + required: + - load_balancer + type: object + properties: + load_balancer: + allOf: + - $ref: '#/components/schemas/LoadBalancer' + links: + allOf: + - $ref: '#/components/schemas/ActionsLinks' + nullable: true + UpdateSshKeyRequest: + required: + - name + type: object + properties: + name: + minLength: 1 + type: string + description: A name to help you identify the key. + default: + type: boolean + description: "Do not provide or leave null to leave the default status of the key unchanged.\nOptional: If true this will be added to all new server installations (if we support SSH Key injection for the server's operating system)." + nullable: true + UpdateVpcRequest: + required: + - name + type: object + properties: + name: + maxLength: 250 + minLength: 0 + type: string + description: A name to help identify this VPC. + route_entries: + type: array + items: + $ref: '#/components/schemas/RouteEntryRequest' + description: The route entries that control how network traffic is directed through the VPC environment. + nullable: true + description: Any properties that are not included will be cleared. + UploadImageRequest: + required: + - replacement_strategy + - url + type: object + properties: + backup_type: + allOf: + - $ref: '#/components/schemas/BackupSlot' + description: "If replacement_strategy is anything other than 'specified', this must be provided.\n\n| Value | Description |\n| ----- | ----------- |\n| daily | A backup which is scheduled to be taken each day. |\n| weekly | A backup which is scheduled to be taken each week. |\n| monthly | A backup which is scheduled to be taken each month. |\n| temporary | A backup which is created on demand and only retained for a maximum of seven days. |\n\n" + nullable: true + replacement_strategy: + allOf: + - $ref: '#/components/schemas/BackupReplacementStrategy' + description: "The strategy for selecting which backup to replace (if any).\n\n| Value | Description |\n| ----- | ----------- |\n| none | Do not replace any existing backup: use a free slot of the provided backup type. If there are no free slots an error will occur. |\n| specified | Replace the specific backup id provided. |\n| oldest | Use any free slots of the provided backup type, and if there are no free slots replace the oldest unlocked and un-attached backup of the provided backup type. |\n| newest | Use any free slots of the provided backup type, and if there are no free slots replace the newest unlocked and un-attached backup of the provided backup type. |\n\n" + backup_id_to_replace: + type: integer + description: If replacement_strategy is 'specified' this property must be set to an existing backup. + format: int64 + nullable: true + label: + maxLength: 250 + type: string + description: An optional label to identify the backup. + nullable: true + url: + minLength: 1 + type: string + description: The source URL for the image to upload. Only HTTP and HTTPS sources are currently supported. + format: uri + Uptime: + required: + - type + type: object + properties: + type: + minLength: 1 + enum: + - uptime + type: string + description: Check the Uptime of a Server + UserData: + type: object + properties: + user_data: + type: string + description: The UserData that was last used to initialise the server. + nullable: true + UserInteractionRequired: + required: + - interaction_type + type: object + properties: + interaction_type: + allOf: + - $ref: '#/components/schemas/UserInteractionType' + UserInteractionType: + enum: + - continue-after-ping-failure + - allow-unclean-power-off + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| continue-after-ping-failure | Whether we should assume the server creation was successful despite failing to ping the server. |\n| allow-unclean-power-off | Whether we are permitted to perform an un-clean power off after the server failed to perform a clean shutdown. |\n\n" + x-enum-descriptions: + - Whether we should assume the server creation was successful despite failing to ping the server. + - Whether we are permitted to perform an un-clean power off after the server failed to perform a clean shutdown. + ValidationProblemDetails: + required: + - errors + - title + type: object + properties: + type: + type: string + nullable: true + title: + type: string + nullable: true + status: + type: integer + format: int32 + nullable: true + detail: + type: string + nullable: true + instance: + type: string + nullable: true + errors: + type: object + additionalProperties: + type: array + items: + type: string + nullable: true + additionalProperties: { } + VideoDevice: + enum: + - cirrus-logic + - standard + - virtio + - virtio-wide + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| cirrus-logic | Cirrus Logic GD5446 |\n| standard | Standard VGA with VESA 2.0 extensions |\n| virtio | Virtio VGA (800x600) |\n| virtio-wide | Virtio VGA (1600x900) |\n\n" + x-enum-descriptions: + - Cirrus Logic GD5446 + - Standard VGA with VESA 2.0 extensions + - Virtio VGA (800x600) + - Virtio VGA (1600x900) + VmMachineType: + enum: + - pc_i440fx_1point5 + - pc_i440fx_2point11 + - pc_i440fx_4point1 + - pc_i440fx_4point2 + - pc_i440fx_5point0 + - pc_i440fx_5point1 + - pc_i440fx_7point2 + - pc_i440fx_7point2point1 + - pc_i440fx_8point2 + type: string + description: "\n| Value | Description |\n| ----- | ----------- |\n| pc_i440fx_1point5 | PC i440FX 1.5 |\n| pc_i440fx_2point11 | PC i440FX 2.11 |\n| pc_i440fx_4point1 | PC i440FX 4.1 |\n| pc_i440fx_4point2 | PC i440FX 4.2 |\n| pc_i440fx_5point0 | PC i440FX 5.0 |\n| pc_i440fx_5point1 | PC i440FX 5.1 |\n| pc_i440fx_7point2 | PC i440FX 7.2 |\n| pc_i440fx_7point2point1 | PC i440FX 7.2.1 |\n| pc_i440fx_8point2 | PC i440FX 8.2 |\n\n" + x-enum-descriptions: + - PC i440FX 1.5 + - PC i440FX 2.11 + - PC i440FX 4.1 + - PC i440FX 4.2 + - PC i440FX 5.0 + - PC i440FX 5.1 + - PC i440FX 7.2 + - PC i440FX 7.2.1 + - PC i440FX 8.2 + Vpc: + required: + - id + - ip_range + - name + - route_entries + type: object + properties: + id: + type: integer + description: The ID of this VPC. + format: int64 + x-cli-format: 1 + name: + type: string + description: The name of this VPC. + x-cli-format: 2 + ip_range: + type: string + description: The IPv4 range for this VPC in CIDR format. + x-cli-format: 3 + route_entries: + type: array + items: + $ref: '#/components/schemas/RouteEntry' + description: The route entries that control how network traffic is directed through the VPC environment. + x-cli-entity-ref: name + VpcMember: + required: + - name + - resource_id + - resource_type + type: object + properties: + name: + type: string + description: The name of this VPC member. + resource_type: + allOf: + - $ref: '#/components/schemas/ResourceType' + resource_id: + type: string + description: The resource ID of this VPC member. + created_at: + type: string + description: The date and time in ISO8601 format of this resource's initial creation. + format: date-time + nullable: true + VpcMembersResponse: + required: + - members + - meta + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + members: + type: array + items: + $ref: '#/components/schemas/VpcMember' + VpcResponse: + required: + - vpc + type: object + properties: + vpc: + allOf: + - $ref: '#/components/schemas/Vpc' + VpcsResponse: + required: + - meta + - vpcs + type: object + properties: + meta: + allOf: + - $ref: '#/components/schemas/Meta' + description: 'Contains metadata about the response, currently this includes the total number of items.' + links: + allOf: + - $ref: '#/components/schemas/Links' + nullable: true + vpcs: + type: array + items: + $ref: '#/components/schemas/Vpc' + x-cli-entity-list: vpcs + securitySchemes: + apiToken: + type: http + scheme: bearer + bearerFormat: Bearer +security: + - apiToken: [ ] +tags: + - name: Customers + x-displayName: Billing Information + - name: DataUsages + x-displayName: Data Usage + - name: FailoverIps + x-displayName: Failover IPs + - name: LoadBalancers + x-displayName: Load Balancers + - name: ReverseNames + x-displayName: Reverse Names + - name: SampleSets + x-displayName: Sample Sets + - name: ServerActions + x-displayName: Server Actions + - name: Vpcs + x-displayName: Virtual Private Cloud + - name: Domains + x-displayName: Manage DNS + - name: NameserverNotes + description: Any domain records that are created or managed here will only have an effect if the nameservers for the domain are set to our local nameservers. + x-displayName: Nameserver Notes + x-traitTag: true +x-tagGroups: + - name: Account + tags: + - Accounts + - Customers + - name: Servers + tags: + - Servers + - ServerActions + - Actions + - DataUsages + - Images + - Keys + - ReverseNames + - SampleSets + - name: Server Create and Resize Options + tags: + - Images + - Regions + - Sizes + - Software + - name: Load Balancers + tags: + - LoadBalancers + - name: Virtual Private Cloud + tags: + - Vpcs + - name: DNS Management + tags: + - NameserverNotes + - Domains \ No newline at end of file diff --git a/eng/openapi/contract.json b/eng/openapi/contract.json new file mode 100644 index 0000000..d9d5965 --- /dev/null +++ b/eng/openapi/contract.json @@ -0,0 +1,6 @@ +{ + "source": "https://api.binarylane.com.au/reference/openapi.yaml", + "upstreamVersion": "0.39.1", + "sha256": "947b62c1649f68e8b0688d113f4fa3b5037b7c514b7e9cd33613e40574f854b1", + "retrievedUtc": "2026-08-16T09:22:01Z" +} diff --git a/eng/openapi/normalization.json b/eng/openapi/normalization.json new file mode 100644 index 0000000..2f50d42 --- /dev/null +++ b/eng/openapi/normalization.json @@ -0,0 +1,64 @@ +{ + "input": "binarylane-v2.openapi.yaml", + "normalizationRules": [ + { + "kind": "exclude-paths-containing", + "value": "#", + "reason": "The upstream document uses fragment-bearing paths to document variants of the one real server actions endpoint. A URI fragment is never sent in an HTTP request." + }, + { + "kind": "preserve-discriminator", + "schema": "ServerAction", + "property": "type", + "reason": "The action body is a discriminated union. Do not flatten or silently discard the discriminator mapping when generating test fixtures or internal wire code." + }, + { + "kind": "open-enum", + "reason": "Provider enum values are represented as strings in public DTOs so new upstream values remain readable by existing SDK versions." + } + ], + "excludedPaths": [ + "/v2/servers/{server_id}/actions#AddDisk", + "/v2/servers/{server_id}/actions#AttachBackup", + "/v2/servers/{server_id}/actions#ChangeAdvancedFeatures", + "/v2/servers/{server_id}/actions#ChangeAdvancedFirewallRules", + "/v2/servers/{server_id}/actions#ChangeBackupSchedule", + "/v2/servers/{server_id}/actions#ChangeIpv6", + "/v2/servers/{server_id}/actions#ChangeIpv6ReverseNameservers", + "/v2/servers/{server_id}/actions#ChangeKernel", + "/v2/servers/{server_id}/actions#ChangeManageOffsiteBackupCopies", + "/v2/servers/{server_id}/actions#ChangeNetwork", + "/v2/servers/{server_id}/actions#ChangeOffsiteBackupLocation", + "/v2/servers/{server_id}/actions#ChangePartner", + "/v2/servers/{server_id}/actions#ChangePortBlocking", + "/v2/servers/{server_id}/actions#ChangeRegion", + "/v2/servers/{server_id}/actions#ChangeReverseName", + "/v2/servers/{server_id}/actions#ChangeSeparatePrivateNetworkInterface", + "/v2/servers/{server_id}/actions#ChangeSourceAndDestinationCheck", + "/v2/servers/{server_id}/actions#ChangeThresholdAlerts", + "/v2/servers/{server_id}/actions#ChangeVpcIpv4", + "/v2/servers/{server_id}/actions#CloneUsingBackup", + "/v2/servers/{server_id}/actions#DeleteDisk", + "/v2/servers/{server_id}/actions#DetachBackup", + "/v2/servers/{server_id}/actions#DisableBackups", + "/v2/servers/{server_id}/actions#DisableSelinux", + "/v2/servers/{server_id}/actions#EnableBackups", + "/v2/servers/{server_id}/actions#EnableIpv6", + "/v2/servers/{server_id}/actions#IsRunning", + "/v2/servers/{server_id}/actions#PasswordReset", + "/v2/servers/{server_id}/actions#Ping", + "/v2/servers/{server_id}/actions#PowerCycle", + "/v2/servers/{server_id}/actions#PowerOff", + "/v2/servers/{server_id}/actions#PowerOn", + "/v2/servers/{server_id}/actions#Reboot", + "/v2/servers/{server_id}/actions#Rebuild", + "/v2/servers/{server_id}/actions#Rename", + "/v2/servers/{server_id}/actions#Resize", + "/v2/servers/{server_id}/actions#ResizeDisk", + "/v2/servers/{server_id}/actions#Restore", + "/v2/servers/{server_id}/actions#Shutdown", + "/v2/servers/{server_id}/actions#TakeBackup", + "/v2/servers/{server_id}/actions#Uncancel", + "/v2/servers/{server_id}/actions#Uptime" + ] +} diff --git a/eng/pack.sh b/eng/pack.sh new file mode 100755 index 0000000..2507438 --- /dev/null +++ b/eng/pack.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +output_directory="${1:-$repository_root/artifacts}" + +dotnet pack "$repository_root/src/BinaryLane.Api/BinaryLane.Api.csproj" \ + --configuration Release \ + --no-restore \ + -m:1 \ + -nodeReuse:false \ + -p:BuildInParallel=false \ + --output "$output_directory" diff --git a/eng/read-package-version.sh b/eng/read-package-version.sh new file mode 100755 index 0000000..56849ea --- /dev/null +++ b/eng/read-package-version.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +project_file="$(CDPATH= cd -- "$script_dir/.." && pwd)/src/BinaryLane.Api/BinaryLane.Api.csproj" + +if [[ ! -f "$project_file" ]]; then + printf 'Package project does not exist: %s\n' "$project_file" >&2 + exit 2 +fi + +read_project_property() { + local property_name="$1" + sed -n "s@^[[:space:]]*<${property_name}>[[:space:]]*\\([^<]*\\)[[:space:]]*.*@\\1@p" \ + "$project_file" | head -n 1 +} + +package_version="$(read_project_property PackageVersion)" +if [[ -z "$package_version" ]]; then + package_version="$(read_project_property Version)" +fi + +if [[ -z "$package_version" ]]; then + printf 'Package project must contain a literal or .\n' >&2 + exit 2 +fi + +printf '%s\n' "$package_version" diff --git a/eng/refresh-openapi-contract.sh b/eng/refresh-openapi-contract.sh new file mode 100755 index 0000000..41616eb --- /dev/null +++ b/eng/refresh-openapi-contract.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Downloads the public upstream contract into the reviewed repository snapshot. +# Run this only as part of a human-reviewed change; CI intentionally verifies +# and reports drift instead of changing the snapshot by itself. + +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +contract_directory="$repository_root/eng/openapi" +contract_file="$contract_directory/binarylane-v2.openapi.yaml" +metadata_file="$contract_directory/contract.json" +source_url="${BINARYLANE_OPENAPI_URL:-https://api.binarylane.com.au/reference/openapi.yaml}" +temporary_file="$(mktemp)" + +cleanup() { + rm -f "$temporary_file" +} +trap cleanup EXIT + +mkdir -p "$contract_directory" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ + --connect-timeout 15 --max-time 90 --retry 3 --retry-all-errors \ + --output "$temporary_file" \ + "$source_url" + +if [[ ! -s "$temporary_file" ]]; then + printf 'Downloaded OpenAPI contract is empty.\n' >&2 + exit 1 +fi + +upstream_version="$({ + awk ' + /^info:$/ { in_info = 1; next } + in_info && /^ version: / { + sub(/^ version: /, "") + gsub(/[[:space:]]+$/, "") + print + exit + } + ' "$temporary_file" +} || true)" + +if [[ -z "$upstream_version" ]]; then + printf 'Unable to read info.version from the downloaded OpenAPI contract.\n' >&2 + exit 1 +fi + +sha256="$(sha256sum "$temporary_file" | awk '{print $1}')" +retrieved_utc="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +cp "$temporary_file" "$contract_file" +printf '{\n "source": "%s",\n "upstreamVersion": "%s",\n "sha256": "%s",\n "retrievedUtc": "%s"\n}\n' \ + "$source_url" "$upstream_version" "$sha256" "$retrieved_utc" > "$metadata_file" + +printf 'Updated %s (upstream %s, sha256 %s).\n' \ + "$contract_file" "$upstream_version" "$sha256" diff --git a/eng/test-demo-package.sh b/eng/test-demo-package.sh new file mode 100755 index 0000000..5e04939 --- /dev/null +++ b/eng/test-demo-package.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Compiles the tracked demo against the nupkg just produced by this repository, +# rather than against the local source project. This catches bad package +# metadata, missing dependencies, and public API regressions. + +if [[ $# -ne 1 ]]; then + printf 'Usage: %s \n' "${0##*/}" >&2 + exit 2 +fi + +package_directory="$(CDPATH= cd -- "$1" && pwd)" +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +demo_source="$repository_root/examples/BinaryLane.Api.Demo" +temporary_directory="$(mktemp -d)" +export NUGET_PACKAGES="$temporary_directory/nuget-packages" + +cleanup() { + rm -rf "$temporary_directory" +} +trap cleanup EXIT + +package_file="$(find "$package_directory" -maxdepth 1 -type f -name 'BinaryLane.Api.*.nupkg' -print -quit)" +if [[ -z "$package_file" ]]; then + printf 'No BinaryLane.Api .nupkg found in %s.\n' "$package_directory" >&2 + exit 2 +fi + +package_name="$(basename -- "$package_file")" +package_version="${package_name#BinaryLane.Api.}" +package_version="${package_version%.nupkg}" +demo_directory="$temporary_directory/BinaryLane.Api.Demo" + +cp -R "$demo_source" "$demo_directory" +cp "$repository_root/Directory.Build.props" "$temporary_directory/Directory.Build.props" +cp "$repository_root/Directory.Packages.props" "$temporary_directory/Directory.Packages.props" +demo_project="$demo_directory/BinaryLane.Api.Demo.csproj" + +if ! grep -Fq '' "$demo_project"; then + printf 'Demo project does not contain the expected local SDK project reference.\n' >&2 + exit 2 +fi + +sed -i "/<\\/ItemGroup>/i\\ " \ + "$temporary_directory/Directory.Packages.props" + +sed -i "s###" \ + "$demo_project" + +dotnet restore "$demo_project" \ + --source "$package_directory" \ + --source https://api.nuget.org/v3/index.json \ + --no-cache \ + -p:RestoreLockedMode=false +dotnet build "$demo_project" \ + --configuration Release \ + --no-restore \ + -m:1 \ + -nodeReuse:false \ + -p:BuildInParallel=false + +printf 'Demo compiled against BinaryLane.Api %s from %s.\n' \ + "$package_version" "$package_directory" diff --git a/eng/verify-openapi-contract.sh b/eng/verify-openapi-contract.sh new file mode 100755 index 0000000..e354449 --- /dev/null +++ b/eng/verify-openapi-contract.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Checks the live public BinaryLane OpenAPI document against the committed +# snapshot. It reports drift but never changes source-controlled files. + +script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repository_root="$(CDPATH= cd -- "$script_dir/.." && pwd)" +metadata_file="$repository_root/eng/openapi/contract.json" +source_url="${BINARYLANE_OPENAPI_URL:-https://api.binarylane.com.au/reference/openapi.yaml}" +temporary_file="$(mktemp)" + +cleanup() { + rm -f "$temporary_file" +} +trap cleanup EXIT + +if [[ ! -f "$metadata_file" ]]; then + printf 'Committed contract metadata does not exist: %s\n' "$metadata_file" >&2 + exit 2 +fi + +expected_sha256="$(sed -n 's/^[[:space:]]*"sha256"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata_file")" +expected_version="$(sed -n 's/^[[:space:]]*"upstreamVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata_file")" + +if [[ -z "$expected_sha256" || -z "$expected_version" ]]; then + printf 'Committed contract metadata is missing a SHA-256 or upstream version.\n' >&2 + exit 2 +fi + +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ + --connect-timeout 15 --max-time 90 --retry 3 --retry-all-errors \ + --output "$temporary_file" \ + "$source_url" + +if [[ ! -s "$temporary_file" ]]; then + printf 'Downloaded OpenAPI contract is empty.\n' >&2 + exit 3 +fi + +actual_sha256="$(sha256sum "$temporary_file" | awk '{print $1}')" +actual_version="$({ + awk ' + /^info:$/ { in_info = 1; next } + in_info && /^ version: / { + sub(/^ version: /, "") + gsub(/[[:space:]]+$/, "") + print + exit + } + ' "$temporary_file" +} || true)" + +if [[ "$expected_sha256" == "$actual_sha256" && "$expected_version" == "$actual_version" ]]; then + printf 'BinaryLane OpenAPI contract matches committed snapshot (%s, %s).\n' \ + "$actual_version" "$actual_sha256" + exit 0 +fi + +printf 'BinaryLane OpenAPI contract changed.\n' >&2 +printf ' expected version: %s\n' "$expected_version" >&2 +printf ' actual version: %s\n' "${actual_version:-}" >&2 +printf ' expected sha256: %s\n' "$expected_sha256" >&2 +printf ' actual sha256: %s\n' "$actual_sha256" >&2 +printf 'Run ./eng/refresh-openapi-contract.sh, review the diff, then update SDK coverage and tests.\n' >&2 +exit 1 diff --git a/examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj b/examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj new file mode 100644 index 0000000..46b3363 --- /dev/null +++ b/examples/BinaryLane.Api.Demo/BinaryLane.Api.Demo.csproj @@ -0,0 +1,19 @@ + + + Exe + net8.0 + false + binarylane-api-demo + + + + + + + + + + + + + diff --git a/examples/BinaryLane.Api.Demo/Program.cs b/examples/BinaryLane.Api.Demo/Program.cs new file mode 100644 index 0000000..27db7fb --- /dev/null +++ b/examples/BinaryLane.Api.Demo/Program.cs @@ -0,0 +1,167 @@ +using System.Text.Json; +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.DependencyInjection; +using BinaryLane.Api.V2.Errors; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +return await DemoProgram.RunAsync(args); + +internal static class DemoProgram +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = true, + }; + + public static async Task RunAsync(string[] args) + { + var command = args.FirstOrDefault()?.ToLowerInvariant(); + if (command is null or "--help" or "-h" or "help") + { + PrintUsage(); + return 0; + } + + using var cancellationSource = new CancellationTokenSource(); + Console.CancelKeyPress += (_, eventArgs) => + { + eventArgs.Cancel = true; + cancellationSource.Cancel(); + }; + + var builder = Host.CreateApplicationBuilder(args); + builder.Configuration + .AddJsonFile("appsettings.json", optional: true) + .AddUserSecrets(typeof(DemoProgram).Assembly, optional: true) + .AddEnvironmentVariables(); + + var token = builder.Configuration["BinaryLane:ApiToken"] + ?? Environment.GetEnvironmentVariable("BINARYLANE_API_TOKEN"); + + if (string.IsNullOrWhiteSpace(token)) + { + Console.Error.WriteLine( + "No API token is configured. Use user secrets or set BINARYLANE_API_TOKEN. " + + "Run with --help for a safe setup command."); + return 2; + } + + builder.Services.AddBinaryLaneApi(options => + { + options.ApiToken = token; + + var baseUrl = builder.Configuration["BinaryLane:BaseUrl"]; + if (!string.IsNullOrWhiteSpace(baseUrl)) + { + options.BaseUrl = baseUrl; + } + }); + + using var host = builder.Build(); + var client = host.Services.GetRequiredService(); + + try + { + switch (command) + { + case "account": + await WriteJsonAsync( + await client.Account.GetAsync(cancellationSource.Token)); + return 0; + + case "servers": + await WriteItemsAsync( + client.Servers.ListAllAsync(cancellationToken: cancellationSource.Token)); + return 0; + + case "server": + if (!TryReadServerId(args, out var serverId)) + { + return 2; + } + + await WriteJsonAsync( + await client.Servers.GetAsync(serverId, cancellationSource.Token)); + return 0; + + case "regions": + await WriteItemsAsync( + client.Regions.ListAllAsync(cancellationToken: cancellationSource.Token)); + return 0; + + default: + Console.Error.WriteLine($"Unknown command: {command}"); + PrintUsage(); + return 2; + } + } + catch (OperationCanceledException) when (cancellationSource.IsCancellationRequested) + { + Console.Error.WriteLine("Cancelled."); + return 130; + } + catch (BinaryLaneApiException exception) + { + // Provider details may contain user data. Do not print Problem, headers, + // or ResponseBody in a sample application. + Console.Error.WriteLine( + $"BinaryLane request failed with HTTP {(int)exception.StatusCode} " + + $"({exception.StatusCode}) at {exception.RequestUri.AbsolutePath}."); + return 1; + } + catch (Exception exception) + { + Console.Error.WriteLine($"Request failed ({exception.GetType().Name})."); + return 1; + } + } + + private static bool TryReadServerId(string[] args, out long serverId) + { + if (args.Length > 1 && long.TryParse(args[1], out serverId) && serverId > 0) + { + return true; + } + + Console.Error.WriteLine("Usage: server "); + serverId = default; + return false; + } + + private static async Task WriteItemsAsync(IAsyncEnumerable items) + { + await foreach (var item in items) + { + await WriteJsonAsync(item); + } + } + + private static Task WriteJsonAsync(T value) + { + Console.WriteLine(JsonSerializer.Serialize(value, JsonOptions)); + return Task.CompletedTask; + } + + private static void PrintUsage() + { + Console.WriteLine( + """ + BinaryLane.Api demo — read-only operations only + + Setup: + dotnet user-secrets set "BinaryLane:ApiToken" "your-token" \ + --project examples/BinaryLane.Api.Demo + + Commands: + account + servers + server + regions + + You can also set BINARYLANE_API_TOKEN for one process. Never commit + a token or add it to appsettings.json. + """); + } +} diff --git a/examples/BinaryLane.Api.Demo/README.md b/examples/BinaryLane.Api.Demo/README.md new file mode 100644 index 0000000..fac778b --- /dev/null +++ b/examples/BinaryLane.Api.Demo/README.md @@ -0,0 +1,28 @@ +# BinaryLane.Api demo + +This console app demonstrates read-only account, server, and region requests. +It does not create, change, or delete infrastructure. + +## Configure a token + +From the repository root, store a token with .NET user secrets: + +```bash +dotnet user-secrets set \ + "BinaryLane:ApiToken" "your-token" \ + --project examples/BinaryLane.Api.Demo +``` + +Alternatively, set `BINARYLANE_API_TOKEN` for one process. Do not add a token +to `appsettings.json`. + +## Run it + +```bash +dotnet run --project examples/BinaryLane.Api.Demo -- account +dotnet run --project examples/BinaryLane.Api.Demo -- servers +dotnet run --project examples/BinaryLane.Api.Demo -- server 12345 +dotnet run --project examples/BinaryLane.Api.Demo -- regions +``` + +Press Ctrl+C to cancel a request or listing. diff --git a/examples/BinaryLane.Api.Demo/appsettings.json b/examples/BinaryLane.Api.Demo/appsettings.json new file mode 100644 index 0000000..2ad85dc --- /dev/null +++ b/examples/BinaryLane.Api.Demo/appsettings.json @@ -0,0 +1,5 @@ +{ + "BinaryLane": { + "BaseUrl": "https://api.binarylane.com.au/" + } +} diff --git a/examples/BinaryLane.Api.Demo/packages.lock.json b/examples/BinaryLane.Api.Demo/packages.lock.json new file mode 100644 index 0000000..7825229 --- /dev/null +++ b/examples/BinaryLane.Api.Demo/packages.lock.json @@ -0,0 +1,320 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "Microsoft.Extensions.Configuration.Json": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "7tYqdPPpAK+3jO9d5LTuCK2VxrEdf85Ol4trUr6ds4jclBecadWZ/RyPCbNjfbN5iGTfUnD/h65TOQuqQv2c+A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Json": "8.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0" + } + }, + "Microsoft.Extensions.Hosting": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "bP9EEkHBEfjgYiG8nUaXqMk/ujwJrffOkNPP7onpRMO8R+OUSESSP4xHkCAXgYZ1COP2Q9lXlU5gkMFh20gRuw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.2", + "Microsoft.Extensions.Configuration.CommandLine": "8.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1", + "Microsoft.Extensions.Configuration.Json": "8.0.1", + "Microsoft.Extensions.Configuration.UserSecrets": "8.0.1", + "Microsoft.Extensions.DependencyInjection": "8.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging.Configuration": "8.0.1", + "Microsoft.Extensions.Logging.Console": "8.0.1", + "Microsoft.Extensions.Logging.Debug": "8.0.1", + "Microsoft.Extensions.Logging.EventLog": "8.0.1", + "Microsoft.Extensions.Logging.EventSource": "8.0.1", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "EJzSNO9oaAXnTdtdNO6npPRsIIeZCBSNmdQ091VDO7fBiOtJAAeEq6dtrVXIi3ZyjC5XRSAtVvF8SzcneRHqKQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileSystemGlobbing": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "QWwTrsgOnJMmn+XUslm8D2H1n3PkP/u/v52FODtyBc/k4W9r3i2vcXXeeX/upnzllJYRRbrzVzT0OclfNJtBJA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.2", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "uzcg/5U2eLyn5LIKlERkdSxw6VPC1yydnOSQiRRWGBGN3kphq3iL4emORzrojScDmxRhv49gp5BI8U3Dz7y4iA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging.Configuration": "8.0.1", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "B8hqNuYudC2RB+L/DI33uO4rf5by41fZVdcVL2oZj0UyoAZqnwTwYHp1KafoH4nkl1/23piNeybFFASaV2HkFg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "ZD1m4GXoxcZeDJIq8qePKj+QAWeQNO/OG8skvrOG8RQfxLp9MAKRoliTc27xanoNUzeqvX5HhS/I7c0BvwAYUg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2", + "System.Diagnostics.EventLog": "8.0.1" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "YMXMAla6B6sEf/SnfZYTty633Ool3AH7KOw2LOaaEqwSo2piK4f7HMtzyc3CNiipDnq1fsUSuG5Oc7ZzpVy8WQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "n1ZP7NM2Gkn/MgD8+eOT5MulMj6wfeQMNS2Pizvq5GHCZfjlFMXV2irQlQmJhwA2VABC57M0auudO89Iu2uRLg==" + }, + "binarylane.api": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[8.0.2, )", + "Microsoft.Extensions.Http": "[8.0.1, )", + "Microsoft.Extensions.Options": "[8.0.2, )" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "7IQhGK+wjyGrNsPBjJcZwWAr+Wf6D4+TwOptUt77bWtgNkiV8tDEbhFS+dDamtQFZ2X7kWG9m71iZQRj2x3zgQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + }, + "Microsoft.Extensions.Http": { + "type": "CentralTransitive", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + } + } + } +} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..c66295e --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.302", + "rollForward": "disable", + "allowPrerelease": false + } +} diff --git a/src/BinaryLane.Api/Authentication/IBinaryLaneTokenProvider.cs b/src/BinaryLane.Api/Authentication/IBinaryLaneTokenProvider.cs new file mode 100644 index 0000000..87cb210 --- /dev/null +++ b/src/BinaryLane.Api/Authentication/IBinaryLaneTokenProvider.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace BinaryLane.Api.V2.Authentication; + +/// Supplies a BinaryLane bearer token for an outgoing request. +public interface IBinaryLaneTokenProvider +{ + /// Gets the current bearer token. + ValueTask GetTokenAsync(CancellationToken cancellationToken = default); +} diff --git a/src/BinaryLane.Api/Authentication/OptionsBinaryLaneTokenProvider.cs b/src/BinaryLane.Api/Authentication/OptionsBinaryLaneTokenProvider.cs new file mode 100644 index 0000000..8239b70 --- /dev/null +++ b/src/BinaryLane.Api/Authentication/OptionsBinaryLaneTokenProvider.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Configuration; +using Microsoft.Extensions.Options; + +namespace BinaryLane.Api.V2.Authentication; + +/// Uses the token configured in . +public sealed class OptionsBinaryLaneTokenProvider : IBinaryLaneTokenProvider +{ + private readonly IOptionsMonitor _options; + + /// Initializes the provider. + public OptionsBinaryLaneTokenProvider(IOptionsMonitor options) => + _options = options ?? throw new ArgumentNullException(nameof(options)); + + /// + public ValueTask GetTokenAsync(CancellationToken cancellationToken = default) + { + var token = _options.CurrentValue.ApiToken; + if (string.IsNullOrWhiteSpace(token)) + { + throw new InvalidOperationException( + "No BinaryLane API token is configured. Set BinaryLaneOptions.ApiToken or register a custom IBinaryLaneTokenProvider."); + } + + return new ValueTask(token!); + } +} diff --git a/src/BinaryLane.Api/Authentication/StaticBinaryLaneTokenProvider.cs b/src/BinaryLane.Api/Authentication/StaticBinaryLaneTokenProvider.cs new file mode 100644 index 0000000..c31af45 --- /dev/null +++ b/src/BinaryLane.Api/Authentication/StaticBinaryLaneTokenProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace BinaryLane.Api.V2.Authentication; + +/// Provides a fixed token. Prefer a custom provider when tokens rotate. +public sealed class StaticBinaryLaneTokenProvider : IBinaryLaneTokenProvider +{ + private readonly string _token; + + /// Initializes the provider with a non-empty bearer token. + public StaticBinaryLaneTokenProvider(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentException("A BinaryLane API token is required.", nameof(token)); + } + + _token = token; + } + + /// + public ValueTask GetTokenAsync(CancellationToken cancellationToken = default) => + new(_token); +} diff --git a/src/BinaryLane.Api/BinaryLane.Api.csproj b/src/BinaryLane.Api/BinaryLane.Api.csproj new file mode 100644 index 0000000..19382a1 --- /dev/null +++ b/src/BinaryLane.Api/BinaryLane.Api.csproj @@ -0,0 +1,48 @@ + + + net8.0;netstandard2.0 + BinaryLane.Api + BinaryLane.Api + BinaryLane.Api.V2 + 0.1.0-beta.1 + Alex Hope-O'Connor + alex.hope.oconnor + Copyright © 2026 Alex Hope-O'Connor + true + $(NoWarn);1591 + true + Unofficial, typed .NET client for BinaryLane's v2 API. + binarylane;api;cloud;vps;dns + https://github.com/alexhopeoconnor/binarylane-dotnet + https://github.com/alexhopeoconnor/binarylane-dotnet + git + true + MIT + README.md + icon.png + true + snupkg + true + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BinaryLane.Api/BinaryLaneClient.cs b/src/BinaryLane.Api/BinaryLaneClient.cs new file mode 100644 index 0000000..c452cb8 --- /dev/null +++ b/src/BinaryLane.Api/BinaryLaneClient.cs @@ -0,0 +1,117 @@ +using System; +using System.Net.Http; +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Resources; +using Microsoft.Extensions.DependencyInjection; + +namespace BinaryLane.Api.V2; + +/// Default composable BinaryLane v2 client. +public sealed class BinaryLaneClient : IBinaryLaneClient +{ + /// + /// Creates a client using the supplied HTTP client and token provider. The HTTP client should + /// normally be obtained through IHttpClientFactory or registered with + /// AddBinaryLaneApi. + /// + public BinaryLaneClient(HttpClient httpClient, IBinaryLaneTokenProvider tokenProvider) + : this(httpClient, tokenProvider, new BinaryLaneJsonSerializerOptions()) + { + } + + /// Creates a client using supplied HTTP, authentication, and JSON services. + [ActivatorUtilitiesConstructor] + public BinaryLaneClient( + HttpClient httpClient, + IBinaryLaneTokenProvider tokenProvider, + BinaryLaneJsonSerializerOptions json) + : this(new BinaryLaneHttpExecutor(httpClient, tokenProvider, json), json) + { + } + + /// Creates a client over a custom request executor. + public BinaryLaneClient(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(executor); + ArgumentNullException.ThrowIfNull(json); +#else + if (executor is null) + { + throw new ArgumentNullException(nameof(executor)); + } + + if (json is null) + { + throw new ArgumentNullException(nameof(json)); + } +#endif + + Executor = executor; + + Account = new AccountApi(executor, json); + Actions = new ActionsApi(executor, json); + Billing = new BillingApi(executor, json); + DataUsage = new DataUsageApi(executor, json); + Domains = new DomainsApi(executor, json); + Images = new ImagesApi(executor, json); + SshKeys = new SshKeysApi(executor, json); + LoadBalancers = new LoadBalancersApi(executor, json); + Regions = new RegionsApi(executor, json); + ReverseNames = new ReverseNamesApi(executor, json); + SampleSets = new SampleSetsApi(executor, json); + Servers = new ServersApi(executor, json); + Sizes = new SizesApi(executor, json); + Software = new SoftwareApi(executor, json); + Vpcs = new VpcsApi(executor, json); + } + + /// + public IBinaryLaneApiExecutor Executor { get; } + + /// + public IAccountApi Account { get; } + + /// + public IActionsApi Actions { get; } + + /// + public IBillingApi Billing { get; } + + /// + public IDataUsageApi DataUsage { get; } + + /// + public IDomainsApi Domains { get; } + + /// + public IImagesApi Images { get; } + + /// + public ISshKeysApi SshKeys { get; } + + /// + public ILoadBalancersApi LoadBalancers { get; } + + /// + public IRegionsApi Regions { get; } + + /// + public IReverseNamesApi ReverseNames { get; } + + /// + public ISampleSetsApi SampleSets { get; } + + /// + public IServersApi Servers { get; } + + /// + public ISizesApi Sizes { get; } + + /// + public ISoftwareApi Software { get; } + + /// + public IVpcsApi Vpcs { get; } +} diff --git a/src/BinaryLane.Api/Configuration/BinaryLaneOptions.cs b/src/BinaryLane.Api/Configuration/BinaryLaneOptions.cs new file mode 100644 index 0000000..218f31d --- /dev/null +++ b/src/BinaryLane.Api/Configuration/BinaryLaneOptions.cs @@ -0,0 +1,36 @@ +using System; + +namespace BinaryLane.Api.V2.Configuration; + +/// Configures a BinaryLane API client. +public sealed class BinaryLaneOptions +{ + /// The configuration section convention used by consuming applications. + public const string SectionName = "BinaryLane"; + + /// The BinaryLane API root URL. + public string BaseUrl { get; set; } = "https://api.binarylane.com.au/"; + + /// + /// The bearer token to use when the default token provider is registered. Prefer a secret store, + /// user secrets, or environment-variable based configuration rather than committing this value. + /// + public string? ApiToken { get; set; } + + /// Timeout applied to individual HTTP requests. + public int RequestTimeoutSeconds { get; set; } = 100; + + internal static bool IsValid(BinaryLaneOptions options) + { + if (options.RequestTimeoutSeconds < 1 || options.RequestTimeoutSeconds > 300) + { + return false; + } + + return Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out var uri) + && uri.Scheme == Uri.UriSchemeHttps + && string.IsNullOrEmpty(uri.UserInfo) + && string.IsNullOrEmpty(uri.Query) + && string.IsNullOrEmpty(uri.Fragment); + } +} diff --git a/src/BinaryLane.Api/Configuration/BinaryLaneOptionsValidator.cs b/src/BinaryLane.Api/Configuration/BinaryLaneOptionsValidator.cs new file mode 100644 index 0000000..1a38bac --- /dev/null +++ b/src/BinaryLane.Api/Configuration/BinaryLaneOptionsValidator.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; + +namespace BinaryLane.Api.V2.Configuration; + +/// Validates client options at application startup. +public sealed class BinaryLaneOptionsValidator : IValidateOptions +{ + /// + public ValidateOptionsResult Validate(string? name, BinaryLaneOptions options) + { + if (options is null) + { + return ValidateOptionsResult.Fail("BinaryLane options are required."); + } + + if (!BinaryLaneOptions.IsValid(options)) + { + return ValidateOptionsResult.Fail( + "BinaryLane BaseUrl must be an absolute HTTPS URL and RequestTimeoutSeconds must be between 1 and 300."); + } + + return ValidateOptionsResult.Success; + } +} diff --git a/src/BinaryLane.Api/DependencyInjection/ServiceCollectionExtensions.cs b/src/BinaryLane.Api/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..cd94fdd --- /dev/null +++ b/src/BinaryLane.Api/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,81 @@ +using System; +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.Configuration; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Resources; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace BinaryLane.Api.V2.DependencyInjection; + +/// Dependency-injection registration for the BinaryLane API client. +public static class ServiceCollectionExtensions +{ + /// + /// Registers a typed BinaryLane client. The returned builder lets an application compose its + /// own proxy, telemetry, and safe GET-only resilience handlers. + /// + public static IHttpClientBuilder AddBinaryLaneApi( + this IServiceCollection services, + Action configure) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(configure); +#else + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure is null) + { + throw new ArgumentNullException(nameof(configure)); + } +#endif + + services.AddOptions() + .Configure(configure) + .ValidateOnStart(); + services.TryAddSingleton, BinaryLaneOptionsValidator>(); + services.TryAddSingleton(); + services.TryAddSingleton(); + + var clientBuilder = services.AddHttpClient((serviceProvider, client) => + { + var options = serviceProvider.GetRequiredService>().Value; + client.BaseAddress = EnsureTrailingSlash(options.BaseUrl); + client.Timeout = TimeSpan.FromSeconds(options.RequestTimeoutSeconds); + }); + + // Register focused resources as well as the façade so applications can depend on precisely + // the API area they use. TryAdd preserves a consumer's explicit test double or custom resource. + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Account); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Actions); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Billing); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().DataUsage); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Domains); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Images); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().SshKeys); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().LoadBalancers); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Regions); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().ReverseNames); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().SampleSets); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Servers); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Sizes); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Software); + services.TryAddTransient(serviceProvider => serviceProvider.GetRequiredService().Vpcs); + + return clientBuilder; + } + + private static Uri EnsureTrailingSlash(string baseUrl) + { + var uri = new Uri(baseUrl, UriKind.Absolute); + var absoluteUri = uri.AbsoluteUri; + return absoluteUri.Length > 0 && absoluteUri[absoluteUri.Length - 1] == '/' + ? uri + : new Uri(absoluteUri + "/", UriKind.Absolute); + } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneApiException.cs b/src/BinaryLane.Api/Errors/BinaryLaneApiException.cs new file mode 100644 index 0000000..9353c48 --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneApiException.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Errors; + +/// Base exception for an unsuccessful BinaryLane API response. +public class BinaryLaneApiException : Exception +{ + internal BinaryLaneApiException( + string message, + HttpStatusCode statusCode, + Uri requestUri, + BinaryLaneApiProblem? problem, + string? responseBody, + IReadOnlyDictionary> headers) + : base(message) + { + StatusCode = statusCode; + RequestUri = requestUri; + Problem = problem; + ResponseBody = responseBody; + Headers = headers; + } + + /// HTTP status code returned by BinaryLane. + public HttpStatusCode StatusCode { get; } + + /// The request URI which produced the response. + public Uri RequestUri { get; } + + /// Structured problem details, when BinaryLane supplied them. + public BinaryLaneApiProblem? Problem { get; } + + /// + /// Bounded raw response text for diagnostics. It omits request headers, but can contain + /// provider-supplied or user-provided data; avoid logging it indiscriminately. + /// + public string? ResponseBody { get; } + + /// Response and content headers. + public IReadOnlyDictionary> Headers { get; } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneApiProblem.cs b/src/BinaryLane.Api/Errors/BinaryLaneApiProblem.cs new file mode 100644 index 0000000..79202ae --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneApiProblem.cs @@ -0,0 +1,17 @@ +namespace BinaryLane.Api.V2.Errors; + +/// A structured problem response returned by BinaryLane. +public sealed class BinaryLaneApiProblem +{ + /// Problem type URI, if supplied. + public string? Type { get; internal set; } + + /// Short problem title, if supplied. + public string? Title { get; internal set; } + + /// Human-readable problem detail, if supplied. + public string? Detail { get; internal set; } + + /// Provider-supplied status code, if supplied. + public int? Status { get; internal set; } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneForbiddenException.cs b/src/BinaryLane.Api/Errors/BinaryLaneForbiddenException.cs new file mode 100644 index 0000000..28f9f7c --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneForbiddenException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Errors; + +/// Thrown when the current account is not authorized for an operation. +public sealed class BinaryLaneForbiddenException : BinaryLaneApiException +{ + internal BinaryLaneForbiddenException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary> headers) + : base(message, HttpStatusCode.Forbidden, requestUri, problem, responseBody, headers) + { + } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneNotFoundException.cs b/src/BinaryLane.Api/Errors/BinaryLaneNotFoundException.cs new file mode 100644 index 0000000..7239417 --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneNotFoundException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Errors; + +/// Thrown when BinaryLane cannot find a requested resource. +public sealed class BinaryLaneNotFoundException : BinaryLaneApiException +{ + internal BinaryLaneNotFoundException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary> headers) + : base(message, HttpStatusCode.NotFound, requestUri, problem, responseBody, headers) + { + } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneUnauthorizedException.cs b/src/BinaryLane.Api/Errors/BinaryLaneUnauthorizedException.cs new file mode 100644 index 0000000..00db41c --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneUnauthorizedException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Errors; + +/// Thrown when BinaryLane rejects credentials. +public sealed class BinaryLaneUnauthorizedException : BinaryLaneApiException +{ + internal BinaryLaneUnauthorizedException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary> headers) + : base(message, HttpStatusCode.Unauthorized, requestUri, problem, responseBody, headers) + { + } +} diff --git a/src/BinaryLane.Api/Errors/BinaryLaneValidationException.cs b/src/BinaryLane.Api/Errors/BinaryLaneValidationException.cs new file mode 100644 index 0000000..2b8d653 --- /dev/null +++ b/src/BinaryLane.Api/Errors/BinaryLaneValidationException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Errors; + +/// Thrown when BinaryLane rejects a malformed or invalid request. +public sealed class BinaryLaneValidationException : BinaryLaneApiException +{ + internal BinaryLaneValidationException(string message, HttpStatusCode statusCode, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary> headers) + : base(message, statusCode, requestUri, problem, responseBody, headers) + { + } +} diff --git a/src/BinaryLane.Api/Http/BinaryLaneHttpExecutor.cs b/src/BinaryLane.Api/Http/BinaryLaneHttpExecutor.cs new file mode 100644 index 0000000..6e9065c --- /dev/null +++ b/src/BinaryLane.Api/Http/BinaryLaneHttpExecutor.cs @@ -0,0 +1,492 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.Errors; + +namespace BinaryLane.Api.V2.Http; + +/// Default HTTP implementation for the BinaryLane API. +public sealed class BinaryLaneHttpExecutor : IBinaryLaneApiExecutor +{ + private const int MaximumDiagnosticBodyLength = 32 * 1024; + private const long MaximumSuccessBodyLength = 16L * 1024 * 1024; + private static readonly Uri DefaultBaseUri = new("https://api.binarylane.com.au/"); + private readonly HttpClient _httpClient; + private readonly IBinaryLaneTokenProvider _tokenProvider; + private readonly BinaryLaneJsonSerializerOptions _json; + + /// Initializes the executor. + public BinaryLaneHttpExecutor( + HttpClient httpClient, + IBinaryLaneTokenProvider tokenProvider, + BinaryLaneJsonSerializerOptions? json = null) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider)); + _json = json ?? new BinaryLaneJsonSerializerOptions(); + } + + /// + public async Task> SendAsync( + BinaryLaneRequest request, + CancellationToken cancellationToken = default) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(request); +#else + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } +#endif + + using var message = await CreateMessageAsync(request, cancellationToken).ConfigureAwait(false); + using var response = await _httpClient + .SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + + var headers = CollectHeaders(response); + var body = response.Content is null + ? string.Empty + : response.IsSuccessStatusCode + ? await ReadBodyAsync(response.Content, cancellationToken).ConfigureAwait(false) + : await ReadDiagnosticBodyAsync(response.Content, cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + throw CreateException(response.StatusCode, message.RequestUri!, body, headers); + } + + var result = Deserialize(body); + return new BinaryLaneResponse(result, response.StatusCode, message.RequestUri!, headers); + } + + /// + public async Task> SendAsync( + BinaryLaneRequest request, + CancellationToken cancellationToken = default) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(request); +#else + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } +#endif + + using var message = await CreateMessageAsync(request, cancellationToken).ConfigureAwait(false); + using var response = await _httpClient + .SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + + var headers = CollectHeaders(response); + var body = response.Content is null + ? string.Empty + : response.IsSuccessStatusCode + ? await ReadBodyAsync(response.Content, cancellationToken).ConfigureAwait(false) + : await ReadDiagnosticBodyAsync(response.Content, cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + throw CreateException(response.StatusCode, message.RequestUri!, body, headers); + } + + return new BinaryLaneResponse(null, response.StatusCode, message.RequestUri!, headers); + } + + /// + public async Task GetAsync( + string path, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + (await SendAsync(new BinaryLaneRequest(HttpMethod.Get, path) { Query = query }, cancellationToken) + .ConfigureAwait(false)).Body; + + /// + public async Task PostAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + (await SendAsync(new BinaryLaneRequest(HttpMethod.Post, path) { Body = body, Query = query }, cancellationToken) + .ConfigureAwait(false)).Body; + + /// + public async Task PutAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + (await SendAsync(new BinaryLaneRequest(HttpMethod.Put, path) { Body = body, Query = query }, cancellationToken) + .ConfigureAwait(false)).Body; + + /// + public async Task PatchAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + (await SendAsync(new BinaryLaneRequest(new HttpMethod("PATCH"), path) { Body = body, Query = query }, cancellationToken) + .ConfigureAwait(false)).Body; + + /// + public async Task DeleteAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) + { + await SendAsync(new BinaryLaneRequest(HttpMethod.Delete, path) { Body = body, Query = query }, cancellationToken) + .ConfigureAwait(false); + } + + private async Task CreateMessageAsync(BinaryLaneRequest request, CancellationToken cancellationToken) + { + var requestUri = CreateUri(request.Path, request.Query); + var token = await _tokenProvider.GetTokenAsync(cancellationToken).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(token)) + { + throw new InvalidOperationException("The configured BinaryLane token provider returned an empty token."); + } + + var message = new HttpRequestMessage(request.Method, requestUri); + try + { + if (request.Headers is not null) + { + foreach (var header in request.Headers) + { + if (string.Equals(header.Key, "Authorization", StringComparison.OrdinalIgnoreCase) || + string.Equals(header.Key, "Host", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException( + $"The '{header.Key}' header is managed by the BinaryLane client and cannot be overridden.", + nameof(request)); + } + + try + { + message.Headers.Add(header.Key, header.Value); + } + catch (Exception exception) when (exception is FormatException or InvalidOperationException) + { + throw new ArgumentException( + $"The '{header.Key}' header is not a valid request header for BinaryLane API requests.", + nameof(request), + exception); + } + } + } + + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + if (request.Body is not null) + { + var json = JsonSerializer.Serialize(request.Body, _json.SerializerOptions); + message.Content = new StringContent(json, Encoding.UTF8, "application/json"); + } + + return message; + } + catch + { + message.Dispose(); + throw; + } + } + + private Uri CreateUri(string path, IReadOnlyDictionary? query) + { + var baseUri = _httpClient.BaseAddress ?? DefaultBaseUri; + if (!baseUri.IsAbsoluteUri || + baseUri.Scheme != Uri.UriSchemeHttps || + !string.IsNullOrEmpty(baseUri.UserInfo) || + !string.IsNullOrEmpty(baseUri.Query) || + !string.IsNullOrEmpty(baseUri.Fragment)) + { + throw new InvalidOperationException( + "HttpClient.BaseAddress must be an absolute HTTPS URL without credentials, a query string, or a fragment."); + } + + Uri uri; + if (path[0] != '/' && + Uri.TryCreate(path, UriKind.Absolute, out var absoluteUri)) + { + if (absoluteUri.Scheme != Uri.UriSchemeHttps) + { + throw new ArgumentException( + "Absolute BinaryLane API URLs must use the HTTPS scheme.", + nameof(path)); + } + + if (!HasSameOrigin(baseUri, absoluteUri)) + { + throw new ArgumentException( + "Absolute BinaryLane API URLs must use the configured API origin.", + nameof(path)); + } + + uri = absoluteUri; + } + else + { + uri = new Uri(baseUri, path.TrimStart('/')); + if (!HasSameOrigin(baseUri, uri)) + { + throw new ArgumentException( + "Relative BinaryLane API paths must resolve on the configured API origin.", + nameof(path)); + } + } + + if (query is null || query.Count == 0) + { + return uri; + } + + var builder = new UriBuilder(uri); + var queryBuilder = new StringBuilder(); + if (!string.IsNullOrWhiteSpace(builder.Query)) + { + queryBuilder.Append(builder.Query.TrimStart('?')); + } + + foreach (var pair in query) + { + if (pair.Value is null) + { + continue; + } + + if (queryBuilder.Length > 0) + { + queryBuilder.Append('&'); + } + + queryBuilder.Append(Uri.EscapeDataString(pair.Key)); + queryBuilder.Append('='); + queryBuilder.Append(Uri.EscapeDataString(FormatQueryValue(pair.Value))); + } + + builder.Query = queryBuilder.ToString(); + return builder.Uri; + } + + private static bool HasSameOrigin(Uri first, Uri second) => + string.Equals(first.Scheme, second.Scheme, StringComparison.OrdinalIgnoreCase) && + string.Equals(first.Host, second.Host, StringComparison.OrdinalIgnoreCase) && + first.Port == second.Port; + + private static string FormatQueryValue(object value) + { + if (value is bool boolean) + { + return boolean ? "true" : "false"; + } + + if (value is DateTimeOffset dateTimeOffset) + { + return dateTimeOffset.ToString("O", CultureInfo.InvariantCulture); + } + + if (value is DateTime dateTime) + { + return dateTime.ToString("O", CultureInfo.InvariantCulture); + } + + if (value is IFormattable formattable) + { + return formattable.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty; + } + + return value.ToString() ?? string.Empty; + } + + private TResponse Deserialize(string body) + { + if (string.IsNullOrWhiteSpace(body)) + { + return default!; + } + + if (typeof(TResponse) == typeof(JsonElement)) + { + using var document = JsonDocument.Parse(body); + return (TResponse)(object)document.RootElement.Clone(); + } + + var value = JsonSerializer.Deserialize(body, _json.SerializerOptions); + if (value is null && typeof(TResponse).IsValueType) + { + throw new JsonException("BinaryLane returned a JSON null for a non-nullable response value."); + } + + return value!; + } + + private static ReadOnlyDictionary> CollectHeaders(HttpResponseMessage response) + { + var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var header in response.Headers) + { + result[header.Key] = new List(header.Value).AsReadOnly(); + } + + if (response.Content is not null) + { + foreach (var header in response.Content.Headers) + { + result[header.Key] = new List(header.Value).AsReadOnly(); + } + } + + return new ReadOnlyDictionary>(result); + } + + private static BinaryLaneApiException CreateException( + HttpStatusCode statusCode, + Uri requestUri, + string body, + IReadOnlyDictionary> headers) + { + var problem = ParseProblem(body); + var message = $"BinaryLane API request failed with HTTP {(int)statusCode} ({statusCode})."; + var diagnosticBody = SanitizeAndTruncate(body); + + if (statusCode == HttpStatusCode.BadRequest || statusCode == (HttpStatusCode)422) + { + return new BinaryLaneValidationException(message, statusCode, requestUri, problem, diagnosticBody, headers); + } + + return statusCode switch + { + HttpStatusCode.Unauthorized => new BinaryLaneUnauthorizedException(message, requestUri, problem, diagnosticBody, headers), + HttpStatusCode.Forbidden => new BinaryLaneForbiddenException(message, requestUri, problem, diagnosticBody, headers), + HttpStatusCode.NotFound => new BinaryLaneNotFoundException(message, requestUri, problem, diagnosticBody, headers), + _ => new BinaryLaneApiException(message, statusCode, requestUri, problem, diagnosticBody, headers), + }; + } + + private static BinaryLaneApiProblem? ParseProblem(string body) + { + if (string.IsNullOrWhiteSpace(body)) + { + return null; + } + + try + { + using var document = JsonDocument.Parse(body); + var root = document.RootElement; + if (root.ValueKind != JsonValueKind.Object) + { + return null; + } + + return new BinaryLaneApiProblem + { + Type = ReadString(root, "type"), + Title = ReadString(root, "title"), + Detail = ReadString(root, "detail"), + Status = ReadInt32(root, "status"), + }; + } + catch (JsonException) + { + return null; + } + } + + private static string? ReadString(JsonElement element, string name) => + element.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String + ? value.GetString() + : null; + + private static int? ReadInt32(JsonElement element, string name) => + element.TryGetProperty(name, out var value) && value.TryGetInt32(out var number) + ? number + : null; + + private static string? SanitizeAndTruncate(string body) + { + if (string.IsNullOrWhiteSpace(body)) + { + return null; + } + + // Do not expose an unbounded provider response in exception telemetry. Consumers should still + // treat this diagnostic field as potentially sensitive and avoid logging it indiscriminately. + if (body.Length <= MaximumDiagnosticBodyLength) + { + return body; + } + +#if NET8_0_OR_GREATER + return string.Concat(body.AsSpan(0, MaximumDiagnosticBodyLength), "…".AsSpan()); +#else + return body.Substring(0, MaximumDiagnosticBodyLength) + "…"; +#endif + } + + private static async Task ReadBodyAsync(HttpContent content, CancellationToken cancellationToken) + { + if (content.Headers.ContentLength is long contentLength && contentLength > MaximumSuccessBodyLength) + { + throw new HttpRequestException( + $"BinaryLane response content exceeded the {MaximumSuccessBodyLength} byte limit."); + } + +#if NET8_0_OR_GREATER + await content.LoadIntoBufferAsync(MaximumSuccessBodyLength).ConfigureAwait(false); + return await content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); +#else + await content.LoadIntoBufferAsync(MaximumSuccessBodyLength).ConfigureAwait(false); + return await content.ReadAsStringAsync().ConfigureAwait(false); +#endif + } + + private static async Task ReadDiagnosticBodyAsync(HttpContent content, CancellationToken cancellationToken) + { +#if NET8_0_OR_GREATER + using var stream = await content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); +#else + using var stream = await content.ReadAsStreamAsync().ConfigureAwait(false); +#endif + using var reader = new StreamReader(stream, Encoding.UTF8, true, 4096, leaveOpen: false); + var buffer = new char[MaximumDiagnosticBodyLength + 1]; + var count = 0; + + while (count < buffer.Length) + { +#if NET8_0_OR_GREATER + var read = await reader + .ReadAsync(buffer.AsMemory(count, buffer.Length - count), cancellationToken) + .ConfigureAwait(false); +#else + var read = await reader + .ReadAsync(buffer, count, buffer.Length - count) + .ConfigureAwait(false); +#endif + if (read == 0) + { + break; + } + + count += read; + } + + return new string(buffer, 0, count); + } +} diff --git a/src/BinaryLane.Api/Http/BinaryLaneJsonSerializerOptions.cs b/src/BinaryLane.Api/Http/BinaryLaneJsonSerializerOptions.cs new file mode 100644 index 0000000..9410c07 --- /dev/null +++ b/src/BinaryLane.Api/Http/BinaryLaneJsonSerializerOptions.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using BinaryLane.Api.V2.Models; + +namespace BinaryLane.Api.V2.Http; + +/// JSON settings used by the BinaryLane API client. +public sealed class BinaryLaneJsonSerializerOptions +{ + /// Creates the default BinaryLane JSON settings. + public BinaryLaneJsonSerializerOptions() + { + SerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + SerializerOptions.Converters.Add(new ServerActionJsonConverter()); + } + + /// The serializer settings used for request and response bodies. + public JsonSerializerOptions SerializerOptions { get; } +} diff --git a/src/BinaryLane.Api/Http/BinaryLaneRequest.cs b/src/BinaryLane.Api/Http/BinaryLaneRequest.cs new file mode 100644 index 0000000..49651d0 --- /dev/null +++ b/src/BinaryLane.Api/Http/BinaryLaneRequest.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace BinaryLane.Api.V2.Http; + +/// Represents one request to the BinaryLane v2 API. +public sealed class BinaryLaneRequest +{ + /// Initializes a request. + public BinaryLaneRequest(HttpMethod method, string path) + { + Method = method ?? throw new ArgumentNullException(nameof(method)); + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("An API path is required.", nameof(path)); + } + + Path = path; + } + + /// HTTP method. + public HttpMethod Method { get; } + + /// + /// Relative v2 path (for example v2/servers) or an absolute HTTPS URL on the + /// configured API origin. Relative paths are resolved against the configured + /// . Other origins are rejected so a + /// provider link cannot receive the configured bearer token. + /// + public string Path { get; } + + /// Optional JSON request body. + public object? Body { get; set; } + + /// Optional query-string values. Null values are omitted. + public IReadOnlyDictionary? Query { get; set; } + + /// + /// Optional request headers. Authorization is supplied by the configured token provider and + /// cannot be overridden here. + /// + public IReadOnlyDictionary? Headers { get; set; } +} diff --git a/src/BinaryLane.Api/Http/BinaryLaneResponse.cs b/src/BinaryLane.Api/Http/BinaryLaneResponse.cs new file mode 100644 index 0000000..4310956 --- /dev/null +++ b/src/BinaryLane.Api/Http/BinaryLaneResponse.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace BinaryLane.Api.V2.Http; + +/// Response metadata and a deserialized BinaryLane API body. +/// The deserialized response type. +public sealed class BinaryLaneResponse +{ + internal BinaryLaneResponse( + T body, + HttpStatusCode statusCode, + Uri requestUri, + IReadOnlyDictionary> headers) + { + Body = body; + StatusCode = statusCode; + RequestUri = requestUri; + Headers = headers; + } + + /// Deserialized response body. + public T Body { get; } + + /// HTTP status code returned by BinaryLane. + public HttpStatusCode StatusCode { get; } + + /// Final request URI. + public Uri RequestUri { get; } + + /// Response and content headers. + public IReadOnlyDictionary> Headers { get; } +} diff --git a/src/BinaryLane.Api/Http/IBinaryLaneApiExecutor.cs b/src/BinaryLane.Api/Http/IBinaryLaneApiExecutor.cs new file mode 100644 index 0000000..dacdb74 --- /dev/null +++ b/src/BinaryLane.Api/Http/IBinaryLaneApiExecutor.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace BinaryLane.Api.V2.Http; + +/// +/// Low-level request executor used by resource APIs. It is public so consuming applications can +/// compose support for a newly introduced BinaryLane endpoint before a typed resource is released. +/// +public interface IBinaryLaneApiExecutor +{ + /// Sends a request and returns response metadata and the deserialized body. + Task> SendAsync( + BinaryLaneRequest request, + CancellationToken cancellationToken = default); + + /// Sends a request which is expected to have no response body. + Task> SendAsync( + BinaryLaneRequest request, + CancellationToken cancellationToken = default); + + /// Sends a GET request. + Task GetAsync( + string path, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default); + + /// Sends a POST request. + Task PostAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default); + + /// Sends a PUT request. + Task PutAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default); + + /// Sends a PATCH request. + Task PatchAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default); + + /// Sends a DELETE request. + Task DeleteAsync( + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default); +} diff --git a/src/BinaryLane.Api/IBinaryLaneClient.cs b/src/BinaryLane.Api/IBinaryLaneClient.cs new file mode 100644 index 0000000..8c03367 --- /dev/null +++ b/src/BinaryLane.Api/IBinaryLaneClient.cs @@ -0,0 +1,59 @@ +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Resources; + +namespace BinaryLane.Api.V2; + +/// +/// Composable entry point for the BinaryLane v2 API. Each resource is independently represented by +/// an interface so applications can depend on and mock only the portion they use. +/// +public interface IBinaryLaneClient +{ + /// Low-level executor for a newly introduced endpoint not yet represented by a resource API. + IBinaryLaneApiExecutor Executor { get; } + + /// Current account information. + IAccountApi Account { get; } + + /// Global asynchronous actions. + IActionsApi Actions { get; } + + /// Billing balance and invoices. + IBillingApi Billing { get; } + + /// Data-transfer usage. + IDataUsageApi DataUsage { get; } + + /// DNS domains and DNS records. + IDomainsApi Domains { get; } + + /// Images and image downloads. + IImagesApi Images { get; } + + /// SSH keys. + ISshKeysApi SshKeys { get; } + + /// Load balancers, their members, and forwarding rules. + ILoadBalancersApi LoadBalancers { get; } + + /// Available regions. + IRegionsApi Regions { get; } + + /// IPv6 reverse nameservers. + IReverseNamesApi ReverseNames { get; } + + /// Monitoring sample sets. + ISampleSetsApi SampleSets { get; } + + /// Servers and server-scoped resources. + IServersApi Servers { get; } + + /// Available server sizes. + ISizesApi Sizes { get; } + + /// Licensed and operating-system software. + ISoftwareApi Software { get; } + + /// Private virtual networks. + IVpcsApi Vpcs { get; } +} diff --git a/src/BinaryLane.Api/Models/BinaryLaneDto.cs b/src/BinaryLane.Api/Models/BinaryLaneDto.cs new file mode 100644 index 0000000..00f8e27 --- /dev/null +++ b/src/BinaryLane.Api/Models/BinaryLaneDto.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// +/// Base class for JSON objects returned by the BinaryLane API. +/// +/// +/// BinaryLane's v2 API is a developer preview and may add properties before this +/// SDK is updated. Unknown properties are retained instead of causing a +/// deserialization failure, so applications that need an early provider field can +/// opt in to reading it from . +/// +public abstract class BinaryLaneDto +{ + /// + /// Gets properties supplied by the service that this version of the SDK does + /// not yet model explicitly. + /// + [JsonExtensionData] + public IDictionary? AdditionalProperties { get; init; } +} + +/// +/// Base class for request payloads accepted by the BinaryLane API. +/// +public abstract class BinaryLaneRequestModel : BinaryLaneDto +{ +} diff --git a/src/BinaryLane.Api/Models/BinaryLaneValues.cs b/src/BinaryLane.Api/Models/BinaryLaneValues.cs new file mode 100644 index 0000000..ae26037 --- /dev/null +++ b/src/BinaryLane.Api/Models/BinaryLaneValues.cs @@ -0,0 +1,275 @@ +namespace BinaryLane.Api.V2.Models; + +/// +/// Known string values in the BinaryLane v2 contract. +/// +/// +/// The API remains a developer preview. Public DTOs use strings rather than +/// closed C# enums so an upstream value added after an SDK release remains +/// readable. These constants make documented values discoverable without +/// removing that forward compatibility. +/// +public static class BinaryLaneValues +{ + public static class AccountStatus + { + public const string Incomplete = "incomplete"; + public const string Active = "active"; + public const string Warning = "warning"; + public const string Locked = "locked"; + } + + public static class ActionStatus + { + public const string InProgress = "in-progress"; + public const string Completed = "completed"; + public const string Errored = "errored"; + } + + public static class ServerStatus + { + public const string New = "new"; + public const string Active = "active"; + public const string Archive = "archive"; + + // This unusual provider value is present in the 0.39.1 OpenAPI contract. + public const string ProviderFalse = "False"; + } + + public static class ResourceType + { + public const string Server = "server"; + public const string LoadBalancer = "load-balancer"; + public const string SshKey = "ssh-key"; + public const string Vpc = "vpc"; + public const string Image = "image"; + public const string RegisteredDomainName = "registered-domain-name"; + } + + public static class ImageType + { + public const string Custom = "custom"; + public const string Snapshot = "snapshot"; + public const string Backup = "backup"; + } + + public static class ImageStatus + { + public const string New = "NEW"; + public const string Available = "available"; + public const string Pending = "pending"; + public const string Deleted = "deleted"; + } + + public static class ImageQueryType + { + public const string Distribution = "distribution"; + public const string Backup = "backup"; + } + + public static class BackupSlot + { + public const string Daily = "daily"; + public const string Weekly = "weekly"; + public const string Monthly = "monthly"; + public const string Temporary = "temporary"; + } + + public static class BackupReplacementStrategy + { + public const string None = "none"; + public const string Specified = "specified"; + public const string Oldest = "oldest"; + public const string Newest = "newest"; + } + + public static class NetworkType + { + public const string Private = "private"; + public const string Public = "public"; + } + + public static class DomainRecordType + { + public const string A = "A"; + public const string Aaaa = "AAAA"; + public const string Caa = "CAA"; + public const string Cname = "CNAME"; + public const string Mx = "MX"; + public const string Ns = "NS"; + public const string Soa = "SOA"; + public const string Srv = "SRV"; + public const string Txt = "TXT"; + } + + public static class LoadBalancerStatus + { + public const string New = "new"; + public const string Active = "active"; + public const string Errored = "errored"; + } + + public static class LoadBalancerRuleProtocol + { + public const string Http = "http"; + public const string Https = "https"; + } + + public static class HealthCheckProtocol + { + public const string Http = "http"; + public const string Https = "https"; + public const string Both = "both"; + } + + public static class AdvancedFirewallRuleProtocol + { + public const string All = "all"; + public const string Icmp = "icmp"; + public const string Tcp = "tcp"; + public const string Udp = "udp"; + } + + public static class AdvancedFirewallRuleAction + { + public const string Drop = "drop"; + public const string Accept = "accept"; + } + + public static class PasswordRecoveryType + { + public const string Manual = "manual"; + public const string OfflineClear = "offline-clear"; + public const string OfflineChange = "offline-change"; + public const string OnlineChange = "online-change"; + } + + public static class DistributionFeature + { + public const string Ssh = "ssh"; + public const string RemoteDesktop = "remote-desktop"; + public const string UserData = "user-data"; + } + + public static class DataInterval + { + public const string FiveMinute = "five-minute"; + public const string HalfHour = "half-hour"; + public const string FourHour = "four-hour"; + public const string Day = "day"; + public const string Week = "week"; + public const string Month = "month"; + } + + public static class ThresholdAlertType + { + public const string Cpu = "cpu"; + public const string StorageRequests = "storage-requests"; + public const string NetworkIncoming = "network-incoming"; + public const string NetworkOutgoing = "network-outgoing"; + public const string DataTransferUsed = "data-transfer-used"; + public const string StorageUsed = "storage-used"; + public const string MemoryUsed = "memory-used"; + public const string LockedBackupSlots = "locked-backup-slots"; + } + + public static class PaymentMethod + { + public const string CreditCard = "credit-card"; + public const string Paypal = "paypal"; + } + + public static class TaxCodeType + { + public const string None = "none"; + public const string Scalar = "scalar"; + } + + public static class UserInteractionType + { + public const string ContinueAfterPingFailure = "continue-after-ping-failure"; + public const string AllowUncleanPowerOff = "allow-unclean-power-off"; + } + + public static class AdvancedFeature + { + public const string EmulatedHyperV = "emulated-hyperv"; + public const string EmulatedDevices = "emulated-devices"; + public const string NestedVirt = "nested-virt"; + public const string DriverDisk = "driver-disk"; + public const string UnsetUuid = "unset-uuid"; + public const string LocalRtc = "local-rtc"; + public const string EmulatedTpm = "emulated-tpm"; + public const string CloudInit = "cloud-init"; + public const string QemuGuestAgent = "qemu-guest-agent"; + public const string UefiBoot = "uefi-boot"; + } + + public static class VideoDevice + { + public const string CirrusLogic = "cirrus-logic"; + public const string Standard = "standard"; + public const string Virtio = "virtio"; + public const string VirtioWide = "virtio-wide"; + } + + public static class VmMachineType + { + public const string PcI440Fx1Point5 = "pc_i440fx_1point5"; + public const string PcI440Fx2Point11 = "pc_i440fx_2point11"; + public const string PcI440Fx4Point1 = "pc_i440fx_4point1"; + public const string PcI440Fx4Point2 = "pc_i440fx_4point2"; + public const string PcI440Fx5Point0 = "pc_i440fx_5point0"; + public const string PcI440Fx5Point1 = "pc_i440fx_5point1"; + public const string PcI440Fx7Point2 = "pc_i440fx_7point2"; + public const string PcI440Fx7Point2Point1 = "pc_i440fx_7point2point1"; + public const string PcI440Fx8Point2 = "pc_i440fx_8point2"; + } + + /// Discriminator values for payloads. + public static class ServerActionType + { + public const string AddDisk = "add_disk"; + public const string AttachBackup = "attach_backup"; + public const string ChangeAdvancedFeatures = "change_advanced_features"; + public const string ChangeAdvancedFirewallRules = "change_advanced_firewall_rules"; + public const string ChangeBackupSchedule = "change_backup_schedule"; + public const string ChangeIpv6 = "change_ipv6"; + public const string ChangeIpv6ReverseNameservers = "change_ipv6_reverse_nameservers"; + public const string ChangeKernel = "change_kernel"; + public const string ChangeManageOffsiteBackupCopies = "change_manage_offsite_backup_copies"; + public const string ChangeNetwork = "change_network"; + public const string ChangeOffsiteBackupLocation = "change_offsite_backup_location"; + public const string ChangePartner = "change_partner"; + public const string ChangePortBlocking = "change_port_blocking"; + public const string ChangeRegion = "change_region"; + public const string ChangeReverseName = "change_reverse_name"; + public const string ChangeSeparatePrivateNetworkInterface = "change_separate_private_network_interface"; + public const string ChangeSourceAndDestinationCheck = "change_source_and_destination_check"; + public const string ChangeThresholdAlerts = "change_threshold_alerts"; + public const string ChangeVpcIpv4 = "change_vpc_ipv4"; + public const string CloneUsingBackup = "clone_using_backup"; + public const string DeleteDisk = "delete_disk"; + public const string DetachBackup = "detach_backup"; + public const string DisableBackups = "disable_backups"; + public const string DisableSelinux = "disable_selinux"; + public const string EnableBackups = "enable_backups"; + public const string EnableIpv6 = "enable_ipv6"; + public const string IsRunning = "is_running"; + public const string PasswordReset = "password_reset"; + public const string Ping = "ping"; + public const string PowerCycle = "power_cycle"; + public const string PowerOff = "power_off"; + public const string PowerOn = "power_on"; + public const string Reboot = "reboot"; + public const string Rebuild = "rebuild"; + public const string Rename = "rename"; + public const string Resize = "resize"; + public const string ResizeDisk = "resize_disk"; + public const string Restore = "restore"; + public const string Shutdown = "shutdown"; + public const string TakeBackup = "take_backup"; + public const string Uncancel = "uncancel"; + public const string Uptime = "uptime"; + } +} diff --git a/src/BinaryLane.Api/Models/CoreModels.cs b/src/BinaryLane.Api/Models/CoreModels.cs new file mode 100644 index 0000000..efbcd64 --- /dev/null +++ b/src/BinaryLane.Api/Models/CoreModels.cs @@ -0,0 +1,615 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// Information about the authenticated BinaryLane account. +public sealed class Account : BinaryLaneDto +{ + [JsonPropertyName("email")] + public string Email { get; init; } = string.Empty; + + [JsonPropertyName("email_verified")] + public bool EmailVerified { get; init; } + + [JsonPropertyName("two_factor_authentication_enabled")] + public bool TwoFactorAuthenticationEnabled { get; init; } + + /// Provider status, for example active. + [JsonPropertyName("status")] + public string Status { get; init; } = string.Empty; + + [JsonPropertyName("tax_code")] + public TaxCode TaxCode { get; init; } = new(); + + /// Provider payment-method values, for example credit-card. + [JsonPropertyName("configured_payment_methods")] + public IReadOnlyList ConfiguredPaymentMethods { get; init; } = Array.Empty(); + + [JsonPropertyName("additional_ipv4_limit")] + public int AdditionalIpv4Limit { get; init; } +} + +/// A tax code currently applicable to an account or invoice. +public sealed class TaxCode : BinaryLaneDto +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + /// Provider tax-code type, for example none or scalar. + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("fixed_percent")] + public double? FixedPercent { get; init; } +} + +/// A provider action, which may still be in progress. +public sealed class BinaryLaneAction : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + /// Provider status, for example in-progress, completed, or errored. + [JsonPropertyName("status")] + public string Status { get; init; } = string.Empty; + + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("started_at")] + public DateTimeOffset StartedAt { get; init; } + + [JsonPropertyName("completed_at")] + public DateTimeOffset? CompletedAt { get; init; } + + /// Provider resource type, when the action is associated with one. + [JsonPropertyName("resource_type")] + public string? ResourceType { get; init; } + + [JsonPropertyName("resource_id")] + public long? ResourceId { get; init; } + + [JsonPropertyName("region")] + public Region? Region { get; init; } + + [JsonPropertyName("region_slug")] + public string? RegionSlug { get; init; } + + [JsonPropertyName("title")] + public string Title { get; init; } = string.Empty; + + [JsonPropertyName("reason")] + public string Reason { get; init; } = string.Empty; + + [JsonPropertyName("progress")] + public ActionProgress Progress { get; init; } = new(); + + [JsonPropertyName("result_data")] + public string? ResultData { get; init; } + + [JsonPropertyName("blocking_invoice_id")] + public long? BlockingInvoiceId { get; init; } + + [JsonPropertyName("user_interaction_required")] + public UserInteractionRequired? UserInteractionRequired { get; init; } +} + +/// Progress information for a long-running provider action. +public sealed class ActionProgress : BinaryLaneDto +{ + [JsonPropertyName("current_step_detail")] + public string? CurrentStepDetail { get; init; } + + [JsonPropertyName("percent_complete")] + public int PercentComplete { get; init; } + + [JsonPropertyName("current_step")] + public string? CurrentStep { get; init; } + + [JsonPropertyName("completed_steps")] + public IReadOnlyList CompletedSteps { get; init; } = Array.Empty(); +} + +/// A link to an action related to a completed request. +public sealed class ActionLink : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("rel")] + public string Rel { get; init; } = string.Empty; + + [JsonPropertyName("href")] + public string Href { get; init; } = string.Empty; +} + +/// Indicates that an action needs an explicit user decision. +public sealed class UserInteractionRequired : BinaryLaneDto +{ + /// Provider interaction type, such as continue-after-ping-failure. + [JsonPropertyName("interaction_type")] + public string InteractionType { get; init; } = string.Empty; +} + +/// A BinaryLane region in which a resource can be created. +public sealed class Region : BinaryLaneDto +{ + [JsonPropertyName("slug")] + public string Slug { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("sizes")] + public IReadOnlyList Sizes { get; init; } = Array.Empty(); + + [JsonPropertyName("available")] + public bool Available { get; init; } + + [JsonPropertyName("features")] + public IReadOnlyList Features { get; init; } = Array.Empty(); + + [JsonPropertyName("name_servers")] + public IReadOnlyList NameServers { get; init; } = Array.Empty(); +} + +/// A BinaryLane server. +public sealed class Server : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("memory")] + public int MemoryMegabytes { get; init; } + + [JsonPropertyName("vcpus")] + public int Vcpus { get; init; } + + [JsonPropertyName("disk")] + public int DiskGigabytes { get; init; } + + [JsonPropertyName("vpc_id")] + public long? VpcId { get; init; } + + [JsonPropertyName("created_at")] + public DateTimeOffset CreatedAt { get; init; } + + /// Provider server status, for example active. + [JsonPropertyName("status")] + public string Status { get; init; } = string.Empty; + + [JsonPropertyName("backup_ids")] + public IReadOnlyList BackupIds { get; init; } = Array.Empty(); + + [JsonPropertyName("features")] + public IReadOnlyList Features { get; init; } = Array.Empty(); + + [JsonPropertyName("region")] + public Region Region { get; init; } = new(); + + [JsonPropertyName("image")] + public Image Image { get; init; } = new(); + + [JsonPropertyName("size")] + public Size Size { get; init; } = new(); + + [JsonPropertyName("size_slug")] + public string SizeSlug { get; init; } = string.Empty; + + [JsonPropertyName("selected_size_options")] + public SelectedSizeOptions? SelectedSizeOptions { get; init; } + + [JsonPropertyName("networks")] + public ServerNetworks Networks { get; init; } = new(); + + [JsonPropertyName("kernel")] + public Kernel? Kernel { get; init; } + + [JsonPropertyName("next_backup_window")] + public BackupWindow? NextBackupWindow { get; init; } + + [JsonPropertyName("disks")] + public IReadOnlyList Disks { get; init; } = Array.Empty(); + + [JsonPropertyName("backup_settings")] + public BackupSettings BackupSettings { get; init; } = new(); + + [JsonPropertyName("cancelled_at")] + public DateTimeOffset? CancelledAt { get; init; } + + [JsonPropertyName("failover_ips")] + public IReadOnlyList FailoverIps { get; init; } = Array.Empty(); + + [JsonPropertyName("host")] + public Host Host { get; init; } = new(); + + [JsonPropertyName("partner_id")] + public long? PartnerId { get; init; } + + [JsonPropertyName("password_change_supported")] + public bool PasswordChangeSupported { get; init; } + + [JsonPropertyName("permalink")] + public string? Permalink { get; init; } + + [JsonPropertyName("attached_backup")] + public AttachedBackup? AttachedBackup { get; init; } + + [JsonPropertyName("advanced_features")] + public AdvancedServerFeatures AdvancedFeatures { get; init; } = new(); + + [JsonPropertyName("is_under_maintenance")] + public bool? IsUnderMaintenance { get; init; } +} + +/// A server disk. +public sealed class Disk : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("size_gigabytes")] + public double SizeGigabytes { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } + + [JsonPropertyName("primary")] + public bool Primary { get; init; } +} + +/// A server network assignment. +public sealed class ServerNetwork : BinaryLaneDto +{ + [JsonPropertyName("ip_address")] + public string IpAddress { get; init; } = string.Empty; + + /// May be represented by the provider as either a number or a string. + [JsonPropertyName("netmask")] + public object? Netmask { get; init; } + + [JsonPropertyName("gateway")] + public string? Gateway { get; init; } + + /// Provider network type, either public or private. + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("reverse_name")] + public string? ReverseName { get; init; } + + [JsonPropertyName("nat_target")] + public string? NatTarget { get; init; } +} + +/// All IPv4/IPv6 network information associated with a server. +public sealed class ServerNetworks : BinaryLaneDto +{ + [JsonPropertyName("v4")] + public IReadOnlyList V4 { get; init; } = Array.Empty(); + + [JsonPropertyName("v6")] + public IReadOnlyList V6 { get; init; } = Array.Empty(); + + [JsonPropertyName("port_blocking")] + public bool PortBlocking { get; init; } + + [JsonPropertyName("separate_private_network_interface")] + public bool? SeparatePrivateNetworkInterface { get; init; } + + [JsonPropertyName("source_and_destination_check")] + public bool? SourceAndDestinationCheck { get; init; } + + [JsonPropertyName("recent_ddos")] + public bool RecentDdos { get; init; } + + [JsonPropertyName("ipv6_reverse_nameservers")] + public IReadOnlyList? Ipv6ReverseNameservers { get; init; } + + [JsonPropertyName("mac_address")] + public string MacAddress { get; init; } = string.Empty; +} + +/// A BinaryLane image, including operating-system and backup images. +public sealed class Image : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + /// Provider image type, for example custom, snapshot, or backup. + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("distribution")] + public string? Distribution { get; init; } + + [JsonPropertyName("full_name")] + public string? FullName { get; init; } + + [JsonPropertyName("slug")] + public string? Slug { get; init; } + + [JsonPropertyName("public")] + public bool IsPublic { get; init; } + + [JsonPropertyName("regions")] + public IReadOnlyList Regions { get; init; } = Array.Empty(); + + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; init; } + + [JsonPropertyName("min_disk_size")] + public int MinDiskSizeGigabytes { get; init; } + + [JsonPropertyName("size_gigabytes")] + public double SizeGigabytes { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } + + /// Provider image status, for example available. + [JsonPropertyName("status")] + public string Status { get; init; } = string.Empty; + + [JsonPropertyName("error_message")] + public string? ErrorMessage { get; init; } + + [JsonPropertyName("min_memory_megabytes")] + public int? MinMemoryMegabytes { get; init; } + + [JsonPropertyName("distribution_surcharges")] + public DistributionSurcharges? DistributionSurcharges { get; init; } + + [JsonPropertyName("distribution_info")] + public DistributionInfo DistributionInfo { get; init; } = new(); + + [JsonPropertyName("backup_info")] + public BackupInfo? BackupInfo { get; init; } +} + +/// Distribution-specific image installation capabilities. +public sealed class DistributionInfo : BinaryLaneDto +{ + [JsonPropertyName("image_id")] + public long ImageId { get; init; } + + /// Provider password recovery mode. + [JsonPropertyName("password_recovery")] + public string PasswordRecovery { get; init; } = string.Empty; + + [JsonPropertyName("remote_access_user")] + public string? RemoteAccessUser { get; init; } + + [JsonPropertyName("features")] + public IReadOnlyList Features { get; init; } = Array.Empty(); +} + +/// Additional provider charges for a distribution image. +public sealed class DistributionSurcharges : BinaryLaneDto +{ + [JsonPropertyName("surcharge_base_cost")] + public double? SurchargeBaseCost { get; init; } + + [JsonPropertyName("surcharge_per_memory_megabyte")] + public double? SurchargePerMemoryMegabyte { get; init; } + + [JsonPropertyName("surcharge_per_memory_max_megabytes")] + public int? SurchargePerMemoryMaxMegabytes { get; init; } + + [JsonPropertyName("surcharge_per_vcpu")] + public double? SurchargePerVcpu { get; init; } + + [JsonPropertyName("surcharge_min_vcpu")] + public int? SurchargeMinVcpu { get; init; } +} + +/// Backup-specific metadata for an image. +public sealed class BackupInfo : BinaryLaneDto +{ + /// Provider backup slot, such as daily or temporary. + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("server_id")] + public long ServerId { get; init; } + + [JsonPropertyName("offsite")] + public bool Offsite { get; init; } + + [JsonPropertyName("locked")] + public bool Locked { get; init; } + + [JsonPropertyName("iso")] + public bool Iso { get; init; } + + [JsonPropertyName("backup_disks")] + public IReadOnlyList BackupDisks { get; init; } = Array.Empty(); +} + +/// A disk contained in a backup image. +public sealed class BackupDisk : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("size_gigabytes")] + public double SizeGigabytes { get; init; } + + [JsonPropertyName("min_disk_size")] + public int MinDiskSizeGigabytes { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// A provider server size and its available customisation options. +public sealed class Size : BinaryLaneDto +{ + [JsonPropertyName("slug")] + public string Slug { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } + + [JsonPropertyName("cpu_description")] + public string? CpuDescription { get; init; } + + [JsonPropertyName("storage_description")] + public string? StorageDescription { get; init; } + + [JsonPropertyName("size_type")] + public SizeType SizeType { get; init; } = new(); + + [JsonPropertyName("available")] + public bool Available { get; init; } + + [JsonPropertyName("regions")] + public IReadOnlyList Regions { get; init; } = Array.Empty(); + + [JsonPropertyName("regions_out_of_stock")] + public IReadOnlyList? RegionsOutOfStock { get; init; } + + [JsonPropertyName("price_monthly")] + public double PriceMonthly { get; init; } + + [JsonPropertyName("price_hourly")] + public double PriceHourly { get; init; } + + [JsonPropertyName("disk")] + public int DiskGigabytes { get; init; } + + [JsonPropertyName("memory")] + public int MemoryMegabytes { get; init; } + + [JsonPropertyName("transfer")] + public double TransferGigabytes { get; init; } + + [JsonPropertyName("excess_transfer_cost_per_gigabyte")] + public double ExcessTransferCostPerGigabyte { get; init; } + + [JsonPropertyName("vcpus")] + public int Vcpus { get; init; } + + [JsonPropertyName("vcpu_units")] + public string VcpuUnits { get; init; } = string.Empty; + + [JsonPropertyName("options")] + public SizeOptions Options { get; init; } = new(); +} + +/// The provider's category for a server size. +public sealed class SizeType : BinaryLaneDto +{ + [JsonPropertyName("slug")] + public string Slug { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// Configurable bounds and costs associated with a server size. +public sealed class SizeOptions : BinaryLaneDto +{ + [JsonPropertyName("disk_min")] + public int DiskMinGigabytes { get; init; } + + [JsonPropertyName("disk_max")] + public int DiskMaxGigabytes { get; init; } + + [JsonPropertyName("disk_cost_per_additional_gigabyte")] + public double DiskCostPerAdditionalGigabyte { get; init; } + + [JsonPropertyName("restricted_disk_values")] + public IReadOnlyList? RestrictedDiskValues { get; init; } + + [JsonPropertyName("memory_max")] + public int MemoryMaxMegabytes { get; init; } + + [JsonPropertyName("memory_cost_per_additional_megabyte")] + public double MemoryCostPerAdditionalMegabyte { get; init; } + + [JsonPropertyName("transfer_max")] + public double TransferMaxGigabytes { get; init; } + + [JsonPropertyName("transfer_cost_per_additional_gigabyte")] + public double TransferCostPerAdditionalGigabyte { get; init; } + + [JsonPropertyName("ipv4_addresses_max")] + public int Ipv4AddressesMax { get; init; } + + [JsonPropertyName("ipv4_addresses_cost_per_address")] + public double Ipv4AddressesCostPerAddress { get; init; } + + [JsonPropertyName("discount_for_no_public_ipv4")] + public double DiscountForNoPublicIpv4 { get; init; } + + [JsonPropertyName("daily_backups")] + public int DailyBackups { get; init; } + + [JsonPropertyName("weekly_backups")] + public int WeeklyBackups { get; init; } + + [JsonPropertyName("monthly_backups")] + public int MonthlyBackups { get; init; } + + [JsonPropertyName("backups_cost_per_backup_per_gigabyte")] + public double BackupsCostPerBackupPerGigabyte { get; init; } + + [JsonPropertyName("offsite_backups_cost_per_gigabyte")] + public double OffsiteBackupsCostPerGigabyte { get; init; } + + [JsonPropertyName("offsite_backup_frequency_cost")] + public OffsiteBackupFrequencyCost OffsiteBackupFrequencyCost { get; init; } = new(); +} + +/// The backup frequency costs published for a size. +public sealed class OffsiteBackupFrequencyCost : BinaryLaneDto +{ + [JsonPropertyName("daily_per_gigabyte")] + public double DailyPerGigabyte { get; init; } + + [JsonPropertyName("weekly_per_gigabyte")] + public double WeeklyPerGigabyte { get; init; } + + [JsonPropertyName("monthly_per_gigabyte")] + public double MonthlyPerGigabyte { get; init; } +} + +/// The options selected for an individual server. +public sealed class SelectedSizeOptions : BinaryLaneDto +{ + [JsonPropertyName("daily_backups")] + public int DailyBackups { get; init; } + + [JsonPropertyName("weekly_backups")] + public int WeeklyBackups { get; init; } + + [JsonPropertyName("monthly_backups")] + public int MonthlyBackups { get; init; } + + [JsonPropertyName("offsite_backups")] + public bool OffsiteBackups { get; init; } + + [JsonPropertyName("ipv4_addresses")] + public int Ipv4Addresses { get; init; } + + [JsonPropertyName("memory")] + public int MemoryMegabytes { get; init; } + + [JsonPropertyName("disk")] + public int DiskGigabytes { get; init; } + + [JsonPropertyName("transfer")] + public double TransferGigabytes { get; init; } +} diff --git a/src/BinaryLane.Api/Models/IsExternalInit.cs b/src/BinaryLane.Api/Models/IsExternalInit.cs new file mode 100644 index 0000000..b227b97 --- /dev/null +++ b/src/BinaryLane.Api/Models/IsExternalInit.cs @@ -0,0 +1,9 @@ +#if NETSTANDARD2_0 +// The compiler emits this marker for init-only properties. netstandard2.0 does +// not provide it, so the package carries the conventional compatibility shim. +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit +{ +} +#endif diff --git a/src/BinaryLane.Api/Models/RequestModels.cs b/src/BinaryLane.Api/Models/RequestModels.cs new file mode 100644 index 0000000..e1d03ec --- /dev/null +++ b/src/BinaryLane.Api/Models/RequestModels.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// Options selected while creating or rebuilding a server image. +public sealed class ImageOptions : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string? Name { get; init; } + + /// Each identifier may be either an SSH key ID or a public-key fingerprint. + [JsonPropertyName("ssh_keys")] + public IReadOnlyList? SshKeys { get; init; } + + /// Do not log user-data; it can contain secrets. + [JsonPropertyName("user_data")] + public string? UserData { get; init; } + + /// Do not log this value. + [JsonPropertyName("password")] + public string? Password { get; init; } +} + +/// Customisable options for a server size. +public sealed class SizeOptionsRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("daily_backups")] + public int? DailyBackups { get; init; } + + [JsonPropertyName("weekly_backups")] + public int? WeeklyBackups { get; init; } + + [JsonPropertyName("monthly_backups")] + public int? MonthlyBackups { get; init; } + + [JsonPropertyName("offsite_backups")] + public bool? OffsiteBackups { get; init; } + + [JsonPropertyName("ipv4_addresses")] + public int? Ipv4Addresses { get; init; } + + [JsonPropertyName("memory")] + public int? MemoryMegabytes { get; init; } + + [JsonPropertyName("disk")] + public int? DiskGigabytes { get; init; } + + [JsonPropertyName("transfer")] + public double? TransferGigabytes { get; init; } +} + +/// A requested software licence allocation. +public sealed class License : BinaryLaneRequestModel +{ + [JsonPropertyName("software_id")] + public long SoftwareId { get; init; } + + [JsonPropertyName("count")] + public int Count { get; init; } +} + +/// Creates a new BinaryLane server. +public sealed class CreateServerRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("backups")] + public bool? Backups { get; init; } + + [JsonPropertyName("ipv6")] + public bool? Ipv6 { get; init; } + + [JsonPropertyName("size")] + public string Size { get; init; } = string.Empty; + + /// An image ID or provider image slug. + [JsonPropertyName("image")] + public object Image { get; init; } = string.Empty; + + [JsonPropertyName("region")] + public string Region { get; init; } = string.Empty; + + [JsonPropertyName("vpc_id")] + public long? VpcId { get; init; } + + [JsonPropertyName("vpc_ipv4_address")] + public string? VpcIpv4Address { get; init; } + + /// Each entry may be an SSH key ID or fingerprint. + [JsonPropertyName("ssh_keys")] + public IReadOnlyList? SshKeys { get; init; } + + [JsonPropertyName("options")] + public SizeOptionsRequest? Options { get; init; } + + [JsonPropertyName("licenses")] + public IReadOnlyList? Licenses { get; init; } + + /// Do not log user-data; it may contain secrets. + [JsonPropertyName("user_data")] + public string? UserData { get; init; } + + [JsonPropertyName("port_blocking")] + public bool? PortBlocking { get; init; } + + [JsonPropertyName("separate_private_network_interface")] + public bool? SeparatePrivateNetworkInterface { get; init; } + + /// Do not log this value. + [JsonPropertyName("password")] + public string? Password { get; init; } +} + +/// Creates a new VPC. +public sealed class CreateVpcRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("route_entries")] + public IReadOnlyList? RouteEntries { get; init; } + + [JsonPropertyName("ip_range")] + public string? IpRange { get; init; } +} + +/// Replaces VPC configuration. Omitted properties are cleared by the provider. +public sealed class UpdateVpcRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("route_entries")] + public IReadOnlyList? RouteEntries { get; init; } +} + +/// Partially updates VPC configuration. +public sealed class PatchVpcRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("route_entries")] + public IReadOnlyList? RouteEntries { get; init; } +} + +/// A VPC route entry supplied in a create or update request. +public sealed class RouteEntryRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("router")] + public string Router { get; init; } = string.Empty; + + [JsonPropertyName("destination")] + public string Destination { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// Creates a new load balancer. +public sealed class CreateLoadBalancerRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("forwarding_rules")] + public IReadOnlyList? ForwardingRules { get; init; } + + [JsonPropertyName("health_check")] + public HealthCheckRequest? HealthCheck { get; init; } + + [JsonPropertyName("server_ids")] + public IReadOnlyList? ServerIds { get; init; } + + [JsonPropertyName("region")] + public string? Region { get; init; } +} + +/// Updates a load balancer. +public sealed class UpdateLoadBalancerRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("forwarding_rules")] + public IReadOnlyList? ForwardingRules { get; init; } + + [JsonPropertyName("health_check")] + public HealthCheckRequest? HealthCheck { get; init; } + + [JsonPropertyName("server_ids")] + public IReadOnlyList? ServerIds { get; init; } +} + +/// A load-balancer forwarding rule supplied in a request. +public sealed class ForwardingRuleRequest : BinaryLaneRequestModel +{ + /// Either http or https. + [JsonPropertyName("entry_protocol")] + public string EntryProtocol { get; init; } = string.Empty; +} + +/// A collection of load-balancer forwarding rules. +public sealed class ForwardingRulesRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("forwarding_rules")] + public IReadOnlyList ForwardingRules { get; init; } = Array.Empty(); +} + +/// A load-balancer health check supplied in a request. +public sealed class HealthCheckRequest : BinaryLaneRequestModel +{ + /// One of http, https, or both. + [JsonPropertyName("protocol")] + public string? Protocol { get; init; } + + [JsonPropertyName("path")] + public string? Path { get; init; } +} + +/// A collection of server IDs supplied to a load-balancer operation. +public sealed class ServerIdsRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("server_ids")] + public IReadOnlyList ServerIds { get; init; } = Array.Empty(); +} + +/// Creates a DNS domain. +public sealed class DomainRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("ip_address")] + public string? IpAddress { get; init; } +} + +/// Requests refresh of the nameserver cache for domains. +public sealed class DomainRefreshRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("domain_names")] + public IReadOnlyList DomainNames { get; init; } = Array.Empty(); +} + +/// Creates a DNS record. +public sealed class DomainRecordRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("data")] + public string Data { get; init; } = string.Empty; + + [JsonPropertyName("priority")] + public int? Priority { get; init; } + + [JsonPropertyName("port")] + public int? Port { get; init; } + + [JsonPropertyName("ttl")] + public int? Ttl { get; init; } + + [JsonPropertyName("weight")] + public int? Weight { get; init; } + + [JsonPropertyName("flags")] + public int? Flags { get; init; } + + [JsonPropertyName("tag")] + public string? Tag { get; init; } +} + +/// Updates only the supplied parts of a DNS record. +public sealed class UpdateDomainRecordRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("type")] + public string? Type { get; init; } + + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("data")] + public string? Data { get; init; } + + [JsonPropertyName("priority")] + public int? Priority { get; init; } + + [JsonPropertyName("port")] + public int? Port { get; init; } + + [JsonPropertyName("ttl")] + public int? Ttl { get; init; } + + [JsonPropertyName("weight")] + public int? Weight { get; init; } + + [JsonPropertyName("flags")] + public int? Flags { get; init; } + + [JsonPropertyName("tag")] + public string? Tag { get; init; } +} + +/// Creates an SSH key. +public sealed class SshKeyRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("public_key")] + public string PublicKey { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("default")] + public bool? IsDefault { get; init; } +} + +/// Updates an SSH key. +public sealed class UpdateSshKeyRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("default")] + public bool? IsDefault { get; init; } +} + +/// Changes mutable metadata on an image. +public sealed class ImageRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("locked")] + public bool? Locked { get; init; } +} + +/// Uploads an image into a backup slot. +public sealed class UploadImageRequest : BinaryLaneRequestModel +{ + /// Provider backup slot, required unless the replacement strategy is specified. + [JsonPropertyName("backup_type")] + public string? BackupType { get; init; } + + /// Provider replacement strategy: none, specified, oldest, or newest. + [JsonPropertyName("replacement_strategy")] + public string ReplacementStrategy { get; init; } = string.Empty; + + [JsonPropertyName("backup_id_to_replace")] + public long? BackupIdToReplace { get; init; } + + [JsonPropertyName("label")] + public string? Label { get; init; } + + [JsonPropertyName("url")] + public string Url { get; init; } = string.Empty; +} + +/// Updates the IPv6 reverse nameservers configured for the account. +public sealed class ReverseNameserversRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("reverse_nameservers")] + public IReadOnlyList ReverseNameservers { get; init; } = Array.Empty(); +} + +/// Allows or declines the interaction requested by an action. +public sealed class ProceedRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("proceed")] + public bool Proceed { get; init; } +} + +/// Updates threshold-alert configuration. +public sealed class ThresholdAlertRequest : BinaryLaneRequestModel +{ + /// Provider alert type. + [JsonPropertyName("alert_type")] + public string AlertType { get; init; } = string.Empty; + + [JsonPropertyName("enabled")] + public bool? Enabled { get; init; } + + [JsonPropertyName("value")] + public int? Value { get; init; } +} diff --git a/src/BinaryLane.Api/Models/ResourceModels.cs b/src/BinaryLane.Api/Models/ResourceModels.cs new file mode 100644 index 0000000..e67e672 --- /dev/null +++ b/src/BinaryLane.Api/Models/ResourceModels.cs @@ -0,0 +1,653 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// The current billing balance for the authenticated account. +public sealed class Balance : BinaryLaneDto +{ + [JsonPropertyName("unbilled_total")] + public double UnbilledTotal { get; init; } + + [JsonPropertyName("available_credit")] + public double AvailableCredit { get; init; } + + [JsonPropertyName("charges")] + public IReadOnlyList Charges { get; init; } = Array.Empty(); + + [JsonPropertyName("generated_at")] + public DateTimeOffset? GeneratedAt { get; init; } +} + +/// A charge included in a balance response. +public sealed class ChargeInformation : BinaryLaneDto +{ + [JsonPropertyName("created")] + public DateTimeOffset Created { get; init; } + + [JsonPropertyName("description")] + public string Description { get; init; } = string.Empty; + + [JsonPropertyName("total")] + public double Total { get; init; } + + [JsonPropertyName("ongoing")] + public bool Ongoing { get; init; } +} + +/// A BinaryLane invoice. +public sealed class Invoice : BinaryLaneDto +{ + [JsonPropertyName("invoice_id")] + public long InvoiceId { get; init; } + + [JsonPropertyName("reference")] + public string? Reference { get; init; } + + [JsonPropertyName("invoice_number")] + public string InvoiceNumber { get; init; } = string.Empty; + + [JsonPropertyName("amount")] + public double Amount { get; init; } + + [JsonPropertyName("tax_code")] + public TaxCode TaxCode { get; init; } = new(); + + [JsonPropertyName("tax")] + public double Tax { get; init; } + + [JsonPropertyName("created")] + public DateTimeOffset Created { get; init; } + + [JsonPropertyName("date_due")] + public DateTimeOffset DateDue { get; init; } + + [JsonPropertyName("date_overdue")] + public DateTimeOffset DateOverdue { get; init; } + + [JsonPropertyName("paid")] + public bool Paid { get; init; } + + [JsonPropertyName("refunded")] + public bool Refunded { get; init; } + + [JsonPropertyName("payment_failure_count")] + public int? PaymentFailureCount { get; init; } + + [JsonPropertyName("invoice_items")] + public IReadOnlyList InvoiceItems { get; init; } = Array.Empty(); + + [JsonPropertyName("invoice_download_url")] + public string? InvoiceDownloadUrl { get; init; } + + [JsonPropertyName("invoice_view_url")] + public string? InvoiceViewUrl { get; init; } +} + +/// A line item in a BinaryLane invoice. +public sealed class InvoiceLineItem : BinaryLaneDto +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("amount")] + public double Amount { get; init; } + + [JsonPropertyName("amount_includes_tax")] + public bool AmountIncludesTax { get; init; } +} + +/// Current data-transfer use for a server. +public sealed class DataUsage : BinaryLaneDto +{ + [JsonPropertyName("server_id")] + public long ServerId { get; init; } + + [JsonPropertyName("expires")] + public DateTimeOffset Expires { get; init; } + + [JsonPropertyName("transfer_gigabytes")] + public long TransferGigabytes { get; init; } + + [JsonPropertyName("current_transfer_usage_gigabytes")] + public double CurrentTransferUsageGigabytes { get; init; } + + [JsonPropertyName("transfer_period_end")] + public DateTimeOffset TransferPeriodEnd { get; init; } +} + +/// A DNS domain managed through the BinaryLane API. +public sealed class Domain : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("current_nameservers")] + public IReadOnlyList CurrentNameservers { get; init; } = Array.Empty(); + + [JsonPropertyName("ttl")] + public int? Ttl { get; init; } + + [JsonPropertyName("zone_file")] + public string ZoneFile { get; init; } = string.Empty; +} + +/// A DNS record in a BinaryLane-managed domain. +public sealed class DomainRecord : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + /// Provider DNS record type, such as A, AAAA, or TXT. + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("data")] + public string? Data { get; init; } + + [JsonPropertyName("priority")] + public int? Priority { get; init; } + + [JsonPropertyName("port")] + public int? Port { get; init; } + + [JsonPropertyName("ttl")] + public int Ttl { get; init; } + + [JsonPropertyName("weight")] + public int? Weight { get; init; } + + [JsonPropertyName("flags")] + public int? Flags { get; init; } + + [JsonPropertyName("tag")] + public string? Tag { get; init; } +} + +/// A temporary download link for an image. +public sealed class ImageDownload : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("expiry")] + public DateTimeOffset Expiry { get; init; } + + [JsonPropertyName("disks")] + public IReadOnlyList Disks { get; init; } = Array.Empty(); +} + +/// Download URLs for one disk in an image download. +public sealed class ImageDiskDownload : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("compressed_url")] + public string CompressedUrl { get; init; } = string.Empty; + + [JsonPropertyName("raw_url")] + public string RawUrl { get; init; } = string.Empty; +} + +/// An SSH public key owned by the authenticated account. +public sealed class SshKey : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("fingerprint")] + public string Fingerprint { get; init; } = string.Empty; + + [JsonPropertyName("public_key")] + public string PublicKey { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("default")] + public bool IsDefault { get; init; } +} + +/// A BinaryLane load balancer. +public sealed class LoadBalancer : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("ip")] + public string Ip { get; init; } = string.Empty; + + /// Provider load balancer status, such as active. + [JsonPropertyName("status")] + public string Status { get; init; } = string.Empty; + + [JsonPropertyName("created_at")] + public DateTimeOffset CreatedAt { get; init; } + + [JsonPropertyName("forwarding_rules")] + public IReadOnlyList ForwardingRules { get; init; } = Array.Empty(); + + [JsonPropertyName("health_check")] + public HealthCheck HealthCheck { get; init; } = new(); + + [JsonPropertyName("region")] + public Region? Region { get; init; } + + [JsonPropertyName("server_ids")] + public IReadOnlyList ServerIds { get; init; } = Array.Empty(); +} + +/// A load-balancer forwarding rule. +public sealed class ForwardingRule : BinaryLaneDto +{ + /// Provider rule protocol, either http or https. + [JsonPropertyName("entry_protocol")] + public string EntryProtocol { get; init; } = string.Empty; +} + +/// A load-balancer health check. +public sealed class HealthCheck : BinaryLaneDto +{ + /// Provider health check protocol, such as http, https, or both. + [JsonPropertyName("protocol")] + public string Protocol { get; init; } = string.Empty; + + [JsonPropertyName("path")] + public string Path { get; init; } = string.Empty; +} + +/// A load-balancer configuration option available to the account. +public sealed class LoadBalancerAvailabilityOption : BinaryLaneDto +{ + [JsonPropertyName("regions")] + public IReadOnlyList? Regions { get; init; } + + [JsonPropertyName("anycast")] + public bool Anycast { get; init; } + + [JsonPropertyName("price_monthly")] + public double PriceMonthly { get; init; } + + [JsonPropertyName("price_hourly")] + public double PriceHourly { get; init; } +} + +/// A virtual private cloud. +public sealed class Vpc : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("ip_range")] + public string IpRange { get; init; } = string.Empty; + + [JsonPropertyName("route_entries")] + public IReadOnlyList RouteEntries { get; init; } = Array.Empty(); +} + +/// A route within a VPC. +public sealed class RouteEntry : BinaryLaneDto +{ + [JsonPropertyName("router")] + public string Router { get; init; } = string.Empty; + + [JsonPropertyName("destination")] + public string Destination { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// A resource connected to a VPC. +public sealed class VpcMember : BinaryLaneDto +{ + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + /// Provider resource type. + [JsonPropertyName("resource_type")] + public string ResourceType { get; init; } = string.Empty; + + /// The provider documents this as a string, unlike action resource identifiers. + [JsonPropertyName("resource_id")] + public string ResourceId { get; init; } = string.Empty; + + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; init; } +} + +/// A scheduled backup window. +public sealed class BackupWindow : BinaryLaneDto +{ + [JsonPropertyName("start")] + public DateTimeOffset Start { get; init; } + + [JsonPropertyName("end")] + public DateTimeOffset End { get; init; } +} + +/// A server's configured backup schedule. +public sealed class BackupSettings : BinaryLaneDto +{ + [JsonPropertyName("backup_hour_of_day")] + public int BackupHourOfDay { get; init; } + + [JsonPropertyName("backup_day_of_week")] + public int BackupDayOfWeek { get; init; } + + [JsonPropertyName("backup_day_of_month")] + public int BackupDayOfMonth { get; init; } + + [JsonPropertyName("offsite_backup_settings")] + public OffsiteBackupSettings? OffsiteBackupSettings { get; init; } +} + +/// Offsite-backup configuration for a server. +public sealed class OffsiteBackupSettings : BinaryLaneDto +{ + [JsonPropertyName("use_custom_backup_location")] + public bool UseCustomBackupLocation { get; init; } + + [JsonPropertyName("offsite_backup_location")] + public string? OffsiteBackupLocation { get; init; } + + [JsonPropertyName("manage_offsite_copies")] + public bool? ManageOffsiteCopies { get; init; } +} + +/// The host backing a server. +public sealed class Host : BinaryLaneDto +{ + [JsonPropertyName("display_name")] + public string DisplayName { get; init; } = string.Empty; + + [JsonPropertyName("uptime_ms")] + public long? UptimeMilliseconds { get; init; } + + [JsonPropertyName("status_page")] + public string? StatusPage { get; init; } +} + +/// Metadata about a backup currently attached to a server. +public sealed class AttachedBackup : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("disk_identifiers")] + public IReadOnlyList DiskIdentifiers { get; init; } = Array.Empty(); + + [JsonPropertyName("attached_at")] + public DateTimeOffset? AttachedAt { get; init; } + + [JsonPropertyName("attachment_expires")] + public DateTimeOffset? AttachmentExpires { get; init; } +} + +/// Advanced virtual-machine features configured for a server. +public sealed class AdvancedServerFeatures : BinaryLaneDto +{ + [JsonPropertyName("processor_model")] + public long? ProcessorModel { get; init; } + + /// Provider virtual machine type. + [JsonPropertyName("machine_type")] + public string? MachineType { get; init; } + + /// Provider virtual video device. + [JsonPropertyName("video_device")] + public string VideoDevice { get; init; } = string.Empty; + + /// Provider advanced-feature values. + [JsonPropertyName("enabled_advanced_features")] + public IReadOnlyList EnabledAdvancedFeatures { get; init; } = Array.Empty(); +} + +/// Advanced server features currently available to the account. +public sealed class AvailableAdvancedServerFeatures : BinaryLaneDto +{ + [JsonPropertyName("processor_models")] + public IReadOnlyList ProcessorModels { get; init; } = Array.Empty(); + + [JsonPropertyName("machine_types")] + public IReadOnlyList MachineTypes { get; init; } = Array.Empty(); + + [JsonPropertyName("advanced_features")] + public IReadOnlyList AdvancedFeatures { get; init; } = Array.Empty(); +} + +/// An available processor model. +public sealed class ProcessorModel : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// An advanced firewall rule applied to a server. +public sealed class AdvancedFirewallRule : BinaryLaneDto +{ + [JsonPropertyName("source_addresses")] + public IReadOnlyList SourceAddresses { get; init; } = Array.Empty(); + + [JsonPropertyName("destination_addresses")] + public IReadOnlyList DestinationAddresses { get; init; } = Array.Empty(); + + [JsonPropertyName("destination_ports")] + public IReadOnlyList? DestinationPorts { get; init; } + + /// Provider protocol value, such as tcp. + [JsonPropertyName("protocol")] + public string Protocol { get; init; } = string.Empty; + + /// Provider action value, such as accept. + [JsonPropertyName("action")] + public string Action { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// An available server kernel. +public sealed class Kernel : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("name")] + public string? Name { get; init; } + + [JsonPropertyName("version")] + public string? Version { get; init; } +} + +/// A configured server threshold alert. +public sealed class ThresholdAlert : BinaryLaneDto +{ + /// Provider alert type. + [JsonPropertyName("alert_type")] + public string AlertType { get; init; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("unit")] + public string Unit { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; init; } = string.Empty; + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } + + [JsonPropertyName("value")] + public int Value { get; init; } + + [JsonPropertyName("current_value")] + public int? CurrentValue { get; init; } + + [JsonPropertyName("last_raised")] + public DateTimeOffset? LastRaised { get; init; } + + [JsonPropertyName("last_cleared")] + public DateTimeOffset? LastCleared { get; init; } +} + +/// Software licensed on a server. +public sealed class LicensedSoftware : BinaryLaneDto +{ + [JsonPropertyName("software")] + public Software Software { get; init; } = new(); + + [JsonPropertyName("licence_count")] + public int LicenceCount { get; init; } + + [JsonPropertyName("incompatible")] + public bool Incompatible { get; init; } +} + +/// A software product that can be licensed on BinaryLane servers. +public sealed class Software : BinaryLaneDto +{ + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; init; } = string.Empty; + + [JsonPropertyName("cost_per_licence_per_month")] + public double CostPerLicencePerMonth { get; init; } + + [JsonPropertyName("minimum_licence_count")] + public int MinimumLicenceCount { get; init; } + + [JsonPropertyName("maximum_licence_count")] + public int MaximumLicenceCount { get; init; } + + [JsonPropertyName("licence_step_count")] + public int LicenceStepCount { get; init; } + + [JsonPropertyName("group")] + public string? Group { get; init; } + + [JsonPropertyName("supported_operating_systems")] + public IReadOnlyList SupportedOperatingSystems { get; init; } = Array.Empty(); +} + +/// A browser console session for a server. +public sealed class ServerConsole : BinaryLaneDto +{ + [JsonPropertyName("iframe")] + public string Iframe { get; init; } = string.Empty; + + [JsonPropertyName("browser")] + public string Browser { get; init; } = string.Empty; + + [JsonPropertyName("width")] + public int Width { get; init; } + + [JsonPropertyName("height")] + public int Height { get; init; } + + [JsonPropertyName("expiry")] + public DateTimeOffset Expiry { get; init; } +} + +/// Server user-data retained by BinaryLane. +public sealed class UserData : BinaryLaneDto +{ + [JsonPropertyName("user_data")] + public string? Value { get; init; } +} + +/// A sample interval for server monitoring data. +public sealed class Period : BinaryLaneDto +{ + [JsonPropertyName("start")] + public DateTimeOffset Start { get; init; } + + [JsonPropertyName("end")] + public DateTimeOffset End { get; init; } + + /// Provider interval, such as five-minute or day. + [JsonPropertyName("data_interval")] + public string DataInterval { get; init; } = string.Empty; +} + +/// One monitoring aggregate sample. +public sealed class Sample : BinaryLaneDto +{ + [JsonPropertyName("cpu_usage_percent")] + public double CpuUsagePercent { get; init; } + + [JsonPropertyName("cpu_usage_detailed")] + public IReadOnlyList CpuUsageDetailed { get; init; } = Array.Empty(); + + [JsonPropertyName("memory_usage_bytes")] + public double MemoryUsageBytes { get; init; } + + [JsonPropertyName("network_incoming_kbps")] + public double NetworkIncomingKbps { get; init; } + + [JsonPropertyName("network_outgoing_kbps")] + public double NetworkOutgoingKbps { get; init; } + + [JsonPropertyName("storage_usage_megabytes")] + public double StorageUsageMegabytes { get; init; } + + [JsonPropertyName("storage_read_kbps")] + public double StorageReadKbps { get; init; } + + [JsonPropertyName("storage_write_kbps")] + public double StorageWriteKbps { get; init; } + + [JsonPropertyName("storage_read_requests_per_second")] + public double StorageReadRequestsPerSecond { get; init; } + + [JsonPropertyName("storage_write_requests_per_second")] + public double StorageWriteRequestsPerSecond { get; init; } +} + +/// A set of monitoring samples for a server. +public sealed class SampleSet : BinaryLaneDto +{ + [JsonPropertyName("server_id")] + public long ServerId { get; init; } + + [JsonPropertyName("period")] + public Period Period { get; init; } = new(); + + [JsonPropertyName("average")] + public Sample Average { get; init; } = new(); + + [JsonPropertyName("maximum_memory_megabytes")] + public double MaximumMemoryMegabytes { get; init; } + + [JsonPropertyName("maximum_storage_gigabytes")] + public double MaximumStorageGigabytes { get; init; } +} diff --git a/src/BinaryLane.Api/Models/ResponseModels.cs b/src/BinaryLane.Api/Models/ResponseModels.cs new file mode 100644 index 0000000..a24e4c8 --- /dev/null +++ b/src/BinaryLane.Api/Models/ResponseModels.cs @@ -0,0 +1,472 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// Pagination metadata returned by BinaryLane list endpoints. +public sealed class PageMeta : BinaryLaneDto +{ + [JsonPropertyName("total")] + public int Total { get; init; } +} + +/// Pagination links returned by BinaryLane list endpoints. +public sealed class PageLinks : BinaryLaneDto +{ + [JsonPropertyName("pages")] + public PageNavigation Pages { get; init; } = new(); +} + +/// URLs for neighbouring pages in a BinaryLane list response. +public sealed class PageNavigation : BinaryLaneDto +{ + [JsonPropertyName("first")] + public string? First { get; init; } + + [JsonPropertyName("prev")] + public string? Previous { get; init; } + + [JsonPropertyName("next")] + public string? Next { get; init; } + + [JsonPropertyName("last")] + public string? Last { get; init; } +} + +/// An RFC 7807-style problem returned by BinaryLane. +public class ProblemDetails : BinaryLaneDto +{ + [JsonPropertyName("type")] + public string? Type { get; init; } + + [JsonPropertyName("title")] + public string? Title { get; init; } + + [JsonPropertyName("status")] + public int? Status { get; init; } + + [JsonPropertyName("detail")] + public string? Detail { get; init; } + + [JsonPropertyName("instance")] + public string? Instance { get; init; } +} + +/// A validation problem returned by BinaryLane. +public sealed class ValidationProblemDetails : ProblemDetails +{ + [JsonPropertyName("errors")] + public IReadOnlyDictionary>? Errors { get; init; } +} + +public sealed class AccountResponse : BinaryLaneDto +{ + [JsonPropertyName("account")] + public Account Account { get; init; } = new(); +} + +public sealed class ActionResponse : BinaryLaneDto +{ + [JsonPropertyName("action")] + public BinaryLaneAction Action { get; init; } = new(); +} + +public sealed class ActionsLinks : BinaryLaneDto +{ + [JsonPropertyName("actions")] + public IReadOnlyList Actions { get; init; } = Array.Empty(); +} + +public sealed class ActionsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("actions")] + public IReadOnlyList Actions { get; init; } = Array.Empty(); +} + +public sealed class BalanceResponse : BinaryLaneDto +{ + [JsonPropertyName("balance")] + public Balance Balance { get; init; } = new(); +} + +public sealed class InvoiceResponse : BinaryLaneDto +{ + [JsonPropertyName("invoice")] + public Invoice Invoice { get; init; } = new(); +} + +public sealed class InvoicesResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("invoices")] + public IReadOnlyList Invoices { get; init; } = Array.Empty(); +} + +public sealed class UnpaidFailedInvoicesResponse : BinaryLaneDto +{ + [JsonPropertyName("invoices")] + public IReadOnlyList Invoices { get; init; } = Array.Empty(); +} + +public sealed class DataUsageResponse : BinaryLaneDto +{ + [JsonPropertyName("data_usage")] + public DataUsage DataUsage { get; init; } = new(); +} + +public sealed class DataUsagesResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("data_usages")] + public IReadOnlyList DataUsages { get; init; } = Array.Empty(); +} + +public sealed class LocalNameserversResponse : BinaryLaneDto +{ + [JsonPropertyName("local_nameservers")] + public IReadOnlyList LocalNameservers { get; init; } = Array.Empty(); +} + +public sealed class DomainResponse : BinaryLaneDto +{ + [JsonPropertyName("domain")] + public Domain Domain { get; init; } = new(); +} + +public sealed class DomainsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("domains")] + public IReadOnlyList Domains { get; init; } = Array.Empty(); +} + +public sealed class DomainRecordResponse : BinaryLaneDto +{ + [JsonPropertyName("domain_record")] + public DomainRecord DomainRecord { get; init; } = new(); +} + +public sealed class DomainRecordsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("domain_records")] + public IReadOnlyList DomainRecords { get; init; } = Array.Empty(); +} + +public sealed class ImageResponse : BinaryLaneDto +{ + [JsonPropertyName("image")] + public Image Image { get; init; } = new(); +} + +public sealed class ImagesResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("images")] + public IReadOnlyList Images { get; init; } = Array.Empty(); +} + +public sealed class ImageDownloadResponse : BinaryLaneDto +{ + [JsonPropertyName("link")] + public ImageDownload Link { get; init; } = new(); +} + +public sealed class SshKeyResponse : BinaryLaneDto +{ + [JsonPropertyName("ssh_key")] + public SshKey SshKey { get; init; } = new(); +} + +public sealed class SshKeysResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("ssh_keys")] + public IReadOnlyList SshKeys { get; init; } = Array.Empty(); +} + +public sealed class LoadBalancerResponse : BinaryLaneDto +{ + [JsonPropertyName("load_balancer")] + public LoadBalancer LoadBalancer { get; init; } = new(); +} + +public sealed class LoadBalancersResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("load_balancers")] + public IReadOnlyList LoadBalancers { get; init; } = Array.Empty(); +} + +public sealed class CreateLoadBalancerResponse : BinaryLaneDto +{ + [JsonPropertyName("load_balancer")] + public LoadBalancer LoadBalancer { get; init; } = new(); + + [JsonPropertyName("links")] + public ActionsLinks Links { get; init; } = new(); +} + +public sealed class UpdateLoadBalancerResponse : BinaryLaneDto +{ + [JsonPropertyName("load_balancer")] + public LoadBalancer LoadBalancer { get; init; } = new(); + + [JsonPropertyName("links")] + public ActionsLinks? Links { get; init; } +} + +public sealed class LoadBalancerAvailabilityResponse : BinaryLaneDto +{ + [JsonPropertyName("load_balancer_availability_options")] + public IReadOnlyList LoadBalancerAvailabilityOptions { get; init; } = Array.Empty(); +} + +public sealed class RegionsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("regions")] + public IReadOnlyList Regions { get; init; } = Array.Empty(); +} + +public sealed class ReverseNameServersResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("reverse_nameservers")] + public IReadOnlyList ReverseNameservers { get; init; } = Array.Empty(); +} + +public sealed class SampleSetResponse : BinaryLaneDto +{ + [JsonPropertyName("sample_set")] + public SampleSet? SampleSet { get; init; } +} + +public sealed class SampleSetsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("sample_sets")] + public IReadOnlyList SampleSets { get; init; } = Array.Empty(); +} + +public sealed class ServerResponse : BinaryLaneDto +{ + [JsonPropertyName("server")] + public Server Server { get; init; } = new(); +} + +public sealed class ServersResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("servers")] + public IReadOnlyList Servers { get; init; } = Array.Empty(); +} + +public sealed class CreateServerResponse : BinaryLaneDto +{ + [JsonPropertyName("server")] + public Server Server { get; init; } = new(); + + [JsonPropertyName("links")] + public ActionsLinks Links { get; init; } = new(); +} + +public sealed class AdvancedFirewallRulesResponse : BinaryLaneDto +{ + [JsonPropertyName("firewall_rules")] + public IReadOnlyList FirewallRules { get; init; } = Array.Empty(); +} + +public sealed class AvailableAdvancedServerFeaturesResponse : BinaryLaneDto +{ + [JsonPropertyName("available_advanced_server_features")] + public AvailableAdvancedServerFeatures AvailableAdvancedServerFeatures { get; init; } = new(); +} + +public sealed class BackupsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("backups")] + public IReadOnlyList Backups { get; init; } = Array.Empty(); +} + +public sealed class KernelsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("kernels")] + public IReadOnlyList Kernels { get; init; } = Array.Empty(); +} + +public sealed class SnapshotsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("snapshots")] + public IReadOnlyList Snapshots { get; init; } = Array.Empty(); +} + +public sealed class ThresholdAlertsResponse : BinaryLaneDto +{ + [JsonPropertyName("threshold_alerts")] + public IReadOnlyList ThresholdAlerts { get; init; } = Array.Empty(); +} + +public sealed class CurrentServerAlertsResponse : BinaryLaneDto +{ + [JsonPropertyName("server_ids")] + public IReadOnlyList ServerIds { get; init; } = Array.Empty(); +} + +public sealed class LicensedSoftwaresResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("licensed_software")] + public IReadOnlyList LicensedSoftware { get; init; } = Array.Empty(); +} + +public sealed class ServerConsoleResponse : BinaryLaneDto +{ + [JsonPropertyName("console")] + public ServerConsole Console { get; init; } = new(); +} + +public sealed class SizesResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("sizes")] + public IReadOnlyList Sizes { get; init; } = Array.Empty(); +} + +public sealed class SoftwareResponse : BinaryLaneDto +{ + [JsonPropertyName("software")] + public Software Software { get; init; } = new(); +} + +public sealed class SoftwaresResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("software")] + public IReadOnlyList Software { get; init; } = Array.Empty(); +} + +public sealed class VpcResponse : BinaryLaneDto +{ + [JsonPropertyName("vpc")] + public Vpc Vpc { get; init; } = new(); +} + +public sealed class VpcsResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("vpcs")] + public IReadOnlyList Vpcs { get; init; } = Array.Empty(); +} + +public sealed class VpcMembersResponse : BinaryLaneDto +{ + [JsonPropertyName("meta")] + public PageMeta Meta { get; init; } = new(); + + [JsonPropertyName("links")] + public PageLinks? Links { get; init; } + + [JsonPropertyName("members")] + public IReadOnlyList Members { get; init; } = Array.Empty(); +} diff --git a/src/BinaryLane.Api/Models/ServerActionJsonConverter.cs b/src/BinaryLane.Api/Models/ServerActionJsonConverter.cs new file mode 100644 index 0000000..eda0cc2 --- /dev/null +++ b/src/BinaryLane.Api/Models/ServerActionJsonConverter.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// +/// Serializes the BinaryLane server-action discriminated union when callers hold +/// an action through its base type. +/// +/// +/// System.Text.Json normally uses the statically declared type. That would emit +/// only type for a variable whose runtime +/// value is, for example, . The BinaryLane API +/// requires the concrete action properties as well, so this converter writes +/// the runtime payload and handles all documented discriminator values. Unknown +/// action values are preserved through . +/// +public sealed class ServerActionJsonConverter : JsonConverter +{ + private static readonly PropertyInfo AdditionalPropertiesProperty = + typeof(BinaryLaneDto).GetProperty(nameof(BinaryLaneDto.AdditionalProperties)) + ?? throw new InvalidOperationException("BinaryLaneDto must expose extension data."); + + /// + public override bool CanConvert(Type typeToConvert) => typeof(ServerAction).IsAssignableFrom(typeToConvert); + + /// + public override ServerAction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using JsonDocument document = JsonDocument.ParseValue(ref reader); + if (document.RootElement.ValueKind == JsonValueKind.Null) + { + return null; + } + + if (document.RootElement.ValueKind != JsonValueKind.Object) + { + throw new JsonException("A BinaryLane server action must be a JSON object."); + } + + string type = ReadType(document.RootElement); + ServerAction action = CreateAction(type); + PopulateAction(action, document.RootElement, options); + return action; + } + + /// + public override void Write(Utf8JsonWriter writer, ServerAction value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString("type", value.Type); + + var writtenProperties = new HashSet(StringComparer.Ordinal) + { + "type", + }; + + foreach (PropertyInfo property in GetPayloadProperties(value.GetType())) + { + string jsonName = GetJsonName(property); + if (!writtenProperties.Add(jsonName)) + { + continue; + } + + object? propertyValue = property.GetValue(value); + if (propertyValue is null && options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull) + { + continue; + } + + writer.WritePropertyName(jsonName); + JsonSerializer.Serialize(writer, propertyValue, property.PropertyType, options); + } + + if (value.AdditionalProperties is not null) + { + foreach (KeyValuePair property in value.AdditionalProperties) + { + if (!writtenProperties.Add(property.Key)) + { + continue; + } + + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } + } + + writer.WriteEndObject(); + } + + private static string ReadType(JsonElement action) + { + if (!action.TryGetProperty("type", out JsonElement typeElement) || + typeElement.ValueKind != JsonValueKind.String || + string.IsNullOrWhiteSpace(typeElement.GetString())) + { + throw new JsonException("A BinaryLane server action requires a non-empty 'type' discriminator."); + } + + return typeElement.GetString()!; + } + + private static ServerAction CreateAction(string type) => type switch + { + BinaryLaneValues.ServerActionType.AddDisk => new AddDiskServerAction(), + BinaryLaneValues.ServerActionType.AttachBackup => new AttachBackupServerAction(), + BinaryLaneValues.ServerActionType.ChangeAdvancedFeatures => new ChangeAdvancedFeaturesServerAction(), + BinaryLaneValues.ServerActionType.ChangeAdvancedFirewallRules => new ChangeAdvancedFirewallRulesServerAction(), + BinaryLaneValues.ServerActionType.ChangeBackupSchedule => new ChangeBackupScheduleServerAction(), + BinaryLaneValues.ServerActionType.ChangeIpv6 => new ChangeIpv6ServerAction(), + BinaryLaneValues.ServerActionType.ChangeIpv6ReverseNameservers => new ChangeIpv6ReverseNameserversServerAction(), + BinaryLaneValues.ServerActionType.ChangeKernel => new ChangeKernelServerAction(), + BinaryLaneValues.ServerActionType.ChangeManageOffsiteBackupCopies => new ChangeManageOffsiteBackupCopiesServerAction(), + BinaryLaneValues.ServerActionType.ChangeNetwork => new ChangeNetworkServerAction(), + BinaryLaneValues.ServerActionType.ChangeOffsiteBackupLocation => new ChangeOffsiteBackupLocationServerAction(), + BinaryLaneValues.ServerActionType.ChangePartner => new ChangePartnerServerAction(), + BinaryLaneValues.ServerActionType.ChangePortBlocking => new ChangePortBlockingServerAction(), + BinaryLaneValues.ServerActionType.ChangeRegion => new ChangeRegionServerAction(), + BinaryLaneValues.ServerActionType.ChangeReverseName => new ChangeReverseNameServerAction(), + BinaryLaneValues.ServerActionType.ChangeSeparatePrivateNetworkInterface => new ChangeSeparatePrivateNetworkInterfaceServerAction(), + BinaryLaneValues.ServerActionType.ChangeSourceAndDestinationCheck => new ChangeSourceAndDestinationCheckServerAction(), + BinaryLaneValues.ServerActionType.ChangeThresholdAlerts => new ChangeThresholdAlertsServerAction(), + BinaryLaneValues.ServerActionType.ChangeVpcIpv4 => new ChangeVpcIpv4ServerAction(), + BinaryLaneValues.ServerActionType.CloneUsingBackup => new CloneUsingBackupServerAction(), + BinaryLaneValues.ServerActionType.DeleteDisk => new DeleteDiskServerAction(), + BinaryLaneValues.ServerActionType.DetachBackup => new DetachBackupServerAction(), + BinaryLaneValues.ServerActionType.DisableBackups => new DisableBackupsServerAction(), + BinaryLaneValues.ServerActionType.DisableSelinux => new DisableSelinuxServerAction(), + BinaryLaneValues.ServerActionType.EnableBackups => new EnableBackupsServerAction(), + BinaryLaneValues.ServerActionType.EnableIpv6 => new EnableIpv6ServerAction(), + BinaryLaneValues.ServerActionType.IsRunning => new IsRunningServerAction(), + BinaryLaneValues.ServerActionType.PasswordReset => new PasswordResetServerAction(), + BinaryLaneValues.ServerActionType.Ping => new PingServerAction(), + BinaryLaneValues.ServerActionType.PowerCycle => new PowerCycleServerAction(), + BinaryLaneValues.ServerActionType.PowerOff => new PowerOffServerAction(), + BinaryLaneValues.ServerActionType.PowerOn => new PowerOnServerAction(), + BinaryLaneValues.ServerActionType.Reboot => new RebootServerAction(), + BinaryLaneValues.ServerActionType.Rebuild => new RebuildServerAction(), + BinaryLaneValues.ServerActionType.Rename => new RenameServerAction(), + BinaryLaneValues.ServerActionType.Resize => new ResizeServerAction(), + BinaryLaneValues.ServerActionType.ResizeDisk => new ResizeDiskServerAction(), + BinaryLaneValues.ServerActionType.Restore => new RestoreServerAction(), + BinaryLaneValues.ServerActionType.Shutdown => new ShutdownServerAction(), + BinaryLaneValues.ServerActionType.TakeBackup => new TakeBackupServerAction(), + BinaryLaneValues.ServerActionType.Uncancel => new UncancelServerAction(), + BinaryLaneValues.ServerActionType.Uptime => new UptimeServerAction(), + _ => new UnknownServerAction(type), + }; + + private static void PopulateAction(ServerAction action, JsonElement source, JsonSerializerOptions options) + { + var knownProperties = new HashSet(StringComparer.Ordinal) + { + "type", + }; + + foreach (PropertyInfo property in GetPayloadProperties(action.GetType())) + { + string jsonName = GetJsonName(property); + knownProperties.Add(jsonName); + if (!source.TryGetProperty(jsonName, out JsonElement value)) + { + continue; + } + + object? deserialized = JsonSerializer.Deserialize(value.GetRawText(), property.PropertyType, options); + property.SetValue(action, deserialized); + } + + var additionalProperties = new Dictionary(StringComparer.Ordinal); + foreach (JsonProperty property in source.EnumerateObject()) + { + if (!knownProperties.Contains(property.Name)) + { + additionalProperties[property.Name] = property.Value.Clone(); + } + } + + if (additionalProperties.Count > 0) + { + AdditionalPropertiesProperty.SetValue(action, additionalProperties); + } + } + + private static IEnumerable GetPayloadProperties(Type actionType) + { + foreach (PropertyInfo property in actionType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (!property.CanRead || !property.CanWrite || + property.Name == nameof(ServerAction.Type) || + property.Name == nameof(BinaryLaneDto.AdditionalProperties) || + property.GetIndexParameters().Length != 0 || + property.GetCustomAttribute()?.Condition == JsonIgnoreCondition.Always) + { + continue; + } + + yield return property; + } + } + + private static string GetJsonName(PropertyInfo property) => + property.GetCustomAttribute()?.Name ?? property.Name; +} diff --git a/src/BinaryLane.Api/Models/ServerActionModels.cs b/src/BinaryLane.Api/Models/ServerActionModels.cs new file mode 100644 index 0000000..e9066b6 --- /dev/null +++ b/src/BinaryLane.Api/Models/ServerActionModels.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace BinaryLane.Api.V2.Models; + +/// +/// Base payload for POST /v2/servers/{server_id}/actions. +/// +/// +/// The upstream contract is a discriminated union on type. Its public +/// documentation also creates fragment-bearing pseudo-paths for each variant; +/// those are not separate HTTP routes. Prefer a concrete derived type for a +/// documented action. permits new provider +/// action types without waiting for an SDK release. +/// +public class ServerAction : BinaryLaneRequestModel +{ + /// Creates an empty action for serializers and advanced callers. + public ServerAction() + { + } + + /// Creates a typed action with the provider discriminator value. + protected ServerAction(string type) + { + Type = type; + } + + [JsonPropertyName("type")] + public string Type { get; init; } = string.Empty; +} + +/// An action type not yet represented by a concrete SDK class. +public sealed class UnknownServerAction : ServerAction +{ + public UnknownServerAction(string type) + : base(type) + { + } +} + +/// Adds an additional disk to a server. +public sealed class AddDiskServerAction : ServerAction +{ + public AddDiskServerAction() : base("add_disk") { } + + [JsonPropertyName("size_gigabytes")] + public int SizeGigabytes { get; init; } + + [JsonPropertyName("description")] + public string? Description { get; init; } +} + +/// Attaches a backup image to a server. +public sealed class AttachBackupServerAction : ServerAction +{ + public AttachBackupServerAction() : base("attach_backup") { } + + [JsonPropertyName("image")] + public long ImageId { get; init; } +} + +/// Changes advanced virtual-machine features. +public sealed class ChangeAdvancedFeaturesServerAction : ServerAction +{ + public ChangeAdvancedFeaturesServerAction() : base("change_advanced_features") { } + + [JsonPropertyName("enabled_advanced_features")] + public IReadOnlyList? EnabledAdvancedFeatures { get; init; } + + [JsonPropertyName("processor_model")] + public long? ProcessorModel { get; init; } + + [JsonPropertyName("automatic_processor_model")] + public bool? AutomaticProcessorModel { get; init; } + + [JsonPropertyName("machine_type")] + public string? MachineType { get; init; } + + [JsonPropertyName("automatic_machine_type")] + public bool? AutomaticMachineType { get; init; } + + [JsonPropertyName("video_device")] + public string? VideoDevice { get; init; } +} + +/// Replaces the advanced firewall rules on a server. +public sealed class ChangeAdvancedFirewallRulesServerAction : ServerAction +{ + public ChangeAdvancedFirewallRulesServerAction() : base("change_advanced_firewall_rules") { } + + [JsonPropertyName("firewall_rules")] + public IReadOnlyList FirewallRules { get; init; } = Array.Empty(); +} + +/// Changes a server backup schedule. +public sealed class ChangeBackupScheduleServerAction : ServerAction +{ + public ChangeBackupScheduleServerAction() : base("change_backup_schedule") { } + + [JsonPropertyName("backup_hour_of_day")] + public int? BackupHourOfDay { get; init; } + + [JsonPropertyName("backup_day_of_week")] + public int? BackupDayOfWeek { get; init; } + + [JsonPropertyName("backup_day_of_month")] + public int? BackupDayOfMonth { get; init; } +} + +/// Enables or disables IPv6 for a server. +public sealed class ChangeIpv6ServerAction : ServerAction +{ + public ChangeIpv6ServerAction() : base("change_ipv6") { } + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } +} + +/// Changes a server's IPv6 reverse nameservers. +public sealed class ChangeIpv6ReverseNameserversServerAction : ServerAction +{ + public ChangeIpv6ReverseNameserversServerAction() : base("change_ipv6_reverse_nameservers") { } + + [JsonPropertyName("ipv6_reverse_nameservers")] + public IReadOnlyList Ipv6ReverseNameservers { get; init; } = Array.Empty(); +} + +/// Changes a server kernel. +public sealed class ChangeKernelServerAction : ServerAction +{ + public ChangeKernelServerAction() : base("change_kernel") { } + + [JsonPropertyName("kernel")] + public long KernelId { get; init; } +} + +/// Changes whether offsite backup copies are managed automatically. +public sealed class ChangeManageOffsiteBackupCopiesServerAction : ServerAction +{ + public ChangeManageOffsiteBackupCopiesServerAction() : base("change_manage_offsite_backup_copies") { } + + [JsonPropertyName("manage_offsite_backup_copies")] + public bool ManageOffsiteBackupCopies { get; init; } +} + +/// Moves a server onto or off a VPC network. +public sealed class ChangeNetworkServerAction : ServerAction +{ + public ChangeNetworkServerAction() : base("change_network") { } + + [JsonPropertyName("vpc_id")] + public long? VpcId { get; init; } +} + +/// Changes the server's offsite backup location. +public sealed class ChangeOffsiteBackupLocationServerAction : ServerAction +{ + public ChangeOffsiteBackupLocationServerAction() : base("change_offsite_backup_location") { } + + [JsonPropertyName("offsite_backup_location")] + public string? OffsiteBackupLocation { get; init; } +} + +/// Changes the server's partner server relationship. +public sealed class ChangePartnerServerAction : ServerAction +{ + public ChangePartnerServerAction() : base("change_partner") { } + + [JsonPropertyName("partner_server_id")] + public long? PartnerServerId { get; init; } +} + +/// Enables or disables port blocking. +public sealed class ChangePortBlockingServerAction : ServerAction +{ + public ChangePortBlockingServerAction() : base("change_port_blocking") { } + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } +} + +/// Moves a server to a new region. +public sealed class ChangeRegionServerAction : ServerAction +{ + public ChangeRegionServerAction() : base("change_region") { } + + [JsonPropertyName("region")] + public string Region { get; init; } = string.Empty; +} + +/// Changes an IPv4 reverse name. +public sealed class ChangeReverseNameServerAction : ServerAction +{ + public ChangeReverseNameServerAction() : base("change_reverse_name") { } + + [JsonPropertyName("ipv4_address")] + public string Ipv4Address { get; init; } = string.Empty; + + [JsonPropertyName("reverse_name")] + public string? ReverseName { get; init; } +} + +/// Enables or disables a separate private network interface. +public sealed class ChangeSeparatePrivateNetworkInterfaceServerAction : ServerAction +{ + public ChangeSeparatePrivateNetworkInterfaceServerAction() : base("change_separate_private_network_interface") { } + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } +} + +/// Enables or disables source-and-destination checking. +public sealed class ChangeSourceAndDestinationCheckServerAction : ServerAction +{ + public ChangeSourceAndDestinationCheckServerAction() : base("change_source_and_destination_check") { } + + [JsonPropertyName("enabled")] + public bool Enabled { get; init; } +} + +/// Replaces a server's threshold-alert configuration. +public sealed class ChangeThresholdAlertsServerAction : ServerAction +{ + public ChangeThresholdAlertsServerAction() : base("change_threshold_alerts") { } + + [JsonPropertyName("threshold_alerts")] + public IReadOnlyList ThresholdAlerts { get; init; } = Array.Empty(); +} + +/// Changes a VPC IPv4 address on a server. +public sealed class ChangeVpcIpv4ServerAction : ServerAction +{ + public ChangeVpcIpv4ServerAction() : base("change_vpc_ipv4") { } + + [JsonPropertyName("current_ipv4_address")] + public string CurrentIpv4Address { get; init; } = string.Empty; + + [JsonPropertyName("new_ipv4_address")] + public string NewIpv4Address { get; init; } = string.Empty; +} + +/// Clones a backup image onto another server. +public sealed class CloneUsingBackupServerAction : ServerAction +{ + public CloneUsingBackupServerAction() : base("clone_using_backup") { } + + [JsonPropertyName("image_id")] + public long ImageId { get; init; } + + [JsonPropertyName("target_server_id")] + public long TargetServerId { get; init; } + + [JsonPropertyName("name")] + public string? Name { get; init; } +} + +/// Deletes a non-primary disk. +public sealed class DeleteDiskServerAction : ServerAction +{ + public DeleteDiskServerAction() : base("delete_disk") { } + + [JsonPropertyName("disk_id")] + public long DiskId { get; init; } +} + +/// Detaches an attached backup from a server. +public sealed class DetachBackupServerAction : ServerAction +{ + public DetachBackupServerAction() : base("detach_backup") { } +} + +/// Disables automatic backups. +public sealed class DisableBackupsServerAction : ServerAction +{ + public DisableBackupsServerAction() : base("disable_backups") { } +} + +/// Disables SELinux in a compatible installed distribution. +public sealed class DisableSelinuxServerAction : ServerAction +{ + public DisableSelinuxServerAction() : base("disable_selinux") { } +} + +/// Enables automatic backups. +public sealed class EnableBackupsServerAction : ServerAction +{ + public EnableBackupsServerAction() : base("enable_backups") { } +} + +/// Enables IPv6 on a server. +public sealed class EnableIpv6ServerAction : ServerAction +{ + public EnableIpv6ServerAction() : base("enable_ipv6") { } +} + +/// Checks whether a server is running. +public sealed class IsRunningServerAction : ServerAction +{ + public IsRunningServerAction() : base("is_running") { } +} + +/// Resets a server password. +public sealed class PasswordResetServerAction : ServerAction +{ + public PasswordResetServerAction() : base("password_reset") { } + + [JsonPropertyName("username")] + public string? Username { get; init; } + + /// Do not log this value. + [JsonPropertyName("password")] + public string? Password { get; init; } +} + +/// Pings a server. +public sealed class PingServerAction : ServerAction +{ + public PingServerAction() : base("ping") { } +} + +/// Power cycles a server. +public sealed class PowerCycleServerAction : ServerAction +{ + public PowerCycleServerAction() : base("power_cycle") { } +} + +/// Powers a server off. +public sealed class PowerOffServerAction : ServerAction +{ + public PowerOffServerAction() : base("power_off") { } +} + +/// Powers a server on. +public sealed class PowerOnServerAction : ServerAction +{ + public PowerOnServerAction() : base("power_on") { } +} + +/// Reboots a server. +public sealed class RebootServerAction : ServerAction +{ + public RebootServerAction() : base("reboot") { } +} + +/// Rebuilds a server from an image. +public sealed class RebuildServerAction : ServerAction +{ + public RebuildServerAction() : base("rebuild") { } + + /// An image ID or image slug. Omit to reuse the existing image. + [JsonPropertyName("image")] + public object? Image { get; init; } + + [JsonPropertyName("options")] + public ImageOptions? Options { get; init; } +} + +/// Renames a server. +public sealed class RenameServerAction : ServerAction +{ + public RenameServerAction() : base("rename") { } + + [JsonPropertyName("name")] + public string Name { get; init; } = string.Empty; +} + +/// Changes selected size options during a resize action. +public sealed class ChangeSizeOptionsRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("daily_backups")] + public int? DailyBackups { get; init; } + + [JsonPropertyName("weekly_backups")] + public int? WeeklyBackups { get; init; } + + [JsonPropertyName("monthly_backups")] + public int? MonthlyBackups { get; init; } + + [JsonPropertyName("offsite_backups")] + public bool? OffsiteBackups { get; init; } + + [JsonPropertyName("ipv4_addresses")] + public int? Ipv4Addresses { get; init; } + + [JsonPropertyName("memory")] + public int? MemoryMegabytes { get; init; } + + [JsonPropertyName("disk")] + public int? DiskGigabytes { get; init; } + + [JsonPropertyName("transfer")] + public double? TransferGigabytes { get; init; } + + [JsonPropertyName("ipv4_addresses_to_remove")] + public IReadOnlyList? Ipv4AddressesToRemove { get; init; } +} + +/// Changes the source image selected by a resize operation. +public sealed class ChangeImage : BinaryLaneRequestModel +{ + /// An image ID or image slug. + [JsonPropertyName("image")] + public object? Image { get; init; } + + [JsonPropertyName("options")] + public ImageOptions? Options { get; init; } +} + +/// Changes licences selected by a resize operation. +public sealed class ChangeLicenses : BinaryLaneRequestModel +{ + [JsonPropertyName("licenses")] + public IReadOnlyList Licenses { get; init; } = Array.Empty(); +} + +/// Resizes a server. +public sealed class ResizeServerAction : ServerAction +{ + public ResizeServerAction() : base("resize") { } + + [JsonPropertyName("size")] + public string? Size { get; init; } + + [JsonPropertyName("options")] + public ChangeSizeOptionsRequest? Options { get; init; } + + [JsonPropertyName("change_image")] + public ChangeImage? ChangeImage { get; init; } + + [JsonPropertyName("change_licenses")] + public ChangeLicenses? ChangeLicenses { get; init; } + + [JsonPropertyName("pre_action_backup")] + public TakeBackupServerAction? PreActionBackup { get; init; } +} + +/// Resizes an individual server disk. +public sealed class ResizeDiskServerAction : ServerAction +{ + public ResizeDiskServerAction() : base("resize_disk") { } + + [JsonPropertyName("disk_id")] + public long DiskId { get; init; } + + [JsonPropertyName("size_gigabytes")] + public int SizeGigabytes { get; init; } +} + +/// Restores a server from an image. +public sealed class RestoreServerAction : ServerAction +{ + public RestoreServerAction() : base("restore") { } + + /// An image ID or image slug. + [JsonPropertyName("image")] + public object Image { get; init; } = string.Empty; +} + +/// Requests a graceful shutdown. +public sealed class ShutdownServerAction : ServerAction +{ + public ShutdownServerAction() : base("shutdown") { } +} + +/// Creates a backup of a server. +public sealed class TakeBackupServerAction : ServerAction +{ + public TakeBackupServerAction() : base("take_backup") { } + + /// Provider backup slot, such as daily or temporary. + [JsonPropertyName("backup_type")] + public string? BackupType { get; init; } + + /// Provider replacement strategy. + [JsonPropertyName("replacement_strategy")] + public string ReplacementStrategy { get; init; } = string.Empty; + + [JsonPropertyName("backup_id_to_replace")] + public long? BackupIdToReplace { get; init; } + + [JsonPropertyName("label")] + public string? Label { get; init; } +} + +/// Reverses a scheduled server cancellation. +public sealed class UncancelServerAction : ServerAction +{ + public UncancelServerAction() : base("uncancel") { } +} + +/// Checks server uptime. +public sealed class UptimeServerAction : ServerAction +{ + public UptimeServerAction() : base("uptime") { } +} + +/// An advanced firewall rule supplied in a server-action request. +public sealed class AdvancedFirewallRuleRequest : BinaryLaneRequestModel +{ + [JsonPropertyName("source_addresses")] + public IReadOnlyList SourceAddresses { get; init; } = Array.Empty(); + + [JsonPropertyName("destination_addresses")] + public IReadOnlyList DestinationAddresses { get; init; } = Array.Empty(); + + [JsonPropertyName("destination_ports")] + public IReadOnlyList? DestinationPorts { get; init; } + + /// Provider protocol value, such as tcp. + [JsonPropertyName("protocol")] + public string Protocol { get; init; } = string.Empty; + + /// Provider action value, such as accept. + [JsonPropertyName("action")] + public string Action { get; init; } = string.Empty; + + [JsonPropertyName("description")] + public string? Description { get; init; } +} diff --git a/src/BinaryLane.Api/Pagination/Page.cs b/src/BinaryLane.Api/Pagination/Page.cs new file mode 100644 index 0000000..d593761 --- /dev/null +++ b/src/BinaryLane.Api/Pagination/Page.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace BinaryLane.Api.V2.Pagination; + +/// A page returned from a BinaryLane list endpoint. +/// Item type. +public sealed class Page +{ + internal Page( + IReadOnlyList items, + int total, + Uri? firstPage, + Uri? previousPage, + Uri? nextPage, + Uri? lastPage) + { + Items = new ReadOnlyCollection(new List(items)); + Total = total; + FirstPage = firstPage; + PreviousPage = previousPage; + NextPage = nextPage; + LastPage = lastPage; + } + + /// Items returned in this response. + public IReadOnlyList Items { get; } + + /// Total number of items available across all pages, when supplied by BinaryLane. + public int Total { get; } + + /// First result page, when supplied by BinaryLane. + public Uri? FirstPage { get; } + + /// Previous result page, when supplied by BinaryLane. + public Uri? PreviousPage { get; } + + /// Next result page, when supplied by BinaryLane. + public Uri? NextPage { get; } + + /// Last result page, when supplied by BinaryLane. + public Uri? LastPage { get; } +} diff --git a/src/BinaryLane.Api/Pagination/PageRequest.cs b/src/BinaryLane.Api/Pagination/PageRequest.cs new file mode 100644 index 0000000..a8b3f5e --- /dev/null +++ b/src/BinaryLane.Api/Pagination/PageRequest.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace BinaryLane.Api.V2.Pagination; + +/// Specifies a one-based BinaryLane API result page. +public sealed class PageRequest +{ + /// One-based page number. + public int Page { get; set; } = 1; + + /// Number of results to return, from 1 through 200. + public int PerPage { get; set; } = 20; + + internal IReadOnlyDictionary ToQuery() + { + if (Page < 1) + { + throw new ArgumentOutOfRangeException(nameof(Page), "Page numbering starts at 1."); + } + + if (PerPage < 1 || PerPage > 200) + { + throw new ArgumentOutOfRangeException(nameof(PerPage), "PerPage must be between 1 and 200."); + } + + return new Dictionary + { + ["page"] = Page, + ["per_page"] = PerPage, + }; + } +} diff --git a/src/BinaryLane.Api/Resources/ActionSubmission.cs b/src/BinaryLane.Api/Resources/ActionSubmission.cs new file mode 100644 index 0000000..a821a9b --- /dev/null +++ b/src/BinaryLane.Api/Resources/ActionSubmission.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Text.Json; +using BinaryLane.Api.V2.Models; + +namespace BinaryLane.Api.V2.Resources; + +/// Result of submitting an asynchronous server action. +public sealed class ActionSubmission +{ + internal ActionSubmission(HttpStatusCode statusCode, BinaryLaneAction? action, JsonElement response) + { + StatusCode = statusCode; + Action = action; + Response = response; + } + + /// HTTP status returned by BinaryLane, commonly 200 or 202. + public HttpStatusCode StatusCode { get; } + + /// The action supplied by BinaryLane, when present in the response. + public BinaryLaneAction? Action { get; } + + /// Raw response for forward-compatible access to provider fields. + public JsonElement Response { get; } + + /// Whether BinaryLane accepted the request for asynchronous completion. + public bool IsAccepted => StatusCode == HttpStatusCode.Accepted; +} diff --git a/src/BinaryLane.Api/Resources/ActionWaitOptions.cs b/src/BinaryLane.Api/Resources/ActionWaitOptions.cs new file mode 100644 index 0000000..776e9a5 --- /dev/null +++ b/src/BinaryLane.Api/Resources/ActionWaitOptions.cs @@ -0,0 +1,26 @@ +using System; + +namespace BinaryLane.Api.V2.Resources; + +/// Controls opt-in polling of a BinaryLane asynchronous action. +public sealed class ActionWaitOptions +{ + /// Delay between action status checks. + public TimeSpan PollInterval { get; set; } = TimeSpan.FromSeconds(2); + + /// Maximum time spent polling before a timeout is reported. + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(15); + + internal void Validate() + { + if (PollInterval < TimeSpan.FromMilliseconds(100) || PollInterval > TimeSpan.FromMinutes(5)) + { + throw new ArgumentOutOfRangeException(nameof(PollInterval), "PollInterval must be between 100 milliseconds and five minutes."); + } + + if (Timeout < PollInterval || Timeout > TimeSpan.FromHours(24)) + { + throw new ArgumentOutOfRangeException(nameof(Timeout), "Timeout must be at least PollInterval and no more than 24 hours."); + } + } +} diff --git a/src/BinaryLane.Api/Resources/BinaryLaneResourceBase.cs b/src/BinaryLane.Api/Resources/BinaryLaneResourceBase.cs new file mode 100644 index 0000000..9b73077 --- /dev/null +++ b/src/BinaryLane.Api/Resources/BinaryLaneResourceBase.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Pagination; + +namespace BinaryLane.Api.V2.Resources; + +/// Shared JSON-envelope and pagination behavior for resource APIs. +public abstract class BinaryLaneResourceBase +{ + /// Initializes a resource using the common request executor. + protected BinaryLaneResourceBase(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + { + Executor = executor ?? throw new ArgumentNullException(nameof(executor)); + Json = json ?? throw new ArgumentNullException(nameof(json)); + } + + /// Common request executor. + protected IBinaryLaneApiExecutor Executor { get; } + + /// Common JSON options. + protected BinaryLaneJsonSerializerOptions Json { get; } + + /// Gets a raw JSON response, allowing forward-compatible access to a newly added API field. + protected Task GetRawAsync( + string path, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + Executor.GetAsync(path, query, cancellationToken); + + /// Sends a raw JSON mutation request. + protected async Task SendRawAsync( + System.Net.Http.HttpMethod method, + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) + { + var response = await Executor.SendAsync( + new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken).ConfigureAwait(false); + return response.Body; + } + + /// Sends a raw request and retains the HTTP response metadata. + protected Task> SendResponseAsync( + System.Net.Http.HttpMethod method, + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) => + Executor.SendAsync( + new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken); + + /// Sends a request expected to have no response body. + protected async Task SendNoContentAsync( + System.Net.Http.HttpMethod method, + string path, + object? body = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) + { + await Executor.SendAsync( + new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken).ConfigureAwait(false); + } + + /// Gets a response whose value is wrapped in a named JSON property. + protected async Task GetItemAsync( + string path, + string propertyName, + CancellationToken cancellationToken = default) + { + var document = await GetRawAsync(path, null, cancellationToken).ConfigureAwait(false); + return DeserializeEnvelope(document, propertyName); + } + + /// Gets a response whose JSON body is the requested value rather than an envelope. + protected async Task GetDirectItemAsync( + string path, + CancellationToken cancellationToken = default) + { + var document = await GetRawAsync(path, null, cancellationToken).ConfigureAwait(false); + var item = JsonSerializer.Deserialize(document.GetRawText(), Json.SerializerOptions); + if (item is null) + { + throw new JsonException("BinaryLane response did not contain a usable JSON value."); + } + + return item; + } + + /// Sends a mutation whose value is wrapped in a named JSON property. + protected async Task SendItemAsync( + System.Net.Http.HttpMethod method, + string path, + object? body, + string propertyName, + CancellationToken cancellationToken = default) + { + var document = await Executor.SendAsync( + new BinaryLaneRequest(method, path) { Body = body }, cancellationToken).ConfigureAwait(false); + return DeserializeEnvelope(document.Body, propertyName); + } + + /// Gets a typed page whose items are wrapped in a named JSON property. + protected async Task> GetPageAsync( + string path, + string propertyName, + PageRequest? page = null, + IReadOnlyDictionary? query = null, + CancellationToken cancellationToken = default) + { + var parameters = MergeQuery(query, page?.ToQuery()); + var document = await GetRawAsync(path, parameters, cancellationToken).ConfigureAwait(false); + return DeserializePage(document, propertyName); + } + + /// Enumerates every available page without enabling retries for mutating requests. + protected async IAsyncEnumerable GetAllPagesAsync( + string path, + string propertyName, + PageRequest? page = null, + IReadOnlyDictionary? query = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var seenNextPageLinks = new HashSet(StringComparer.Ordinal); + var current = await GetPageAsync(path, propertyName, page, query, cancellationToken).ConfigureAwait(false); + foreach (var item in current.Items) + { + yield return item; + } + + while (current.NextPage is not null) + { + cancellationToken.ThrowIfCancellationRequested(); + var nextPagePath = current.NextPage.IsAbsoluteUri + ? current.NextPage.AbsoluteUri + : current.NextPage.OriginalString; + + if (!seenNextPageLinks.Add(nextPagePath)) + { + throw new InvalidOperationException( + $"BinaryLane returned a repeated pagination link: '{nextPagePath}'."); + } + + current = await GetPageAsync( + nextPagePath, + propertyName, + null, + null, + cancellationToken).ConfigureAwait(false); + + foreach (var item in current.Items) + { + yield return item; + } + } + } + + /// Deserializes an item from a standard BinaryLane JSON response envelope. + protected T DeserializeEnvelope(JsonElement document, string propertyName) + { + if (document.ValueKind != JsonValueKind.Object || + !document.TryGetProperty(propertyName, out var payload)) + { + throw new JsonException($"BinaryLane response did not contain the expected '{propertyName}' envelope property."); + } + + var item = JsonSerializer.Deserialize(payload.GetRawText(), Json.SerializerOptions); + if (item is null) + { + throw new JsonException($"BinaryLane response did not contain a usable '{propertyName}' value."); + } + + return item; + } + + /// Attempts to deserialize a value only when the named envelope property is present. + protected bool TryDeserializeEnvelope(JsonElement document, string propertyName, out T? item) + { + item = default; + if (document.ValueKind != JsonValueKind.Object || !document.TryGetProperty(propertyName, out var payload)) + { + return false; + } + + item = JsonSerializer.Deserialize(payload.GetRawText(), Json.SerializerOptions); + return item is not null; + } + + /// Deserializes a standard BinaryLane list response. + protected Page DeserializePage(JsonElement document, string propertyName) + { + if (document.ValueKind != JsonValueKind.Object || + !document.TryGetProperty(propertyName, out var collection) || + collection.ValueKind != JsonValueKind.Array) + { + throw new JsonException($"BinaryLane response did not contain the expected '{propertyName}' array."); + } + + var items = new List(); + foreach (var value in collection.EnumerateArray()) + { + var item = JsonSerializer.Deserialize(value.GetRawText(), Json.SerializerOptions); + if (item is null) + { + throw new JsonException( + $"BinaryLane response contained an unusable item in the '{propertyName}' array."); + } + + items.Add(item); + } + + var total = items.Count; + if (document.TryGetProperty("meta", out var meta) && + meta.ValueKind == JsonValueKind.Object && + meta.TryGetProperty("total", out var totalValue) && + totalValue.TryGetInt32(out var providedTotal)) + { + total = providedTotal; + } + + Uri? first = null; + Uri? previous = null; + Uri? next = null; + Uri? last = null; + if (document.TryGetProperty("links", out var links) && + links.ValueKind == JsonValueKind.Object && + links.TryGetProperty("pages", out var pages) && + pages.ValueKind == JsonValueKind.Object) + { + first = ReadUri(pages, "first"); + previous = ReadUri(pages, "prev"); + next = ReadUri(pages, "next"); + last = ReadUri(pages, "last"); + } + + return new Page(items, total, first, previous, next, last); + } + + private static Uri? ReadUri(JsonElement element, string name) + { + if (!element.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.String) + { + return null; + } + + return Uri.TryCreate(value.GetString(), UriKind.RelativeOrAbsolute, out var uri) ? uri : null; + } + + /// Escapes an arbitrary API path segment. + protected static string EscapePathSegment(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("A path segment is required.", nameof(value)); + } + + return Uri.EscapeDataString(value); + } + + private static Dictionary? MergeQuery( + IReadOnlyDictionary? primary, + IReadOnlyDictionary? secondary) + { + if (primary is null && secondary is null) + { + return null; + } + + var result = new Dictionary(); + if (primary is not null) + { + foreach (var pair in primary) + { + result[pair.Key] = pair.Value; + } + } + + if (secondary is not null) + { + foreach (var pair in secondary) + { + result[pair.Key] = pair.Value; + } + } + + return result; + } +} diff --git a/src/BinaryLane.Api/Resources/ResourceClients.Part1.cs b/src/BinaryLane.Api/Resources/ResourceClients.Part1.cs new file mode 100644 index 0000000..f7809e0 --- /dev/null +++ b/src/BinaryLane.Api/Resources/ResourceClients.Part1.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Models; +using BinaryLane.Api.V2.Pagination; + +namespace BinaryLane.Api.V2.Resources; + +/// +public sealed class AccountApi : BinaryLaneResourceBase, IAccountApi +{ + /// Initializes the account resource. + public AccountApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task GetAsync(CancellationToken cancellationToken = default) => + GetItemAsync("v2/account", "account", cancellationToken); + + /// + public Task GetRawAsync(CancellationToken cancellationToken = default) => + base.GetRawAsync("v2/account", null, cancellationToken); +} + +/// +public sealed class ActionsApi : BinaryLaneResourceBase, IActionsApi +{ + /// Initializes the actions resource. + public ActionsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task GetAsync(long actionId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/actions/{actionId}", "action", cancellationToken); + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/actions", "actions", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/actions", "actions", page, null, cancellationToken); + + /// + public Task ProceedAsync(long actionId, ProceedRequest request, CancellationToken cancellationToken = default) => + SendNoContentAsync(HttpMethod.Post, $"v2/actions/{actionId}/proceed", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); + + /// + public async Task WaitForCompletionAsync( + long actionId, + ActionWaitOptions? options = null, + CancellationToken cancellationToken = default) + { + options ??= new ActionWaitOptions(); + options.Validate(); + var startedAt = DateTimeOffset.UtcNow; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + var action = await GetAsync(actionId, cancellationToken).ConfigureAwait(false); + if (IsTerminal(action.Status)) + { + return action; + } + + if (DateTimeOffset.UtcNow - startedAt >= options.Timeout) + { + throw new TimeoutException( + $"Timed out waiting for BinaryLane action {actionId} after {options.Timeout}."); + } + + await Task.Delay(options.PollInterval, cancellationToken).ConfigureAwait(false); + } + } + + private static bool IsTerminal(string status) => + string.Equals(status, "completed", StringComparison.OrdinalIgnoreCase) || + string.Equals(status, "errored", StringComparison.OrdinalIgnoreCase) || + string.Equals(status, "failed", StringComparison.OrdinalIgnoreCase) || + string.Equals(status, "cancelled", StringComparison.OrdinalIgnoreCase) || + string.Equals(status, "canceled", StringComparison.OrdinalIgnoreCase); +} + +/// +public sealed class BillingApi : BinaryLaneResourceBase, IBillingApi +{ + /// Initializes the billing resource. + public BillingApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task GetBalanceAsync(CancellationToken cancellationToken = default) => + GetItemAsync("v2/customers/my/balance", "balance", cancellationToken); + + /// + public Task GetInvoiceAsync(long invoiceId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/customers/my/invoices/{invoiceId}", "invoice", cancellationToken); + + /// + public Task> ListInvoicesAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/customers/my/invoices", "invoices", page, null, cancellationToken); + + /// + public Task> ListUnpaidPaymentFailedInvoicesAsync(CancellationToken cancellationToken = default) => + GetItemAsync>("v2/customers/my/unpaid-payment-failed-invoices", "invoices", cancellationToken); +} + +/// +public sealed class DataUsageApi : BinaryLaneResourceBase, IDataUsageApi +{ + /// Initializes the data-usage resource. + public DataUsageApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/data_usages/current", "data_usages", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/data_usages/current", "data_usages", page, null, cancellationToken); + + /// + public Task GetCurrentAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/data_usages/{serverId}/current", "data_usage", cancellationToken); +} + +/// +public sealed class DomainsApi : BinaryLaneResourceBase, IDomainsApi +{ + /// Initializes the domains resource. + public DomainsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> GetNameserversAsync(CancellationToken cancellationToken = default) => + GetItemAsync>("v2/domains/nameservers", "local_nameservers", cancellationToken); + + /// + public Task RefreshNameserverCacheAsync(DomainRefreshRequest request, CancellationToken cancellationToken = default) => + SendNoContentAsync(HttpMethod.Post, "v2/domains/refresh_nameserver_cache", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/domains", "domains", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/domains", "domains", page, null, cancellationToken); + + /// + public Task CreateAsync(DomainRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, "v2/domains", request ?? throw new ArgumentNullException(nameof(request)), "domain", cancellationToken); + + /// + public Task GetAsync(string domainName, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/domains/{EscapePathSegment(domainName)}", "domain", cancellationToken); + + /// + public Task DeleteAsync(string domainName, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/domains/{EscapePathSegment(domainName)}", null, null, cancellationToken); + + /// + public Task> ListRecordsAsync(string domainName, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/domains/{EscapePathSegment(domainName)}/records", "domain_records", page, null, cancellationToken); + + /// + public Task CreateRecordAsync(string domainName, DomainRecordRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, $"v2/domains/{EscapePathSegment(domainName)}/records", request ?? throw new ArgumentNullException(nameof(request)), "domain_record", cancellationToken); + + /// + public Task GetRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", "domain_record", cancellationToken); + + /// + public Task UpdateRecordAsync(string domainName, long recordId, UpdateDomainRecordRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Put, $"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", request ?? throw new ArgumentNullException(nameof(request)), "domain_record", cancellationToken); + + /// + public Task DeleteRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", null, null, cancellationToken); + +} + +/// +public sealed class ImagesApi : BinaryLaneResourceBase, IImagesApi +{ + /// Initializes the images resource. + public ImagesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/images", "images", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/images", "images", page, null, cancellationToken); + + /// + public Task GetAsync(string imageIdOrSlug, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/images/{EscapePathSegment(imageIdOrSlug)}", "image", cancellationToken); + + /// + public Task UpdateAsync(long imageId, ImageRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Put, $"v2/images/{imageId}", request ?? throw new ArgumentNullException(nameof(request)), "image", cancellationToken); + + /// + public Task GetDownloadAsync(long imageId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/images/{imageId}/download", "link", cancellationToken); +} + +/// +public sealed class SshKeysApi : BinaryLaneResourceBase, ISshKeysApi +{ + /// Initializes the SSH-key resource. + public SshKeysApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/account/keys", "ssh_keys", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/account/keys", "ssh_keys", page, null, cancellationToken); + + /// + public Task GetAsync(long keyId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/account/keys/{keyId}", "ssh_key", cancellationToken); + + /// + public Task CreateAsync(SshKeyRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, "v2/account/keys", request ?? throw new ArgumentNullException(nameof(request)), "ssh_key", cancellationToken); + + /// + public Task UpdateAsync(long keyId, UpdateSshKeyRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Put, $"v2/account/keys/{keyId}", request ?? throw new ArgumentNullException(nameof(request)), "ssh_key", cancellationToken); + + /// + public Task DeleteAsync(long keyId, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/account/keys/{keyId}", null, null, cancellationToken); +} + +/// +public sealed class LoadBalancersApi : BinaryLaneResourceBase, ILoadBalancersApi +{ + /// Initializes the load-balancer resource. + public LoadBalancersApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/load_balancers", "load_balancers", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/load_balancers", "load_balancers", page, null, cancellationToken); + + /// + public Task GetAsync(long loadBalancerId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/load_balancers/{loadBalancerId}", "load_balancer", cancellationToken); + + /// + public Task CreateAsync(CreateLoadBalancerRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, "v2/load_balancers", request ?? throw new ArgumentNullException(nameof(request)), "load_balancer", cancellationToken); + + /// + public Task UpdateAsync(long loadBalancerId, UpdateLoadBalancerRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Put, $"v2/load_balancers/{loadBalancerId}", request ?? throw new ArgumentNullException(nameof(request)), "load_balancer", cancellationToken); + + /// + public Task DeleteAsync(long loadBalancerId, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/load_balancers/{loadBalancerId}", null, null, cancellationToken); + + /// + public Task> GetAvailabilityAsync(CancellationToken cancellationToken = default) => + GetItemAsync>( + "v2/load_balancers/availability", + "load_balancer_availability_options", + cancellationToken); + + /// + public Task AddServersAsync(long loadBalancerId, ServerIdsRequest request, CancellationToken cancellationToken = default) => + SendNoContentAsync(HttpMethod.Post, $"v2/load_balancers/{loadBalancerId}/servers", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); + + /// + public Task RemoveServersAsync(long loadBalancerId, ServerIdsRequest request, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/load_balancers/{loadBalancerId}/servers", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); + + /// + public Task AddForwardingRulesAsync(long loadBalancerId, ForwardingRulesRequest request, CancellationToken cancellationToken = default) => + SendNoContentAsync(HttpMethod.Post, $"v2/load_balancers/{loadBalancerId}/forwarding_rules", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); + + /// + public Task RemoveForwardingRulesAsync(long loadBalancerId, ForwardingRulesRequest request, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/load_balancers/{loadBalancerId}/forwarding_rules", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken); +} + +/// +public sealed class RegionsApi : BinaryLaneResourceBase, IRegionsApi +{ + /// Initializes the regions resource. + public RegionsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/regions", "regions", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/regions", "regions", page, null, cancellationToken); +} diff --git a/src/BinaryLane.Api/Resources/ResourceClients.Part2.cs b/src/BinaryLane.Api/Resources/ResourceClients.Part2.cs new file mode 100644 index 0000000..d2e8c46 --- /dev/null +++ b/src/BinaryLane.Api/Resources/ResourceClients.Part2.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Models; +using BinaryLane.Api.V2.Pagination; + +namespace BinaryLane.Api.V2.Resources; + +/// +public sealed class ReverseNamesApi : BinaryLaneResourceBase, IReverseNamesApi +{ + /// Initializes the reverse-names resource. + public ReverseNamesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/reverse_names/ipv6", "reverse_nameservers", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/reverse_names/ipv6", "reverse_nameservers", page, null, cancellationToken); + + /// + public async Task UpdateIpv6Async(ReverseNameserversRequest request, CancellationToken cancellationToken = default) + { + var response = await SendResponseAsync( + HttpMethod.Post, + "v2/reverse_names/ipv6", + request ?? throw new ArgumentNullException(nameof(request)), + null, + cancellationToken).ConfigureAwait(false); + TryDeserializeEnvelope(response.Body, "action", out var action); + return new ActionSubmission(response.StatusCode, action, response.Body); + } +} + +/// +public sealed class SampleSetsApi : BinaryLaneResourceBase, ISampleSetsApi +{ + /// Initializes the monitoring sample-set resource. + public SampleSetsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public async Task GetLatestAsync(long serverId, string? dataInterval = null, CancellationToken cancellationToken = default) + { + IReadOnlyDictionary? query = string.IsNullOrWhiteSpace(dataInterval) + ? null + : new Dictionary { ["data_interval"] = dataInterval }; + var response = await GetRawAsync($"v2/samplesets/{serverId}/latest", query, cancellationToken).ConfigureAwait(false); + return TryDeserializeEnvelope(response, "sample_set", out var sampleSet) ? sampleSet : null; + } + + /// + public Task> ListAsync( + long serverId, + PageRequest? page = null, + string? dataInterval = null, + DateTimeOffset? start = null, + DateTimeOffset? endAt = null, + CancellationToken cancellationToken = default) + { + var query = new Dictionary + { + ["data_interval"] = dataInterval, + ["start"] = start, + ["end"] = endAt, + }; + return GetPageAsync($"v2/samplesets/{serverId}", "sample_sets", page, query, cancellationToken); + } +} + +/// +public sealed class ServersApi : BinaryLaneResourceBase, IServersApi +{ + /// Initializes the servers resource. + public ServersApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task GetAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/servers/{serverId}", "server", cancellationToken); + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/servers", "servers", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/servers", "servers", page, null, cancellationToken); + + /// + public Task CreateAsync(CreateServerRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, "v2/servers", request ?? throw new ArgumentNullException(nameof(request)), "server", cancellationToken); + + /// + public Task DeleteAsync(long serverId, string? reason = null, CancellationToken cancellationToken = default) => + Executor.DeleteAsync( + $"v2/servers/{serverId}", + null, + string.IsNullOrWhiteSpace(reason) ? null : new Dictionary { ["reason"] = reason }, + cancellationToken); + + /// + public Task> ListActionsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/servers/{serverId}/actions", "actions", page, null, cancellationToken); + + /// + public Task GetActionAsync(long serverId, long actionId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/servers/{serverId}/actions/{actionId}", "action", cancellationToken); + + /// + public async Task SubmitActionAsync(long serverId, ServerAction request, CancellationToken cancellationToken = default) + { +#if NET8_0_OR_GREATER + ArgumentNullException.ThrowIfNull(request); +#else + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } +#endif + + var response = await SendResponseAsync( + HttpMethod.Post, + $"v2/servers/{serverId}/actions", + request, + null, + cancellationToken).ConfigureAwait(false); + + TryDeserializeEnvelope(response.Body, "action", out var action); + return new ActionSubmission(response.StatusCode, action, response.Body); + } + + /// + public Task> GetAdvancedFirewallRulesAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync>( + $"v2/servers/{serverId}/advanced_firewall_rules", + "firewall_rules", + cancellationToken); + + /// + public Task GetAvailableAdvancedFeaturesAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync( + $"v2/servers/{serverId}/available_advanced_features", + "available_advanced_server_features", + cancellationToken); + + /// + public Task> ListBackupsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/servers/{serverId}/backups", "backups", page, null, cancellationToken); + + /// + public Task CreateBackupAsync(long serverId, UploadImageRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, $"v2/servers/{serverId}/backups", request ?? throw new ArgumentNullException(nameof(request)), "action", cancellationToken); + + /// + public Task> ListKernelsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/servers/{serverId}/kernels", "kernels", page, null, cancellationToken); + + /// + public Task> ListSnapshotsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/servers/{serverId}/snapshots", "snapshots", page, null, cancellationToken); + + /// + public Task> GetThresholdAlertsAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync>($"v2/servers/{serverId}/threshold_alerts", "threshold_alerts", cancellationToken); + + /// + public Task> GetThresholdAlertServerIdsAsync(CancellationToken cancellationToken = default) => + GetItemAsync>("v2/servers/threshold_alerts", "server_ids", cancellationToken); + + /// + public Task> ListSoftwareAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/servers/{serverId}/software", "licensed_software", page, null, cancellationToken); + + /// + public Task GetUserDataAsync(long serverId, CancellationToken cancellationToken = default) => + GetDirectItemAsync($"v2/servers/{serverId}/user_data", cancellationToken); + + /// + public Task GetConsoleAsync(long serverId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/servers/{serverId}/console", "console", cancellationToken); +} + +/// +public sealed class SizesApi : BinaryLaneResourceBase, ISizesApi +{ + /// Initializes the sizes resource. + public SizesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/sizes", "sizes", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/sizes", "sizes", page, null, cancellationToken); +} + +/// +public sealed class SoftwareApi : BinaryLaneResourceBase, ISoftwareApi +{ + /// Initializes the software resource. + public SoftwareApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/software", "software", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/software", "software", page, null, cancellationToken); + + /// + public Task GetAsync(long softwareId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/software/{softwareId}", "software", cancellationToken); + + /// + public Task> ListOperatingSystemAsync(string operatingSystemIdOrSlug, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync( + $"v2/software/operating_system/{EscapePathSegment(operatingSystemIdOrSlug)}", + "software", + page, + null, + cancellationToken); +} + +/// +public sealed class VpcsApi : BinaryLaneResourceBase, IVpcsApi +{ + /// Initializes the VPC resource. + public VpcsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json) + : base(executor, json) + { + } + + /// + public Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync("v2/vpcs", "vpcs", page, null, cancellationToken); + + /// + public IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) => + GetAllPagesAsync("v2/vpcs", "vpcs", page, null, cancellationToken); + + /// + public Task GetAsync(long vpcId, CancellationToken cancellationToken = default) => + GetItemAsync($"v2/vpcs/{vpcId}", "vpc", cancellationToken); + + /// + public Task CreateAsync(CreateVpcRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Post, "v2/vpcs", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken); + + /// + public Task ReplaceAsync(long vpcId, UpdateVpcRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(HttpMethod.Put, $"v2/vpcs/{vpcId}", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken); + + /// + public Task UpdateAsync(long vpcId, PatchVpcRequest request, CancellationToken cancellationToken = default) => + SendItemAsync(new HttpMethod("PATCH"), $"v2/vpcs/{vpcId}", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken); + + /// + public Task DeleteAsync(long vpcId, CancellationToken cancellationToken = default) => + Executor.DeleteAsync($"v2/vpcs/{vpcId}", null, null, cancellationToken); + + /// + public Task> ListMembersAsync(long vpcId, PageRequest? page = null, CancellationToken cancellationToken = default) => + GetPageAsync($"v2/vpcs/{vpcId}/members", "members", page, null, cancellationToken); +} diff --git a/src/BinaryLane.Api/Resources/ResourceInterfaces.cs b/src/BinaryLane.Api/Resources/ResourceInterfaces.cs new file mode 100644 index 0000000..254a2e9 --- /dev/null +++ b/src/BinaryLane.Api/Resources/ResourceInterfaces.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Models; +using BinaryLane.Api.V2.Pagination; + +namespace BinaryLane.Api.V2.Resources; + +/// Reads information about the authenticated account. +public interface IAccountApi +{ + /// Gets the current account. + Task GetAsync(CancellationToken cancellationToken = default); + + /// Gets the untyped account response for forward compatibility. + Task GetRawAsync(CancellationToken cancellationToken = default); +} + +/// Reads and waits for asynchronous account and server actions. +public interface IActionsApi +{ + /// Gets an action by its global action ID. + Task GetAsync(long actionId, CancellationToken cancellationToken = default); + + /// Lists global actions. + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + + /// Streams all global actions. + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + + /// Supplies a requested user interaction for an action. + Task ProceedAsync(long actionId, ProceedRequest request, CancellationToken cancellationToken = default); + + /// Polls an action until BinaryLane reports a terminal state. + Task WaitForCompletionAsync( + long actionId, + ActionWaitOptions? options = null, + CancellationToken cancellationToken = default); +} + +/// Reads account billing and invoice information. +public interface IBillingApi +{ + Task GetBalanceAsync(CancellationToken cancellationToken = default); + Task GetInvoiceAsync(long invoiceId, CancellationToken cancellationToken = default); + Task> ListInvoicesAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task> ListUnpaidPaymentFailedInvoicesAsync(CancellationToken cancellationToken = default); +} + +/// Reads data-transfer usage data. +public interface IDataUsageApi +{ + Task> ListCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetCurrentAsync(long serverId, CancellationToken cancellationToken = default); +} + +/// Manages DNS domains and DNS records. +public interface IDomainsApi +{ + Task> GetNameserversAsync(CancellationToken cancellationToken = default); + Task RefreshNameserverCacheAsync(DomainRefreshRequest request, CancellationToken cancellationToken = default); + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task CreateAsync(DomainRequest request, CancellationToken cancellationToken = default); + Task GetAsync(string domainName, CancellationToken cancellationToken = default); + Task DeleteAsync(string domainName, CancellationToken cancellationToken = default); + Task> ListRecordsAsync(string domainName, PageRequest? page = null, CancellationToken cancellationToken = default); + Task CreateRecordAsync(string domainName, DomainRecordRequest request, CancellationToken cancellationToken = default); + Task GetRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default); + Task UpdateRecordAsync(string domainName, long recordId, UpdateDomainRecordRequest request, CancellationToken cancellationToken = default); + Task DeleteRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default); +} + +/// Reads and manages account images and backups represented as images. +public interface IImagesApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetAsync(string imageIdOrSlug, CancellationToken cancellationToken = default); + Task UpdateAsync(long imageId, ImageRequest request, CancellationToken cancellationToken = default); + Task GetDownloadAsync(long imageId, CancellationToken cancellationToken = default); +} + +/// Manages account SSH keys. +public interface ISshKeysApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetAsync(long keyId, CancellationToken cancellationToken = default); + Task CreateAsync(SshKeyRequest request, CancellationToken cancellationToken = default); + Task UpdateAsync(long keyId, UpdateSshKeyRequest request, CancellationToken cancellationToken = default); + Task DeleteAsync(long keyId, CancellationToken cancellationToken = default); +} + +/// Manages load balancers, their members, and forwarding rules. +public interface ILoadBalancersApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetAsync(long loadBalancerId, CancellationToken cancellationToken = default); + Task CreateAsync(CreateLoadBalancerRequest request, CancellationToken cancellationToken = default); + Task UpdateAsync(long loadBalancerId, UpdateLoadBalancerRequest request, CancellationToken cancellationToken = default); + Task DeleteAsync(long loadBalancerId, CancellationToken cancellationToken = default); + Task> GetAvailabilityAsync(CancellationToken cancellationToken = default); + Task AddServersAsync(long loadBalancerId, ServerIdsRequest request, CancellationToken cancellationToken = default); + Task RemoveServersAsync(long loadBalancerId, ServerIdsRequest request, CancellationToken cancellationToken = default); + Task AddForwardingRulesAsync(long loadBalancerId, ForwardingRulesRequest request, CancellationToken cancellationToken = default); + Task RemoveForwardingRulesAsync(long loadBalancerId, ForwardingRulesRequest request, CancellationToken cancellationToken = default); +} + +/// Lists BinaryLane regions. +public interface IRegionsApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); +} + +/// Manages IPv6 reverse-name configuration. +public interface IReverseNamesApi +{ + Task> ListIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default); + Task UpdateIpv6Async(ReverseNameserversRequest request, CancellationToken cancellationToken = default); +} + +/// Reads monitoring samples for servers. +public interface ISampleSetsApi +{ + Task GetLatestAsync(long serverId, string? dataInterval = null, CancellationToken cancellationToken = default); + Task> ListAsync(long serverId, PageRequest? page = null, string? dataInterval = null, DateTimeOffset? start = null, DateTimeOffset? endAt = null, CancellationToken cancellationToken = default); +} + +/// Manages BinaryLane servers and their server-scoped resources. +public interface IServersApi +{ + Task GetAsync(long serverId, CancellationToken cancellationToken = default); + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task CreateAsync(CreateServerRequest request, CancellationToken cancellationToken = default); + Task DeleteAsync(long serverId, string? reason = null, CancellationToken cancellationToken = default); + Task> ListActionsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetActionAsync(long serverId, long actionId, CancellationToken cancellationToken = default); + Task SubmitActionAsync(long serverId, ServerAction request, CancellationToken cancellationToken = default); + Task> GetAdvancedFirewallRulesAsync(long serverId, CancellationToken cancellationToken = default); + Task GetAvailableAdvancedFeaturesAsync(long serverId, CancellationToken cancellationToken = default); + Task> ListBackupsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default); + Task CreateBackupAsync(long serverId, UploadImageRequest request, CancellationToken cancellationToken = default); + Task> ListKernelsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default); + Task> ListSnapshotsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default); + Task> GetThresholdAlertsAsync(long serverId, CancellationToken cancellationToken = default); + Task> GetThresholdAlertServerIdsAsync(CancellationToken cancellationToken = default); + Task> ListSoftwareAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetUserDataAsync(long serverId, CancellationToken cancellationToken = default); + Task GetConsoleAsync(long serverId, CancellationToken cancellationToken = default); +} + +/// Lists available server sizes. +public interface ISizesApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); +} + +/// Lists available licensed software and operating-system software. +public interface ISoftwareApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetAsync(long softwareId, CancellationToken cancellationToken = default); + Task> ListOperatingSystemAsync(string operatingSystemIdOrSlug, PageRequest? page = null, CancellationToken cancellationToken = default); +} + +/// Manages private virtual networks and their members. +public interface IVpcsApi +{ + Task> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + IAsyncEnumerable ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default); + Task GetAsync(long vpcId, CancellationToken cancellationToken = default); + Task CreateAsync(CreateVpcRequest request, CancellationToken cancellationToken = default); + Task ReplaceAsync(long vpcId, UpdateVpcRequest request, CancellationToken cancellationToken = default); + Task UpdateAsync(long vpcId, PatchVpcRequest request, CancellationToken cancellationToken = default); + Task DeleteAsync(long vpcId, CancellationToken cancellationToken = default); + Task> ListMembersAsync(long vpcId, PageRequest? page = null, CancellationToken cancellationToken = default); +} diff --git a/src/BinaryLane.Api/icon.png b/src/BinaryLane.Api/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..12c644c9a7a9a24bf6f13d0fe6620764d1e3e451 GIT binary patch literal 12048 zcmai)Wl$Vl^yX)UBZ>g%ONw z21GoC(l2%+@ghh@AeUe#6}gDvA|t?~Ac)Zw&=sU8)(PG{P?O%=-D?KKGFuuQ?Cxe} zj*Vssa^-ZqpneId#8-_ZhHWret1N=vDSW>$76H-?WPNpW#Lwg>Zg|=LCi5lXR`fGw zA4Y_`f)L2@*Sq5ayAYSVNUx95x%W%kLHqpRxDV4nAupH4PW~}Nwx|<}fK=vCG%E*^ z`J_VpHe(O z9X!6H2PM)(1XZ%323NvCJ;NX5N5UL<9+syp>=)e+n>^zhw)hsPsfv|;qvX(!^hd?k z(%I-&Q8>}<&W~)sInmfG0pF*y*OK)q)dt)_)3!aY*uz`z)yc6N$Mwd9Bz1C1d{F5T zT6FF9*x944c0pkM5jzFo5rfSLm5Im9q{Ic1T^5tj?kv!UyiDk{0dpCt-e~Rey^Gto zjgl{D*1Qc@`u{R|SeoLG>nW{ev4Qv=y7>5j2>0|X79Q}|*`u#16rrvQyhOLILF8Sh zLnv5*1;z0@(`@2XE0P@hJ}To(-ifu~@gK;YKpaNiA29&JOh<$d?^aUuxzk_eV=&1F zU7g^)MYZBw_t;|(EYmBn{+fUI)qNwHG8O$R_o@)-=7q7>;jV``i&vb@u<49N8hW4? zQ_R=^B$)wp!Q|~85TOB|M!)fQotQa3`{;C9-!QG{b?=L=aAHkBcPG?Fi3LR^A~yQe zuyOmrrU+NT9+90;VyW(?`wTJ|WIi@(mpa!-tI!AY^xfHK(lW?oBdzpv zg6Xq<%)ixg-oI&!)FO)@vK)8*FRKQaL#=DT+}zJS^S^B`XF^z>uz17zPm-5xDKI#a z#zlN9MZ#^i6Ynt-t&&uK<6r(skb_2)`R3c43%V`-1U-~xQgUB5D zpf6Q*)fZm+i(?9PR_J9RiBV$vWmy!Laem9pqIpbK!!bMAtmLs*8X86?^52pcf^2^u zScPd&uD@CJPpr>DcFk3du<6R2s!ftd9ed^BBpAg3rX7#-{+ z*}jO5XOcHK-%>ucN`r@oC+GF6DyZ*aZIw$0)8^L@jqDdLQj2Fjp@`whRB#A2%0)a_;*3W!?kBM zYlK8B6`j&&pGL(e$;=WMZj?t_2|y#L46mMArG3~{M;?~6tcDTo#y91Iv4t^TwzDYP ze%GaH7B?0_u4tUifiYcy*Xz^&{P)M?`5w(sjtky)Kvvz zd?9#B7X80&gHp&hA9FKHup;u~%SIbI?{6lJe4>ip&~n=8H>F&j(pvrV&CU^yaf`Dk*=z(S~#X+Z~(AN0Kq}K>==~Pt>%W*t71l*h|QrlRoLO59K3o8 z>$_f8HsHF4?DN-~H4u6IYb*P=av%mcs%B@#NSD^!>4GV)$Q`hbI!Jv$pRFu;bxa6P z&;t1qIr2Wi{!((v>+?M&up1U^nI?cbFVcS1?`tdy12MD^cLl;SIpvyj=Wck?+hpwd8`j?sLvYFDH6I|F^Y+FU1w_9GyYpDJGPGbNyU6)OjalYU$4%1hEcfhnsqvJEJ zciic1DIU9r&&4z`i&5RO}a$_s~x`MvIMG zIpf4bGPX9N=c>J~nT^}9ISpm%#Xaf0TE|h&{((qis=Yl~&*M-huW|MRC%2EjEs4YW z`fXZxhlk^AnbBCZ9eg;r2!@}-q>L`Z93Ijuc4F%Q5U>358$#?u#OYzwr_;`#TGdK& z=tn^=LJYu;BTnsacLXu(l}uUN!JqI03)`j@ zEFiN>hp{8%kQ_A;+s~ih56*eiq9NzSH|}RkImf^lbpuAsYPr>7piY&l26Dr~*jD_E zTDcwfBx4;F_1-o4*7CS2+XHm`#g&b$)i0fu>cvymjPdd3*}8lO$TTO%B4#JMZ()uo zV8di_<9Z6X9LN^ar<2Z~()bWU<}}pg%E?U<#X`Tl?nyHCa+0i#IB(7EF2Vw^=Dy?T zsKiw{cbOj}=}Hl)1MLbEGpvb94UW_a49de=(E%+#yE5{c!XDcD#-Tm?zh?AVVn#{HsD*jwVW2x>8h*DvY8je2WZv{kwcL z=O!v-?C*P1+)cI8GsKVnPFPuGdV4E!p{2rzfA(Wb z3zNsiRr-Yy`gpBU7I{(NxM4~)DLO6P%tXitmAB^j!?O#&C1uA8j1K!1`pBSqYGJQ-RGU}BU z4Y~@z;o*W#Sn@JKc!o7}2~)?BKSN1Mv(`w&~5 z8pMKH(z>m4ZKOiYtjQ7rB@MU_@Q~FYT{k@Lyo=d7{F0ZoewRwv}N)unF9Q#Y6w}T`4+j(H`-yv7Egt;ctdM;A5sZR%E6Bk&sxme@@{o9^XND|^#iA0El z{>Dsoc^5I$QYhFAH8hU;JzKPw$Gd#p&lTsC#jfmW=*R2mAr8%WU))jYoE z<<)wKFhGzEPaz%+LO~lUmWwYHT*>u49u*46EEVO+<;9x9wPsj1;lv)%x()g$v@uhU z7a=ks9Pj?FZan9%(+OMc-Pu+ zl-=~#&AmKmG2?Ufz``F}zd~bnt^7`&O{Q+Ei|l0pH^7D@19Yz@(6*>xkuqJ8ja(|C6C2T^jnadtKYoV*Gx+Q4T?iro+NUGyUo z6R7PX{WP{5a*a+}o;cfQ?~Ik&05MtKum?{OF_-*Qx@rmpL+Eyi&zSFqzQaNzaPWmw zP75hV6sp^v2r*DB{lAvIrpFWyqh(dq8E3OC)7X6ZNRKr_KS^^emJ~Y4UtpTE90N5k+L`&o0M1FA*@e@$sykcj0B%N%Cf@v9 zCwPtO1a7u(ls5L_n1J(BHGwEK`h|v~aeZme6>Te8IqAS0=OQcq$PGe*u~%zC zyj%}eAXTbOvS_nedRDFWn9c3yWPA76gX>PnY$-3??rm*Ns3zpZ`$W6g-~IyD#0-n< z!W-HK3*m6ziBi2gC5S-&8swCCo(ZVZlSU-6%~Ic!NNhK;7`lcr%KRuCR6`GqF&q#8 ziRBIi(}igULDl!0(4vE2MX*3*q^0N}GKT9V`_$FZYBIQinP^I)g8(~dRe%{#346%; zBrNb-R$8O;LNY*t|49e^itAmqAr{;W-b~VOnr_n3&0182!6ywV5OMLGd__Q>^r15I zCAN~g1RF$h$}|j^T2!Sr5oqLkKS_j8gpuvLh6TBzFC9$kHaWGBOpL5h^nR}we2vi; zqr@M-kA_H&+KAVIU{;md@1bv%{$DJ>I;C{+o8JywnirV|t1@siUdh9I&6;#H{&z5R zg6IUob1asajR7-goa#~m9$9jCd)ziQU~I~vyIl}=*IXvcowz7Z+62A{W|D;D?hgtf zRAj0;N&3tsiUficA}6<9rI9-zW}TtW*OSq@jy7JvTPjeGL#aL6d7psQ%dl`r#|8?R zmb)Ubq3^OCtu&O{R@@53L`5se`GiF{j@L!g1}*F~kSA~-=LhINWR)FmJ)(>(FbCL( zq#5rJs}xNVQ73Z$Ob(j1t&53JsU?E7!xl2kHf5e`ZKgU+{!RNV!^(Lvb7u`#kie`d z-}JMyk~$dt*@FMBG`nv+VV^E>=5=E6Z^zoA1HZ%}Ed_~?gba_WaKaQ3t}_oY2)p?B z5+`~dX!Z?Uz@0b)5=1p|AEG#6xfD_VaOWWJMm?zJw(K; z%H669c}vr1$KQDQ9g!kOLI?$7V}`hyR~xI}Z-Gxka1v9JwzkoOF_0H>mH=TzA(bLL zBzUELC|oqQL<3CQNUKZd!P0-{`I8l%G*}rx{kSz9H|L2C_SU~R7Eiimh`8Je%wsl0 zUm`6};Xlenr`W@!MXoTI$mz|DVJ1g^zSF!%p0>d!Wlq{!a3rQi&8M;neIQ>VO#D(` zNU_*{&n7SRXR8n1BM5n(XJHJ{Fts*^3XO<1nn?sMrnGvH1$;2r%SjoYm@{bq;7os2 zS(ofDfp%;fT!JInTYxr;G2hgOaQ3(uqY)Z-$P{@BdMIbE#{}*Zz{ev(IKm&ciDmc` znztTzF4H#RDgA_zj@r1q_kG}78zZXg#vdsgJEh&BZG3=4f*ZFVireD!llYM=^E|$=xsm*IiqcF_nP6p~41tp9V!?C@mw)%03ZYDd=(oAxZO8z5T+q2#J-$ zM2^6B6f)23WB3HT`nF3Hz$c@&$q!ein+4D+i1~QF*%JA| zcp8oP&j{AR+uh!1j3O!m|I6jl6{A`7<8iKf9$M()2gdJL-4bcNUa_SmoGI}pmtAmy zHy3%;HMl^q5?81@)t#<$kQngCMCF$@PoChtV7Bk9aQ*Mik~*OQO56vXlt7*1Dst3k zzCZDQ=;n;iN?3!2E7t^D{ZPy?qXg#i=^}3_mx`qMHEymy?$?4`P+C9%>ya&6ReTk zS2uH}hXk~jn8gDmJY#oq^{v0zA0pmTdj8S)M^H9vckS`Z_@~yzzbwHncspPCrdcC6 z?){H{02?EZb(Fci6m9x4()6BrIP{=CpAi>p;Q?X6Aw)glZ`un=&<*P~%$U3Go-}rG zRAjoXv10HBZH^%Xj&Z3Qa9V<8hIh*i|I5g)TB`RZs}B5X6|OLPhTTOXe3ivxVN3Sh zX)~~XsF~}nb5NZM_~ja$kJOw32VoGjdCOOJ@wAgDhLO`K#}5xr8vgjLN@uBar=Pgf zSG!x>#7p?5{HNhdH`y0Nm$gHJm2rQ}wDzVEaO_lff~3WwLnN$z+Y_7K7B4`g6!rr$ z!72se>PDa`ahIPo?h`?U$&pDZR(o;}xa`K+y~tz-Arn4lDm5`tA`CI=*PgR8e{-?>8I6>^82_KBNWvxfSgi^ns zE_bDfH1}_=ML?7T0MBJr5KHR|TY!*JbI`qFOP&ufIqU}sVMEA|oZw)4zMym5r~E%R z*d`PrZi5mk6tgk2VE|JY&jW`)CVr=C;n@cs6$f}nWcMNf?T61b%_Ay21kB>}YRW)j znRGbCBLxoj5Hen9Nd%T1m|`HCE@0o^AY~tEwGFuWs{Qy|t5*0>Qw~E~3_mRScQ_P~|#N0kSQ#RJ1M={6thxY~%FIT6x_C04UP^=eGqIA9k%p<R~l*LfHl0c_dJ%C=>#^YDu*{(fDeYOaS2K zE3z32Ut4ND-OA+n;6jCZQk3sy>$V*=2?s4QOT>IvC;b!I0{~Av-?b49QE7FM zXC>YR5?b(UGoTxB3hj3G{HM1IpCgIK8M$mWjVIM0CsEJpEGLwDM?zoW@m-RSEQ zGHZ@bCI5r+HB17J`j!RI&L#9vAnH`RG!d6bYd#!`zGRUrlDVF^*h)PZEDtxvaMn?j zIl2HlF-gE)keT)mAndD!64O1BhfMo>uO$K8^Z)7Vqj3s_M1BZ-GFnS&r^q!PxF8^} zG2EZN=glns*zpBn|3ors)E;^?wW?VZ0>Pr4UY}2MICy)=MLx)oKm&=^L(D&H61LZ- zm;418iCkoT0;*XP0qD&B6~jJE6+eFc@ck`c5mIo$=9`racmO&7M$Lk>)ADjI&8Ip3 z&t(cs2$CRwdqZ$HUsC^l(?V_zW)v-&0p}#6L>|OI97iJgO#pww-HG6?a1MOp2?LJG z4SpP9vuBKnp#ffH-a@-lj{Tit7uzfisY>K%$+k+SsB5y^N<#!J0xDJr|w;K z1oqVjyxS2bMiLC|OzNlpF{FpezE~Bu;xMDB5;Gi19g%Gyb34YT3adKrez@8)naUAWA3>d#4 zY+1(;xm9M3*HH6Pwggry&3|9Kpj6$6cKq;oQZ9msxrC|o$29iRGccY2%c_06gWMZn zcJQYo=@Vrgn^pBN7W`gfoq8m&R{k3vC7dKphF@~qG@)>oL|p*kt|=S@i9^;8WVhfd zvey(Es!Ysh*^WGCb@bcyc5~X$v)hL0aDGx-HaM`fPIbz0P=N6+wH<6<<-$~j**^p` zmj_{ssC<_Wup=r#^Ivi5h`;=UXw4)rMK;bWnCNl^XCahDl6Z~Hu6qAH4WbU7oX6_T zl1BqHWl#tS#E$GT`XN5FM#fWY=IB`kT+m(0WSPn>giMZ=Iy*UQU#{=)Y;+K1nr8_J zDj;UHu^RIcDJT`?prwf4mStW)961aNdp+$bgFkrfO!^9$KIOOBP}%?xS5RSS#y}2T zZXBfCdtSuA>G*!l_uoOhL&}SYPVHpW+JRnDYiukgDP`Up?|>NDO*oB(1c!YTVJ8vXwhhtc+Ly0D zw9MH{bzT=+beo?J;Q8ZBkdZ*68`pSMw6vInei4OZT@V+}{tJVt3O0C!kT778_V|)JeCfCDD72(N;X)^LT+1_!#ncdD2T{l$57< zAGd1r<8*$8K9`DqdH$S6thPmk!gfseRgOX`Jsg!p)gsz^Xi|y8x22YH!emH^`LnVW zfv9SF!4g%p?8sQt(}m;3eH&y5$5nyE5SC^KXi+9|3XA)T^;XvDPmce z0Pb`CiNS3antTy68P6I9NjvFc;k;3e#xIv&67WcWEg9W)O1IPLY+EK0G zSZvia`1$7R1T;ZT$Z0GpGb{`n$#hNv3S0fx<_GsW4Qoc&y(2vz(|ka=4#e zmFe!gM1w7LY&ike9|Y7d+%|T-Q?wB2saur(Nzfnq%~eh=_4G=I6}p~bVLZd{*fhd7 zqsk-3{>Z6mFpZ_*Etj3qo4k7!U#bC*w zL#Uj9@*Y#01MX|X?464|RBj~^bZQ0f<(Z`o{j_VlL@bl|JQ##G!US&o?zN3l;@dYl z(TnL;`sDx8flKyaExv{;FRG;`Jq*THOq$QDuT_59?=oHHhjPY0SteGX3;$gI#dr~H zhnSUZjd>YmFya=QvZtA*gGtBwQj`UD*1?LdMBzie)Gg(2$wZn~83HNDM2t9OBjVLi zo{HeL-IVtXXkR(q09j491O^QXm>CC7G6IKb9a34+Nd?5F#<_%?%OG|pQYL;M!aL*? zuY^o`Dmw1@-mwzHvGIt(ru%xt(XR{-WC$tOd973Ex72;SFP}_s5XtgZ?+{}NEy@&-_Drv; z_z{6_4MD!fpaIe7v_C>`wYG_&m|;GQJ5ClrVqE)sE=%8%rhfl?djS81d)b)-0?{D- zXH5v4+>cTFxCvH=ECv#yu)u#9w2Tw*-t^mI#L->BaM92cD>>>X$6WgX9ZNusHi2U) zT$M5du_yShRJ(w1_Q&ml<^_n=Idg&^6SO3cxwVHXGaw}Q-%tgdCLBY>e@?->%(KYn)xe+@4)V~G9U&VuH- z3T{a={)iBr{eP=v$uLxhz(G#L=BG>oz_3j4ub6&p5$dbu8!)>fharOjTzd~XMR|8H zdbbQby@wtzqcH0JuTzyLfa0Wj1Ny*OP!@9VGoi)KHm-?moZws-^pEAI^C-rwGeVZ% z4@le6bY!yEA%U2P`VmHO9NMF1K8OW76mPSW^IeaeP#l7l_(XsIXHmDc%*E#m2pK_J ze_n(PWGIFxNN9{|#CYwi9LSlpMaLKQFoYav$^WlDzV>m+epn@e67S?*;|%RDfgAqx zT9I@NoLKlb98{~ufr5VC&`k1{8JI6L=o?{<7DV7&RS!TH+&#+e;5Nc^|@$Pb;~=~Cg8rK@iG zOK*p+8#Skis2so_DyaTo4w$gDB9CB>{(lt5n8JUWfysy2{4ZSpn=0>8BolSemUNdg zV0U6NT~Xe59auftEY!zOP-@JjKiIJZUt)#s^C`Pd>Hffh^N4y^9E6MscKzW>tA|qC z#jq9oFmVvg;4nXV$`7!m-R=a_MVlCn`^a+wKq((Rz$Vl41+pmVo>a_ReB zpT(f>5}8T~WVfKb@k*Y`y%3##5b5fpU_W zT#SWCQC{*^8XCgg$azp2`8hS#SgXi_a*)Dh(l*)r^D!$Lw1ENG|QB1B#nQX8i zsTo@kZyxyq+;rnI3j=ESH{`dXGd8(h96r)Se&jyZh{R_JT3{!*e@{ECD5llMs4>>M03n&fvJ4b$SUWI zs%nR!shpJVsigtB>?06N=z(J+D3vwal*r_dni|SskfU7^7zi+dQ%Ds_Z# z^Ze{v)`e^Az*EF^Pw)$-x2hs;c=jts89!T_I3 zv>915_{z#~MN4wa`{nW6Q4dgJT_}0-K}`nqP@hRl7y-#-ZX#kNdb?pHgAf#Xz9lZ_nLj-S*yfNH+w2{v$u3EX-xW?*az3f|Tb4qU zl(W9>!MBpLJ((8bKyiHqEFT2CE<~OK%{7$H^SM9Xwg{d^MkxW-=5_yp76!CkmQ0I= z?4xs70sH0|3KfFDzw5s`0$0Z&E2@=Qq_pCF(7_zG3Q*+kB&I_Tv9vy&asE_dNINr^ zrTFDA?0MLh%*C%j+-c?jLiOFbB5XWkImad)qpH$?!Z8zA7y6B!vevbZheR(Uc?-zJ(M9_4gA3azqtdCt|X77@8^T7q^wxvJ~?A&7!9Bp$2+EOQ^r z%0Q>J1LO1Bg;Uo5k{YgzgjwlEu)3}5o+ed56rOIW;tP0}I))E43u*p-&-TD>=eB`J zIU&>MofyCA5tO(8tYg8`C;2;*j~8xlgw609B@rRYqE$ZBlsgt_QrJUus9%1tys7E` zdN0yB?5lsXh|`Yq$Z~o#FMu^WF?(T&87uQ{!PYm4Bx?V)rYfssTuUTnq5Fh-q9^IN zCs80F8Dkyp{A8`u>4B2w-X9%+=}iU~rMT6?s_gf%K#=>Ms>W!{xLa+5Q%rF?27}`b zFTv22-*qbUkz|E4l+H@v#G#!f#+@wYtatZz_?l1gdVBFowRzNvIhVEdFgj${8`1NU z`lOX|oI9^7Sjq&HCSzX~AmmPeid3r$O8E34#))M19LCW??lpcSP$Q{N)$FHNYDqhg#A2Zg`Y15a8H>mL1}q@r#? zKK5+T$wEJ4ZK7DVem`3J?>KlM8xgDHiDrTHu(N9mkSoDxgRe|RJKEDq^bGpGZ*;9~Zf|uSIRw{h~vaM2plsduu zAEA$iMpH_OdS_!%QAawsr*%vhATZUJiWw5G8fZCfCLOgTownBY6YwNs*W> zXS*kTDA-ZV+RV%R{Ji$NxE#{^C4kN*$+W;O&HFC~8amdo@8(oW=**4uJ?Ht3>kOY* zSV7|%$w0s*VbVwVuXHy!4`{J+B)ZS4DQ`sAGl!G1ux?#Kyk)#)z%e340+YKFIUL)# zr`x~FgE48%sCI<*nwZHdg5(-SLfaNFWpNkx6SLlv zdU4U@lfoCiFV~ymldM}kaTqyBzMw2!?UP%Y{M9!dLOy)2?vYBOAzG>9TL6#Q3uzmd ziT-1i(Lj~hbigj}kssuZW&`aRT9%a{ZddTrYQg8_;|{G9z>d# zYDY17E$t{@Q?YI>ok|i&*oxEaU3PH|>UdeklFophBL z_&x^LZLYN!P?aC>J+jdLfv)K{eD|xe*C4nBS*=dp+w%O41fw<*NkIdRlm7!VlD~gX z9h1EQQhmxRHb?^yIOc&vqS6 zw)XzKCOu2x^`(3UJ@Qn22K|=}A{D=-tiOU-&xDQxvN?&~fOC2mNgWqwH!A^`FD~CK y0bnp#)Xdq%$<*8hY-j1>Ztr9RcCvSMu>^lHx3}9+Fm!$E1CW(elBg0h3H)Coknv3b literal 0 HcmV?d00001 diff --git a/src/BinaryLane.Api/packages.lock.json b/src/BinaryLane.Api/packages.lock.json new file mode 100644 index 0000000..c220c01 --- /dev/null +++ b/src/BinaryLane.Api/packages.lock.json @@ -0,0 +1,348 @@ +{ + "version": 2, + "dependencies": { + ".NETStandard,Version=v2.0": { + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "8.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Extensions.Http": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0", + "System.ComponentModel.Annotations": "5.0.0" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "System.Text.Json": { + "type": "Direct", + "requested": "[8.0.5, )", + "resolved": "8.0.5", + "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "8.0.0", + "System.Buffers": "4.5.1", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "8.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "8.0.0", + "Microsoft.Extensions.DependencyInjection": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2", + "System.Diagnostics.DiagnosticSource": "8.0.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "System.Buffers": "4.5.1", + "System.Diagnostics.DiagnosticSource": "8.0.1", + "System.Memory": "4.5.5" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + } + }, + "net8.0": { + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + }, + "Microsoft.Extensions.Http": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + } + } + } +} \ No newline at end of file diff --git a/tests/BinaryLane.Api.Tests/AssemblyInfo.cs b/tests/BinaryLane.Api.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..2171200 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj b/tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj new file mode 100644 index 0000000..64f4e48 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/BinaryLane.Api.Tests.csproj @@ -0,0 +1,19 @@ + + + net8.0 + false + true + + + + + + + + + + + + + + diff --git a/tests/BinaryLane.Api.Tests/BinaryLaneHttpExecutorTests.cs b/tests/BinaryLane.Api.Tests/BinaryLaneHttpExecutorTests.cs new file mode 100644 index 0000000..744fec5 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/BinaryLaneHttpExecutorTests.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.Errors; +using BinaryLane.Api.V2.Http; +using Xunit; + +namespace BinaryLane.Api.Tests; + +public sealed class BinaryLaneHttpExecutorTests +{ + [Fact] + public async Task GetAsyncAddsBearerHeaderAndSerializesQueryValues() + { + HttpRequestMessage? received = null; + using var httpClient = new HttpClient(new HttpClientTestHandler((request, _) => + { + received = request; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{\"value\":\"ok\"}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("test-token")); + + var response = await executor.GetAsync( + "v2/servers", + new System.Collections.Generic.Dictionary + { + ["page"] = 2, + ["include_archived"] = false, + }); + + Assert.Equal("ok", response.GetProperty("value").GetString()); + Assert.NotNull(received); + Assert.Equal("Bearer", received!.Headers.Authorization!.Scheme); + Assert.Equal("test-token", received.Headers.Authorization.Parameter); + Assert.Equal("?page=2&include_archived=false", received.RequestUri!.Query); + } + + [Fact] + public async Task GetAsyncMapsUnauthorizedResponseWithoutLeakingBearerToken() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + Task.FromResult(JsonResponse(HttpStatusCode.Unauthorized, "{\"title\":\"Unauthorized\",\"detail\":\"Invalid token\"}")))) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("super-secret-token")); + + var exception = await Assert.ThrowsAsync( + () => executor.GetAsync("v2/account")); + + Assert.Equal(HttpStatusCode.Unauthorized, exception.StatusCode); + Assert.Equal("Invalid token", exception.Problem!.Detail); + Assert.DoesNotContain("Invalid token", exception.Message, StringComparison.Ordinal); + Assert.DoesNotContain("super-secret-token", exception.ToString(), StringComparison.Ordinal); + Assert.DoesNotContain("super-secret-token", exception.ResponseBody ?? string.Empty, StringComparison.Ordinal); + } + + [Fact] + public async Task GetAsyncPropagatesCancellationToTheHttpHandler() + { + using var cancellation = new CancellationTokenSource(); + using var httpClient = new HttpClient(new HttpClientTestHandler(async (_, token) => + { + await Task.Delay(Timeout.InfiniteTimeSpan, token); + throw new InvalidOperationException("Unreachable."); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + cancellation.Cancel(); + + await Assert.ThrowsAnyAsync( + () => executor.GetAsync("v2/account", cancellationToken: cancellation.Token)); + } + + [Fact] + public async Task TokenProviderIsEvaluatedForEveryRequestSoTokensCanRotate() + { + var authorizationTokens = new List(); + using var httpClient = new HttpClient(new HttpClientTestHandler((request, _) => + { + authorizationTokens.Add(request.Headers.Authorization?.Parameter); + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor( + httpClient, + new RotatingTokenProvider("first-token", "second-token")); + + await executor.GetAsync("v2/account"); + await executor.GetAsync("v2/account"); + + Assert.Collection( + authorizationTokens, + token => Assert.Equal("first-token", token), + token => Assert.Equal("second-token", token)); + } + + [Fact] + public async Task QueryFormattingOmitsNullsAndUsesInvariantWireValues() + { + HttpRequestMessage? received = null; + using var httpClient = new HttpClient(new HttpClientTestHandler((request, _) => + { + received = request; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + var timestamp = new DateTimeOffset(2026, 8, 16, 12, 34, 56, TimeSpan.Zero); + + await executor.GetAsync( + "v2/actions?existing=true", + new Dictionary + { + ["after"] = timestamp, + ["amount"] = 10.5m, + ["omit_me"] = null, + }); + + Assert.NotNull(received); + var query = received!.RequestUri!.Query; + Assert.Contains("existing=true", query, StringComparison.Ordinal); + Assert.Contains("after=2026-08-16T12%3A34%3A56.0000000%2B00%3A00", query, StringComparison.Ordinal); + Assert.Contains("amount=10.5", query, StringComparison.Ordinal); + Assert.DoesNotContain("omit_me", query, StringComparison.Ordinal); + } + + [Fact] + public async Task ValidationErrorsMapToValidationExceptionAndPreserveProblemDetails() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + Task.FromResult(JsonResponse( + (HttpStatusCode)422, + "{\"title\":\"Validation failed\",\"detail\":\"region is required\",\"status\":422}")))) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync( + () => executor.PostAsync("v2/servers", new { })); + + Assert.Equal((HttpStatusCode)422, exception.StatusCode); + Assert.Equal("Validation failed", exception.Problem!.Title); + Assert.Equal("region is required", exception.Problem.Detail); + } + + [Fact] + public async Task MutationsAreNotRetriedImplicitly() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.InternalServerError, "{\"title\":\"Failure\"}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + await Assert.ThrowsAsync( + () => executor.PostAsync("v2/servers", new { name = "must-not-retry" })); + + Assert.Equal(1, requestCount); + } + + [Fact] + public async Task EmptyTokenFailsBeforeTheRequestIsDispatched() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new RotatingTokenProvider(string.Empty)); + + await Assert.ThrowsAsync( + () => executor.GetAsync("v2/account")); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task AbsoluteUrlsOutsideTheConfiguredOriginDoNotReceiveTheBearerToken() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + await Assert.ThrowsAsync( + () => executor.GetAsync("https://other.example.test/v2/servers")); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task NonHttpAbsoluteUrlsAreRejectedBeforeTheRequestIsDispatched() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + await Assert.ThrowsAsync( + () => executor.GetAsync("ftp://other.example.test/v2/servers")); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task CallerCannotOverrideTheTokenProviderAuthorizationHeader() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + var request = new BinaryLaneRequest(HttpMethod.Get, "v2/account") + { + Headers = new Dictionary + { + ["Authorization"] = "Bearer caller-token", + }, + }; + + await Assert.ThrowsAsync(() => executor.SendAsync(request)); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task CallerCannotOverrideTheRequestAuthorityWithAHostHeader() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + var request = new BinaryLaneRequest(HttpMethod.Get, "v2/account") + { + Headers = new Dictionary + { + ["Host"] = "other.example.test", + }, + }; + + await Assert.ThrowsAsync(() => executor.SendAsync(request)); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task PlaintextBaseAddressesAreRejectedBeforeTheRequestIsDispatched() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + return Task.FromResult(JsonResponse(HttpStatusCode.OK, "{}")); + })) + { + BaseAddress = new Uri("http://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + await Assert.ThrowsAsync( + () => executor.GetAsync("v2/account")); + + Assert.Equal(0, requestCount); + } + + [Fact] + public async Task SuccessResponseBodiesLargerThanTheLimitAreRejectedBeforeBuffering() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + var content = new ByteArrayContent(Array.Empty()); + content.Headers.ContentLength = (16L * 1024 * 1024) + 1; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = content }); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync( + () => executor.GetAsync("v2/account")); + + Assert.Contains("exceeded", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public async Task ErrorResponseBodyIsBoundedBeforeItIsStoredOnTheException() + { + var responseBody = "{\"detail\":\"" + new string('x', 40_000) + "\"}"; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + Task.FromResult(JsonResponse(HttpStatusCode.InternalServerError, responseBody)))) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var executor = new BinaryLaneHttpExecutor(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync( + () => executor.GetAsync("v2/account")); + + Assert.NotNull(exception.ResponseBody); + Assert.Equal(32_769, exception.ResponseBody!.Length); + Assert.EndsWith("…", exception.ResponseBody, StringComparison.Ordinal); + } + + private static HttpResponseMessage JsonResponse(HttpStatusCode statusCode, string json) => + new(statusCode) + { + Content = new StringContent(json), + }; + + private sealed class RotatingTokenProvider : IBinaryLaneTokenProvider + { + private readonly Queue _tokens; + + public RotatingTokenProvider(params string[] tokens) + { + _tokens = new Queue(tokens); + } + + public ValueTask GetTokenAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(_tokens.Dequeue()); + } + } +} diff --git a/tests/BinaryLane.Api.Tests/ClientResourceTests.cs b/tests/BinaryLane.Api.Tests/ClientResourceTests.cs new file mode 100644 index 0000000..e5d5d7c --- /dev/null +++ b/tests/BinaryLane.Api.Tests/ClientResourceTests.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.Authentication; +using BinaryLane.Api.V2.Models; +using Xunit; + +namespace BinaryLane.Api.Tests; + +public sealed class ClientResourceTests +{ + [Fact] + public async Task ServersListAllFollowsTheProviderNextPageLink() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((request, _) => + { + requestCount++; + var json = request.RequestUri!.Query.Contains("page=2", StringComparison.Ordinal) + ? "{\"meta\":{\"total\":2},\"links\":{\"pages\":{}},\"servers\":[{\"id\":2,\"name\":\"two\",\"status\":\"active\"}]}" + : "{\"meta\":{\"total\":2},\"links\":{\"pages\":{\"next\":\"/v2/servers?page=2\"}},\"servers\":[{\"id\":1,\"name\":\"one\",\"status\":\"active\"}]}"; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(json) }); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + var servers = new List(); + + await foreach (var server in client.Servers.ListAllAsync()) + { + servers.Add(server); + } + + Assert.Equal(2, requestCount); + Assert.Collection( + servers, + server => Assert.Equal("one", server.Name), + server => Assert.Equal("two", server.Name)); + } + + [Fact] + public async Task ServersListAllStopsWhenTheProviderRepeatsANextPageLink() + { + var requestCount = 0; + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + { + requestCount++; + const string json = "{\"meta\":{\"total\":1},\"links\":{\"pages\":{\"next\":\"/v2/servers?page=1\"}},\"servers\":[{\"id\":1,\"name\":\"one\",\"status\":\"active\"}]}"; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(json) }); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync(async () => + { + await foreach (var _ in client.Servers.ListAllAsync()) + { + } + }); + + Assert.Contains("repeated pagination link", exception.Message, StringComparison.Ordinal); + Assert.Equal(2, requestCount); + } + + [Fact] + public async Task SubmitActionUsesRealActionsEndpointAndReportsAcceptedStatus() + { + HttpRequestMessage? received = null; + string? body = null; + using var httpClient = new HttpClient(new HttpClientTestHandler(async (request, cancellationToken) => + { + received = request; + body = request.Content is null ? null : await request.Content.ReadAsStringAsync(cancellationToken); + return new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("{\"action\":{\"id\":9,\"type\":\"power_on\",\"status\":\"in-progress\",\"started_at\":\"2026-01-01T00:00:00Z\"}}"), + }; + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var submission = await client.Servers.SubmitActionAsync(123, new PowerOnServerAction()); + + Assert.NotNull(received); + Assert.Equal("/v2/servers/123/actions", received!.RequestUri!.AbsolutePath); + Assert.Equal("POST", received.Method.Method); + Assert.Contains("\"type\":\"power_on\"", body!, StringComparison.Ordinal); + Assert.True(submission.IsAccepted); + Assert.Equal(9, submission.Action!.Id); + } + + [Fact] + public async Task ListEndpointsRejectAResponseWithoutTheExpectedCollectionEnvelope() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"meta\":{\"total\":0},\"wrong_collection\":[]}"), + }))) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync(() => client.Servers.ListAsync()); + + Assert.Contains("servers", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public async Task UserDataUsesTheDocumentedDirectResponseBody() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((request, _) => + { + Assert.Equal("/v2/servers/123/user_data", request.RequestUri!.AbsolutePath); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"user_data\":\"#cloud-config\"}"), + }); + })) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var userData = await client.Servers.GetUserDataAsync(123); + + Assert.Equal("#cloud-config", userData.Value); + } + + [Fact] + public async Task ListEndpointsRejectNullItemsRatherThanSilentlyDroppingThem() + { + using var httpClient = new HttpClient(new HttpClientTestHandler((_, _) => + Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"meta\":{\"total\":1},\"servers\":[null]}"), + }))) + { + BaseAddress = new Uri("https://api.example.test/"), + }; + var client = new BinaryLaneClient(httpClient, new StaticBinaryLaneTokenProvider("token")); + + var exception = await Assert.ThrowsAsync(() => client.Servers.ListAsync()); + + Assert.Contains("servers", exception.Message, StringComparison.Ordinal); + } +} diff --git a/tests/BinaryLane.Api.Tests/DependencyInjectionTests.cs b/tests/BinaryLane.Api.Tests/DependencyInjectionTests.cs new file mode 100644 index 0000000..4681402 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/DependencyInjectionTests.cs @@ -0,0 +1,85 @@ +using System; +using BinaryLane.Api.V2; +using BinaryLane.Api.V2.Configuration; +using BinaryLane.Api.V2.DependencyInjection; +using BinaryLane.Api.V2.Resources; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace BinaryLane.Api.Tests; + +public sealed class DependencyInjectionTests +{ + [Fact] + public void AddBinaryLaneApiRegistersAComposableClient() + { + var services = new ServiceCollection(); + services.AddBinaryLaneApi(options => options.ApiToken = "test-token"); + + using var provider = services.BuildServiceProvider(); + var client = provider.GetRequiredService(); + + Assert.NotNull(client.Account); + Assert.NotNull(client.Actions); + Assert.NotNull(client.Servers); + Assert.NotNull(client.Regions); + Assert.NotNull(client.Vpcs); + Assert.NotNull(provider.GetRequiredService()); + Assert.NotNull(provider.GetRequiredService()); + } + + [Fact] + public void InvalidOptionsAreRejectedWhenResolved() + { + var services = new ServiceCollection(); + services.AddBinaryLaneApi(options => + { + options.BaseUrl = "not-a-url"; + options.ApiToken = "test-token"; + }); + + using var provider = services.BuildServiceProvider(); + + Assert.Throws( + () => provider.GetRequiredService>().Value); + } + + [Theory] + [InlineData(0)] + [InlineData(301)] + public void OutOfRangeTimeoutIsRejectedWhenResolved(int timeoutSeconds) + { + var services = new ServiceCollection(); + services.AddBinaryLaneApi(options => + { + options.ApiToken = "test-token"; + options.RequestTimeoutSeconds = timeoutSeconds; + }); + + using var provider = services.BuildServiceProvider(); + + Assert.Throws( + () => provider.GetRequiredService>().Value); + } + + [Theory] + [InlineData("http://api.example.test/")] + [InlineData("https://user:password@api.example.test/")] + [InlineData("https://api.example.test/?unexpected=value")] + [InlineData("https://api.example.test/#fragment")] + public void BaseUrlWithCredentialsQueryOrFragmentIsRejected(string baseUrl) + { + var services = new ServiceCollection(); + services.AddBinaryLaneApi(options => + { + options.BaseUrl = baseUrl; + options.ApiToken = "test-token"; + }); + + using var provider = services.BuildServiceProvider(); + + Assert.Throws( + () => provider.GetRequiredService>().Value); + } +} diff --git a/tests/BinaryLane.Api.Tests/HttpClientTestHandler.cs b/tests/BinaryLane.Api.Tests/HttpClientTestHandler.cs new file mode 100644 index 0000000..084d0e5 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/HttpClientTestHandler.cs @@ -0,0 +1,17 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace BinaryLane.Api.Tests; + +internal sealed class HttpClientTestHandler : HttpMessageHandler +{ + private readonly Func> _send; + + public HttpClientTestHandler(Func> send) => + _send = send ?? throw new ArgumentNullException(nameof(send)); + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => + _send(request, cancellationToken); +} diff --git a/tests/BinaryLane.Api.Tests/ModelSerializationTests.cs b/tests/BinaryLane.Api.Tests/ModelSerializationTests.cs new file mode 100644 index 0000000..46b613e --- /dev/null +++ b/tests/BinaryLane.Api.Tests/ModelSerializationTests.cs @@ -0,0 +1,149 @@ +using System.Text.Json; +using BinaryLane.Api.V2.Http; +using BinaryLane.Api.V2.Models; +using Xunit; + +namespace BinaryLane.Api.Tests; + +public sealed class ModelSerializationTests +{ + private static readonly JsonSerializerOptions Json = + new BinaryLaneJsonSerializerOptions().SerializerOptions; + + [Fact] + public void PowerOnActionSerializesTheProviderDiscriminator() + { + string json = JsonSerializer.Serialize(new PowerOnServerAction(), Json); + + using JsonDocument document = JsonDocument.Parse(json); + Assert.Equal("power_on", document.RootElement.GetProperty("type").GetString()); + Assert.Single(document.RootElement.EnumerateObject()); + } + + [Fact] + public void CreateServerRequestSerializesBinaryLaneWireNamesAndOmitsNulls() + { + CreateServerRequest request = new() + { + Name = "demo-server", + Image = "ubuntu-24.04", + Region = "melbourne", + Size = "std-min", + PortBlocking = false, + }; + + string json = JsonSerializer.Serialize(request, Json); + using JsonDocument document = JsonDocument.Parse(json); + + Assert.Equal("demo-server", document.RootElement.GetProperty("name").GetString()); + Assert.Equal("ubuntu-24.04", document.RootElement.GetProperty("image").GetString()); + Assert.Equal("melbourne", document.RootElement.GetProperty("region").GetString()); + Assert.Equal("std-min", document.RootElement.GetProperty("size").GetString()); + Assert.False(document.RootElement.GetProperty("port_blocking").GetBoolean()); + Assert.False(document.RootElement.TryGetProperty("password", out _)); + Assert.False(document.RootElement.TryGetProperty("user_data", out _)); + } + + [Fact] + public void UnknownResponsePropertiesAreRetainedForForwardCompatibility() + { + const string json = """ + { + "email": "alex@example.test", + "email_verified": true, + "two_factor_authentication_enabled": false, + "status": "active", + "tax_code": { "name": "GST", "type": "scalar" }, + "configured_payment_methods": ["credit-card"], + "additional_ipv4_limit": 2, + "future_provider_field": "kept" + } + """; + + Account? account = JsonSerializer.Deserialize(json, Json); + + Assert.NotNull(account); + Assert.Equal("active", account.Status); + Assert.NotNull(account.AdditionalProperties); + Assert.Equal( + "kept", + account.AdditionalProperties!["future_provider_field"].GetString()); + } + + [Fact] + public void PreviewEnumValuesRemainRawStrings() + { + const string json = """ + { + "id": 42, + "status": "future-status", + "type": "future-action", + "started_at": "2026-08-16T12:34:56Z", + "title": "Future action", + "reason": "A future provider action", + "progress": { "percent_complete": 0, "completed_steps": [] } + } + """; + + BinaryLaneAction? action = JsonSerializer.Deserialize(json, Json); + + Assert.NotNull(action); + Assert.Equal("future-status", action.Status); + Assert.Equal("future-action", action.Type); + } + + [Fact] + public void DocumentedValuesAreAvailableWithoutClosedEnums() + { + Assert.Equal("completed", BinaryLaneValues.ActionStatus.Completed); + Assert.Equal("power_on", BinaryLaneValues.ServerActionType.PowerOn); + Assert.Equal("AAAA", BinaryLaneValues.DomainRecordType.Aaaa); + } + + [Fact] + public void ComplexServerActionUsesTheSharedEndpointDiscriminatorAndWireNames() + { + ServerAction action = new ChangeVpcIpv4ServerAction + { + CurrentIpv4Address = "10.0.0.10", + NewIpv4Address = "10.0.0.20", + }; + + string json = JsonSerializer.Serialize(action, Json); + using JsonDocument document = JsonDocument.Parse(json); + + Assert.Equal("change_vpc_ipv4", document.RootElement.GetProperty("type").GetString()); + Assert.Equal("10.0.0.10", document.RootElement.GetProperty("current_ipv4_address").GetString()); + Assert.Equal("10.0.0.20", document.RootElement.GetProperty("new_ipv4_address").GetString()); + } + + [Fact] + public void ServerActionConverterDeserializesKnownAndUnknownProviderActions() + { + const string knownJson = """ + { + "type": "change_vpc_ipv4", + "current_ipv4_address": "10.0.0.10", + "new_ipv4_address": "10.0.0.20" + } + """; + const string unknownJson = """ + { + "type": "future_action", + "provider_option": "kept" + } + """; + + ServerAction? known = JsonSerializer.Deserialize(knownJson, Json); + ServerAction? unknown = JsonSerializer.Deserialize(unknownJson, Json); + + ChangeVpcIpv4ServerAction typed = Assert.IsType(known); + Assert.Equal("10.0.0.10", typed.CurrentIpv4Address); + Assert.Equal("10.0.0.20", typed.NewIpv4Address); + + UnknownServerAction future = Assert.IsType(unknown); + Assert.Equal("future_action", future.Type); + Assert.NotNull(future.AdditionalProperties); + Assert.Equal("kept", future.AdditionalProperties!["provider_option"].GetString()); + } +} diff --git a/tests/BinaryLane.Api.Tests/packages.lock.json b/tests/BinaryLane.Api.Tests/packages.lock.json new file mode 100644 index 0000000..0401099 --- /dev/null +++ b/tests/BinaryLane.Api.Tests/packages.lock.json @@ -0,0 +1,240 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.2, )", + "resolved": "6.0.2", + "contentHash": "bJShQ6uWRTQ100ZeyiMqcFlhP7WJ+bCuabUs885dJiBEzMsJMSFr7BOyeCw4rgvQokteGi5rKQTlkhfQPUXg2A==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.11.1, )", + "resolved": "17.11.1", + "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "dependencies": { + "Microsoft.CodeCoverage": "17.11.1", + "Microsoft.TestPlatform.TestHost": "17.11.1" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.2, )", + "resolved": "2.9.2", + "contentHash": "7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==", + "dependencies": { + "xunit.analyzers": "1.16.0", + "xunit.assert": "2.9.2", + "xunit.core": "[2.9.2]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.8.2, )", + "resolved": "2.8.2", + "contentHash": "vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.1", + "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.11.1", + "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.16.0", + "contentHash": "hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.2", + "contentHash": "QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.2", + "contentHash": "O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]", + "xunit.extensibility.execution": "[2.9.2]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.2", + "contentHash": "Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.2", + "contentHash": "rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]" + } + }, + "binarylane.api": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[8.0.2, )", + "Microsoft.Extensions.Http": "[8.0.1, )", + "Microsoft.Extensions.Options": "[8.0.2, )" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + }, + "Microsoft.Extensions.Http": { + "type": "CentralTransitive", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" + } + }, + "Microsoft.Extensions.Options": { + "type": "CentralTransitive", + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "CentralTransitive", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + } + } + } +} \ No newline at end of file