Skip to content

Commit

Permalink
Source container variables from status
Browse files Browse the repository at this point in the history
This data is now available from DCP, so we don't have to launch processes to query docker for this data any more, which simplifies things quite nicely.
  • Loading branch information
drewnoakes committed Dec 11, 2023
1 parent e515c5e commit 0f0b74b
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 81 deletions.
82 changes: 1 addition & 81 deletions src/Aspire.Hosting/Dashboard/DcpDataSource.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Dashboard.Model;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Dcp;
using Aspire.Hosting.Dcp.Model;
using Aspire.Hosting.Dcp.Process;
using Aspire.Hosting.Utils;
using k8s;
using Microsoft.Extensions.Logging;

Expand All @@ -25,16 +20,13 @@ internal sealed class DcpDataSource
private readonly KubernetesService _kubernetesService;
private readonly DistributedApplicationModel _applicationModel;
private readonly Func<ResourceViewModel, ObjectChangeType, ValueTask> _onResourceChanged;
private readonly CancellationToken _cancellationToken;
private readonly ILogger _logger;

private readonly Dictionary<string, Container> _containersMap = [];
private readonly Dictionary<string, Executable> _executablesMap = [];
private readonly Dictionary<string, Service> _servicesMap = [];
private readonly Dictionary<string, Endpoint> _endpointsMap = [];
private readonly Dictionary<(ResourceKind, string), List<string>> _resourceAssociatedServicesMap = [];
private readonly ConcurrentDictionary<string, List<EnvVar>> _dockerEnvironmentByContainerId = [];
private readonly HashSet<string> _containerIdsHavingDockerInspections = [];

public DcpDataSource(
KubernetesService kubernetesService,
Expand All @@ -46,7 +38,6 @@ public DcpDataSource(
_kubernetesService = kubernetesService;
_applicationModel = applicationModel;
_onResourceChanged = onResourceChanged;
_cancellationToken = cancellationToken;

_logger = loggerFactory.CreateLogger<ResourceService>();

Expand Down Expand Up @@ -239,9 +230,7 @@ private ContainerViewModel ConvertToContainerViewModel(Container container)
var containerId = container.Status?.ContainerId;
var (endpoints, services) = GetEndpointsAndServices(container, ResourceKind.Container);

var environment = containerId is not null && _dockerEnvironmentByContainerId.TryGetValue(containerId, out var dockerEnvironment)
? GetEnvironmentVariables(dockerEnvironment, null)
: GetEnvironmentVariables(container.Spec.Env, null);
var environment = GetEnvironmentVariables(container.Status?.EffectiveEnv ?? container.Spec.Env, container.Spec.Env);

var model = new ContainerViewModel
{
Expand All @@ -262,14 +251,6 @@ private ContainerViewModel ConvertToContainerViewModel(Container container)
Ports = GetPorts()
};

if (containerId is not null && _containerIdsHavingDockerInspections.Add(containerId))
{
// This container has not yet been inspected. Call kubernetes on the CLI to obtain the environment
// for this container. When returned, the values will be cached in _dockerEnvironmentByContainerId
// and an updated container resource published, which will pick up the docker environment.
Task.Run(() => ComputeEnvironmentVariablesFromDocker(containerId, container.Metadata.Name));
}

return model;

ImmutableArray<int> GetPorts()
Expand Down Expand Up @@ -478,67 +459,6 @@ private void UpdateAssociatedServicesMap(ResourceKind resourceKind, WatchEventTy
}
}

private async Task ComputeEnvironmentVariablesFromDocker(string containerId, string containerName)
{
IAsyncDisposable? processDisposable = null;
try
{
Task<ProcessResult> task;
var outputStringBuilder = new StringBuilder();
var spec = new ProcessSpec(FileUtil.FindFullPathFromPath("docker"))
{
Arguments = $"container inspect --format=\"{{{{json .Config.Env}}}}\" {containerId}",
OnOutputData = s => outputStringBuilder.Append(s),
KillEntireProcessTree = false,
ThrowOnNonZeroReturnCode = false
};

(task, processDisposable) = ProcessUtil.Run(spec);

var exitCode = (await task.WaitAsync(TimeSpan.FromSeconds(30), _cancellationToken).ConfigureAwait(false)).ExitCode;
if (exitCode == 0)
{
var output = outputStringBuilder.ToString();
if (output == string.Empty)
{
return;
}

var jsonArray = JsonNode.Parse(output)?.AsArray();
if (jsonArray is not null)
{
var dockerEnvironment = new List<EnvVar>(capacity: jsonArray.Count);
foreach (var item in jsonArray)
{
if (item is not null)
{
var parts = item.ToString().Split('=', 2);
dockerEnvironment.Add(new EnvVar { Name = parts[0], Value = parts[1] });
}
}

_dockerEnvironmentByContainerId[containerId] = dockerEnvironment;

if (_containersMap.TryGetValue(containerName, out var container))
{
await ProcessContainerChange(WatchEventType.Modified, container).ConfigureAwait(false);
}
}
}
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Failed to retrieve environment variables from docker container for {containerId}", containerId);
}
finally
{
if (processDisposable != null)
{
await processDisposable.DisposeAsync().ConfigureAwait(false);
}
}
}

private static bool ProcessResourceChange<T>(Dictionary<string, T> map, WatchEventType watchEventType, T resource)
where T : CustomResource
{
Expand Down
4 changes: 4 additions & 0 deletions src/Aspire.Hosting/Dcp/Model/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ internal sealed class ContainerStatus : V1Status
[JsonPropertyName("exitCode")]
public int ExitCode { get; set; } = Conventions.UnknownExitCode;

// Effective values of environment variables, after all substitutions have been applied
[JsonPropertyName("effectiveEnv")]
public List<EnvVar>? EffectiveEnv { get; set; }

// Note: the ContainerStatus has "Message" property that represents a human-readable information about Container state.
// It is provided by V1Status base class.
}
Expand Down

0 comments on commit 0f0b74b

Please sign in to comment.