Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(restore): add cifs client to unc paths usage to work on any OS #447

Merged
merged 7 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions cmf-cli/Commands/assemble/AssembleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,12 @@ private void AssembleTestPackages(IDirectoryInfo outputDir, IDirectoryInfo[] rep
// Load test package from repo if is not loaded yet
if (testPackage.CmfPackage == null || (testPackage.CmfPackage != null && testPackage.CmfPackage.Uri == null))
{
string _dependencyFileName = $"{testPackage.Id}.{testPackage.Version}.zip";

IFileInfo dependencyFile = repoDirectories?
.Select(r => r.GetFiles(_dependencyFileName).FirstOrDefault())
.Where(r => r != null)
.FirstOrDefault();

if (dependencyFile != null)
{
testPackage.CmfPackage = new(testPackage.Id, testPackage.Version, new(dependencyFile.FullName));
}
else
string packageName = $"{testPackage.Id}.{testPackage.Version}";
testPackage.CmfPackage = CmfPackage.LoadFromRepo(repoDirectories, testPackage.Id, testPackage.Version, fromManifest: false);
if(testPackage.CmfPackage == null)
{
throw new CliException(string.Format(CliMessages.SomePackagesNotFound, _dependencyFileName));
string errorMessage = string.Format(CliMessages.SomePackagesNotFound, $"{packageName}.zip");
throw new CliException(errorMessage);
}
}

Expand Down Expand Up @@ -270,7 +262,7 @@ private void AssembleDependencies(IDirectoryInfo outputDir, Uri ciRepo, IDirecto
// Save all external dependencies and locations in a dictionary
else
{
packagesLocation[$"{dependency.Id}@{dependency.Version}"] = dependency.CmfPackage.Uri.GetFileName();
packagesLocation[$"{dependency.Id}@{dependency.Version}"] = ExecutionContext.Instance.RunningOnWindows ? dependency.CmfPackage.Uri.GetFileName() : dependency.CmfPackage.Uri.LocalPath;
}

AssembleDependencies(outputDir, ciRepo, repoDirectories, dependency.CmfPackage, assembledDependencies, includeTestPackages);
Expand Down Expand Up @@ -308,7 +300,18 @@ private void AssemblePackage(IDirectoryInfo outputDir, IDirectoryInfo[] repoDire
}

Log.Information(string.Format(CliMessages.GetPackage, cmfPackage.PackageId, cmfPackage.Version));
cmfPackage.Uri.GetFile().CopyTo(destinationFile);
if(cmfPackage.SharedFolder == null)
{
cmfPackage.Uri.GetFile().CopyTo(destinationFile);
}
else
{
var file = cmfPackage.SharedFolder.GetFile(cmfPackage.ZipPackageName);
using var fileStream = file.Item2;
fileStream.Position = 0; // Reset stream position to the beginning
using var fileStreamOutput = fileSystem.File.Create(destinationFile);
fileStream.CopyTo(fileStreamOutput);
}

// Assemble Tests
if (includeTestPackages)
Expand Down
1 change: 0 additions & 1 deletion cmf-cli/Handlers/PackageType/BusinessPackageTypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public BusinessPackageTypeHandler(CmfPackage cmfPackage) : base(cmfPackage)
Command = "restore",
DisplayName = "NuGet restore",
Solution = this.fileSystem.FileInfo.New(Path.Join(cmfPackage.GetFileInfo().Directory.FullName, "Business.sln")),
NuGetConfig = this.fileSystem.FileInfo.New(Path.Join(FileSystemUtilities.GetProjectRoot(this.fileSystem, throwException: true).FullName, "NuGet.Config")),
WorkingDirectory = cmfPackage.GetFileInfo().Directory
},
new DotnetCommand()
Expand Down
80 changes: 45 additions & 35 deletions cmf-cli/Handlers/PackageType/PackageTypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -662,50 +662,60 @@ public virtual void RestoreDependencies(Uri[] repoUris)
var identifier = $"{dependency.Id}@{dependency.Version}";
Log.Debug($"Processing dependency {identifier}...");
Log.Debug($"Found package {identifier} at {dependency.CmfPackage.Uri.AbsoluteUri}");
if (dependency.CmfPackage.Uri.IsDirectory())
if(dependency.CmfPackage.SharedFolder != null)
{
var file = dependency.CmfPackage.SharedFolder.GetFile(dependency.CmfPackage.ZipPackageName);
ExtractZip(file.Item2, identifier);
}
else if (dependency.CmfPackage.Uri.IsDirectory())
{
using (Stream zipToOpen = this.fileSystem.FileInfo.New(dependency.CmfPackage.Uri.LocalPath).OpenRead())
{
using (ZipArchive zip = new(zipToOpen, ZipArchiveMode.Read))
{
// these tuples allow us to rewrite entry paths
var entriesToExtract = new List<Tuple<ZipArchiveEntry, string>>();
entriesToExtract.AddRange(zip.Entries.Select(entry => new Tuple<ZipArchiveEntry, string>(entry, entry.FullName)));

foreach (var entry in entriesToExtract)
{
var target = this.fileSystem.Path.Join(this.DependenciesFolder.FullName, omitIdentifier ? null : identifier, entry.Item2);
var targetDir = this.fileSystem.Path.GetDirectoryName(target);
if (target.EndsWith("/"))
{
// this a dotnet bug: if a folder contains a ., the library assumes it's a file and adds it as an entry
// however, afterwards all folder contents are separate entries, so we can just skip these
continue;
}

if (!fileSystem.File.Exists(target)) // TODO: support overwriting if requested
{
var overwrite = false;
Log.Debug($"Extracting {entry.Item1.FullName} to {target}");
if (!string.IsNullOrEmpty(targetDir))
{
fileSystem.Directory.CreateDirectory(targetDir);
}

entry.Item1.ExtractToFile(target, overwrite, fileSystem);
}
else
{
Log.Debug($"Skipping {target}, file exists");
}
}
}
ExtractZip(zipToOpen, identifier);
}
}
}
});
}

