Initial BinaryLane v2 API client

This commit is contained in:
2026-08-16 21:52:42 +10:00
commit fcd0d22b0d
91 changed files with 18691 additions and 0 deletions
+40
View File
@@ -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<package-version>\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"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 1 ]]; then
printf 'Usage: %s <package-version>\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"
+54
View File
@@ -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
+21
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+6
View File
@@ -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"
}
+64
View File
@@ -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"
]
}
Executable
+14
View File
@@ -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"
+28
View File
@@ -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:]]*</${property_name}>.*@\\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 <PackageVersion> or <Version>.\n' >&2
exit 2
fi
printf '%s\n' "$package_version"
+57
View File
@@ -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"
+65
View File
@@ -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 <package-directory>\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 '<ProjectReference Include="../../src/BinaryLane.Api/BinaryLane.Api.csproj" />' "$demo_project"; then
printf 'Demo project does not contain the expected local SDK project reference.\n' >&2
exit 2
fi
sed -i "/<\\/ItemGroup>/i\\ <PackageVersion Include=\"BinaryLane.Api\" Version=\"$package_version\" />" \
"$temporary_directory/Directory.Packages.props"
sed -i "s#<ProjectReference Include=\"../../src/BinaryLane.Api/BinaryLane.Api.csproj\" />#<PackageReference Include=\"BinaryLane.Api\" />#" \
"$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"
+66
View File
@@ -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:-<unreadable>}" >&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