Skip to content

Commit 1d74242

Browse files
committed
Add profile for uploading files in a given directory. Add support for maintaining relative directory structure for file uploads.
1 parent 396b163 commit 1d74242

File tree

9 files changed

+393
-1
lines changed

9 files changed

+393
-1
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.28
1+
2.0.29

src/VirtualClient/Module.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
<!-- Microsoft.Windows.Compatibility -->
8484
<Microsoft_Windows_Compatibility_PackageVersion>9.0.3</Microsoft_Windows_Compatibility_PackageVersion>
8585

86+
<!-- MimeMapping -->
87+
<MimeMapping_PackageVersion>3.1.0</MimeMapping_PackageVersion>
88+
8689
<!-- Moq -->
8790
<Moq_PackageVersion>4.18.2</Moq_PackageVersion>
8891

src/VirtualClient/VirtualClient.Contracts.UnitTests/VirtualClientComponentExtensionsTests.cs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ namespace VirtualClient.Contracts
66
using System;
77
using System.Collections.Generic;
88
using System.Diagnostics;
9+
using System.IO;
910
using System.Linq;
1011
using System.Runtime.InteropServices;
1112
using System.Text;
1213
using System.Threading;
1314
using System.Threading.Tasks;
1415
using Microsoft.Extensions.DependencyInjection;
1516
using Microsoft.Extensions.DependencyInjection.Extensions;
17+
using Moq;
1618
using NUnit.Framework;
1719
using VirtualClient.Common.Contracts;
1820
using VirtualClient.Common.Extensions;
@@ -249,6 +251,200 @@ public void CombineExtensionProducesTheExpectedPathOnUnixSystems()
249251
}
250252
}
251253