private void ExtractZip(Stream zipToOpen, string identifier)
{
using (ZipArchive zip = new(zipToOpen, ZipArchiveMode.Read))
{
// these tuples allow us to rewrite entry paths
var entriesToExtract = new List<Tuple<ZipArchiveEntry, string>>();
entriesToExtract.AddRange(zip.Entries.Select(entry => new Tuple<ZipArchiveEntry, string>(entry, entry.FullName)));

foreach (var entry in entriesToExtract)
{
var target = this.fileSystem.Path.Join(this.DependenciesFolder.FullName, omitIdentifier ? null : identifier, entry.Item2);
var targetDir = this.fileSystem.Path.GetDirectoryName(target);
if (target.EndsWith("/"))
{
// this a dotnet bug: if a folder contains a ., the library assumes it's a file and adds it as an entry
// however, afterwards all folder contents are separate entries, so we can just skip these
continue;
}

if (!fileSystem.File.Exists(target)) // TODO: support overwriting if requested
{
var overwrite = false;
Log.Debug($"Extracting {entry.Item1.FullName} to {target}");
if (!string.IsNullOrEmpty(targetDir))
{
fileSystem.Directory.CreateDirectory(targetDir);
}

entry.Item1.ExtractToFile(target, overwrite, fileSystem);
}
else
{
Log.Debug($"Skipping {target}, file exists");
}
}
}
}

#endregion Public Methods
}
}
14 changes: 14 additions & 0 deletions core/Interfaces/ICIFSClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using Core.Objects;

namespace Cmf.CLI.Core.Interfaces
{
public interface ICIFSClient
{
public string Server { get; }
public List<SharedFolder> SharedFolders { get;}
public bool IsConnected { get; }
public void Connect();
public void Disconnect();
}
}
10 changes: 10 additions & 0 deletions core/Interfaces/ISharedFolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.IO;

