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
@@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
namespace BinaryLane.Api.V2.Authentication;
/// <summary>Supplies a BinaryLane bearer token for an outgoing request.</summary>
public interface IBinaryLaneTokenProvider
{
/// <summary>Gets the current bearer token.</summary>
ValueTask<string> GetTokenAsync(CancellationToken cancellationToken = default);
}
@@ -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;
/// <summary>Uses the token configured in <see cref="BinaryLaneOptions"/>.</summary>
public sealed class OptionsBinaryLaneTokenProvider : IBinaryLaneTokenProvider
{
private readonly IOptionsMonitor<BinaryLaneOptions> _options;
/// <summary>Initializes the provider.</summary>
public OptionsBinaryLaneTokenProvider(IOptionsMonitor<BinaryLaneOptions> options) =>
_options = options ?? throw new ArgumentNullException(nameof(options));
/// <inheritdoc />
public ValueTask<string> 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<string>(token!);
}
}
@@ -0,0 +1,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace BinaryLane.Api.V2.Authentication;
/// <summary>Provides a fixed token. Prefer a custom provider when tokens rotate.</summary>
public sealed class StaticBinaryLaneTokenProvider : IBinaryLaneTokenProvider
{
private readonly string _token;
/// <summary>Initializes the provider with a non-empty bearer token.</summary>
public StaticBinaryLaneTokenProvider(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentException("A BinaryLane API token is required.", nameof(token));
}
_token = token;
}
/// <inheritdoc />
public ValueTask<string> GetTokenAsync(CancellationToken cancellationToken = default) =>
new(_token);
}
+48
View File
@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
<PackageId>BinaryLane.Api</PackageId>
<AssemblyName>BinaryLane.Api</AssemblyName>
<RootNamespace>BinaryLane.Api.V2</RootNamespace>
<Version>0.1.0-beta.1</Version>
<Authors>Alex Hope-O'Connor</Authors>
<Owners>alex.hope.oconnor</Owners>
<Copyright>Copyright © 2026 Alex Hope-O'Connor</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<IsPackable>true</IsPackable>
<Description>Unofficial, typed .NET client for BinaryLane's v2 API.</Description>
<PackageTags>binarylane;api;cloud;vps;dns</PackageTags>
<PackageProjectUrl>https://github.com/alexhopeoconnor/binarylane-dotnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/alexhopeoconnor/binarylane-dotnet</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Pack must serialize multi-target nuspec and symbols generation. -->
<BuildInParallel>false</BuildInParallel>
</PropertyGroup>
<!-- Versions are centrally managed by Directory.Packages.props. -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" Link="README.md" />
<None Include="../../LICENSE" Pack="true" PackagePath="/" Link="LICENSE" />
<None Include="icon.png" Pack="true" PackagePath="/" />
</ItemGroup>
</Project>
+117
View File
@@ -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;
/// <summary>Default composable BinaryLane v2 client.</summary>
public sealed class BinaryLaneClient : IBinaryLaneClient
{
/// <summary>
/// Creates a client using the supplied HTTP client and token provider. The HTTP client should
/// normally be obtained through <c>IHttpClientFactory</c> or registered with
/// <c>AddBinaryLaneApi</c>.
/// </summary>
public BinaryLaneClient(HttpClient httpClient, IBinaryLaneTokenProvider tokenProvider)
: this(httpClient, tokenProvider, new BinaryLaneJsonSerializerOptions())
{
}
/// <summary>Creates a client using supplied HTTP, authentication, and JSON services.</summary>
[ActivatorUtilitiesConstructor]
public BinaryLaneClient(
HttpClient httpClient,
IBinaryLaneTokenProvider tokenProvider,
BinaryLaneJsonSerializerOptions json)
: this(new BinaryLaneHttpExecutor(httpClient, tokenProvider, json), json)
{
}
/// <summary>Creates a client over a custom request executor.</summary>
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);
}
/// <inheritdoc />
public IBinaryLaneApiExecutor Executor { get; }
/// <inheritdoc />
public IAccountApi Account { get; }
/// <inheritdoc />
public IActionsApi Actions { get; }
/// <inheritdoc />
public IBillingApi Billing { get; }
/// <inheritdoc />
public IDataUsageApi DataUsage { get; }
/// <inheritdoc />
public IDomainsApi Domains { get; }
/// <inheritdoc />
public IImagesApi Images { get; }
/// <inheritdoc />
public ISshKeysApi SshKeys { get; }
/// <inheritdoc />
public ILoadBalancersApi LoadBalancers { get; }
/// <inheritdoc />
public IRegionsApi Regions { get; }
/// <inheritdoc />
public IReverseNamesApi ReverseNames { get; }
/// <inheritdoc />
public ISampleSetsApi SampleSets { get; }
/// <inheritdoc />
public IServersApi Servers { get; }
/// <inheritdoc />
public ISizesApi Sizes { get; }
/// <inheritdoc />
public ISoftwareApi Software { get; }
/// <inheritdoc />
public IVpcsApi Vpcs { get; }
}
@@ -0,0 +1,36 @@
using System;
namespace BinaryLane.Api.V2.Configuration;
/// <summary>Configures a BinaryLane API client.</summary>
public sealed class BinaryLaneOptions
{
/// <summary>The configuration section convention used by consuming applications.</summary>
public const string SectionName = "BinaryLane";
/// <summary>The BinaryLane API root URL.</summary>
public string BaseUrl { get; set; } = "https://api.binarylane.com.au/";
/// <summary>
/// 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.
/// </summary>
public string? ApiToken { get; set; }
/// <summary>Timeout applied to individual HTTP requests.</summary>
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);
}
}
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Options;
namespace BinaryLane.Api.V2.Configuration;
/// <summary>Validates client options at application startup.</summary>
public sealed class BinaryLaneOptionsValidator : IValidateOptions<BinaryLaneOptions>
{
/// <inheritdoc />
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;
}
}
@@ -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;
/// <summary>Dependency-injection registration for the BinaryLane API client.</summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers a typed BinaryLane client. The returned builder lets an application compose its
/// own proxy, telemetry, and safe GET-only resilience handlers.
/// </summary>
public static IHttpClientBuilder AddBinaryLaneApi(
this IServiceCollection services,
Action<BinaryLaneOptions> 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<BinaryLaneOptions>()
.Configure(configure)
.ValidateOnStart();
services.TryAddSingleton<IValidateOptions<BinaryLaneOptions>, BinaryLaneOptionsValidator>();
services.TryAddSingleton<IBinaryLaneTokenProvider, OptionsBinaryLaneTokenProvider>();
services.TryAddSingleton<BinaryLaneJsonSerializerOptions>();
var clientBuilder = services.AddHttpClient<IBinaryLaneClient, BinaryLaneClient>((serviceProvider, client) =>
{
var options = serviceProvider.GetRequiredService<IOptions<BinaryLaneOptions>>().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<IAccountApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Account);
services.TryAddTransient<IActionsApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Actions);
services.TryAddTransient<IBillingApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Billing);
services.TryAddTransient<IDataUsageApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().DataUsage);
services.TryAddTransient<IDomainsApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Domains);
services.TryAddTransient<IImagesApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Images);
services.TryAddTransient<ISshKeysApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().SshKeys);
services.TryAddTransient<ILoadBalancersApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().LoadBalancers);
services.TryAddTransient<IRegionsApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Regions);
services.TryAddTransient<IReverseNamesApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().ReverseNames);
services.TryAddTransient<ISampleSetsApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().SampleSets);
services.TryAddTransient<IServersApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Servers);
services.TryAddTransient<ISizesApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Sizes);
services.TryAddTransient<ISoftwareApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().Software);
services.TryAddTransient<IVpcsApi>(serviceProvider => serviceProvider.GetRequiredService<IBinaryLaneClient>().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);
}
}
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Errors;
/// <summary>Base exception for an unsuccessful BinaryLane API response.</summary>
public class BinaryLaneApiException : Exception
{
internal BinaryLaneApiException(
string message,
HttpStatusCode statusCode,
Uri requestUri,
BinaryLaneApiProblem? problem,
string? responseBody,
IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
: base(message)
{
StatusCode = statusCode;
RequestUri = requestUri;
Problem = problem;
ResponseBody = responseBody;
Headers = headers;
}
/// <summary>HTTP status code returned by BinaryLane.</summary>
public HttpStatusCode StatusCode { get; }
/// <summary>The request URI which produced the response.</summary>
public Uri RequestUri { get; }
/// <summary>Structured problem details, when BinaryLane supplied them.</summary>
public BinaryLaneApiProblem? Problem { get; }
/// <summary>
/// Bounded raw response text for diagnostics. It omits request headers, but can contain
/// provider-supplied or user-provided data; avoid logging it indiscriminately.
/// </summary>
public string? ResponseBody { get; }
/// <summary>Response and content headers.</summary>
public IReadOnlyDictionary<string, IReadOnlyList<string>> Headers { get; }
}
@@ -0,0 +1,17 @@
namespace BinaryLane.Api.V2.Errors;
/// <summary>A structured problem response returned by BinaryLane.</summary>
public sealed class BinaryLaneApiProblem
{
/// <summary>Problem type URI, if supplied.</summary>
public string? Type { get; internal set; }
/// <summary>Short problem title, if supplied.</summary>
public string? Title { get; internal set; }
/// <summary>Human-readable problem detail, if supplied.</summary>
public string? Detail { get; internal set; }
/// <summary>Provider-supplied status code, if supplied.</summary>
public int? Status { get; internal set; }
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Errors;
/// <summary>Thrown when the current account is not authorized for an operation.</summary>
public sealed class BinaryLaneForbiddenException : BinaryLaneApiException
{
internal BinaryLaneForbiddenException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
: base(message, HttpStatusCode.Forbidden, requestUri, problem, responseBody, headers)
{
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Errors;
/// <summary>Thrown when BinaryLane cannot find a requested resource.</summary>
public sealed class BinaryLaneNotFoundException : BinaryLaneApiException
{
internal BinaryLaneNotFoundException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
: base(message, HttpStatusCode.NotFound, requestUri, problem, responseBody, headers)
{
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Errors;
/// <summary>Thrown when BinaryLane rejects credentials.</summary>
public sealed class BinaryLaneUnauthorizedException : BinaryLaneApiException
{
internal BinaryLaneUnauthorizedException(string message, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
: base(message, HttpStatusCode.Unauthorized, requestUri, problem, responseBody, headers)
{
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Errors;
/// <summary>Thrown when BinaryLane rejects a malformed or invalid request.</summary>
public sealed class BinaryLaneValidationException : BinaryLaneApiException
{
internal BinaryLaneValidationException(string message, HttpStatusCode statusCode, Uri requestUri, BinaryLaneApiProblem? problem, string? responseBody, IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
: base(message, statusCode, requestUri, problem, responseBody, headers)
{
}
}
@@ -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;
/// <summary>Default HTTP implementation for the BinaryLane API.</summary>
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;
/// <summary>Initializes the executor.</summary>
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();
}
/// <inheritdoc />
public async Task<BinaryLaneResponse<TResponse>> SendAsync<TResponse>(
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<TResponse>(body);
return new BinaryLaneResponse<TResponse>(result, response.StatusCode, message.RequestUri!, headers);
}
/// <inheritdoc />
public async Task<BinaryLaneResponse<object?>> 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<object?>(null, response.StatusCode, message.RequestUri!, headers);
}
/// <inheritdoc />
public async Task<TResponse> GetAsync<TResponse>(
string path,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
(await SendAsync<TResponse>(new BinaryLaneRequest(HttpMethod.Get, path) { Query = query }, cancellationToken)
.ConfigureAwait(false)).Body;
/// <inheritdoc />
public async Task<TResponse> PostAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
(await SendAsync<TResponse>(new BinaryLaneRequest(HttpMethod.Post, path) { Body = body, Query = query }, cancellationToken)
.ConfigureAwait(false)).Body;
/// <inheritdoc />
public async Task<TResponse> PutAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
(await SendAsync<TResponse>(new BinaryLaneRequest(HttpMethod.Put, path) { Body = body, Query = query }, cancellationToken)
.ConfigureAwait(false)).Body;
/// <inheritdoc />
public async Task<TResponse> PatchAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
(await SendAsync<TResponse>(new BinaryLaneRequest(new HttpMethod("PATCH"), path) { Body = body, Query = query }, cancellationToken)
.ConfigureAwait(false)).Body;
/// <inheritdoc />
public async Task DeleteAsync(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default)
{
await SendAsync(new BinaryLaneRequest(HttpMethod.Delete, path) { Body = body, Query = query }, cancellationToken)
.ConfigureAwait(false);
}
private async Task<HttpRequestMessage> 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<string, object?>? 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<TResponse>(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<TResponse>(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<string, IReadOnlyList<string>> CollectHeaders(HttpResponseMessage response)
{
var result = new Dictionary<string, IReadOnlyList<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var header in response.Headers)
{
result[header.Key] = new List<string>(header.Value).AsReadOnly();
}
if (response.Content is not null)
{
foreach (var header in response.Content.Headers)
{
result[header.Key] = new List<string>(header.Value).AsReadOnly();
}
}
return new ReadOnlyDictionary<string, IReadOnlyList<string>>(result);
}
private static BinaryLaneApiException CreateException(
HttpStatusCode statusCode,
Uri requestUri,
string body,
IReadOnlyDictionary<string, IReadOnlyList<string>> 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<string> 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<string> 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);
}
}
@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using BinaryLane.Api.V2.Models;
namespace BinaryLane.Api.V2.Http;
/// <summary>JSON settings used by the BinaryLane API client.</summary>
public sealed class BinaryLaneJsonSerializerOptions
{
/// <summary>Creates the default BinaryLane JSON settings.</summary>
public BinaryLaneJsonSerializerOptions()
{
SerializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
SerializerOptions.Converters.Add(new JsonStringEnumConverter());
SerializerOptions.Converters.Add(new ServerActionJsonConverter());
}
/// <summary>The serializer settings used for request and response bodies.</summary>
public JsonSerializerOptions SerializerOptions { get; }
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace BinaryLane.Api.V2.Http;
/// <summary>Represents one request to the BinaryLane v2 API.</summary>
public sealed class BinaryLaneRequest
{
/// <summary>Initializes a request.</summary>
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;
}
/// <summary>HTTP method.</summary>
public HttpMethod Method { get; }
/// <summary>
/// Relative v2 path (for example <c>v2/servers</c>) or an absolute HTTPS URL on the
/// configured API origin. Relative paths are resolved against the configured
/// <see cref="System.Net.Http.HttpClient.BaseAddress"/>. Other origins are rejected so a
/// provider link cannot receive the configured bearer token.
/// </summary>
public string Path { get; }
/// <summary>Optional JSON request body.</summary>
public object? Body { get; set; }
/// <summary>Optional query-string values. Null values are omitted.</summary>
public IReadOnlyDictionary<string, object?>? Query { get; set; }
/// <summary>
/// Optional request headers. Authorization is supplied by the configured token provider and
/// cannot be overridden here.
/// </summary>
public IReadOnlyDictionary<string, string>? Headers { get; set; }
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace BinaryLane.Api.V2.Http;
/// <summary>Response metadata and a deserialized BinaryLane API body.</summary>
/// <typeparam name="T">The deserialized response type.</typeparam>
public sealed class BinaryLaneResponse<T>
{
internal BinaryLaneResponse(
T body,
HttpStatusCode statusCode,
Uri requestUri,
IReadOnlyDictionary<string, IReadOnlyList<string>> headers)
{
Body = body;
StatusCode = statusCode;
RequestUri = requestUri;
Headers = headers;
}
/// <summary>Deserialized response body.</summary>
public T Body { get; }
/// <summary>HTTP status code returned by BinaryLane.</summary>
public HttpStatusCode StatusCode { get; }
/// <summary>Final request URI.</summary>
public Uri RequestUri { get; }
/// <summary>Response and content headers.</summary>
public IReadOnlyDictionary<string, IReadOnlyList<string>> Headers { get; }
}
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace BinaryLane.Api.V2.Http;
/// <summary>
/// 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.
/// </summary>
public interface IBinaryLaneApiExecutor
{
/// <summary>Sends a request and returns response metadata and the deserialized body.</summary>
Task<BinaryLaneResponse<TResponse>> SendAsync<TResponse>(
BinaryLaneRequest request,
CancellationToken cancellationToken = default);
/// <summary>Sends a request which is expected to have no response body.</summary>
Task<BinaryLaneResponse<object?>> SendAsync(
BinaryLaneRequest request,
CancellationToken cancellationToken = default);
/// <summary>Sends a GET request.</summary>
Task<TResponse> GetAsync<TResponse>(
string path,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default);
/// <summary>Sends a POST request.</summary>
Task<TResponse> PostAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default);
/// <summary>Sends a PUT request.</summary>
Task<TResponse> PutAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default);
/// <summary>Sends a PATCH request.</summary>
Task<TResponse> PatchAsync<TResponse>(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default);
/// <summary>Sends a DELETE request.</summary>
Task DeleteAsync(
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default);
}
+59
View File
@@ -0,0 +1,59 @@
using BinaryLane.Api.V2.Http;
using BinaryLane.Api.V2.Resources;
namespace BinaryLane.Api.V2;
/// <summary>
/// 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.
/// </summary>
public interface IBinaryLaneClient
{
/// <summary>Low-level executor for a newly introduced endpoint not yet represented by a resource API.</summary>
IBinaryLaneApiExecutor Executor { get; }
/// <summary>Current account information.</summary>
IAccountApi Account { get; }
/// <summary>Global asynchronous actions.</summary>
IActionsApi Actions { get; }
/// <summary>Billing balance and invoices.</summary>
IBillingApi Billing { get; }
/// <summary>Data-transfer usage.</summary>
IDataUsageApi DataUsage { get; }
/// <summary>DNS domains and DNS records.</summary>
IDomainsApi Domains { get; }
/// <summary>Images and image downloads.</summary>
IImagesApi Images { get; }
/// <summary>SSH keys.</summary>
ISshKeysApi SshKeys { get; }
/// <summary>Load balancers, their members, and forwarding rules.</summary>
ILoadBalancersApi LoadBalancers { get; }
/// <summary>Available regions.</summary>
IRegionsApi Regions { get; }
/// <summary>IPv6 reverse nameservers.</summary>
IReverseNamesApi ReverseNames { get; }
/// <summary>Monitoring sample sets.</summary>
ISampleSetsApi SampleSets { get; }
/// <summary>Servers and server-scoped resources.</summary>
IServersApi Servers { get; }
/// <summary>Available server sizes.</summary>
ISizesApi Sizes { get; }
/// <summary>Licensed and operating-system software.</summary>
ISoftwareApi Software { get; }
/// <summary>Private virtual networks.</summary>
IVpcsApi Vpcs { get; }
}
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>
/// Base class for JSON objects returned by the BinaryLane API.
/// </summary>
/// <remarks>
/// 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 <see cref="AdditionalProperties"/>.
/// </remarks>
public abstract class BinaryLaneDto
{
/// <summary>
/// Gets properties supplied by the service that this version of the SDK does
/// not yet model explicitly.
/// </summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? AdditionalProperties { get; init; }
}
/// <summary>
/// Base class for request payloads accepted by the BinaryLane API.
/// </summary>
public abstract class BinaryLaneRequestModel : BinaryLaneDto
{
}
@@ -0,0 +1,275 @@
namespace BinaryLane.Api.V2.Models;
/// <summary>
/// Known string values in the BinaryLane v2 contract.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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";
}
/// <summary>Discriminator values for <see cref="ServerAction"/> payloads.</summary>
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";
}
}
+615
View File
@@ -0,0 +1,615 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>Information about the authenticated BinaryLane account.</summary>
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; }
/// <summary>Provider status, for example <c>active</c>.</summary>
[JsonPropertyName("status")]
public string Status { get; init; } = string.Empty;
[JsonPropertyName("tax_code")]
public TaxCode TaxCode { get; init; } = new();
/// <summary>Provider payment-method values, for example <c>credit-card</c>.</summary>
[JsonPropertyName("configured_payment_methods")]
public IReadOnlyList<string> ConfiguredPaymentMethods { get; init; } = Array.Empty<string>();
[JsonPropertyName("additional_ipv4_limit")]
public int AdditionalIpv4Limit { get; init; }
}
/// <summary>A tax code currently applicable to an account or invoice.</summary>
public sealed class TaxCode : BinaryLaneDto
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
/// <summary>Provider tax-code type, for example <c>none</c> or <c>scalar</c>.</summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
[JsonPropertyName("fixed_percent")]
public double? FixedPercent { get; init; }
}
/// <summary>A provider action, which may still be in progress.</summary>
public sealed class BinaryLaneAction : BinaryLaneDto
{
[JsonPropertyName("id")]
public long Id { get; init; }
/// <summary>Provider status, for example <c>in-progress</c>, <c>completed</c>, or <c>errored</c>.</summary>
[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; }
/// <summary>Provider resource type, when the action is associated with one.</summary>
[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; }
}
/// <summary>Progress information for a long-running provider action.</summary>
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<string> CompletedSteps { get; init; } = Array.Empty<string>();
}
/// <summary>A link to an action related to a completed request.</summary>
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;
}
/// <summary>Indicates that an action needs an explicit user decision.</summary>
public sealed class UserInteractionRequired : BinaryLaneDto
{
/// <summary>Provider interaction type, such as <c>continue-after-ping-failure</c>.</summary>
[JsonPropertyName("interaction_type")]
public string InteractionType { get; init; } = string.Empty;
}
/// <summary>A BinaryLane region in which a resource can be created.</summary>
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<string> Sizes { get; init; } = Array.Empty<string>();
[JsonPropertyName("available")]
public bool Available { get; init; }
[JsonPropertyName("features")]
public IReadOnlyList<string> Features { get; init; } = Array.Empty<string>();
[JsonPropertyName("name_servers")]
public IReadOnlyList<string> NameServers { get; init; } = Array.Empty<string>();
}
/// <summary>A BinaryLane server.</summary>
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; }
/// <summary>Provider server status, for example <c>active</c>.</summary>
[JsonPropertyName("status")]
public string Status { get; init; } = string.Empty;
[JsonPropertyName("backup_ids")]
public IReadOnlyList<long> BackupIds { get; init; } = Array.Empty<long>();
[JsonPropertyName("features")]
public IReadOnlyList<string> Features { get; init; } = Array.Empty<string>();
[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<Disk> Disks { get; init; } = Array.Empty<Disk>();
[JsonPropertyName("backup_settings")]
public BackupSettings BackupSettings { get; init; } = new();
[JsonPropertyName("cancelled_at")]
public DateTimeOffset? CancelledAt { get; init; }
[JsonPropertyName("failover_ips")]
public IReadOnlyList<string> FailoverIps { get; init; } = Array.Empty<string>();
[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; }
}
/// <summary>A server disk.</summary>
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; }
}
/// <summary>A server network assignment.</summary>
public sealed class ServerNetwork : BinaryLaneDto
{
[JsonPropertyName("ip_address")]
public string IpAddress { get; init; } = string.Empty;
/// <summary>May be represented by the provider as either a number or a string.</summary>
[JsonPropertyName("netmask")]
public object? Netmask { get; init; }
[JsonPropertyName("gateway")]
public string? Gateway { get; init; }
/// <summary>Provider network type, either <c>public</c> or <c>private</c>.</summary>
[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; }
}
/// <summary>All IPv4/IPv6 network information associated with a server.</summary>
public sealed class ServerNetworks : BinaryLaneDto
{
[JsonPropertyName("v4")]
public IReadOnlyList<ServerNetwork> V4 { get; init; } = Array.Empty<ServerNetwork>();
[JsonPropertyName("v6")]
public IReadOnlyList<ServerNetwork> V6 { get; init; } = Array.Empty<ServerNetwork>();
[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<string>? Ipv6ReverseNameservers { get; init; }
[JsonPropertyName("mac_address")]
public string MacAddress { get; init; } = string.Empty;
}
/// <summary>A BinaryLane image, including operating-system and backup images.</summary>
public sealed class Image : BinaryLaneDto
{
[JsonPropertyName("id")]
public long Id { get; init; }
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
/// <summary>Provider image type, for example <c>custom</c>, <c>snapshot</c>, or <c>backup</c>.</summary>
[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<string> Regions { get; init; } = Array.Empty<string>();
[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; }
/// <summary>Provider image status, for example <c>available</c>.</summary>
[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; }
}
/// <summary>Distribution-specific image installation capabilities.</summary>
public sealed class DistributionInfo : BinaryLaneDto
{
[JsonPropertyName("image_id")]
public long ImageId { get; init; }
/// <summary>Provider password recovery mode.</summary>
[JsonPropertyName("password_recovery")]
public string PasswordRecovery { get; init; } = string.Empty;
[JsonPropertyName("remote_access_user")]
public string? RemoteAccessUser { get; init; }
[JsonPropertyName("features")]
public IReadOnlyList<string> Features { get; init; } = Array.Empty<string>();
}
/// <summary>Additional provider charges for a distribution image.</summary>
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; }
}
/// <summary>Backup-specific metadata for an image.</summary>
public sealed class BackupInfo : BinaryLaneDto
{
/// <summary>Provider backup slot, such as <c>daily</c> or <c>temporary</c>.</summary>
[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<BackupDisk> BackupDisks { get; init; } = Array.Empty<BackupDisk>();
}
/// <summary>A disk contained in a backup image.</summary>
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; }
}
/// <summary>A provider server size and its available customisation options.</summary>
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<string> Regions { get; init; } = Array.Empty<string>();
[JsonPropertyName("regions_out_of_stock")]
public IReadOnlyList<string>? 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();
}
/// <summary>The provider's category for a server size.</summary>
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; }
}
/// <summary>Configurable bounds and costs associated with a server size.</summary>
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<int>? 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();
}
/// <summary>The backup frequency costs published for a size.</summary>
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; }
}
/// <summary>The options selected for an individual server.</summary>
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; }
}
@@ -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
+390
View File
@@ -0,0 +1,390 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>Options selected while creating or rebuilding a server image.</summary>
public sealed class ImageOptions : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string? Name { get; init; }
/// <summary>Each identifier may be either an SSH key ID or a public-key fingerprint.</summary>
[JsonPropertyName("ssh_keys")]
public IReadOnlyList<object>? SshKeys { get; init; }
/// <remarks>Do not log user-data; it can contain secrets.</remarks>
[JsonPropertyName("user_data")]
public string? UserData { get; init; }
/// <remarks>Do not log this value.</remarks>
[JsonPropertyName("password")]
public string? Password { get; init; }
}
/// <summary>Customisable options for a server size.</summary>
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; }
}
/// <summary>A requested software licence allocation.</summary>
public sealed class License : BinaryLaneRequestModel
{
[JsonPropertyName("software_id")]
public long SoftwareId { get; init; }
[JsonPropertyName("count")]
public int Count { get; init; }
}
/// <summary>Creates a new BinaryLane server.</summary>
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;
/// <summary>An image ID or provider image slug.</summary>
[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; }
/// <summary>Each entry may be an SSH key ID or fingerprint.</summary>
[JsonPropertyName("ssh_keys")]
public IReadOnlyList<object>? SshKeys { get; init; }
[JsonPropertyName("options")]
public SizeOptionsRequest? Options { get; init; }
[JsonPropertyName("licenses")]
public IReadOnlyList<License>? Licenses { get; init; }
/// <remarks>Do not log user-data; it may contain secrets.</remarks>
[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; }
/// <remarks>Do not log this value.</remarks>
[JsonPropertyName("password")]
public string? Password { get; init; }
}
/// <summary>Creates a new VPC.</summary>
public sealed class CreateVpcRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("route_entries")]
public IReadOnlyList<RouteEntryRequest>? RouteEntries { get; init; }
[JsonPropertyName("ip_range")]
public string? IpRange { get; init; }
}
/// <summary>Replaces VPC configuration. Omitted properties are cleared by the provider.</summary>
public sealed class UpdateVpcRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("route_entries")]
public IReadOnlyList<RouteEntryRequest>? RouteEntries { get; init; }
}
/// <summary>Partially updates VPC configuration.</summary>
public sealed class PatchVpcRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string? Name { get; init; }
[JsonPropertyName("route_entries")]
public IReadOnlyList<RouteEntryRequest>? RouteEntries { get; init; }
}
/// <summary>A VPC route entry supplied in a create or update request.</summary>
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; }
}
/// <summary>Creates a new load balancer.</summary>
public sealed class CreateLoadBalancerRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("forwarding_rules")]
public IReadOnlyList<ForwardingRuleRequest>? ForwardingRules { get; init; }
[JsonPropertyName("health_check")]
public HealthCheckRequest? HealthCheck { get; init; }
[JsonPropertyName("server_ids")]
public IReadOnlyList<long>? ServerIds { get; init; }
[JsonPropertyName("region")]
public string? Region { get; init; }
}
/// <summary>Updates a load balancer.</summary>
public sealed class UpdateLoadBalancerRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("forwarding_rules")]
public IReadOnlyList<ForwardingRuleRequest>? ForwardingRules { get; init; }
[JsonPropertyName("health_check")]
public HealthCheckRequest? HealthCheck { get; init; }
[JsonPropertyName("server_ids")]
public IReadOnlyList<long>? ServerIds { get; init; }
}
/// <summary>A load-balancer forwarding rule supplied in a request.</summary>
public sealed class ForwardingRuleRequest : BinaryLaneRequestModel
{
/// <summary>Either <c>http</c> or <c>https</c>.</summary>
[JsonPropertyName("entry_protocol")]
public string EntryProtocol { get; init; } = string.Empty;
}
/// <summary>A collection of load-balancer forwarding rules.</summary>
public sealed class ForwardingRulesRequest : BinaryLaneRequestModel
{
[JsonPropertyName("forwarding_rules")]
public IReadOnlyList<ForwardingRuleRequest> ForwardingRules { get; init; } = Array.Empty<ForwardingRuleRequest>();
}
/// <summary>A load-balancer health check supplied in a request.</summary>
public sealed class HealthCheckRequest : BinaryLaneRequestModel
{
/// <summary>One of <c>http</c>, <c>https</c>, or <c>both</c>.</summary>
[JsonPropertyName("protocol")]
public string? Protocol { get; init; }
[JsonPropertyName("path")]
public string? Path { get; init; }
}
/// <summary>A collection of server IDs supplied to a load-balancer operation.</summary>
public sealed class ServerIdsRequest : BinaryLaneRequestModel
{
[JsonPropertyName("server_ids")]
public IReadOnlyList<long> ServerIds { get; init; } = Array.Empty<long>();
}
/// <summary>Creates a DNS domain.</summary>
public sealed class DomainRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("ip_address")]
public string? IpAddress { get; init; }
}
/// <summary>Requests refresh of the nameserver cache for domains.</summary>
public sealed class DomainRefreshRequest : BinaryLaneRequestModel
{
[JsonPropertyName("domain_names")]
public IReadOnlyList<string> DomainNames { get; init; } = Array.Empty<string>();
}
/// <summary>Creates a DNS record.</summary>
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; }
}
/// <summary>Updates only the supplied parts of a DNS record.</summary>
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; }
}
/// <summary>Creates an SSH key.</summary>
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; }
}
/// <summary>Updates an SSH key.</summary>
public sealed class UpdateSshKeyRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("default")]
public bool? IsDefault { get; init; }
}
/// <summary>Changes mutable metadata on an image.</summary>
public sealed class ImageRequest : BinaryLaneRequestModel
{
[JsonPropertyName("name")]
public string? Name { get; init; }
[JsonPropertyName("locked")]
public bool? Locked { get; init; }
}
/// <summary>Uploads an image into a backup slot.</summary>
public sealed class UploadImageRequest : BinaryLaneRequestModel
{
/// <summary>Provider backup slot, required unless the replacement strategy is <c>specified</c>.</summary>
[JsonPropertyName("backup_type")]
public string? BackupType { get; init; }
/// <summary>Provider replacement strategy: <c>none</c>, <c>specified</c>, <c>oldest</c>, or <c>newest</c>.</summary>
[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;
}
/// <summary>Updates the IPv6 reverse nameservers configured for the account.</summary>
public sealed class ReverseNameserversRequest : BinaryLaneRequestModel
{
[JsonPropertyName("reverse_nameservers")]
public IReadOnlyList<string> ReverseNameservers { get; init; } = Array.Empty<string>();
}
/// <summary>Allows or declines the interaction requested by an action.</summary>
public sealed class ProceedRequest : BinaryLaneRequestModel
{
[JsonPropertyName("proceed")]
public bool Proceed { get; init; }
}
/// <summary>Updates threshold-alert configuration.</summary>
public sealed class ThresholdAlertRequest : BinaryLaneRequestModel
{
/// <summary>Provider alert type.</summary>
[JsonPropertyName("alert_type")]
public string AlertType { get; init; } = string.Empty;
[JsonPropertyName("enabled")]
public bool? Enabled { get; init; }
[JsonPropertyName("value")]
public int? Value { get; init; }
}
+653
View File
@@ -0,0 +1,653 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>The current billing balance for the authenticated account.</summary>
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<ChargeInformation> Charges { get; init; } = Array.Empty<ChargeInformation>();
[JsonPropertyName("generated_at")]
public DateTimeOffset? GeneratedAt { get; init; }
}
/// <summary>A charge included in a balance response.</summary>
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; }
}
/// <summary>A BinaryLane invoice.</summary>
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<InvoiceLineItem> InvoiceItems { get; init; } = Array.Empty<InvoiceLineItem>();
[JsonPropertyName("invoice_download_url")]
public string? InvoiceDownloadUrl { get; init; }
[JsonPropertyName("invoice_view_url")]
public string? InvoiceViewUrl { get; init; }
}
/// <summary>A line item in a BinaryLane invoice.</summary>
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; }
}
/// <summary>Current data-transfer use for a server.</summary>
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; }
}
/// <summary>A DNS domain managed through the BinaryLane API.</summary>
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<string> CurrentNameservers { get; init; } = Array.Empty<string>();
[JsonPropertyName("ttl")]
public int? Ttl { get; init; }
[JsonPropertyName("zone_file")]
public string ZoneFile { get; init; } = string.Empty;
}
/// <summary>A DNS record in a BinaryLane-managed domain.</summary>
public sealed class DomainRecord : BinaryLaneDto
{
[JsonPropertyName("id")]
public long Id { get; init; }
/// <summary>Provider DNS record type, such as <c>A</c>, <c>AAAA</c>, or <c>TXT</c>.</summary>
[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; }
}
/// <summary>A temporary download link for an image.</summary>
public sealed class ImageDownload : BinaryLaneDto
{
[JsonPropertyName("id")]
public long Id { get; init; }
[JsonPropertyName("expiry")]
public DateTimeOffset Expiry { get; init; }
[JsonPropertyName("disks")]
public IReadOnlyList<ImageDiskDownload> Disks { get; init; } = Array.Empty<ImageDiskDownload>();
}
/// <summary>Download URLs for one disk in an image download.</summary>
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;
}
/// <summary>An SSH public key owned by the authenticated account.</summary>
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; }
}
/// <summary>A BinaryLane load balancer.</summary>
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;
/// <summary>Provider load balancer status, such as <c>active</c>.</summary>
[JsonPropertyName("status")]
public string Status { get; init; } = string.Empty;
[JsonPropertyName("created_at")]
public DateTimeOffset CreatedAt { get; init; }
[JsonPropertyName("forwarding_rules")]
public IReadOnlyList<ForwardingRule> ForwardingRules { get; init; } = Array.Empty<ForwardingRule>();
[JsonPropertyName("health_check")]
public HealthCheck HealthCheck { get; init; } = new();
[JsonPropertyName("region")]
public Region? Region { get; init; }
[JsonPropertyName("server_ids")]
public IReadOnlyList<long> ServerIds { get; init; } = Array.Empty<long>();
}
/// <summary>A load-balancer forwarding rule.</summary>
public sealed class ForwardingRule : BinaryLaneDto
{
/// <summary>Provider rule protocol, either <c>http</c> or <c>https</c>.</summary>
[JsonPropertyName("entry_protocol")]
public string EntryProtocol { get; init; } = string.Empty;
}
/// <summary>A load-balancer health check.</summary>
public sealed class HealthCheck : BinaryLaneDto
{
/// <summary>Provider health check protocol, such as <c>http</c>, <c>https</c>, or <c>both</c>.</summary>
[JsonPropertyName("protocol")]
public string Protocol { get; init; } = string.Empty;
[JsonPropertyName("path")]
public string Path { get; init; } = string.Empty;
}
/// <summary>A load-balancer configuration option available to the account.</summary>
public sealed class LoadBalancerAvailabilityOption : BinaryLaneDto
{
[JsonPropertyName("regions")]
public IReadOnlyList<string>? 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; }
}
/// <summary>A virtual private cloud.</summary>
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<RouteEntry> RouteEntries { get; init; } = Array.Empty<RouteEntry>();
}
/// <summary>A route within a VPC.</summary>
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; }
}
/// <summary>A resource connected to a VPC.</summary>
public sealed class VpcMember : BinaryLaneDto
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
/// <summary>Provider resource type.</summary>
[JsonPropertyName("resource_type")]
public string ResourceType { get; init; } = string.Empty;
/// <remarks>The provider documents this as a string, unlike action resource identifiers.</remarks>
[JsonPropertyName("resource_id")]
public string ResourceId { get; init; } = string.Empty;
[JsonPropertyName("created_at")]
public DateTimeOffset? CreatedAt { get; init; }
}
/// <summary>A scheduled backup window.</summary>
public sealed class BackupWindow : BinaryLaneDto
{
[JsonPropertyName("start")]
public DateTimeOffset Start { get; init; }
[JsonPropertyName("end")]
public DateTimeOffset End { get; init; }
}
/// <summary>A server's configured backup schedule.</summary>
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; }
}
/// <summary>Offsite-backup configuration for a server.</summary>
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; }
}
/// <summary>The host backing a server.</summary>
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; }
}
/// <summary>Metadata about a backup currently attached to a server.</summary>
public sealed class AttachedBackup : BinaryLaneDto
{
[JsonPropertyName("id")]
public long Id { get; init; }
[JsonPropertyName("disk_identifiers")]
public IReadOnlyList<string> DiskIdentifiers { get; init; } = Array.Empty<string>();
[JsonPropertyName("attached_at")]
public DateTimeOffset? AttachedAt { get; init; }
[JsonPropertyName("attachment_expires")]
public DateTimeOffset? AttachmentExpires { get; init; }
}
/// <summary>Advanced virtual-machine features configured for a server.</summary>
public sealed class AdvancedServerFeatures : BinaryLaneDto
{
[JsonPropertyName("processor_model")]
public long? ProcessorModel { get; init; }
/// <summary>Provider virtual machine type.</summary>
[JsonPropertyName("machine_type")]
public string? MachineType { get; init; }
/// <summary>Provider virtual video device.</summary>
[JsonPropertyName("video_device")]
public string VideoDevice { get; init; } = string.Empty;
/// <summary>Provider advanced-feature values.</summary>
[JsonPropertyName("enabled_advanced_features")]
public IReadOnlyList<string> EnabledAdvancedFeatures { get; init; } = Array.Empty<string>();
}
/// <summary>Advanced server features currently available to the account.</summary>
public sealed class AvailableAdvancedServerFeatures : BinaryLaneDto
{
[JsonPropertyName("processor_models")]
public IReadOnlyList<ProcessorModel> ProcessorModels { get; init; } = Array.Empty<ProcessorModel>();
[JsonPropertyName("machine_types")]
public IReadOnlyList<string> MachineTypes { get; init; } = Array.Empty<string>();
[JsonPropertyName("advanced_features")]
public IReadOnlyList<string> AdvancedFeatures { get; init; } = Array.Empty<string>();
}
/// <summary>An available processor model.</summary>
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; }
}
/// <summary>An advanced firewall rule applied to a server.</summary>
public sealed class AdvancedFirewallRule : BinaryLaneDto
{
[JsonPropertyName("source_addresses")]
public IReadOnlyList<string> SourceAddresses { get; init; } = Array.Empty<string>();
[JsonPropertyName("destination_addresses")]
public IReadOnlyList<string> DestinationAddresses { get; init; } = Array.Empty<string>();
[JsonPropertyName("destination_ports")]
public IReadOnlyList<string>? DestinationPorts { get; init; }
/// <summary>Provider protocol value, such as <c>tcp</c>.</summary>
[JsonPropertyName("protocol")]
public string Protocol { get; init; } = string.Empty;
/// <summary>Provider action value, such as <c>accept</c>.</summary>
[JsonPropertyName("action")]
public string Action { get; init; } = string.Empty;
[JsonPropertyName("description")]
public string? Description { get; init; }
}
/// <summary>An available server kernel.</summary>
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; }
}
/// <summary>A configured server threshold alert.</summary>
public sealed class ThresholdAlert : BinaryLaneDto
{
/// <summary>Provider alert type.</summary>
[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; }
}
/// <summary>Software licensed on a server.</summary>
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; }
}
/// <summary>A software product that can be licensed on BinaryLane servers.</summary>
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<string> SupportedOperatingSystems { get; init; } = Array.Empty<string>();
}
/// <summary>A browser console session for a server.</summary>
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; }
}
/// <summary>Server user-data retained by BinaryLane.</summary>
public sealed class UserData : BinaryLaneDto
{
[JsonPropertyName("user_data")]
public string? Value { get; init; }
}
/// <summary>A sample interval for server monitoring data.</summary>
public sealed class Period : BinaryLaneDto
{
[JsonPropertyName("start")]
public DateTimeOffset Start { get; init; }
[JsonPropertyName("end")]
public DateTimeOffset End { get; init; }
/// <summary>Provider interval, such as <c>five-minute</c> or <c>day</c>.</summary>
[JsonPropertyName("data_interval")]
public string DataInterval { get; init; } = string.Empty;
}
/// <summary>One monitoring aggregate sample.</summary>
public sealed class Sample : BinaryLaneDto
{
[JsonPropertyName("cpu_usage_percent")]
public double CpuUsagePercent { get; init; }
[JsonPropertyName("cpu_usage_detailed")]
public IReadOnlyList<double> CpuUsageDetailed { get; init; } = Array.Empty<double>();
[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; }
}
/// <summary>A set of monitoring samples for a server.</summary>
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; }
}
+472
View File
@@ -0,0 +1,472 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>Pagination metadata returned by BinaryLane list endpoints.</summary>
public sealed class PageMeta : BinaryLaneDto
{
[JsonPropertyName("total")]
public int Total { get; init; }
}
/// <summary>Pagination links returned by BinaryLane list endpoints.</summary>
public sealed class PageLinks : BinaryLaneDto
{
[JsonPropertyName("pages")]
public PageNavigation Pages { get; init; } = new();
}
/// <summary>URLs for neighbouring pages in a BinaryLane list response.</summary>
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; }
}
/// <summary>An RFC 7807-style problem returned by BinaryLane.</summary>
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; }
}
/// <summary>A validation problem returned by BinaryLane.</summary>
public sealed class ValidationProblemDetails : ProblemDetails
{
[JsonPropertyName("errors")]
public IReadOnlyDictionary<string, IReadOnlyList<string>>? 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<ActionLink> Actions { get; init; } = Array.Empty<ActionLink>();
}
public sealed class ActionsResponse : BinaryLaneDto
{
[JsonPropertyName("meta")]
public PageMeta Meta { get; init; } = new();
[JsonPropertyName("links")]
public PageLinks? Links { get; init; }
[JsonPropertyName("actions")]
public IReadOnlyList<BinaryLaneAction> Actions { get; init; } = Array.Empty<BinaryLaneAction>();
}
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<Invoice> Invoices { get; init; } = Array.Empty<Invoice>();
}
public sealed class UnpaidFailedInvoicesResponse : BinaryLaneDto
{
[JsonPropertyName("invoices")]
public IReadOnlyList<Invoice> Invoices { get; init; } = Array.Empty<Invoice>();
}
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<DataUsage> DataUsages { get; init; } = Array.Empty<DataUsage>();
}
public sealed class LocalNameserversResponse : BinaryLaneDto
{
[JsonPropertyName("local_nameservers")]
public IReadOnlyList<string> LocalNameservers { get; init; } = Array.Empty<string>();
}
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<Domain> Domains { get; init; } = Array.Empty<Domain>();
}
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<DomainRecord> DomainRecords { get; init; } = Array.Empty<DomainRecord>();
}
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<Image> Images { get; init; } = Array.Empty<Image>();
}
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<SshKey> SshKeys { get; init; } = Array.Empty<SshKey>();
}
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<LoadBalancer> LoadBalancers { get; init; } = Array.Empty<LoadBalancer>();
}
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<LoadBalancerAvailabilityOption> LoadBalancerAvailabilityOptions { get; init; } = Array.Empty<LoadBalancerAvailabilityOption>();
}
public sealed class RegionsResponse : BinaryLaneDto
{
[JsonPropertyName("meta")]
public PageMeta Meta { get; init; } = new();
[JsonPropertyName("links")]
public PageLinks? Links { get; init; }
[JsonPropertyName("regions")]
public IReadOnlyList<Region> Regions { get; init; } = Array.Empty<Region>();
}
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<string> ReverseNameservers { get; init; } = Array.Empty<string>();
}
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<SampleSet> SampleSets { get; init; } = Array.Empty<SampleSet>();
}
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<Server> Servers { get; init; } = Array.Empty<Server>();
}
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<AdvancedFirewallRule> FirewallRules { get; init; } = Array.Empty<AdvancedFirewallRule>();
}
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<Image> Backups { get; init; } = Array.Empty<Image>();
}
public sealed class KernelsResponse : BinaryLaneDto
{
[JsonPropertyName("meta")]
public PageMeta Meta { get; init; } = new();
[JsonPropertyName("links")]
public PageLinks? Links { get; init; }
[JsonPropertyName("kernels")]
public IReadOnlyList<Kernel> Kernels { get; init; } = Array.Empty<Kernel>();
}
public sealed class SnapshotsResponse : BinaryLaneDto
{
[JsonPropertyName("meta")]
public PageMeta Meta { get; init; } = new();
[JsonPropertyName("links")]
public PageLinks? Links { get; init; }
[JsonPropertyName("snapshots")]
public IReadOnlyList<Image> Snapshots { get; init; } = Array.Empty<Image>();
}
public sealed class ThresholdAlertsResponse : BinaryLaneDto
{
[JsonPropertyName("threshold_alerts")]
public IReadOnlyList<ThresholdAlert> ThresholdAlerts { get; init; } = Array.Empty<ThresholdAlert>();
}
public sealed class CurrentServerAlertsResponse : BinaryLaneDto
{
[JsonPropertyName("server_ids")]
public IReadOnlyList<long> ServerIds { get; init; } = Array.Empty<long>();
}
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> LicensedSoftware { get; init; } = Array.Empty<LicensedSoftware>();
}
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<Size> Sizes { get; init; } = Array.Empty<Size>();
}
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> Software { get; init; } = Array.Empty<Software>();
}
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<Vpc> Vpcs { get; init; } = Array.Empty<Vpc>();
}
public sealed class VpcMembersResponse : BinaryLaneDto
{
[JsonPropertyName("meta")]
public PageMeta Meta { get; init; } = new();
[JsonPropertyName("links")]
public PageLinks? Links { get; init; }
[JsonPropertyName("members")]
public IReadOnlyList<VpcMember> Members { get; init; } = Array.Empty<VpcMember>();
}
@@ -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;
/// <summary>
/// Serializes the BinaryLane server-action discriminated union when callers hold
/// an action through its <see cref="ServerAction"/> base type.
/// </summary>
/// <remarks>
/// System.Text.Json normally uses the statically declared type. That would emit
/// only <c>type</c> for a <see cref="ServerAction"/> variable whose runtime
/// value is, for example, <see cref="ResizeServerAction"/>. 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 <see cref="UnknownServerAction"/>.
/// </remarks>
public sealed class ServerActionJsonConverter : JsonConverter<ServerAction>
{
private static readonly PropertyInfo AdditionalPropertiesProperty =
typeof(BinaryLaneDto).GetProperty(nameof(BinaryLaneDto.AdditionalProperties))
?? throw new InvalidOperationException("BinaryLaneDto must expose extension data.");
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeof(ServerAction).IsAssignableFrom(typeToConvert);
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, ServerAction value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString("type", value.Type);
var writtenProperties = new HashSet<string>(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<string, JsonElement> 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<string>(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<string, JsonElement>(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<PropertyInfo> 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<JsonIgnoreAttribute>()?.Condition == JsonIgnoreCondition.Always)
{
continue;
}
yield return property;
}
}
private static string GetJsonName(PropertyInfo property) =>
property.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? property.Name;
}
@@ -0,0 +1,521 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace BinaryLane.Api.V2.Models;
/// <summary>
/// Base payload for <c>POST /v2/servers/{server_id}/actions</c>.
/// </summary>
/// <remarks>
/// The upstream contract is a discriminated union on <c>type</c>. 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. <see cref="UnknownServerAction"/> permits new provider
/// action types without waiting for an SDK release.
/// </remarks>
public class ServerAction : BinaryLaneRequestModel
{
/// <summary>Creates an empty action for serializers and advanced callers.</summary>
public ServerAction()
{
}
/// <summary>Creates a typed action with the provider discriminator value.</summary>
protected ServerAction(string type)
{
Type = type;
}
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
}
/// <summary>An action type not yet represented by a concrete SDK class.</summary>
public sealed class UnknownServerAction : ServerAction
{
public UnknownServerAction(string type)
: base(type)
{
}
}
/// <summary>Adds an additional disk to a server.</summary>
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; }
}
/// <summary>Attaches a backup image to a server.</summary>
public sealed class AttachBackupServerAction : ServerAction
{
public AttachBackupServerAction() : base("attach_backup") { }
[JsonPropertyName("image")]
public long ImageId { get; init; }
}
/// <summary>Changes advanced virtual-machine features.</summary>
public sealed class ChangeAdvancedFeaturesServerAction : ServerAction
{
public ChangeAdvancedFeaturesServerAction() : base("change_advanced_features") { }
[JsonPropertyName("enabled_advanced_features")]
public IReadOnlyList<string>? 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; }
}
/// <summary>Replaces the advanced firewall rules on a server.</summary>
public sealed class ChangeAdvancedFirewallRulesServerAction : ServerAction
{
public ChangeAdvancedFirewallRulesServerAction() : base("change_advanced_firewall_rules") { }
[JsonPropertyName("firewall_rules")]
public IReadOnlyList<AdvancedFirewallRuleRequest> FirewallRules { get; init; } = Array.Empty<AdvancedFirewallRuleRequest>();
}
/// <summary>Changes a server backup schedule.</summary>
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; }
}
/// <summary>Enables or disables IPv6 for a server.</summary>
public sealed class ChangeIpv6ServerAction : ServerAction
{
public ChangeIpv6ServerAction() : base("change_ipv6") { }
[JsonPropertyName("enabled")]
public bool Enabled { get; init; }
}
/// <summary>Changes a server's IPv6 reverse nameservers.</summary>
public sealed class ChangeIpv6ReverseNameserversServerAction : ServerAction
{
public ChangeIpv6ReverseNameserversServerAction() : base("change_ipv6_reverse_nameservers") { }
[JsonPropertyName("ipv6_reverse_nameservers")]
public IReadOnlyList<string> Ipv6ReverseNameservers { get; init; } = Array.Empty<string>();
}
/// <summary>Changes a server kernel.</summary>
public sealed class ChangeKernelServerAction : ServerAction
{
public ChangeKernelServerAction() : base("change_kernel") { }
[JsonPropertyName("kernel")]
public long KernelId { get; init; }
}
/// <summary>Changes whether offsite backup copies are managed automatically.</summary>
public sealed class ChangeManageOffsiteBackupCopiesServerAction : ServerAction
{
public ChangeManageOffsiteBackupCopiesServerAction() : base("change_manage_offsite_backup_copies") { }
[JsonPropertyName("manage_offsite_backup_copies")]
public bool ManageOffsiteBackupCopies { get; init; }
}
/// <summary>Moves a server onto or off a VPC network.</summary>
public sealed class ChangeNetworkServerAction : ServerAction
{
public ChangeNetworkServerAction() : base("change_network") { }
[JsonPropertyName("vpc_id")]
public long? VpcId { get; init; }
}
/// <summary>Changes the server's offsite backup location.</summary>
public sealed class ChangeOffsiteBackupLocationServerAction : ServerAction
{
public ChangeOffsiteBackupLocationServerAction() : base("change_offsite_backup_location") { }
[JsonPropertyName("offsite_backup_location")]
public string? OffsiteBackupLocation { get; init; }
}
/// <summary>Changes the server's partner server relationship.</summary>
public sealed class ChangePartnerServerAction : ServerAction
{
public ChangePartnerServerAction() : base("change_partner") { }
[JsonPropertyName("partner_server_id")]
public long? PartnerServerId { get; init; }
}
/// <summary>Enables or disables port blocking.</summary>
public sealed class ChangePortBlockingServerAction : ServerAction
{
public ChangePortBlockingServerAction() : base("change_port_blocking") { }
[JsonPropertyName("enabled")]
public bool Enabled { get; init; }
}
/// <summary>Moves a server to a new region.</summary>
public sealed class ChangeRegionServerAction : ServerAction
{
public ChangeRegionServerAction() : base("change_region") { }
[JsonPropertyName("region")]
public string Region { get; init; } = string.Empty;
}
/// <summary>Changes an IPv4 reverse name.</summary>
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; }
}
/// <summary>Enables or disables a separate private network interface.</summary>
public sealed class ChangeSeparatePrivateNetworkInterfaceServerAction : ServerAction
{
public ChangeSeparatePrivateNetworkInterfaceServerAction() : base("change_separate_private_network_interface") { }
[JsonPropertyName("enabled")]
public bool Enabled { get; init; }
}
/// <summary>Enables or disables source-and-destination checking.</summary>
public sealed class ChangeSourceAndDestinationCheckServerAction : ServerAction
{
public ChangeSourceAndDestinationCheckServerAction() : base("change_source_and_destination_check") { }
[JsonPropertyName("enabled")]
public bool Enabled { get; init; }
}
/// <summary>Replaces a server's threshold-alert configuration.</summary>
public sealed class ChangeThresholdAlertsServerAction : ServerAction
{
public ChangeThresholdAlertsServerAction() : base("change_threshold_alerts") { }
[JsonPropertyName("threshold_alerts")]
public IReadOnlyList<ThresholdAlertRequest> ThresholdAlerts { get; init; } = Array.Empty<ThresholdAlertRequest>();
}
/// <summary>Changes a VPC IPv4 address on a server.</summary>
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;
}
/// <summary>Clones a backup image onto another server.</summary>
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; }
}
/// <summary>Deletes a non-primary disk.</summary>
public sealed class DeleteDiskServerAction : ServerAction
{
public DeleteDiskServerAction() : base("delete_disk") { }
[JsonPropertyName("disk_id")]
public long DiskId { get; init; }
}
/// <summary>Detaches an attached backup from a server.</summary>
public sealed class DetachBackupServerAction : ServerAction
{
public DetachBackupServerAction() : base("detach_backup") { }
}
/// <summary>Disables automatic backups.</summary>
public sealed class DisableBackupsServerAction : ServerAction
{
public DisableBackupsServerAction() : base("disable_backups") { }
}
/// <summary>Disables SELinux in a compatible installed distribution.</summary>
public sealed class DisableSelinuxServerAction : ServerAction
{
public DisableSelinuxServerAction() : base("disable_selinux") { }
}
/// <summary>Enables automatic backups.</summary>
public sealed class EnableBackupsServerAction : ServerAction
{
public EnableBackupsServerAction() : base("enable_backups") { }
}
/// <summary>Enables IPv6 on a server.</summary>
public sealed class EnableIpv6ServerAction : ServerAction
{
public EnableIpv6ServerAction() : base("enable_ipv6") { }
}
/// <summary>Checks whether a server is running.</summary>
public sealed class IsRunningServerAction : ServerAction
{
public IsRunningServerAction() : base("is_running") { }
}
/// <summary>Resets a server password.</summary>
public sealed class PasswordResetServerAction : ServerAction
{
public PasswordResetServerAction() : base("password_reset") { }
[JsonPropertyName("username")]
public string? Username { get; init; }
/// <remarks>Do not log this value.</remarks>
[JsonPropertyName("password")]
public string? Password { get; init; }
}
/// <summary>Pings a server.</summary>
public sealed class PingServerAction : ServerAction
{
public PingServerAction() : base("ping") { }
}
/// <summary>Power cycles a server.</summary>
public sealed class PowerCycleServerAction : ServerAction
{
public PowerCycleServerAction() : base("power_cycle") { }
}
/// <summary>Powers a server off.</summary>
public sealed class PowerOffServerAction : ServerAction
{
public PowerOffServerAction() : base("power_off") { }
}
/// <summary>Powers a server on.</summary>
public sealed class PowerOnServerAction : ServerAction
{
public PowerOnServerAction() : base("power_on") { }
}
/// <summary>Reboots a server.</summary>
public sealed class RebootServerAction : ServerAction
{
public RebootServerAction() : base("reboot") { }
}
/// <summary>Rebuilds a server from an image.</summary>
public sealed class RebuildServerAction : ServerAction
{
public RebuildServerAction() : base("rebuild") { }
/// <summary>An image ID or image slug. Omit to reuse the existing image.</summary>
[JsonPropertyName("image")]
public object? Image { get; init; }
[JsonPropertyName("options")]
public ImageOptions? Options { get; init; }
}
/// <summary>Renames a server.</summary>
public sealed class RenameServerAction : ServerAction
{
public RenameServerAction() : base("rename") { }
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
}
/// <summary>Changes selected size options during a resize action.</summary>
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<string>? Ipv4AddressesToRemove { get; init; }
}
/// <summary>Changes the source image selected by a resize operation.</summary>
public sealed class ChangeImage : BinaryLaneRequestModel
{
/// <summary>An image ID or image slug.</summary>
[JsonPropertyName("image")]
public object? Image { get; init; }
[JsonPropertyName("options")]
public ImageOptions? Options { get; init; }
}
/// <summary>Changes licences selected by a resize operation.</summary>
public sealed class ChangeLicenses : BinaryLaneRequestModel
{
[JsonPropertyName("licenses")]
public IReadOnlyList<License> Licenses { get; init; } = Array.Empty<License>();
}
/// <summary>Resizes a server.</summary>
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; }
}
/// <summary>Resizes an individual server disk.</summary>
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; }
}
/// <summary>Restores a server from an image.</summary>
public sealed class RestoreServerAction : ServerAction
{
public RestoreServerAction() : base("restore") { }
/// <summary>An image ID or image slug.</summary>
[JsonPropertyName("image")]
public object Image { get; init; } = string.Empty;
}
/// <summary>Requests a graceful shutdown.</summary>
public sealed class ShutdownServerAction : ServerAction
{
public ShutdownServerAction() : base("shutdown") { }
}
/// <summary>Creates a backup of a server.</summary>
public sealed class TakeBackupServerAction : ServerAction
{
public TakeBackupServerAction() : base("take_backup") { }
/// <summary>Provider backup slot, such as <c>daily</c> or <c>temporary</c>.</summary>
[JsonPropertyName("backup_type")]
public string? BackupType { get; init; }
/// <summary>Provider replacement strategy.</summary>
[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; }
}
/// <summary>Reverses a scheduled server cancellation.</summary>
public sealed class UncancelServerAction : ServerAction
{
public UncancelServerAction() : base("uncancel") { }
}
/// <summary>Checks server uptime.</summary>
public sealed class UptimeServerAction : ServerAction
{
public UptimeServerAction() : base("uptime") { }
}
/// <summary>An advanced firewall rule supplied in a server-action request.</summary>
public sealed class AdvancedFirewallRuleRequest : BinaryLaneRequestModel
{
[JsonPropertyName("source_addresses")]
public IReadOnlyList<string> SourceAddresses { get; init; } = Array.Empty<string>();
[JsonPropertyName("destination_addresses")]
public IReadOnlyList<string> DestinationAddresses { get; init; } = Array.Empty<string>();
[JsonPropertyName("destination_ports")]
public IReadOnlyList<string>? DestinationPorts { get; init; }
/// <summary>Provider protocol value, such as <c>tcp</c>.</summary>
[JsonPropertyName("protocol")]
public string Protocol { get; init; } = string.Empty;
/// <summary>Provider action value, such as <c>accept</c>.</summary>
[JsonPropertyName("action")]
public string Action { get; init; } = string.Empty;
[JsonPropertyName("description")]
public string? Description { get; init; }
}
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace BinaryLane.Api.V2.Pagination;
/// <summary>A page returned from a BinaryLane list endpoint.</summary>
/// <typeparam name="T">Item type.</typeparam>
public sealed class Page<T>
{
internal Page(
IReadOnlyList<T> items,
int total,
Uri? firstPage,
Uri? previousPage,
Uri? nextPage,
Uri? lastPage)
{
Items = new ReadOnlyCollection<T>(new List<T>(items));
Total = total;
FirstPage = firstPage;
PreviousPage = previousPage;
NextPage = nextPage;
LastPage = lastPage;
}
/// <summary>Items returned in this response.</summary>
public IReadOnlyList<T> Items { get; }
/// <summary>Total number of items available across all pages, when supplied by BinaryLane.</summary>
public int Total { get; }
/// <summary>First result page, when supplied by BinaryLane.</summary>
public Uri? FirstPage { get; }
/// <summary>Previous result page, when supplied by BinaryLane.</summary>
public Uri? PreviousPage { get; }
/// <summary>Next result page, when supplied by BinaryLane.</summary>
public Uri? NextPage { get; }
/// <summary>Last result page, when supplied by BinaryLane.</summary>
public Uri? LastPage { get; }
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
namespace BinaryLane.Api.V2.Pagination;
/// <summary>Specifies a one-based BinaryLane API result page.</summary>
public sealed class PageRequest
{
/// <summary>One-based page number.</summary>
public int Page { get; set; } = 1;
/// <summary>Number of results to return, from 1 through 200.</summary>
public int PerPage { get; set; } = 20;
internal IReadOnlyDictionary<string, object?> 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<string, object?>
{
["page"] = Page,
["per_page"] = PerPage,
};
}
}
@@ -0,0 +1,28 @@
using System.Net;
using System.Text.Json;
using BinaryLane.Api.V2.Models;
namespace BinaryLane.Api.V2.Resources;
/// <summary>Result of submitting an asynchronous server action.</summary>
public sealed class ActionSubmission
{
internal ActionSubmission(HttpStatusCode statusCode, BinaryLaneAction? action, JsonElement response)
{
StatusCode = statusCode;
Action = action;
Response = response;
}
/// <summary>HTTP status returned by BinaryLane, commonly 200 or 202.</summary>
public HttpStatusCode StatusCode { get; }
/// <summary>The action supplied by BinaryLane, when present in the response.</summary>
public BinaryLaneAction? Action { get; }
/// <summary>Raw response for forward-compatible access to provider fields.</summary>
public JsonElement Response { get; }
/// <summary>Whether BinaryLane accepted the request for asynchronous completion.</summary>
public bool IsAccepted => StatusCode == HttpStatusCode.Accepted;
}
@@ -0,0 +1,26 @@
using System;
namespace BinaryLane.Api.V2.Resources;
/// <summary>Controls opt-in polling of a BinaryLane asynchronous action.</summary>
public sealed class ActionWaitOptions
{
/// <summary>Delay between action status checks.</summary>
public TimeSpan PollInterval { get; set; } = TimeSpan.FromSeconds(2);
/// <summary>Maximum time spent polling before a timeout is reported.</summary>
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.");
}
}
}
@@ -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;
/// <summary>Shared JSON-envelope and pagination behavior for resource APIs.</summary>
public abstract class BinaryLaneResourceBase
{
/// <summary>Initializes a resource using the common request executor.</summary>
protected BinaryLaneResourceBase(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
{
Executor = executor ?? throw new ArgumentNullException(nameof(executor));
Json = json ?? throw new ArgumentNullException(nameof(json));
}
/// <summary>Common request executor.</summary>
protected IBinaryLaneApiExecutor Executor { get; }
/// <summary>Common JSON options.</summary>
protected BinaryLaneJsonSerializerOptions Json { get; }
/// <summary>Gets a raw JSON response, allowing forward-compatible access to a newly added API field.</summary>
protected Task<JsonElement> GetRawAsync(
string path,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
Executor.GetAsync<JsonElement>(path, query, cancellationToken);
/// <summary>Sends a raw JSON mutation request.</summary>
protected async Task<JsonElement> SendRawAsync(
System.Net.Http.HttpMethod method,
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default)
{
var response = await Executor.SendAsync<JsonElement>(
new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken).ConfigureAwait(false);
return response.Body;
}
/// <summary>Sends a raw request and retains the HTTP response metadata.</summary>
protected Task<BinaryLaneResponse<JsonElement>> SendResponseAsync(
System.Net.Http.HttpMethod method,
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default) =>
Executor.SendAsync<JsonElement>(
new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken);
/// <summary>Sends a request expected to have no response body.</summary>
protected async Task SendNoContentAsync(
System.Net.Http.HttpMethod method,
string path,
object? body = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default)
{
await Executor.SendAsync(
new BinaryLaneRequest(method, path) { Body = body, Query = query }, cancellationToken).ConfigureAwait(false);
}
/// <summary>Gets a response whose value is wrapped in a named JSON property.</summary>
protected async Task<T> GetItemAsync<T>(
string path,
string propertyName,
CancellationToken cancellationToken = default)
{
var document = await GetRawAsync(path, null, cancellationToken).ConfigureAwait(false);
return DeserializeEnvelope<T>(document, propertyName);
}
/// <summary>Gets a response whose JSON body is the requested value rather than an envelope.</summary>
protected async Task<T> GetDirectItemAsync<T>(
string path,
CancellationToken cancellationToken = default)
{
var document = await GetRawAsync(path, null, cancellationToken).ConfigureAwait(false);
var item = JsonSerializer.Deserialize<T>(document.GetRawText(), Json.SerializerOptions);
if (item is null)
{
throw new JsonException("BinaryLane response did not contain a usable JSON value.");
}
return item;
}
/// <summary>Sends a mutation whose value is wrapped in a named JSON property.</summary>
protected async Task<T> SendItemAsync<T>(
System.Net.Http.HttpMethod method,
string path,
object? body,
string propertyName,
CancellationToken cancellationToken = default)
{
var document = await Executor.SendAsync<JsonElement>(
new BinaryLaneRequest(method, path) { Body = body }, cancellationToken).ConfigureAwait(false);
return DeserializeEnvelope<T>(document.Body, propertyName);
}
/// <summary>Gets a typed page whose items are wrapped in a named JSON property.</summary>
protected async Task<Page<T>> GetPageAsync<T>(
string path,
string propertyName,
PageRequest? page = null,
IReadOnlyDictionary<string, object?>? query = null,
CancellationToken cancellationToken = default)
{
var parameters = MergeQuery(query, page?.ToQuery());
var document = await GetRawAsync(path, parameters, cancellationToken).ConfigureAwait(false);
return DeserializePage<T>(document, propertyName);
}
/// <summary>Enumerates every available page without enabling retries for mutating requests.</summary>
protected async IAsyncEnumerable<T> GetAllPagesAsync<T>(
string path,
string propertyName,
PageRequest? page = null,
IReadOnlyDictionary<string, object?>? query = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var seenNextPageLinks = new HashSet<string>(StringComparer.Ordinal);
var current = await GetPageAsync<T>(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<T>(
nextPagePath,
propertyName,
null,
null,
cancellationToken).ConfigureAwait(false);
foreach (var item in current.Items)
{
yield return item;
}
}
}
/// <summary>Deserializes an item from a standard BinaryLane JSON response envelope.</summary>
protected T DeserializeEnvelope<T>(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<T>(payload.GetRawText(), Json.SerializerOptions);
if (item is null)
{
throw new JsonException($"BinaryLane response did not contain a usable '{propertyName}' value.");
}
return item;
}
/// <summary>Attempts to deserialize a value only when the named envelope property is present.</summary>
protected bool TryDeserializeEnvelope<T>(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<T>(payload.GetRawText(), Json.SerializerOptions);
return item is not null;
}
/// <summary>Deserializes a standard BinaryLane list response.</summary>
protected Page<T> DeserializePage<T>(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<T>();
foreach (var value in collection.EnumerateArray())
{
var item = JsonSerializer.Deserialize<T>(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<T>(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;
}
/// <summary>Escapes an arbitrary API path segment.</summary>
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<string, object?>? MergeQuery(
IReadOnlyDictionary<string, object?>? primary,
IReadOnlyDictionary<string, object?>? secondary)
{
if (primary is null && secondary is null)
{
return null;
}
var result = new Dictionary<string, object?>();
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;
}
}
@@ -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;
/// <inheritdoc />
public sealed class AccountApi : BinaryLaneResourceBase, IAccountApi
{
/// <summary>Initializes the account resource.</summary>
public AccountApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Account> GetAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<Account>("v2/account", "account", cancellationToken);
/// <inheritdoc />
public Task<JsonElement> GetRawAsync(CancellationToken cancellationToken = default) =>
base.GetRawAsync("v2/account", null, cancellationToken);
}
/// <inheritdoc />
public sealed class ActionsApi : BinaryLaneResourceBase, IActionsApi
{
/// <summary>Initializes the actions resource.</summary>
public ActionsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<BinaryLaneAction> GetAsync(long actionId, CancellationToken cancellationToken = default) =>
GetItemAsync<BinaryLaneAction>($"v2/actions/{actionId}", "action", cancellationToken);
/// <inheritdoc />
public Task<Page<BinaryLaneAction>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<BinaryLaneAction>("v2/actions", "actions", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<BinaryLaneAction> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<BinaryLaneAction>("v2/actions", "actions", page, null, cancellationToken);
/// <inheritdoc />
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);
/// <inheritdoc />
public async Task<BinaryLaneAction> 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);
}
/// <inheritdoc />
public sealed class BillingApi : BinaryLaneResourceBase, IBillingApi
{
/// <summary>Initializes the billing resource.</summary>
public BillingApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Balance> GetBalanceAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<Balance>("v2/customers/my/balance", "balance", cancellationToken);
/// <inheritdoc />
public Task<Invoice> GetInvoiceAsync(long invoiceId, CancellationToken cancellationToken = default) =>
GetItemAsync<Invoice>($"v2/customers/my/invoices/{invoiceId}", "invoice", cancellationToken);
/// <inheritdoc />
public Task<Page<Invoice>> ListInvoicesAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Invoice>("v2/customers/my/invoices", "invoices", page, null, cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<Invoice>> ListUnpaidPaymentFailedInvoicesAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<Invoice>>("v2/customers/my/unpaid-payment-failed-invoices", "invoices", cancellationToken);
}
/// <inheritdoc />
public sealed class DataUsageApi : BinaryLaneResourceBase, IDataUsageApi
{
/// <summary>Initializes the data-usage resource.</summary>
public DataUsageApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<DataUsage>> ListCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<DataUsage>("v2/data_usages/current", "data_usages", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<DataUsage> ListAllCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<DataUsage>("v2/data_usages/current", "data_usages", page, null, cancellationToken);
/// <inheritdoc />
public Task<DataUsage> GetCurrentAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<DataUsage>($"v2/data_usages/{serverId}/current", "data_usage", cancellationToken);
}
/// <inheritdoc />
public sealed class DomainsApi : BinaryLaneResourceBase, IDomainsApi
{
/// <summary>Initializes the domains resource.</summary>
public DomainsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<IReadOnlyList<string>> GetNameserversAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<string>>("v2/domains/nameservers", "local_nameservers", cancellationToken);
/// <inheritdoc />
public Task RefreshNameserverCacheAsync(DomainRefreshRequest request, CancellationToken cancellationToken = default) =>
SendNoContentAsync(HttpMethod.Post, "v2/domains/refresh_nameserver_cache", request ?? throw new ArgumentNullException(nameof(request)), null, cancellationToken);
/// <inheritdoc />
public Task<Page<Domain>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Domain>("v2/domains", "domains", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Domain> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Domain>("v2/domains", "domains", page, null, cancellationToken);
/// <inheritdoc />
public Task<Domain> CreateAsync(DomainRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Domain>(HttpMethod.Post, "v2/domains", request ?? throw new ArgumentNullException(nameof(request)), "domain", cancellationToken);
/// <inheritdoc />
public Task<Domain> GetAsync(string domainName, CancellationToken cancellationToken = default) =>
GetItemAsync<Domain>($"v2/domains/{EscapePathSegment(domainName)}", "domain", cancellationToken);
/// <inheritdoc />
public Task DeleteAsync(string domainName, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync($"v2/domains/{EscapePathSegment(domainName)}", null, null, cancellationToken);
/// <inheritdoc />
public Task<Page<DomainRecord>> ListRecordsAsync(string domainName, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<DomainRecord>($"v2/domains/{EscapePathSegment(domainName)}/records", "domain_records", page, null, cancellationToken);
/// <inheritdoc />
public Task<DomainRecord> CreateRecordAsync(string domainName, DomainRecordRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<DomainRecord>(HttpMethod.Post, $"v2/domains/{EscapePathSegment(domainName)}/records", request ?? throw new ArgumentNullException(nameof(request)), "domain_record", cancellationToken);
/// <inheritdoc />
public Task<DomainRecord> GetRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default) =>
GetItemAsync<DomainRecord>($"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", "domain_record", cancellationToken);
/// <inheritdoc />
public Task<DomainRecord> UpdateRecordAsync(string domainName, long recordId, UpdateDomainRecordRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<DomainRecord>(HttpMethod.Put, $"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", request ?? throw new ArgumentNullException(nameof(request)), "domain_record", cancellationToken);
/// <inheritdoc />
public Task DeleteRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync($"v2/domains/{EscapePathSegment(domainName)}/records/{recordId}", null, null, cancellationToken);
}
/// <inheritdoc />
public sealed class ImagesApi : BinaryLaneResourceBase, IImagesApi
{
/// <summary>Initializes the images resource.</summary>
public ImagesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<Image>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Image>("v2/images", "images", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Image> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Image>("v2/images", "images", page, null, cancellationToken);
/// <inheritdoc />
public Task<Image> GetAsync(string imageIdOrSlug, CancellationToken cancellationToken = default) =>
GetItemAsync<Image>($"v2/images/{EscapePathSegment(imageIdOrSlug)}", "image", cancellationToken);
/// <inheritdoc />
public Task<Image> UpdateAsync(long imageId, ImageRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Image>(HttpMethod.Put, $"v2/images/{imageId}", request ?? throw new ArgumentNullException(nameof(request)), "image", cancellationToken);
/// <inheritdoc />
public Task<ImageDownload> GetDownloadAsync(long imageId, CancellationToken cancellationToken = default) =>
GetItemAsync<ImageDownload>($"v2/images/{imageId}/download", "link", cancellationToken);
}
/// <inheritdoc />
public sealed class SshKeysApi : BinaryLaneResourceBase, ISshKeysApi
{
/// <summary>Initializes the SSH-key resource.</summary>
public SshKeysApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<SshKey>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<SshKey>("v2/account/keys", "ssh_keys", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<SshKey> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<SshKey>("v2/account/keys", "ssh_keys", page, null, cancellationToken);
/// <inheritdoc />
public Task<SshKey> GetAsync(long keyId, CancellationToken cancellationToken = default) =>
GetItemAsync<SshKey>($"v2/account/keys/{keyId}", "ssh_key", cancellationToken);
/// <inheritdoc />
public Task<SshKey> CreateAsync(SshKeyRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<SshKey>(HttpMethod.Post, "v2/account/keys", request ?? throw new ArgumentNullException(nameof(request)), "ssh_key", cancellationToken);
/// <inheritdoc />
public Task<SshKey> UpdateAsync(long keyId, UpdateSshKeyRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<SshKey>(HttpMethod.Put, $"v2/account/keys/{keyId}", request ?? throw new ArgumentNullException(nameof(request)), "ssh_key", cancellationToken);
/// <inheritdoc />
public Task DeleteAsync(long keyId, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync($"v2/account/keys/{keyId}", null, null, cancellationToken);
}
/// <inheritdoc />
public sealed class LoadBalancersApi : BinaryLaneResourceBase, ILoadBalancersApi
{
/// <summary>Initializes the load-balancer resource.</summary>
public LoadBalancersApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<LoadBalancer>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<LoadBalancer>("v2/load_balancers", "load_balancers", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<LoadBalancer> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<LoadBalancer>("v2/load_balancers", "load_balancers", page, null, cancellationToken);
/// <inheritdoc />
public Task<LoadBalancer> GetAsync(long loadBalancerId, CancellationToken cancellationToken = default) =>
GetItemAsync<LoadBalancer>($"v2/load_balancers/{loadBalancerId}", "load_balancer", cancellationToken);
/// <inheritdoc />
public Task<LoadBalancer> CreateAsync(CreateLoadBalancerRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<LoadBalancer>(HttpMethod.Post, "v2/load_balancers", request ?? throw new ArgumentNullException(nameof(request)), "load_balancer", cancellationToken);
/// <inheritdoc />
public Task<LoadBalancer> UpdateAsync(long loadBalancerId, UpdateLoadBalancerRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<LoadBalancer>(HttpMethod.Put, $"v2/load_balancers/{loadBalancerId}", request ?? throw new ArgumentNullException(nameof(request)), "load_balancer", cancellationToken);
/// <inheritdoc />
public Task DeleteAsync(long loadBalancerId, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync($"v2/load_balancers/{loadBalancerId}", null, null, cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<LoadBalancerAvailabilityOption>> GetAvailabilityAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<LoadBalancerAvailabilityOption>>(
"v2/load_balancers/availability",
"load_balancer_availability_options",
cancellationToken);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
}
/// <inheritdoc />
public sealed class RegionsApi : BinaryLaneResourceBase, IRegionsApi
{
/// <summary>Initializes the regions resource.</summary>
public RegionsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<Region>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Region>("v2/regions", "regions", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Region> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Region>("v2/regions", "regions", page, null, cancellationToken);
}
@@ -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;
/// <inheritdoc />
public sealed class ReverseNamesApi : BinaryLaneResourceBase, IReverseNamesApi
{
/// <summary>Initializes the reverse-names resource.</summary>
public ReverseNamesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<string>> ListIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<string>("v2/reverse_names/ipv6", "reverse_nameservers", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<string> ListAllIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<string>("v2/reverse_names/ipv6", "reverse_nameservers", page, null, cancellationToken);
/// <inheritdoc />
public async Task<ActionSubmission> 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<BinaryLaneAction>(response.Body, "action", out var action);
return new ActionSubmission(response.StatusCode, action, response.Body);
}
}
/// <inheritdoc />
public sealed class SampleSetsApi : BinaryLaneResourceBase, ISampleSetsApi
{
/// <summary>Initializes the monitoring sample-set resource.</summary>
public SampleSetsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public async Task<SampleSet?> GetLatestAsync(long serverId, string? dataInterval = null, CancellationToken cancellationToken = default)
{
IReadOnlyDictionary<string, object?>? query = string.IsNullOrWhiteSpace(dataInterval)
? null
: new Dictionary<string, object?> { ["data_interval"] = dataInterval };
var response = await GetRawAsync($"v2/samplesets/{serverId}/latest", query, cancellationToken).ConfigureAwait(false);
return TryDeserializeEnvelope<SampleSet>(response, "sample_set", out var sampleSet) ? sampleSet : null;
}
/// <inheritdoc />
public Task<Page<SampleSet>> ListAsync(
long serverId,
PageRequest? page = null,
string? dataInterval = null,
DateTimeOffset? start = null,
DateTimeOffset? endAt = null,
CancellationToken cancellationToken = default)
{
var query = new Dictionary<string, object?>
{
["data_interval"] = dataInterval,
["start"] = start,
["end"] = endAt,
};
return GetPageAsync<SampleSet>($"v2/samplesets/{serverId}", "sample_sets", page, query, cancellationToken);
}
}
/// <inheritdoc />
public sealed class ServersApi : BinaryLaneResourceBase, IServersApi
{
/// <summary>Initializes the servers resource.</summary>
public ServersApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Server> GetAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<Server>($"v2/servers/{serverId}", "server", cancellationToken);
/// <inheritdoc />
public Task<Page<Server>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Server>("v2/servers", "servers", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Server> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Server>("v2/servers", "servers", page, null, cancellationToken);
/// <inheritdoc />
public Task<Server> CreateAsync(CreateServerRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Server>(HttpMethod.Post, "v2/servers", request ?? throw new ArgumentNullException(nameof(request)), "server", cancellationToken);
/// <inheritdoc />
public Task DeleteAsync(long serverId, string? reason = null, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync(
$"v2/servers/{serverId}",
null,
string.IsNullOrWhiteSpace(reason) ? null : new Dictionary<string, object?> { ["reason"] = reason },
cancellationToken);
/// <inheritdoc />
public Task<Page<BinaryLaneAction>> ListActionsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<BinaryLaneAction>($"v2/servers/{serverId}/actions", "actions", page, null, cancellationToken);
/// <inheritdoc />
public Task<BinaryLaneAction> GetActionAsync(long serverId, long actionId, CancellationToken cancellationToken = default) =>
GetItemAsync<BinaryLaneAction>($"v2/servers/{serverId}/actions/{actionId}", "action", cancellationToken);
/// <inheritdoc />
public async Task<ActionSubmission> 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<BinaryLaneAction>(response.Body, "action", out var action);
return new ActionSubmission(response.StatusCode, action, response.Body);
}
/// <inheritdoc />
public Task<IReadOnlyList<AdvancedFirewallRule>> GetAdvancedFirewallRulesAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<AdvancedFirewallRule>>(
$"v2/servers/{serverId}/advanced_firewall_rules",
"firewall_rules",
cancellationToken);
/// <inheritdoc />
public Task<AvailableAdvancedServerFeatures> GetAvailableAdvancedFeaturesAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<AvailableAdvancedServerFeatures>(
$"v2/servers/{serverId}/available_advanced_features",
"available_advanced_server_features",
cancellationToken);
/// <inheritdoc />
public Task<Page<Image>> ListBackupsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Image>($"v2/servers/{serverId}/backups", "backups", page, null, cancellationToken);
/// <inheritdoc />
public Task<BinaryLaneAction> CreateBackupAsync(long serverId, UploadImageRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<BinaryLaneAction>(HttpMethod.Post, $"v2/servers/{serverId}/backups", request ?? throw new ArgumentNullException(nameof(request)), "action", cancellationToken);
/// <inheritdoc />
public Task<Page<Kernel>> ListKernelsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Kernel>($"v2/servers/{serverId}/kernels", "kernels", page, null, cancellationToken);
/// <inheritdoc />
public Task<Page<Image>> ListSnapshotsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Image>($"v2/servers/{serverId}/snapshots", "snapshots", page, null, cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<ThresholdAlert>> GetThresholdAlertsAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<ThresholdAlert>>($"v2/servers/{serverId}/threshold_alerts", "threshold_alerts", cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<long>> GetThresholdAlertServerIdsAsync(CancellationToken cancellationToken = default) =>
GetItemAsync<IReadOnlyList<long>>("v2/servers/threshold_alerts", "server_ids", cancellationToken);
/// <inheritdoc />
public Task<Page<LicensedSoftware>> ListSoftwareAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<LicensedSoftware>($"v2/servers/{serverId}/software", "licensed_software", page, null, cancellationToken);
/// <inheritdoc />
public Task<UserData> GetUserDataAsync(long serverId, CancellationToken cancellationToken = default) =>
GetDirectItemAsync<UserData>($"v2/servers/{serverId}/user_data", cancellationToken);
/// <inheritdoc />
public Task<ServerConsole> GetConsoleAsync(long serverId, CancellationToken cancellationToken = default) =>
GetItemAsync<ServerConsole>($"v2/servers/{serverId}/console", "console", cancellationToken);
}
/// <inheritdoc />
public sealed class SizesApi : BinaryLaneResourceBase, ISizesApi
{
/// <summary>Initializes the sizes resource.</summary>
public SizesApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<Size>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Size>("v2/sizes", "sizes", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Size> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Size>("v2/sizes", "sizes", page, null, cancellationToken);
}
/// <inheritdoc />
public sealed class SoftwareApi : BinaryLaneResourceBase, ISoftwareApi
{
/// <summary>Initializes the software resource.</summary>
public SoftwareApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<Software>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Software>("v2/software", "software", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Software> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Software>("v2/software", "software", page, null, cancellationToken);
/// <inheritdoc />
public Task<Software> GetAsync(long softwareId, CancellationToken cancellationToken = default) =>
GetItemAsync<Software>($"v2/software/{softwareId}", "software", cancellationToken);
/// <inheritdoc />
public Task<Page<Software>> ListOperatingSystemAsync(string operatingSystemIdOrSlug, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Software>(
$"v2/software/operating_system/{EscapePathSegment(operatingSystemIdOrSlug)}",
"software",
page,
null,
cancellationToken);
}
/// <inheritdoc />
public sealed class VpcsApi : BinaryLaneResourceBase, IVpcsApi
{
/// <summary>Initializes the VPC resource.</summary>
public VpcsApi(IBinaryLaneApiExecutor executor, BinaryLaneJsonSerializerOptions json)
: base(executor, json)
{
}
/// <inheritdoc />
public Task<Page<Vpc>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<Vpc>("v2/vpcs", "vpcs", page, null, cancellationToken);
/// <inheritdoc />
public IAsyncEnumerable<Vpc> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetAllPagesAsync<Vpc>("v2/vpcs", "vpcs", page, null, cancellationToken);
/// <inheritdoc />
public Task<Vpc> GetAsync(long vpcId, CancellationToken cancellationToken = default) =>
GetItemAsync<Vpc>($"v2/vpcs/{vpcId}", "vpc", cancellationToken);
/// <inheritdoc />
public Task<Vpc> CreateAsync(CreateVpcRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Vpc>(HttpMethod.Post, "v2/vpcs", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken);
/// <inheritdoc />
public Task<Vpc> ReplaceAsync(long vpcId, UpdateVpcRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Vpc>(HttpMethod.Put, $"v2/vpcs/{vpcId}", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken);
/// <inheritdoc />
public Task<Vpc> UpdateAsync(long vpcId, PatchVpcRequest request, CancellationToken cancellationToken = default) =>
SendItemAsync<Vpc>(new HttpMethod("PATCH"), $"v2/vpcs/{vpcId}", request ?? throw new ArgumentNullException(nameof(request)), "vpc", cancellationToken);
/// <inheritdoc />
public Task DeleteAsync(long vpcId, CancellationToken cancellationToken = default) =>
Executor.DeleteAsync($"v2/vpcs/{vpcId}", null, null, cancellationToken);
/// <inheritdoc />
public Task<Page<VpcMember>> ListMembersAsync(long vpcId, PageRequest? page = null, CancellationToken cancellationToken = default) =>
GetPageAsync<VpcMember>($"v2/vpcs/{vpcId}/members", "members", page, null, cancellationToken);
}
@@ -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;
/// <summary>Reads information about the authenticated account.</summary>
public interface IAccountApi
{
/// <summary>Gets the current account.</summary>
Task<Account> GetAsync(CancellationToken cancellationToken = default);
/// <summary>Gets the untyped account response for forward compatibility.</summary>
Task<JsonElement> GetRawAsync(CancellationToken cancellationToken = default);
}
/// <summary>Reads and waits for asynchronous account and server actions.</summary>
public interface IActionsApi
{
/// <summary>Gets an action by its global action ID.</summary>
Task<BinaryLaneAction> GetAsync(long actionId, CancellationToken cancellationToken = default);
/// <summary>Lists global actions.</summary>
Task<Page<BinaryLaneAction>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
/// <summary>Streams all global actions.</summary>
IAsyncEnumerable<BinaryLaneAction> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
/// <summary>Supplies a requested user interaction for an action.</summary>
Task ProceedAsync(long actionId, ProceedRequest request, CancellationToken cancellationToken = default);
/// <summary>Polls an action until BinaryLane reports a terminal state.</summary>
Task<BinaryLaneAction> WaitForCompletionAsync(
long actionId,
ActionWaitOptions? options = null,
CancellationToken cancellationToken = default);
}
/// <summary>Reads account billing and invoice information.</summary>
public interface IBillingApi
{
Task<Balance> GetBalanceAsync(CancellationToken cancellationToken = default);
Task<Invoice> GetInvoiceAsync(long invoiceId, CancellationToken cancellationToken = default);
Task<Page<Invoice>> ListInvoicesAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Invoice>> ListUnpaidPaymentFailedInvoicesAsync(CancellationToken cancellationToken = default);
}
/// <summary>Reads data-transfer usage data.</summary>
public interface IDataUsageApi
{
Task<Page<DataUsage>> ListCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<DataUsage> ListAllCurrentAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<DataUsage> GetCurrentAsync(long serverId, CancellationToken cancellationToken = default);
}
/// <summary>Manages DNS domains and DNS records.</summary>
public interface IDomainsApi
{
Task<IReadOnlyList<string>> GetNameserversAsync(CancellationToken cancellationToken = default);
Task RefreshNameserverCacheAsync(DomainRefreshRequest request, CancellationToken cancellationToken = default);
Task<Page<Domain>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Domain> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Domain> CreateAsync(DomainRequest request, CancellationToken cancellationToken = default);
Task<Domain> GetAsync(string domainName, CancellationToken cancellationToken = default);
Task DeleteAsync(string domainName, CancellationToken cancellationToken = default);
Task<Page<DomainRecord>> ListRecordsAsync(string domainName, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<DomainRecord> CreateRecordAsync(string domainName, DomainRecordRequest request, CancellationToken cancellationToken = default);
Task<DomainRecord> GetRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default);
Task<DomainRecord> UpdateRecordAsync(string domainName, long recordId, UpdateDomainRecordRequest request, CancellationToken cancellationToken = default);
Task DeleteRecordAsync(string domainName, long recordId, CancellationToken cancellationToken = default);
}
/// <summary>Reads and manages account images and backups represented as images.</summary>
public interface IImagesApi
{
Task<Page<Image>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Image> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Image> GetAsync(string imageIdOrSlug, CancellationToken cancellationToken = default);
Task<Image> UpdateAsync(long imageId, ImageRequest request, CancellationToken cancellationToken = default);
Task<ImageDownload> GetDownloadAsync(long imageId, CancellationToken cancellationToken = default);
}
/// <summary>Manages account SSH keys.</summary>
public interface ISshKeysApi
{
Task<Page<SshKey>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<SshKey> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<SshKey> GetAsync(long keyId, CancellationToken cancellationToken = default);
Task<SshKey> CreateAsync(SshKeyRequest request, CancellationToken cancellationToken = default);
Task<SshKey> UpdateAsync(long keyId, UpdateSshKeyRequest request, CancellationToken cancellationToken = default);
Task DeleteAsync(long keyId, CancellationToken cancellationToken = default);
}
/// <summary>Manages load balancers, their members, and forwarding rules.</summary>
public interface ILoadBalancersApi
{
Task<Page<LoadBalancer>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<LoadBalancer> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<LoadBalancer> GetAsync(long loadBalancerId, CancellationToken cancellationToken = default);
Task<LoadBalancer> CreateAsync(CreateLoadBalancerRequest request, CancellationToken cancellationToken = default);
Task<LoadBalancer> UpdateAsync(long loadBalancerId, UpdateLoadBalancerRequest request, CancellationToken cancellationToken = default);
Task DeleteAsync(long loadBalancerId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<LoadBalancerAvailabilityOption>> 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);
}
/// <summary>Lists BinaryLane regions.</summary>
public interface IRegionsApi
{
Task<Page<Region>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Region> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
}
/// <summary>Manages IPv6 reverse-name configuration.</summary>
public interface IReverseNamesApi
{
Task<Page<string>> ListIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<string> ListAllIpv6Async(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<ActionSubmission> UpdateIpv6Async(ReverseNameserversRequest request, CancellationToken cancellationToken = default);
}
/// <summary>Reads monitoring samples for servers.</summary>
public interface ISampleSetsApi
{
Task<SampleSet?> GetLatestAsync(long serverId, string? dataInterval = null, CancellationToken cancellationToken = default);
Task<Page<SampleSet>> ListAsync(long serverId, PageRequest? page = null, string? dataInterval = null, DateTimeOffset? start = null, DateTimeOffset? endAt = null, CancellationToken cancellationToken = default);
}
/// <summary>Manages BinaryLane servers and their server-scoped resources.</summary>
public interface IServersApi
{
Task<Server> GetAsync(long serverId, CancellationToken cancellationToken = default);
Task<Page<Server>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Server> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Server> CreateAsync(CreateServerRequest request, CancellationToken cancellationToken = default);
Task DeleteAsync(long serverId, string? reason = null, CancellationToken cancellationToken = default);
Task<Page<BinaryLaneAction>> ListActionsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<BinaryLaneAction> GetActionAsync(long serverId, long actionId, CancellationToken cancellationToken = default);
Task<ActionSubmission> SubmitActionAsync(long serverId, ServerAction request, CancellationToken cancellationToken = default);
Task<IReadOnlyList<AdvancedFirewallRule>> GetAdvancedFirewallRulesAsync(long serverId, CancellationToken cancellationToken = default);
Task<AvailableAdvancedServerFeatures> GetAvailableAdvancedFeaturesAsync(long serverId, CancellationToken cancellationToken = default);
Task<Page<Image>> ListBackupsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<BinaryLaneAction> CreateBackupAsync(long serverId, UploadImageRequest request, CancellationToken cancellationToken = default);
Task<Page<Kernel>> ListKernelsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Page<Image>> ListSnapshotsAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ThresholdAlert>> GetThresholdAlertsAsync(long serverId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<long>> GetThresholdAlertServerIdsAsync(CancellationToken cancellationToken = default);
Task<Page<LicensedSoftware>> ListSoftwareAsync(long serverId, PageRequest? page = null, CancellationToken cancellationToken = default);
Task<UserData> GetUserDataAsync(long serverId, CancellationToken cancellationToken = default);
Task<ServerConsole> GetConsoleAsync(long serverId, CancellationToken cancellationToken = default);
}
/// <summary>Lists available server sizes.</summary>
public interface ISizesApi
{
Task<Page<Size>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Size> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
}
/// <summary>Lists available licensed software and operating-system software.</summary>
public interface ISoftwareApi
{
Task<Page<Software>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Software> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Software> GetAsync(long softwareId, CancellationToken cancellationToken = default);
Task<Page<Software>> ListOperatingSystemAsync(string operatingSystemIdOrSlug, PageRequest? page = null, CancellationToken cancellationToken = default);
}
/// <summary>Manages private virtual networks and their members.</summary>
public interface IVpcsApi
{
Task<Page<Vpc>> ListAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
IAsyncEnumerable<Vpc> ListAllAsync(PageRequest? page = null, CancellationToken cancellationToken = default);
Task<Vpc> GetAsync(long vpcId, CancellationToken cancellationToken = default);
Task<Vpc> CreateAsync(CreateVpcRequest request, CancellationToken cancellationToken = default);
Task<Vpc> ReplaceAsync(long vpcId, UpdateVpcRequest request, CancellationToken cancellationToken = default);
Task<Vpc> UpdateAsync(long vpcId, PatchVpcRequest request, CancellationToken cancellationToken = default);
Task DeleteAsync(long vpcId, CancellationToken cancellationToken = default);
Task<Page<VpcMember>> ListMembersAsync(long vpcId, PageRequest? page = null, CancellationToken cancellationToken = default);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+348
View File
@@ -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"
}
}
}
}
}