Skip to content

Commit

Permalink
De-dupe processing from k8s object to view models in Get/Watch methods
Browse files Browse the repository at this point in the history
  • Loading branch information
smitpatel committed Oct 6, 2023
1 parent 80673de commit 08a9e06
Showing 1 changed file with 107 additions and 190 deletions.
297 changes: 107 additions & 190 deletions src/Aspire.Hosting/Dashboard/DashboardViewModelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,21 @@ namespace Aspire.Hosting.Dashboard;
public class DashboardViewModelService(DistributedApplicationModel applicationModel) : IDashboardViewModelService, IDisposable
{
private readonly DistributedApplicationModel _applicationModel = applicationModel;
private readonly KubernetesService _kubernetesService = new KubernetesService();
private readonly KubernetesService _kubernetesService = new();

public async Task<List<ContainerViewModel>> GetContainersAsync()
{
var containers = await _kubernetesService.ListAsync<Container>().ConfigureAwait(false);

return containers.Select(ConvertToContainerViewModel).OrderBy(e => e.Name).ToList();
}

public async Task<List<ExecutableViewModel>> GetExecutablesAsync()
{
var executables = await _kubernetesService.ListAsync<Executable>().ConfigureAwait(false);
return executables
.Where(executable => executable.Metadata.Annotations?.ContainsKey(Executable.CSharpProjectPathAnnotation) == false)
.Select(executable =>
{
var model = new ExecutableViewModel()
{
Name = executable.Metadata.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ExecutablePath = executable.Spec.ExecutablePath,
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile)
};

if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv);
}

return model;
})
.OrderBy(e => e.Name)
.ToList();
.Select(ConvertToExecutableViewModel).OrderBy(e => e.Name).ToList();
}

public async Task<List<ProjectViewModel>> GetProjectsAsync()
Expand All @@ -52,98 +39,7 @@ public async Task<List<ProjectViewModel>> GetProjectsAsync()

return executables
.Where(executable => executable.Metadata.Annotations?.ContainsKey(Executable.CSharpProjectPathAnnotation) == true)
.Select(executable =>
{
var expectedEndpointCount = 0;
if (executable.Metadata?.Annotations?.TryGetValue(Executable.ServiceProducerAnnotation, out var annotationJson) == true)
{
var serviceProducerAnnotations = JsonSerializer.Deserialize<ServiceProducerAnnotation[]>(annotationJson);
if (serviceProducerAnnotations is not null)
{
expectedEndpointCount = serviceProducerAnnotations.Length;
}
}

var model = new ProjectViewModel
{
Name = executable.Metadata!.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ProjectPath = executable.Metadata?.Annotations?[Executable.CSharpProjectPathAnnotation] ?? "",
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile),
ExpectedEndpointCount = expectedEndpointCount
};

if (_applicationModel.TryGetProjectWithPath(model.ProjectPath, out var project) && project.TryGetAllocatedEndPoints(out var allocatedEndpoints))
{
model.Addresses.AddRange(allocatedEndpoints.Select(ep => ep.UriString));
}

model.Endpoints.AddRange(endpoints
.Where(ep => ep.Metadata.OwnerReferences.Any(or => or.Kind == executable.Kind && or.Name == executable.Metadata?.Name))
.Select(ep =>
{
// CONSIDER: a more robust way to store application protocol information in DCP model
string scheme = "http://";
if (ep.Spec.ServiceName?.EndsWith("https") is true)
{
scheme = "https://";
}

return new ServiceEndpoint($"{scheme}{ep.Spec.Address}:{ep.Spec.Port}", ep.Spec.ServiceName ?? "");
})
);

if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv);
}

return model;
})
.OrderBy(m => m.Name)
.ToList();
}