namespace Cmf.CLI.Core.Interfaces
{
public interface ISharedFolder
{
public Tuple<Uri, Stream> GetFile(string fileName);
}
}
137 changes: 137 additions & 0 deletions core/Objects/CIFSClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using Cmf.CLI.Core;
using Cmf.CLI.Core.Interfaces;
using Cmf.CLI.Utilities;
using Microsoft.TemplateEngine.Utils;
using SMBLibrary;
using SMBLibrary.Client;

namespace Core.Objects
{
public class CIFSClient : ICIFSClient
{
public string Server { get; private set; }
public List<SharedFolder> SharedFolders { get; private set; }
public bool IsConnected { get; private set; }

private ISMBClient _smbClient;
private string _domain;
private string _username;
private string _password;

public CIFSClient(string server, IEnumerable<Uri> uris, ISMBClient smbClient = null)
{
Server = server;
_smbClient = smbClient ?? new SMB2Client();
_domain = Environment.GetEnvironmentVariable("CIFS_DOMAIN");
_username = Environment.GetEnvironmentVariable("CIFS_USERNAME");
_password = Environment.GetEnvironmentVariable("CIFS_PASSWORD");
if(string.IsNullOrEmpty(_domain) && string.IsNullOrEmpty(_username) && string.IsNullOrEmpty(_password))
{
Log.Warning("CIFS credentials not found. Please set CIFS_DOMAIN, CIFS_USERNAME and CIFS_PASSWORD environment variables");
}
else
{
Connect();

if(IsConnected)
{
SharedFolders = [];
uris.ForEach(uri => SharedFolders.Add(new SharedFolder(uri, _smbClient)));
}
}
}

public void Connect()
{
Log.Debug($"Connecting to SMB server {Server} with username {_username}");
IsConnected = _smbClient.Connect(Server, SMBTransportType.DirectTCPTransport);
if (!IsConnected)
{
Log.Debug($"Failed to connect to {Server}");
Log.Warning($"Failed to connect to {Server}");
}

var status = _smbClient.Login(_domain, _username, _password);
if (status != NTStatus.STATUS_SUCCESS)
{
Log.Debug($"Fail status {status}");
Log.Warning($"Failed to login to {Server} with username {_username}");
}
}

public void Disconnect()
{
_smbClient.Disconnect();
}
}

public class SharedFolder : ISharedFolder
{
public bool Exists { get; private set; }
private ISMBFileStore _smbFileStore { get; set; }
private ISMBClient _client { get; set; }
private string _server { get; set; }
private string _share { get; set; }
private string _path { get; set; }
private Uri _uri { get; set; }

public SharedFolder(Uri uri, ISMBClient client)
{
_client = client;
_server = uri.Host;
_share = uri.PathAndQuery.Split("/")[1];
_path = uri.PathAndQuery.Replace($"/{_share}", "").Substring(1);
_uri = uri;
Load();
}

private void Load()
{
_smbFileStore = _client.TreeConnect(_share, out NTStatus status);
if (status != NTStatus.STATUS_SUCCESS)
{
Log.Debug($"Fail status {status}");
Log.Warning($"Failed to connect to share {_share} on {_server}");
Exists = false;
}
else
{
Exists = true;
}
}

public Tuple<Uri, Stream> GetFile(string fileName)
{
Tuple<Uri, Stream> fileStream = null;
var filepath = $"{_path}/{fileName}";
var status = _smbFileStore.CreateFile(out object fileHandle, out FileStatus fileStatus, filepath, AccessMask.GENERIC_READ, SMBLibrary.FileAttributes.Normal, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null);
if (status == NTStatus.STATUS_SUCCESS)
{
var stream = new MemoryStream();
long bytesRead = 0;
while (true)
{
status = _smbFileStore.ReadFile(out byte[] data, fileHandle, bytesRead, (int)_client.MaxReadSize);
if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
{
throw new Exception($"Failed to read file {filepath}");
}

if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0)
{
break;
}
bytesRead += data.Length;
stream.Write(data, 0, data.Length);
}
var uri = new Uri(_uri, filepath);
fileStream = new Tuple<Uri, Stream>(uri, stream);
}

return fileStream;
}
}
}
Loading
Loading