diff --git a/Consul.Test/BaseFixture.cs b/Consul.Test/BaseFixture.cs index 97096ade4..db45aff5b 100644 --- a/Consul.Test/BaseFixture.cs +++ b/Consul.Test/BaseFixture.cs @@ -118,7 +118,7 @@ public async Task InitializeAsync() } } - public class EnterpriseOnlyFact : FactAttribute + public class EnterpriseOnlyFact : SkippableFactAttribute { public EnterpriseOnlyFact() { diff --git a/Consul.Test/NamespaceTest.cs b/Consul.Test/NamespaceTest.cs new file mode 100644 index 000000000..28c5d127a --- /dev/null +++ b/Consul.Test/NamespaceTest.cs @@ -0,0 +1,171 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2020 G-Research Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NuGet.Versioning; +using Xunit; + +namespace Consul.Test +{ + public class NamespaceTest : BaseFixture + { + [EnterpriseOnlyFact] + public async Task Namespaces_CreateNamespace() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var name = "test"; + + var ns = new Namespace + { + Name = name + }; + + var request = await _client.Namespaces.Create(ns); + + Assert.Equal(request.Response.Name, name); + } + + [EnterpriseOnlyFact] + public async Task Namespaces_UpdateNamespace() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var name = "test"; + + var ns = new Namespace + { + Name = name + }; + + await _client.Namespaces.Create(ns); + + var description = "updated namespace"; + + var newNamespace = new Namespace + { + Name = name, + Description = description + }; + + var updateRequest = await _client.Namespaces.Update(newNamespace); + + Assert.Equal(updateRequest.Response.Description, description); + } + + [EnterpriseOnlyFact] + public async Task Namespaces_ReadNamespace() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var name = "test"; + + var ns = new Namespace + { + Name = name + }; + + await _client.Namespaces.Create(ns); + var request = await _client.Namespaces.Read(name); + + Assert.Equal(request.Response.Name, name); + } + + + [EnterpriseOnlyFact] + public async Task Namespaces_ListNamespaces() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var testNames = new HashSet { "test-a", "test-b", "test-c" }; + + foreach (var name in testNames) + { + var ns = new Namespace + { + Name = name + }; + + await _client.Namespaces.Create(ns); + } + + var request = await _client.Namespaces.List(); + testNames.Add("default"); + Assert.True(new HashSet(request.Response.Select(x => x.Name)).SetEquals(testNames)); + } + + [EnterpriseOnlyFact] + public async Task Namespaces_DeleteNamespace() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var name = "test"; + + var ns = new Namespace + { + Name = name + }; + + var createRequest = await _client.Namespaces.Create(ns); + Assert.Equal(name, createRequest.Response.Name); + Assert.Null(createRequest.Response.DeletedAt); + + await _client.Namespaces.Delete(name); + + var readRequest = await _client.Namespaces.Read(name); + Assert.NotNull(readRequest.Response.DeletedAt); + } + + [EnterpriseOnlyFact] + public async Task Namespaces_KVIsolation() + { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `Namespaces` is only supported from Consul {cutOffVersion}"); + + var name = "test"; + + await _client.Namespaces.Create(new Namespace + { + Name = name + }); + + var key = "key"; + + var requestPair = new KVPair + { + Key = key, + Value = Encoding.UTF8.GetBytes("value") + }; + + await _client.KV.Put(requestPair, new WriteOptions { Namespace = name }); + var namespaceResponsePair = await _client.KV.Get(key, new QueryOptions { Namespace = name }); + Assert.Equal(requestPair.Value, namespaceResponsePair.Response.Value); + + var defaultNamespaceResponsePair = await _client.KV.Get(key); + Assert.Null(defaultNamespaceResponsePair.Response?.Value); + } + } +} diff --git a/Consul/Client.cs b/Consul/Client.cs index fd76ac99a..674ea7ff5 100644 --- a/Consul/Client.cs +++ b/Consul/Client.cs @@ -68,6 +68,12 @@ internal bool DisableServerCertificateValidation /// public Uri Address { get; set; } + /// + /// Namespace is the name of the namespace to send along for the request + /// when no other Namespace is present in the QueryOptions + /// + public string Namespace { get; set; } + /// /// Datacenter to provide with each request. If not provided, the default agent datacenter is used. /// @@ -86,10 +92,7 @@ internal bool DisableServerCertificateValidation #endif public NetworkCredential HttpAuth { - internal get - { - return _httpauth; - } + internal get => _httpauth; set { _httpauth = value; @@ -148,6 +151,12 @@ public ConsulClientConfiguration() UriBuilder consulAddress = new UriBuilder("http://127.0.0.1:8500"); ConfigureFromEnvironment(consulAddress); Address = consulAddress.Uri; + + string ns = Environment.GetEnvironmentVariable("CONSUL_NAMESPACE"); + if (!string.IsNullOrEmpty(ns)) + { + Namespace = ns; + } } /// @@ -234,9 +243,10 @@ private void ConfigureFromEnvironment(UriBuilder consulAddress) #pragma warning restore CS0618 // Type or member is obsolete } - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CONSUL_HTTP_TOKEN"))) + string token = Environment.GetEnvironmentVariable("CONSUL_HTTP_TOKEN"); + if (!string.IsNullOrEmpty(token)) { - Token = Environment.GetEnvironmentVariable("CONSUL_HTTP_TOKEN"); + Token = token; } } @@ -471,6 +481,7 @@ private void InitializeEndpoints() _token = new Lazy(() => new Token(this)); _aclReplication = new Lazy(() => new ACLReplication(this)); _authMethod = new Lazy(() => new AuthMethod(this)); + _namespaces = new Lazy(() => new Namespaces(this)); } #region IDisposable Support diff --git a/Consul/Client_DeleteRequests.cs b/Consul/Client_DeleteRequests.cs index 34b3e42db..7d8b98dce 100644 --- a/Consul/Client_DeleteRequests.cs +++ b/Consul/Client_DeleteRequests.cs @@ -38,7 +38,7 @@ public DeleteReturnRequest(ConsulClient client, string url, WriteOptions options { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } Options = options ?? WriteOptions.Default; } @@ -93,6 +93,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -119,7 +124,7 @@ public DeleteRequest(ConsulClient client, string url, WriteOptions options = nul { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } Options = options ?? WriteOptions.Default; } @@ -168,6 +173,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -190,13 +200,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class DeleteAcceptingRequest : ConsulRequest { public WriteOptions Options { get; set; } - private TIn _body; + private readonly TIn _body; public DeleteAcceptingRequest(ConsulClient client, string url, TIn body, WriteOptions options = null) : base(client, url, HttpMethod.Delete) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -217,7 +227,7 @@ public async Task Execute(CancellationToken ct) if (typeof(TIn) == typeof(byte[])) { - content = new ByteArrayContent((_body as byte[]) ?? new byte[0]); + content = new ByteArrayContent((_body as byte[]) ?? Array.Empty()); } else if (typeof(TIn) == typeof(Stream)) { @@ -263,6 +273,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; diff --git a/Consul/Client_GetRequests.cs b/Consul/Client_GetRequests.cs index d15cb2a0c..dbc3c9ded 100644 --- a/Consul/Client_GetRequests.cs +++ b/Consul/Client_GetRequests.cs @@ -144,6 +144,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -348,6 +353,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; diff --git a/Consul/Client_Options.cs b/Consul/Client_Options.cs index ebd5e72dc..958b7ccbe 100644 --- a/Consul/Client_Options.cs +++ b/Consul/Client_Options.cs @@ -37,6 +37,12 @@ public class QueryOptions WaitIndex = 0 }; + /// + /// Namespace is the name of the namespace to send along for the request when no other Namespace is present in the QueryOptions. + /// Namespace is an Enterprise-only feature. + /// + public string Namespace { get; set; } + /// /// Providing a datacenter overwrites the DC provided by the Config. /// @@ -48,6 +54,7 @@ public class QueryOptions public ConsistencyMode Consistency { get; set; } /// + /// WaitIndex is used to enable a blocking query. Waits until the timeout or the next index is reached. /// UseCache requests that the agent cache results locally. /// See https://www.consul.io/api/features/caching.html for more details on the semantics. /// @@ -106,6 +113,12 @@ public class WriteOptions Token = string.Empty }; + /// + /// Namespace is the name of the namespace to send along for the request when no other Namespace is present in the QueryOptions + /// Namespace is an Enterprise-only feature. + /// + public string Namespace { get; set; } + /// /// Providing a datacenter overwrites the DC provided by the Config /// diff --git a/Consul/Client_PostRequests.cs b/Consul/Client_PostRequests.cs index f8a6a769c..ba1850061 100644 --- a/Consul/Client_PostRequests.cs +++ b/Consul/Client_PostRequests.cs @@ -39,7 +39,7 @@ public PostReturningRequest(ConsulClient client, string url, WriteOptions option { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } Options = options ?? WriteOptions.Default; @@ -97,6 +97,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -119,13 +124,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class PostRequest : ConsulRequest { public WriteOptions Options { get; set; } - private TIn _body; + private readonly TIn _body; public PostRequest(ConsulClient client, string url, TIn body, WriteOptions options = null) : base(client, url, HttpMethod.Post) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -192,6 +197,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -215,13 +225,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class PostRequest : ConsulRequest { public WriteOptions Options { get; set; } - private TIn _body; + private readonly TIn _body; public PostRequest(ConsulClient client, string url, TIn body, WriteOptions options = null) : base(client, url, HttpMethod.Post) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -293,6 +303,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -314,13 +329,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class PostRequest : ConsulRequest { public WriteOptions Options { get; set; } - private string _body; + private readonly string _body; public PostRequest(ConsulClient client, string url, string body, WriteOptions options = null) : base(client, url, HttpMethod.Post) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -336,10 +351,8 @@ public async Task> Execute(CancellationToken ct) Client.CheckDisposed(); var timer = Stopwatch.StartNew(); var result = new WriteResult(); - - HttpContent content = null; var bodyBytes = Encoding.UTF8.GetBytes(_body); - content = new ByteArrayContent(bodyBytes); + HttpContent content = new ByteArrayContent(bodyBytes); var message = new HttpRequestMessage(HttpMethod.Post, BuildConsulUri(Endpoint, Params)); ApplyHeaders(message, Client.Config); @@ -383,6 +396,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; diff --git a/Consul/Client_PutRequests.cs b/Consul/Client_PutRequests.cs index 11cdd4daa..7ec0b3915 100644 --- a/Consul/Client_PutRequests.cs +++ b/Consul/Client_PutRequests.cs @@ -38,7 +38,7 @@ public PutReturningRequest(ConsulClient client, string url, WriteOptions options { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } Options = options ?? WriteOptions.Default; } @@ -92,6 +92,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -118,7 +123,7 @@ public PutNothingRequest(ConsulClient client, string url, WriteOptions options = { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } Options = options ?? WriteOptions.Default; } @@ -167,6 +172,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -189,13 +199,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class PutRequest : ConsulRequest { public WriteOptions Options { get; set; } - private TIn _body; + private readonly TIn _body; public PutRequest(ConsulClient client, string url, TIn body, WriteOptions options = null) : base(client, url, HttpMethod.Put) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -216,7 +226,7 @@ public async Task Execute(CancellationToken ct) if (typeof(TIn) == typeof(byte[])) { - content = new ByteArrayContent((_body as byte[]) ?? new byte[0]); + content = new ByteArrayContent((_body as byte[]) ?? Array.Empty()); } else if (typeof(TIn) == typeof(Stream)) { @@ -262,6 +272,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; @@ -285,13 +300,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon public class PutRequest : ConsulRequest { public WriteOptions Options { get; set; } - private TIn _body; + private readonly TIn _body; public PutRequest(ConsulClient client, string url, TIn body, WriteOptions options = null) : base(client, url, HttpMethod.Put) { if (string.IsNullOrEmpty(url)) { - throw new ArgumentException(nameof(url)); + throw new ArgumentException(null, nameof(url)); } _body = body; Options = options ?? WriteOptions.Default; @@ -312,7 +327,7 @@ public async Task> Execute(CancellationToken ct) if (typeof(TIn) == typeof(byte[])) { - var bodyBytes = (_body as byte[]); + var bodyBytes = _body as byte[]; if (bodyBytes != null) { content = new ByteArrayContent(bodyBytes); @@ -367,6 +382,11 @@ protected override void ApplyOptions(ConsulClientConfiguration clientConfig) return; } + if (!string.IsNullOrEmpty(Options.Namespace)) + { + Params["ns"] = Options.Namespace; + } + if (!string.IsNullOrEmpty(Options.Datacenter)) { Params["dc"] = Options.Datacenter; diff --git a/Consul/Client_Request.cs b/Consul/Client_Request.cs index 24723f819..965aa54da 100644 --- a/Consul/Client_Request.cs +++ b/Consul/Client_Request.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Net.Http; using Newtonsoft.Json; @@ -72,6 +71,10 @@ internal ConsulRequest(ConsulClient client, string url, HttpMethod method) { Params["wait"] = client.Config.WaitTime.Value.ToGoDuration(); } + if (!string.IsNullOrEmpty(client.Config.Namespace)) + { + Params["ns"] = client.Config.Namespace; + } } protected abstract void ApplyOptions(ConsulClientConfiguration clientConfig); @@ -79,8 +82,10 @@ internal ConsulRequest(ConsulClient client, string url, HttpMethod method) protected Uri BuildConsulUri(string url, Dictionary p) { - var builder = new UriBuilder(Client.Config.Address); - builder.Path = url; + var builder = new UriBuilder(Client.Config.Address) + { + Path = url + }; ApplyOptions(Client.Config); @@ -113,7 +118,7 @@ protected TOut Deserialize(Stream stream) } } - protected byte[] Serialize(object value) + protected static byte[] Serialize(object value) { return System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)); } diff --git a/Consul/Interfaces/INamespacesEndpoint.cs b/Consul/Interfaces/INamespacesEndpoint.cs new file mode 100644 index 000000000..25f40d2d2 --- /dev/null +++ b/Consul/Interfaces/INamespacesEndpoint.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2020 G-Research Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------- + +using System.Threading; +using System.Threading.Tasks; + +namespace Consul +{ + /// + /// The interface for the Namespaces API Endpoints + /// + public interface INamespacesEndpoint + { + Task> Create(Namespace ns, WriteOptions q, CancellationToken ct = default); + Task> Create(Namespace ns, CancellationToken ct = default); + Task> Update(Namespace ns, WriteOptions q, CancellationToken ct = default); + Task> Update(Namespace ns, CancellationToken ct = default); + Task> Read(string name, QueryOptions q, CancellationToken ct = default); + Task> Read(string name, CancellationToken ct = default); + Task> List(QueryOptions q, CancellationToken ct = default); + Task> List(CancellationToken ct = default); + Task Delete(string name, WriteOptions q, CancellationToken ct = default); + Task Delete(string name, CancellationToken ct = default); + } +} diff --git a/Consul/Namespace.cs b/Consul/Namespace.cs new file mode 100644 index 000000000..7ae11f85d --- /dev/null +++ b/Consul/Namespace.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Consul +{ + public class Namespace + { + public string Name { get; set; } + + public string Description { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public NamespaceACLConfig ACLs { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Meta { get; set; } + } + + public class NamespaceResponse : Namespace + { + public ulong CreateIndex { get; set; } + public ulong ModifyIndex { get; set; } + public DateTime? DeletedAt { get; set; } + } + + public class NamespaceACLConfig + { + public List PolicyDefaults { get; set; } + public List RoleDefaults { get; set; } + } + + public class Namespaces : INamespacesEndpoint + { + private readonly ConsulClient _client; + + internal Namespaces(ConsulClient c) + { + _client = c; + } + + public async Task> Create(Namespace ns, WriteOptions q, CancellationToken ct = default) + { + var res = await _client.Put("v1/namespace", ns, q).Execute(ct).ConfigureAwait(false); + return new WriteResult(res, res.Response); + } + + public async Task> Create(Namespace ns, CancellationToken ct = default) + { + var res = await _client.Put("v1/namespace", ns, WriteOptions.Default).Execute(ct).ConfigureAwait(false); + return new WriteResult(res, res.Response); + } + + public async Task> Update(Namespace ns, WriteOptions q, CancellationToken ct = default) + { + var res = await _client.Put($"v1/namespace/{ns.Name}", ns, q).Execute(ct).ConfigureAwait(false); + return new WriteResult(res, res.Response); + } + + public async Task> Update(Namespace ns, CancellationToken ct = default) + { + var res = await _client.Put($"v1/namespace/{ns.Name}", ns, WriteOptions.Default).Execute(ct).ConfigureAwait(false); + return new WriteResult(res, res.Response); + } + + public Task> Read(string name, QueryOptions q, CancellationToken ct = default) + { + return _client.Get($"v1/namespace/{name}", q).Execute(ct); + } + + public Task> Read(string name, CancellationToken ct = default) + { + return _client.Get($"v1/namespace/{name}", QueryOptions.Default).Execute(ct); + } + + public Task> List(QueryOptions q, CancellationToken ct = default) + { + return _client.Get($"v1/namespaces", q).Execute(ct); + } + + public Task> List(CancellationToken ct = default) + { + return _client.Get($"v1/namespaces", QueryOptions.Default).Execute(ct); + } + + public Task Delete(string name, WriteOptions q, CancellationToken ct = default) + { + return _client.Delete($"v1/namespace/{name}", q).Execute(ct); + } + + public Task Delete(string name, CancellationToken ct = default) + { + return _client.Delete($"v1/namespace/{name}", WriteOptions.Default).Execute(ct); + } + } + + public partial class ConsulClient : IConsulClient + { + private Lazy _namespaces; + + /// + /// Namespaces returns a handle to the namespaces endpoint + /// + public INamespacesEndpoint Namespaces => _namespaces.Value; + } +} diff --git a/docs/docs/2-guides/1-basic-usage.mdx b/docs/docs/2-guides/1-basic-usage.mdx index 222ccbc5d..721b4e8a1 100644 --- a/docs/docs/2-guides/1-basic-usage.mdx +++ b/docs/docs/2-guides/1-basic-usage.mdx @@ -100,3 +100,10 @@ lock. It is an implementation of the [Consul Leader Election](https://consul.io/ Semaphore is used to implement a distributed semaphore using the Consul KV primitives. It is an implementation of the [Consul Semaphore](https://www.consul.io/docs/guides/semaphore.html) guide. + +### Namespaces + +Namespaces help reduce operational challenges by removing restrictions +around uniqueness of resource names across distinct teams, and enable operators to provide self-service through +delegation of administrative privileges. It is an implementation of +[Consul Namespaces](https://developer.hashicorp.com/consul/docs/enterprise/namespaces). diff --git a/docs/docs/2-guides/3-supported-apis.mdx b/docs/docs/2-guides/3-supported-apis.mdx index 938abe801..4ef452995 100644 --- a/docs/docs/2-guides/3-supported-apis.mdx +++ b/docs/docs/2-guides/3-supported-apis.mdx @@ -45,4 +45,10 @@ import {ConsulAPIBadge} from "@site/src/components/CustomBadge"; | KV Store | Read Key | GET /v1/kv/:key | ✅ | | | Create/Update Key | PUT /v1/kv/:key | ✅ | | | Delete Key | DELETE /v2/kv/:key | ✅ | +| Namespace | List Namespaces | GET /v1/namespaces | ✅ | +| | Read Namespace | GET /v1/namespace/:name | ✅ | +| | Create Namespace | PUT /v1/namespace | ✅ | +| | Update Namespace | PUT /v1/namespace/:name | ✅ | +| | Delete Namespace | DELETE /v1/namespace/:name | ✅ | +