mirror of
https://github.com/alexhopeoconnor/binarylane-dotnet.git
synced 2026-10-21 17:22:35 +10:00
Initial BinaryLane v2 API client
This commit is contained in:
Executable
+40
@@ -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"
|
||||
Executable
+39
@@ -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"
|
||||
Executable
+54
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
Executable
+28
@@ -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"
|
||||
Executable
+57
@@ -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"
|
||||
Executable
+65
@@ -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"
|
||||
Executable
+66
@@ -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
|
||||
Reference in New Issue
Block a user