Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaomi7732 committed Aug 12, 2022
2 parents eba1dd3 + 34afbdf commit ad92fb8
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#nullable enable

using System;
using System.Text.RegularExpressions;
using Microsoft.ApplicationInsights.Kubernetes.Debugging;

namespace Microsoft.ApplicationInsights.Kubernetes.ContainerIdProviders;

/// <summary>
/// A simple container id normalizer that picks out 64 digits of GUID/UUID from a container id with prefix / suffix.
/// For example:
/// cri-containerd-5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06.scope will be normalized to
/// 5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06
/// </summary>
internal class ContainerIdNormalizer : IContainerIdNormalizer
{
// Simple rule: First 64-characters GUID/UUID.
private const string ContainerIdIdentifierPattern = @"([a-f\d]{64})";
private readonly Regex _matcher = new Regex(ContainerIdIdentifierPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, matchTimeout: TimeSpan.FromSeconds(1));
private readonly ApplicationInsightsKubernetesDiagnosticSource _logger = ApplicationInsightsKubernetesDiagnosticSource.Instance;

/// <summary>
/// Gets normalized container id.
/// </summary>
/// <param name="input">The original container id. String.Empty yields string.Empty with true. Null is not accepted.</param>
/// <param name="normalized">The normalized container id.</param>
/// <returns>True when the normalized succeeded. False otherwise.</returns>
public bool TryNormalize(string input, out string? normalized)
{
// Should not happen. Put here just in case.
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}

// Special case: string.Empty in, string.Empty out.
if (input == string.Empty)
{
normalized = string.Empty;
return true;
}

_logger.LogDebug($"Normalize container id: {input}");

Match match = _matcher.Match(input);
if (!match.Success)
{
_logger.LogDebug($"Failed match any container id by pattern: {ContainerIdIdentifierPattern}.");
normalized = null;
return false;
}
normalized = match.Groups[1].Value;
_logger.LogTrace($"Container id normalized to: {normalized}");
return true;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

namespace Microsoft.ApplicationInsights.Kubernetes.ContainerIdProviders;

/// <summary>
/// A service to normalize container id.
/// </summary>
internal interface IContainerIdNormalizer
{
/// <summary>
/// Tries to normalize container id.
/// </summary>
/// <param name="input">The container id.</param>
/// <param name="normalized">The normalized container id.</param>
/// <returns>True when normalized. False otherwise.</returns>
bool TryNormalize(string input, out string? normalized);
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ protected virtual void RegisterSettingsProvider(IServiceCollection serviceCollec
_logger.LogError("Unsupported OS.");
}

serviceCollection.TryAddSingleton<IContainerIdNormalizer, ContainerIdNormalizer>();

// Notes: pay attention to the order. Injecting uses the order of registering in this case.
// For backward compatibility, $APPINSIGHTS_KUBERNETES_POD_NAME has been agreed upon to allow customize pod name with downward API.
serviceCollection.TryAddEnumerable(ServiceDescriptor.Singleton<IPodNameProvider, UserSetPodNameProvider>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ namespace Microsoft.ApplicationInsights.Kubernetes
internal abstract class KubeHttpClientSettingsBase : IKubeHttpClientSettingsProvider
{
private readonly IEnumerable<IContainerIdProvider> _containerIdProviders;
private readonly IContainerIdNormalizer _containerIdNormalizer;
protected readonly ApplicationInsightsKubernetesDiagnosticSource _logger = ApplicationInsightsKubernetesDiagnosticSource.Instance;

public KubeHttpClientSettingsBase(
string? kubernetesServiceHost,
string? kubernetesServicePort,
IEnumerable<IContainerIdProvider> containerIdProviders)
IEnumerable<IContainerIdProvider> containerIdProviders,
IContainerIdNormalizer containerIdNormalizer)
{
kubernetesServiceHost = kubernetesServiceHost ?? Environment.GetEnvironmentVariable(@"KUBERNETES_SERVICE_HOST");
if (string.IsNullOrEmpty(kubernetesServiceHost))
Expand All @@ -38,7 +40,7 @@ public KubeHttpClientSettingsBase(
_logger.LogDebug("Kubernetes base address: {0}", baseAddress);
ServiceBaseAddress = new Uri(baseAddress, UriKind.Absolute);
_containerIdProviders = containerIdProviders ?? throw new ArgumentNullException(nameof(containerIdProviders));

_containerIdNormalizer = containerIdNormalizer ?? throw new ArgumentNullException(nameof(containerIdNormalizer));
ContainerId = GetContainerIdOrThrow();
}

Expand Down Expand Up @@ -162,7 +164,12 @@ private string GetContainerIdOrThrow()
{
throw new InvalidOperationException("Valid containerId can't be null.");
}
return containerId;

if (!_containerIdNormalizer.TryNormalize(containerId, out string? normalized))
{
throw new InvalidOperationException($"Container id format can't be recognized: {containerId}");
}
return normalized!;
}
}
throw new InvalidOperationException("Failed fetching container id.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ internal class KubeHttpClientSettingsProvider : KubeHttpClientSettingsBase, IKub
private readonly string _certFilePath;
private readonly string _tokenFilePath;

public KubeHttpClientSettingsProvider(IEnumerable<IContainerIdProvider> containerIdProviders)
: this(containerIdProviders, kubernetesServiceHost: null)
public KubeHttpClientSettingsProvider(IEnumerable<IContainerIdProvider> containerIdProviders, IContainerIdNormalizer containerIdNormalizer)
: this(containerIdProviders, containerIdNormalizer, kubernetesServiceHost: null)
{
}

public KubeHttpClientSettingsProvider(
IEnumerable<IContainerIdProvider> containerIdProviders,
IContainerIdNormalizer containerIdNormalizer,
string pathToToken = @"/var/run/secrets/kubernetes.io/serviceaccount/token",
string pathToCert = @"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
string pathToNamespace = @"/var/run/secrets/kubernetes.io/serviceaccount/namespace",
string? kubernetesServiceHost = null,
string? kubernetesServicePort = null)
: base(kubernetesServiceHost, kubernetesServicePort, containerIdProviders)
: base(kubernetesServiceHost, kubernetesServicePort, containerIdProviders, containerIdNormalizer)
{
_tokenFilePath = Arguments.IsNotNullOrEmpty(pathToToken, nameof(pathToToken));
_certFilePath = Arguments.IsNotNullOrEmpty(pathToCert, nameof(pathToCert));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ internal class KubeHttpSettingsWinContainerProvider : KubeHttpClientSettingsBase

public KubeHttpSettingsWinContainerProvider(
IEnumerable<IContainerIdProvider> containerIdProviders,
IContainerIdNormalizer containerIdNormalizer,
string serviceAccountFolder = @"C:\var\run\secrets\kubernetes.io\serviceaccount",
string tokenFileName = "token",
string certFileName = "ca.crt",
string namespaceFileName = "namespace",
string kubernetesServiceHost = null,
string kubernetesServicePort = null)
: base(kubernetesServiceHost, kubernetesServicePort, containerIdProviders)
: base(kubernetesServiceHost, kubernetesServicePort, containerIdProviders, containerIdNormalizer)
{
// Container id won't be fetched for windows container.
DirectoryInfo serviceAccountDirectory =
Expand Down
65 changes: 65 additions & 0 deletions tests/UnitTests/ContainerIdNormalizerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using Xunit;

namespace Microsoft.ApplicationInsights.Kubernetes.ContainerIdProviders.Tests;

public class ContainerIdNormalizerTests
{
[Theory]
// With prefix and suffix:
[InlineData(@"cri-containerd-5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06.scope", "5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06")]
// With prefix only:
[InlineData(@"docker://5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06", "5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06")]
// With suffix only:
[InlineData(@"5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06-scope", "5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06")]
// Same as normalized:
[InlineData(@"5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06", "5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06")]
// Longer than 64 digits - notes: so that the match regex is simplified.
[InlineData(@"5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06a", "5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f06")]
public void TryGetNormalizedShouldNormalizeContainerIds(string input, string expected)
{
ContainerIdNormalizer target = new ContainerIdNormalizer();
bool result = target.TryNormalize(input, out string actual);

Assert.True(result);
Assert.Equal(expected, actual);
}

[Theory]
// Input has no container id
[InlineData("Input has no container id")]
// Short guid is not accepted
[InlineData("f78375b1c487")]
// Shorter than 64 digits
[InlineData("5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f0")]
// Not a valid guid with character of z
[InlineData("5146b2bcd77ab4f2624bc1fbd98cf9751741344a80b043dbd77a4e847bff4f0z")]
public void TryGetNormalizedShouldNotAcceptInvalidContainerIds(string input)
{
ContainerIdNormalizer target = new ContainerIdNormalizer();
bool result = target.TryNormalize(input, out string actual);

Assert.False(result);
Assert.Null(actual);
}

[Fact]
public void TryGetNormalizedShouldHandleStringEmpty()
{
ContainerIdNormalizer target = new ContainerIdNormalizer();
bool result = target.TryNormalize(string.Empty, out string actual);

Assert.True(result);
Assert.Equal(string.Empty, actual);
}

[Fact]
public void TryGetNormalizedShouldDoesNotAcceptNull()
{
ContainerIdNormalizer target = new ContainerIdNormalizer();
Assert.Throws<ArgumentNullException>(() =>
{
_ = target.TryNormalize(null, out string actual);
});
}
}
17 changes: 17 additions & 0 deletions tests/UnitTests/KuteHttpClientSettingsProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using Microsoft.ApplicationInsights.Kubernetes.ContainerIdProviders;
using Moq;
using Xunit;

namespace Microsoft.ApplicationInsights.Kubernetes
Expand All @@ -11,8 +12,10 @@ public class KuteHttpClientSettingsProviderTests
[Fact(DisplayName = "Base address is formed by constructor")]
public void BaseAddressShouldBeFormed()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
IKubeHttpClientSettingsProvider target = new KubeHttpClientSettingsProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
pathToNamespace: "namespace",
kubernetesServiceHost: "127.0.0.1",
kubernetesServicePort: "8001");
Expand All @@ -25,8 +28,10 @@ public void BaseAddressShouldBeFormed()
[Fact(DisplayName = "Base address is formed by constructor of windows kube settings provider")]
public void BaseAddressShouldBeFormedWin()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
IKubeHttpClientSettingsProvider target = new KubeHttpSettingsWinContainerProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
serviceAccountFolder: ".",
namespaceFileName: "namespace",
kubernetesServiceHost: "127.0.0.1",
Expand All @@ -39,8 +44,10 @@ public void BaseAddressShouldBeFormedWin()
[Fact(DisplayName = "Container id is set to string.Empty for windows container settings")]
public void ContainerIdIsAlwaysNullForWinSettings()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
IKubeHttpClientSettingsProvider target = new KubeHttpSettingsWinContainerProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
serviceAccountFolder: ".",
namespaceFileName: "namespace",
kubernetesServiceHost: "127.0.0.1",
Expand All @@ -52,8 +59,10 @@ public void ContainerIdIsAlwaysNullForWinSettings()

public void TokenShoudBeFetched()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
IKubeHttpClientSettingsProvider target = new KubeHttpClientSettingsProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
pathToNamespace: "namespace",
pathToToken: "token",
kubernetesServiceHost: "127.0.0.1",
Expand All @@ -64,8 +73,10 @@ public void TokenShoudBeFetched()
[Fact(DisplayName = "Token can be fetched by windows settings provider")]
public void TokenShouldBeFetchedForWin()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
IKubeHttpClientSettingsProvider target = new KubeHttpSettingsWinContainerProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
serviceAccountFolder: ".",
namespaceFileName: "namespace",
tokenFileName:"token",
Expand All @@ -78,8 +89,10 @@ public void TokenShouldBeFetchedForWin()
[Fact(DisplayName = "Return true when certificate chain is valid")]
public void TrueWhenValidCertificate()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
KubeHttpClientSettingsProvider target = new KubeHttpClientSettingsProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
pathToNamespace: "namespace",
kubernetesServiceHost: "127.0.0.1",
kubernetesServicePort: "8001");
Expand All @@ -95,8 +108,10 @@ public void TrueWhenValidCertificate()
[Fact(DisplayName = "Return false when certificate chain is invalid")]
public void FalseWhenInvalidCertificate()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
KubeHttpClientSettingsProvider target = new KubeHttpClientSettingsProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
pathToNamespace: "namespace",
kubernetesServiceHost: "127.0.0.1",
kubernetesServicePort: "8001");
Expand All @@ -112,8 +127,10 @@ public void FalseWhenInvalidCertificate()
[Fact(DisplayName = "Return false when certificate out of date")]
public void FalseWhenOutOfDateCertificate()
{
IContainerIdNormalizer containerIdNormalizer = new ContainerIdNormalizer();
KubeHttpClientSettingsProvider target = new KubeHttpClientSettingsProvider(
GetConatinerIdProviders(),
containerIdNormalizer,
pathToNamespace: "namespace",
kubernetesServiceHost: "127.0.0.1",
kubernetesServicePort: "8001");
Expand Down

0 comments on commit ad92fb8

Please sign in to comment.