Skip to content

Commit

Permalink
feat: introduce Namespace (G-Research#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
winstxnhdw authored Aug 31, 2023
1 parent 14beaea commit 16419e9
Show file tree
Hide file tree
Showing 13 changed files with 458 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Consul.Test/BaseFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public async Task InitializeAsync()
}
}

public class EnterpriseOnlyFact : FactAttribute
public class EnterpriseOnlyFact : SkippableFactAttribute
{
public EnterpriseOnlyFact()
{
Expand Down
171 changes: 171 additions & 0 deletions Consul.Test/NamespaceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// -----------------------------------------------------------------------
// <copyright file="NamespaceTest.cs" company="G-Research Limited">
// 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.
// </copyright>
// -----------------------------------------------------------------------

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<string> { "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<string>(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);
}
}
}
23 changes: 17 additions & 6 deletions Consul/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ internal bool DisableServerCertificateValidation
/// </summary>
public Uri Address { get; set; }

/// <summary>
/// Namespace is the name of the namespace to send along for the request
/// when no other Namespace is present in the QueryOptions
/// </summary>
public string Namespace { get; set; }

/// <summary>
/// Datacenter to provide with each request. If not provided, the default agent datacenter is used.
/// </summary>
Expand All @@ -86,10 +92,7 @@ internal bool DisableServerCertificateValidation
#endif
public NetworkCredential HttpAuth
{
internal get
{
return _httpauth;
}
internal get => _httpauth;
set
{
_httpauth = value;
Expand Down Expand Up @@ -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;
}
}

/// <summary>
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -471,6 +481,7 @@ private void InitializeEndpoints()
_token = new Lazy<Token>(() => new Token(this));
_aclReplication = new Lazy<ACLReplication>(() => new ACLReplication(this));
_authMethod = new Lazy<AuthMethod>(() => new AuthMethod(this));
_namespaces = new Lazy<Namespaces>(() => new Namespaces(this));
}

#region IDisposable Support
Expand Down
25 changes: 20 additions & 5 deletions Consul/Client_DeleteRequests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -190,13 +200,13 @@ protected override void ApplyHeaders(HttpRequestMessage message, ConsulClientCon
public class DeleteAcceptingRequest<TIn> : 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;
Expand All @@ -217,7 +227,7 @@ public async Task<WriteResult> 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<byte>());
}
else if (typeof(TIn) == typeof(Stream))
{
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions Consul/Client_GetRequests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions Consul/Client_Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public class QueryOptions
WaitIndex = 0
};

/// <summary>
/// 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.
/// </summary>
public string Namespace { get; set; }

/// <summary>
/// Providing a datacenter overwrites the DC provided by the Config.
/// </summary>
Expand All @@ -48,6 +54,7 @@ public class QueryOptions
public ConsistencyMode Consistency { get; set; }

/// <summary>
/// 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.
/// </summary>
Expand Down Expand Up @@ -106,6 +113,12 @@ public class WriteOptions
Token = string.Empty
};

/// <summary>
/// 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.
/// </summary>
public string Namespace { get; set; }

/// <summary>
/// Providing a datacenter overwrites the DC provided by the Config
/// </summary>
Expand Down
Loading

0 comments on commit 16419e9

Please sign in to comment.