254+
[Test]
255+
public void CreateCreateFileUploadDescriptorsExtensionCreatesTheExpectedDescriptorsOnUnixSystems_1()
256+
{
257+
this.fixture.Setup(PlatformID.Unix);
258+
259+
string directory = "/home/user/Logs";
260+
string[] expectedFiles = new string[]
261+
{
262+
$"{directory}/log1.txt",
263+
$"{directory}/log2.txt"
264+
};
265+
266+
this.fixture.FileSystem
267+
.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
268+
.Returns<string>(file => directory);
269+
270+
this.fixture.FileSystem
271+
.Setup(fs => fs.Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
272+
.Returns(expectedFiles);
273+
274+
using (var component = new TestVirtualClientComponent(this.fixture))
275+
{
276+
IEnumerable<FileUploadDescriptor> descriptors = component.CreateFileUploadDescriptors(directory, timestamped: false);
277+
278+
Assert.IsNotNull(descriptors);
279+
Assert.IsTrue(descriptors.Count() == 2);
280+
281+
FileUploadDescriptor descriptor1 = descriptors.ElementAt(0);
282+
Assert.AreEqual(expectedFiles[0], descriptor1.FilePath);
283+
Assert.IsTrue(descriptor1.BlobName.EndsWith("log1.txt"));
284+
Assert.IsTrue(descriptor1.BlobPath.EndsWith("/log1.txt"));
285+
Assert.AreEqual(component.ExperimentId, descriptor1.ContainerName);
286+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor1.ContentEncoding);
287+
Assert.IsNotNull(descriptor1.ContentType);
288+
289+
FileUploadDescriptor descriptor2 = descriptors.ElementAt(1);
290+
Assert.AreEqual(expectedFiles[1], descriptor2.FilePath);
291+
Assert.IsTrue(descriptor2.BlobName.EndsWith("log2.txt"));
292+
Assert.IsTrue(descriptor2.BlobPath.EndsWith("/log2.txt"));
293+
Assert.AreEqual(component.ExperimentId, descriptor2.ContainerName);
294+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor2.ContentEncoding);
295+
Assert.IsNotNull(descriptor2.ContentType);
296+
}
297+
}
298+
299+
[Test]
300+
public void CreateCreateFileUploadDescriptorsExtensionCreatesTheExpectedDescriptorsOnUnixSystems_2()
301+
{
302+
this.fixture.Setup(PlatformID.Unix);
303+
304+
string directory = "/home/user/Logs";
305+
string[] expectedFiles = new string[]
306+
{
307+
$"{directory}/log1.txt",
308+
$"{directory}/directory2/log2.txt",
309+
$"{directory}/directory2/directory3/log3.txt"
310+
};
311+
312+
this.fixture.FileSystem
313+
.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
314+
.Returns<string>(file => file.Replace(Path.GetFileName(file), string.Empty));
315+
316+
this.fixture.FileSystem
317+
.Setup(fs => fs.Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
318+
.Returns(expectedFiles);
319+
320+
using (var component = new TestVirtualClientComponent(this.fixture))
321+
{
322+
IEnumerable<FileUploadDescriptor> descriptors = component.CreateFileUploadDescriptors(directory, timestamped: false);
323+
324+
Assert.IsNotNull(descriptors);
325+
Assert.IsTrue(descriptors.Count() == 3);
326+
327+
FileUploadDescriptor descriptor1 = descriptors.ElementAt(0);
328+
Assert.AreEqual(expectedFiles[0], descriptor1.FilePath);
329+
Assert.IsTrue(descriptor1.BlobName.EndsWith("log1.txt"));
330+
Assert.IsTrue(descriptor1.BlobPath.EndsWith("/log1.txt"));
331+
Assert.AreEqual(component.ExperimentId, descriptor1.ContainerName);
332+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor1.ContentEncoding);
333+
Assert.IsNotNull(descriptor1.ContentType);
334+
335+
FileUploadDescriptor descriptor2 = descriptors.ElementAt(1);
336+
Assert.AreEqual(expectedFiles[1], descriptor2.FilePath);
337+
Assert.IsTrue(descriptor2.BlobName.EndsWith("log2.txt"));
338+
Assert.IsTrue(descriptor2.BlobPath.EndsWith("/directory2/log2.txt"));
339+
Assert.AreEqual(component.ExperimentId, descriptor2.ContainerName);
340+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor2.ContentEncoding);
341+
Assert.IsNotNull(descriptor2.ContentType);
342+
343+
FileUploadDescriptor descriptor3 = descriptors.ElementAt(2);
344+
Assert.AreEqual(expectedFiles[2], descriptor3.FilePath);
345+
Assert.IsTrue(descriptor3.BlobName.EndsWith("log3.txt"));
346+
Assert.IsTrue(descriptor3.BlobPath.EndsWith("/directory2/directory3/log3.txt"));
347+
Assert.AreEqual(component.ExperimentId, descriptor3.ContainerName);
348+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor3.ContentEncoding);
349+
Assert.IsNotNull(descriptor3.ContentType);
350+
}
351+
}
352+
353+
[Test]
354+
public void CreateCreateFileUploadDescriptorsExtensionCreatesTheExpectedDescriptorsOnWindowsSystems_1()
355+
{
356+
string directory = "C:\\Users\\User\\Logs";
357+
string[] expectedFiles = new string[]
358+
{
359+
$"{directory}\\log1.txt",
360+
$"{directory}\\log2.txt"
361+
};
362+
363+
this.fixture.FileSystem
364+
.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
365+
.Returns<string>(file => directory);
366+
367+
this.fixture.FileSystem
368+
.Setup(fs => fs.Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
369+
.Returns(expectedFiles);
370+
371+
using (var component = new TestVirtualClientComponent(this.fixture))
372+
{
373+
IEnumerable<FileUploadDescriptor> descriptors = component.CreateFileUploadDescriptors(directory, timestamped: false);
374+
375+
Assert.IsNotNull(descriptors);
376+
Assert.IsTrue(descriptors.Count() == 2);
377+
378+
FileUploadDescriptor descriptor1 = descriptors.ElementAt(0);
379+
Assert.AreEqual(expectedFiles[0], descriptor1.FilePath);
380+
Assert.IsTrue(descriptor1.BlobName.EndsWith("log1.txt"));
381+
Assert.IsTrue(descriptor1.BlobPath.EndsWith("/log1.txt"));
382+
Assert.AreEqual(component.ExperimentId, descriptor1.ContainerName);
383+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor1.ContentEncoding);
384+
Assert.IsNotNull(descriptor1.ContentType);
385+
386+
FileUploadDescriptor descriptor2 = descriptors.ElementAt(1);
387+
Assert.AreEqual(expectedFiles[1], descriptor2.FilePath);
388+
Assert.IsTrue(descriptor2.BlobName.EndsWith("log2.txt"));
389+
Assert.IsTrue(descriptor2.BlobPath.EndsWith("/log2.txt"));
390+
Assert.AreEqual(component.ExperimentId, descriptor2.ContainerName);
391+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor2.ContentEncoding);
392+
Assert.IsNotNull(descriptor2.ContentType);
393+
}
394+
}
395+
396+
[Test]
397+
public void CreateCreateFileUploadDescriptorsExtensionCreatesTheExpectedDescriptorsOnWindowsSystems_2()
398+
{
399+
string directory = "C:\\Users\\User\\Logs";
400+
string[] expectedFiles = new string[]
401+
{
402+
$"{directory}\\log1.txt",
403+
$"{directory}\\directory2\\log2.txt",
404+
$"{directory}\\directory2\\directory3\\log3.txt"
405+
};
406+
407+
this.fixture.FileSystem
408+
.Setup(fs => fs.Path.GetDirectoryName(It.IsAny<string>()))
409+
.Returns<string>(file => file.Replace(Path.GetFileName(file), string.Empty));
410+
411+
this.fixture.FileSystem
412+
.Setup(fs => fs.Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
413+
.Returns(expectedFiles);
414+
415+
using (var component = new TestVirtualClientComponent(this.fixture))
416+
{
417+
IEnumerable<FileUploadDescriptor> descriptors = component.CreateFileUploadDescriptors(directory, timestamped: false);
418+
419+
Assert.IsNotNull(descriptors);
420+
Assert.IsTrue(descriptors.Count() == 3);
421+
422+
FileUploadDescriptor descriptor1 = descriptors.ElementAt(0);
423+
Assert.AreEqual(expectedFiles[0], descriptor1.FilePath);
424+
Assert.IsTrue(descriptor1.BlobName.EndsWith("log1.txt"));
425+
Assert.IsTrue(descriptor1.BlobPath.EndsWith("/log1.txt"));
426+
Assert.AreEqual(component.ExperimentId, descriptor1.ContainerName);
427+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor1.ContentEncoding);
428+
Assert.IsNotNull(descriptor1.ContentType);
429+
430+
FileUploadDescriptor descriptor2 = descriptors.ElementAt(1);
431+
Assert.AreEqual(expectedFiles[1], descriptor2.FilePath);
432+
Assert.IsTrue(descriptor2.BlobName.EndsWith("log2.txt"));
433+
Assert.IsTrue(descriptor2.BlobPath.EndsWith("/directory2/log2.txt"));
434+
Assert.AreEqual(component.ExperimentId, descriptor2.ContainerName);
435+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor2.ContentEncoding);
436+
Assert.IsNotNull(descriptor2.ContentType);
437+
438+
FileUploadDescriptor descriptor3 = descriptors.ElementAt(2);
439+
Assert.AreEqual(expectedFiles[2], descriptor3.FilePath);
440+
Assert.IsTrue(descriptor3.BlobName.EndsWith("log3.txt"));
441+
Assert.IsTrue(descriptor3.BlobPath.EndsWith("/directory2/directory3/log3.txt"));
442+
Assert.AreEqual(component.ExperimentId, descriptor3.ContainerName);
443+
Assert.AreEqual(Encoding.UTF8.WebName, descriptor3.ContentEncoding);
444+
Assert.IsNotNull(descriptor3.ContentType);
445+
}
446+
}
447+
252448
[Test]
253449
public void VerifyLayoutDefinedExtensionThrowsWhenVerifyingTheEnvironmentLayoutIfItDoesNotExist()
254450
{

src/VirtualClient/VirtualClient.Contracts/VirtualClient.Contracts.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="MathNet.Numerics.Signed" Version="$(MathNet_Numerics_Signed_PackageVersion)" />
1111
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="$(Microsoft_Extensions_Http_Polly_PackageVersion)" />
12+
<PackageReference Include="MimeMapping " Version="[$(MimeMapping_PackageVersion)]" />
1213
<PackageReference Include="System.IO.Abstractions" Version="$(System_IO_Abstractions_PackageVersion)" />
1314
<PackageReference Include="System.IO.FileSystem.Primitives" Version="$(System_IO_FileSystem_Primitives_PackageVersion)" />
1415
<PackageReference Include="YamlDotNet" Version="$(YamlDotNet_PackageVersion)" />

src/VirtualClient/VirtualClient.Contracts/VirtualClientComponentExtensions.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ namespace VirtualClient.Contracts
99
using System.IO.Abstractions;
1010
using System.Linq;
1111
using System.Runtime.InteropServices;
12+
using System.Text;
1213
using System.Text.RegularExpressions;
1314
using System.Threading;
1415
using System.Threading.Tasks;
16+
using Microsoft.CodeAnalysis;
1517
using Microsoft.Extensions.DependencyInjection;
1618
using Microsoft.Extensions.Logging;
1719
using Polly;
@@ -128,6 +130,59 @@ public static FileUploadDescriptor CreateFileUploadDescriptor(this VirtualClient
128130
return descriptor;
129131
}
130132

133+
/// <summary>
134+
/// Creates a descriptor that can be used to publish a request
135+
/// </summary>
136+
/// <param name="component">The component requesting the file upload descriptor.</param>
137+
/// <param name="targetDirectory">A directory with files to upload.</param>
138+
/// <param name="parameters">Parameters related to the component that produced the file (e.g. the parameters from the component).</param>
139+
/// <param name="metadata">Additional information and metadata related to the blob/file to include in the descriptor alongside the default manifest information.</param>
140+
/// <param name="timestamped">
141+
/// True to to include the file creation time in the file name (e.g. 2023-05-21t09-23-30-23813z-file.log). This is explicit to allow for cases where modification of the
142+
/// file name is not desirable. Default = true (timestamped file names).
143+
/// </param>
144+
public static IEnumerable<FileUploadDescriptor> CreateFileUploadDescriptors(this VirtualClientComponent component, string targetDirectory, IDictionary<string, IConvertible> parameters = null, IDictionary<string, IConvertible> metadata = null, bool timestamped = true)
145+
{
146+
component.ThrowIfNull(nameof(component));
147+
targetDirectory.ThrowIfNullOrWhiteSpace(nameof(targetDirectory));
148+
149+
IFileSystem fileSystem = component.Dependencies.GetService<IFileSystem>();
150+
IEnumerable<string> filesToUpload = fileSystem.Directory.GetFiles(targetDirectory, "*.*", SearchOption.AllDirectories);
151+
List<FileUploadDescriptor> descriptors = new List<FileUploadDescriptor>();
152+
153+
if (filesToUpload?.Any() == true)
154+
{
155+
foreach (string file in filesToUpload)
156+
{
157+
string contentType = MimeMapping.MimeUtility.GetMimeMapping(file);
158+
string subDirectory = fileSystem.Path.GetDirectoryName(file).Substring(targetDirectory.Length)?.Trim('\\')?.Trim('/');
159+
string uploadDirectory = null;
160+
161+
if (!string.IsNullOrWhiteSpace(subDirectory))
162+
{
163+
uploadDirectory = subDirectory.Trim('\\').Trim('/');
164+
}
165+
166+
FileUploadDescriptor descriptor = VirtualClientComponentExtensions.CreateFileUploadDescriptor(
167+
component,
168+
new FileContext(
169+
fileSystem.FileInfo.New(file),
170+
contentType,
171+
Encoding.UTF8.WebName,
172+
component.ExperimentId,
173+
component.AgentId,
174+
null,
175+
uploadDirectory),
176+
parameters: parameters,
177+
timestamped: timestamped);
178+
179+
descriptors.Add(descriptor);
180+
}
181+
}
182+
183+
return descriptors;
184+
}
185+
131186
/// <summary>
132187
/// Evaluates each of the parameters provided to the component to replace
133188
/// supported placeholder expressions (e.g. {PackagePath:anytool} -> replace with path to 'anytool' package).

0 commit comments

Comments
 (0)