| File: Agents\AspireSkills\EmbeddedAspireSkillsBundleProvider.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; using Aspire.Cli.Utils; using Microsoft.Extensions.Logging; namespace Aspire.Cli.Agents.AspireSkills; /// <summary> /// Provides the validated Aspire skills bundle embedded in the CLI assembly. /// </summary> internal interface IEmbeddedAspireSkillsBundleProvider { /// <summary> /// Gets the parsed metadata embedded alongside the Aspire skills bundle archive. /// </summary> EmbeddedAspireSkillsBundleMetadata? Metadata { get; } /// <summary> /// Creates the embedded Aspire skills bundle in the specified directory. /// </summary> Task<AspireSkillsBundle?> CreateBundleAsync( DirectoryInfo bundleDirectory, CancellationToken cancellationToken); } internal sealed class EmbeddedAspireSkillsBundleProvider : IEmbeddedAspireSkillsBundleProvider { private const string ArchiveResourceName = "aspire-skills.bundle.tgz"; private const string MetadataResourceName = "aspire-skills.metadata.json"; private readonly IAspireSkillsBundleProvider _bundleProvider; private readonly ILogger<EmbeddedAspireSkillsBundleProvider> _logger; private readonly Lazy<EmbeddedAspireSkillsBundleMetadata?> _metadata; public EmbeddedAspireSkillsBundleProvider( IAspireSkillsBundleProvider bundleProvider, ILogger<EmbeddedAspireSkillsBundleProvider> logger) { ArgumentNullException.ThrowIfNull(bundleProvider); ArgumentNullException.ThrowIfNull(logger); _bundleProvider = bundleProvider; _logger = logger; _metadata = new Lazy<EmbeddedAspireSkillsBundleMetadata?>(LoadMetadata); } public EmbeddedAspireSkillsBundleMetadata? Metadata => _metadata.Value; public async Task<AspireSkillsBundle?> CreateBundleAsync( DirectoryInfo bundleDirectory, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(bundleDirectory); var metadata = Metadata; if (metadata is null || string.IsNullOrWhiteSpace(metadata.Sha512)) { return null; } await using var archiveStream = OpenArchive(); if (archiveStream is null) { return null; } Directory.CreateDirectory(bundleDirectory.FullName); var temporaryDirectoryRoot = bundleDirectory.Parent ?? throw new InvalidOperationException("The Aspire skills bundle staging directory must have a parent directory."); // Keep the archive beside the staging directory so a transient Windows file lock during // best-effort cleanup cannot prevent the validated staging directory from being published. using var temporaryDirectory = TemporaryCacheDirectory.Create( temporaryDirectoryRoot.FullName, "embedded", path => FileDeleteHelper.TryDeleteDirectory(path), path => FileDeleteHelper.TryDeleteFile(path)); var archivePath = Path.Combine(temporaryDirectory.FullName, "bundle.tgz"); await using (var fileStream = File.Create(archivePath)) { await archiveStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } return await _bundleProvider.CreateAsync( new FileInfo(archivePath), bundleDirectory, metadata.Sha512, cancellationToken, skipCompatibilityCheck: true).ConfigureAwait(false); } private Stream? OpenArchive() { var stream = typeof(EmbeddedAspireSkillsBundleProvider).Assembly.GetManifestResourceStream(ArchiveResourceName); if (stream is null) { _logger.LogDebug("Embedded Aspire skills archive resource {ResourceName} was not found.", ArchiveResourceName); } return stream; } private EmbeddedAspireSkillsBundleMetadata? LoadMetadata() { using var stream = typeof(EmbeddedAspireSkillsBundleProvider).Assembly.GetManifestResourceStream(MetadataResourceName); if (stream is null) { _logger.LogDebug("Embedded Aspire skills metadata resource {ResourceName} was not found.", MetadataResourceName); return null; } try { var metadata = JsonSerializer.Deserialize( stream, AspireSkillsJsonSerializerContext.Default.EmbeddedAspireSkillsBundleMetadata); if (metadata is null) { _logger.LogDebug("Embedded Aspire skills metadata resource {ResourceName} was empty.", MetadataResourceName); } return metadata; } catch (JsonException ex) { _logger.LogWarning(ex, "Embedded Aspire skills metadata resource {ResourceName} could not be parsed.", MetadataResourceName); return null; } } }