public async Task<List<ContainerViewModel>> GetContainersAsync()
{
var containers = await _kubernetesService.ListAsync<Container>().ConfigureAwait(false);

return containers
.Select(container =>
{
var model = new ContainerViewModel
{
Name = container.Metadata.Name,
NamespacedName = new(container.Metadata.Name, null),
ContainerId = container.Status?.ContainerId,
CreationTimeStamp = container.Metadata.CreationTimestamp?.ToLocalTime(),
Image = container.Spec.Image!,
LogSource = new DockerContainerLogSource(container.Status!.ContainerId!),
State = container.Status?.State
};

if (container.Spec.Ports != null)
{
foreach (var port in container.Spec.Ports)
{
if (port.ContainerPort != null)
{
model.Ports.Add(port.ContainerPort.Value);
}
}
}

if (container.Spec.Env is not null)
{
FillEnvironmentVariables(model.Environment, container.Spec.Env);
}

return model;
})
.OrderBy(e => e.Name)
.ToList();
.Select(executable => ConvertToProjectViewModel(executable, endpoints)).OrderBy(m => m.Name).ToList();
}

public async IAsyncEnumerable<ComponentChanged<ContainerViewModel>> WatchContainersAsync(
Expand All @@ -158,32 +54,7 @@ public async IAsyncEnumerable<ComponentChanged<ContainerViewModel>> WatchContain
continue;
}

var containerViewModel = new ContainerViewModel
{
Name = container.Metadata.Name,
NamespacedName = new(container.Metadata.Name, null),
ContainerId = container.Status?.ContainerId,
CreationTimeStamp = container.Metadata.CreationTimestamp?.ToLocalTime(),
Image = container.Spec.Image!,
LogSource = new DockerContainerLogSource(container.Status!.ContainerId!),
State = container.Status?.State
};

if (container.Spec.Ports != null)
{
foreach (var port in container.Spec.Ports)
{
if (port.ContainerPort != null)
{
containerViewModel.Ports.Add(port.ContainerPort.Value);
}
}
}

if (container.Spec.Env is not null)
{
FillEnvironmentVariables(containerViewModel.Environment, container.Spec.Env);
}
var containerViewModel = ConvertToContainerViewModel(container);

yield return new ComponentChanged<ContainerViewModel>(objectChangeType, containerViewModel);
}
Expand All @@ -206,20 +77,7 @@ public async IAsyncEnumerable<ComponentChanged<ExecutableViewModel>> WatchExecut
continue;
}

var executableViewModel = new ExecutableViewModel
{
Name = executable.Metadata.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ExecutablePath = executable.Spec.ExecutablePath,
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile)
};

if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(executableViewModel.Environment, executable.Status.EffectiveEnv);
}
var executableViewModel = ConvertToExecutableViewModel(executable);

yield return new ComponentChanged<ExecutableViewModel>(objectChangeType, executableViewModel);
}
Expand All @@ -242,55 +100,114 @@ public async IAsyncEnumerable<ComponentChanged<ProjectViewModel>> WatchProjectsA
continue;
}

var expectedEndpointCount = 0;
if (executable.Metadata?.Annotations?.TryGetValue(Executable.ServiceProducerAnnotation, out var annotationJson) == true)
var endpoints = await _kubernetesService.ListAsync<Endpoint>(cancellationToken: cancellationToken).ConfigureAwait(false);
var projectViewModel = ConvertToProjectViewModel(executable, endpoints);

yield return new ComponentChanged<ProjectViewModel>(objectChangeType, projectViewModel);
}
}

private static ContainerViewModel ConvertToContainerViewModel(Container container)
{
var model = new ContainerViewModel
{
Name = container.Metadata.Name,
NamespacedName = new(container.Metadata.Name, null),
ContainerId = container.Status?.ContainerId,
CreationTimeStamp = container.Metadata.CreationTimestamp?.ToLocalTime(),
Image = container.Spec.Image!,
LogSource = new DockerContainerLogSource(container.Status!.ContainerId!),
State = container.Status?.State
};

if (container.Spec.Ports != null)
{
foreach (var port in container.Spec.Ports)
{
var serviceProducerAnnotations = JsonSerializer.Deserialize<ServiceProducerAnnotation[]>(annotationJson);
if (serviceProducerAnnotations is not null)
if (port.ContainerPort != null)
{
expectedEndpointCount = serviceProducerAnnotations.Length;
model.Ports.Add(port.ContainerPort.Value);
}
}
}

var projectViewModel = new ProjectViewModel
{
Name = executable.Metadata!.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ProjectPath = executable.Metadata?.Annotations?[Executable.CSharpProjectPathAnnotation] ?? "",
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile),
ExpectedEndpointCount = expectedEndpointCount
};

