-
Notifications
You must be signed in to change notification settings - Fork 931
Add support for copying existing files via WithContainerFiles API #8908
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
Changes from 4 commits
fd41c81
9471516
f27d19e
051e4ef
8a165da
53b6813
a2f9177
e94fed1
02fd484
6b63f0e
9d6444d
ab8b7ac
4830606
56659ba
235e7c2
4ae4402
cf90b60
29d0e0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ public string Name | |
|
|
||
| if (Path.GetDirectoryName(value) != string.Empty) | ||
| { | ||
| throw new ArgumentException("Name must be a simple file or folder name and not include any path separators (eg, / or \\). To specify parent folders, use one or more ContainerDirectory entries.", nameof(value)); | ||
| throw new ArgumentException($"Name '{value}' must be a simple file or folder name and not include any path separators (eg, / or \\). To specify parent folders, use one or more ContainerDirectory entries.", nameof(value)); | ||
| } | ||
|
|
||
| _name = value; | ||
|
|
@@ -52,10 +52,17 @@ public string Name | |
| /// </summary> | ||
| public sealed class ContainerFile : ContainerFileSystemItem | ||
| { | ||
|
|
||
| /// <summary> | ||
| /// The contents of the file. If null, the file will be created as an empty file. | ||
| /// The contents of the file. Setting Contents is mutually exclusive with <see cref="SourcePath"/>. If both are set, an exception will be thrown. | ||
| /// </summary> | ||
| public string? Contents { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The path to a file on the host system to copy into the container. This path must be absolute and point to a file on the host system. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does the path need to be absolute? We can use relative paths elsewhere.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don’t explicitly check for an absolute path; it’s more of a SHOULD requirement. We can’t necessarily guarantee the orchestrator and container runtime will have the correct working directory in all scenarios, so absolute paths guarantees consistent behavior. We probably should call out the same for the bind mount annotation. |
||
| /// Setting SourcePath is mutually exclusive with <see cref="Contents"/>. If both are set, an exception will be thrown. | ||
| /// </summary> | ||
| public string? SourcePath { get; set; } | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -67,6 +74,110 @@ public sealed class ContainerDirectory : ContainerFileSystemItem | |
| /// The contents of the directory to create in the container. Will create specified <see cref="ContainerFile"/> and <see cref="ContainerDirectory"/> entries in the directory. | ||
| /// </summary> | ||
| public IEnumerable<ContainerFileSystemItem> Entries { get; set; } = []; | ||
|
|
||
| private class FileTree : Dictionary<string, FileTree> | ||
| { | ||
| public required ContainerFileSystemItem Value { get; set; } | ||
|
|
||
| public static IEnumerable<ContainerFileSystemItem> GetItems(KeyValuePair<string, FileTree> node) | ||
| { | ||
| return node.Value.Value switch | ||
| { | ||
| ContainerDirectory dir => [ | ||
| new ContainerDirectory | ||
| { | ||
| Name = dir.Name, | ||
| Entries = node.Value.SelectMany(GetItems), | ||
| }, | ||
| ], | ||
| ContainerFile file => [file], | ||
| _ => throw new InvalidOperationException($"Unknown file system item type: {node.Value.GetType().Name}"), | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Enumerates files from a specified directory and converts them to <see cref="ContainerFile"/> objects. | ||
| /// </summary> | ||
| /// <param name="path">The directory path to enumerate files from.</param> | ||
| /// <param name="searchPattern"></param> | ||
| /// <param name="searchOptions"></param> | ||
| /// <returns> | ||
| /// An enumerable collection of <see cref="ContainerFileSystemItem"/> objects. | ||
| /// </returns> | ||
| /// <exception cref="DirectoryNotFoundException">Thrown when the specified path does not exist.</exception> | ||
| public static IEnumerable<ContainerFileSystemItem> GetFileSystemItemsFromPath(string path, string searchPattern = "*", SearchOption searchOptions = SearchOption.TopDirectoryOnly) | ||
| { | ||
| var fullPath = Path.GetFullPath(path); | ||
|
|
||
| if (Directory.Exists(fullPath)) | ||
| { | ||
| // Build a tree of the directories and files found | ||
| FileTree root = new FileTree | ||
| { | ||
| Value = new ContainerDirectory | ||
| { | ||
| Name = "root", | ||
| } | ||
| }; | ||
|
|
||
| foreach (var file in Directory.GetFiles(path, searchPattern, searchOptions).Order(StringComparer.Ordinal)) | ||
| { | ||
| var relativePath = file.Substring(fullPath.Length + 1); | ||
| var fileName = Path.GetFileName(relativePath); | ||
| var parts = relativePath.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], StringSplitOptions.RemoveEmptyEntries); | ||
| var node = root; | ||
| foreach (var part in parts.SkipLast(1)) | ||
| { | ||
| if (node.TryGetValue(part, out var childNode)) | ||
| { | ||
| node = childNode; | ||
| } | ||
| else | ||
| { | ||
| var newNode = new FileTree | ||
| { | ||
| Value = new ContainerDirectory | ||
| { | ||
| Name = part, | ||
| } | ||
| }; | ||
| node.Add(part, newNode); | ||
| node = newNode; | ||
| } | ||
| } | ||
|
|
||
| node.Add(fileName, new FileTree | ||
| { | ||
| Value = new ContainerFile | ||
| { | ||
| Name = fileName, | ||
| SourcePath = file, | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| return root.SelectMany(FileTree.GetItems); | ||
| } | ||
|
|
||
| if (File.Exists(fullPath)) | ||
| { | ||
| if (searchPattern != "*") | ||
| { | ||
| throw new ArgumentException($"A search pattern was specified, but the given path '{fullPath}' is a file. Search patterns are only valid for directories.", nameof(searchPattern)); | ||
| } | ||
|
|
||
| return [ | ||
| new ContainerFile | ||
| { | ||
| Name = Path.GetFileName(fullPath), | ||
| SourcePath = fullPath, | ||
| } | ||
| ]; | ||
| } | ||
|
|
||
| throw new InvalidOperationException($"The specified path '{fullPath}' does not exist."); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.