if (_applicationModel.TryGetProjectWithPath(projectViewModel.ProjectPath, out var project) && project.TryGetAllocatedEndPoints(out var allocatedEndpoints))
if (container.Spec.Env is not null)
{
FillEnvironmentVariables(model.Environment, container.Spec.Env);
}

return model;
}

private static ExecutableViewModel ConvertToExecutableViewModel(Executable executable)
{
var model = new ExecutableViewModel()
{
Name = executable.Metadata.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ExecutablePath = executable.Spec.ExecutablePath,
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile)
};

if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv);
}

return model;
}

private ProjectViewModel ConvertToProjectViewModel(Executable executable, List<Endpoint> endpoints)
{
var expectedEndpointCount = 0;
if (executable.Metadata?.Annotations?.TryGetValue(Executable.ServiceProducerAnnotation, out var annotationJson) == true)
{
var serviceProducerAnnotations = JsonSerializer.Deserialize<ServiceProducerAnnotation[]>(annotationJson);
if (serviceProducerAnnotations is not null)
{
projectViewModel.Addresses.AddRange(allocatedEndpoints.Select(ep => ep.UriString));
expectedEndpointCount = serviceProducerAnnotations.Length;
}
}

projectViewModel.Endpoints.AddRange((await _kubernetesService.ListAsync<Endpoint>(cancellationToken: cancellationToken).ConfigureAwait(true))
.Where(ep => ep.Metadata.OwnerReferences.Any(or => or.Kind == executable.Kind && or.Name == executable.Metadata?.Name))
.Select(ep =>
var model = new ProjectViewModel
{
Name = executable.Metadata!.Name,
NamespacedName = new(executable.Metadata.Name, null),
CreationTimeStamp = executable.Metadata?.CreationTimestamp?.ToLocalTime(),
ProjectPath = executable.Metadata?.Annotations?[Executable.CSharpProjectPathAnnotation] ?? "",
State = executable.Status?.State,
LogSource = new FileLogSource(executable.Status?.StdOutFile, executable.Status?.StdErrFile),
ExpectedEndpointCount = expectedEndpointCount
};

if (_applicationModel.TryGetProjectWithPath(model.ProjectPath, out var project) && project.TryGetAllocatedEndPoints(out var allocatedEndpoints))
{
model.Addresses.AddRange(allocatedEndpoints.Select(ep => ep.UriString));
}

model.Endpoints.AddRange(endpoints
.Where(ep => ep.Metadata.OwnerReferences.Any(or => or.Kind == executable.Kind && or.Name == executable.Metadata?.Name))
.Select(ep =>
{
// CONSIDER: a more robust way to store application protocol information in DCP model
string scheme = "http://";
if (ep.Spec.ServiceName?.EndsWith("https") is true)
{
// CONSIDER: a more robust way to store application protocol information in DCP model
string scheme = "http://";
if (ep.Spec.ServiceName?.EndsWith("https") is true)
{
scheme = "https://";
}

return new ServiceEndpoint($"{scheme}{ep.Spec.Address}:{ep.Spec.Port}", ep.Spec.ServiceName ?? "");
scheme = "https://";
}
)
);

if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(projectViewModel.Environment, executable.Status.EffectiveEnv);
}
return new ServiceEndpoint($"{scheme}{ep.Spec.Address}:{ep.Spec.Port}", ep.Spec.ServiceName ?? "");
})
);

yield return new ComponentChanged<ProjectViewModel>(objectChangeType, projectViewModel);
if (executable.Status?.EffectiveEnv is not null)
{
FillEnvironmentVariables(model.Environment, executable.Status.EffectiveEnv);
}

return model;
}

public void Dispose()
Expand Down

0 comments on commit 08a9e06

Please sign in to comment.