mirror of
https://github.com/aphrody-labs/WSL.git
synced 2026-10-17 12:45:55 +00:00
Support reading the wslc image build context from stdin (#41765)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
4c49847670
commit
ffb4e18696
6 files changed
+257
-39
No files matched your search
@@ -2451,6 +2451,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
|
||||
<value>Cannot write build output to the console for --output value '{}'.</value>
|
||||
<comment>{FixedPlaceholder="{}"}{Locked="--output "}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name="MessageWslcBuildStdinContextAndDockerfile" xml:space="preserve">
|
||||
<value>Can't use stdin for both the build context and the Dockerfile.</value>
|
||||
<comment>{Locked="stdin"}{Locked="Dockerfile"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name = "MessageWslcMissingVolumeOption" xml:space = "preserve" >
|
||||
<value>Missing required option: '{}'</value>
|
||||
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
@@ -3457,7 +3461,8 @@ On first run, creates the file with all settings commented out at their defaults
|
||||
<value>Path for the saved image</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_PathArgDescription" xml:space="preserve">
|
||||
<value>Path to the build context directory</value>
|
||||
<value>Path to the build context directory (use "-" to read a tar archive or Dockerfile from stdin)</value>
|
||||
<comment>{Locked="Dockerfile"}{Locked="stdin"}{Locked="-"}Command line arguments should not be translated</comment>
|
||||
</data>
|
||||
<data name="WSLCCLI_PublishArgDescription" xml:space="preserve">
|
||||
<value>Publish a port from a container to host</value>
|
||||
@@ -3862,6 +3867,10 @@ On first run, creates the file with all settings commented out at their defaults
|
||||
<data name="WSLCCLI_ImageSaveStdoutIsTerminalError" xml:space="preserve">
|
||||
<value>Cannot write image to terminal. Use the -o option or redirect stdout.</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_BuildStdinIsTerminalError" xml:space="preserve">
|
||||
<value>Cannot read build input from terminal. Pipe a Dockerfile or tar archive to stdin.</value>
|
||||
<comment>{Locked="Dockerfile"}{Locked="stdin"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name="WSLCCLI_ContainerExportStdoutIsTerminalError" xml:space="preserve">
|
||||
<value>Cannot export container to terminal. Use the -o option or redirect stdout.</value>
|
||||
</data>
|
||||
|
||||
@@ -603,8 +603,8 @@ typedef struct _WSLCDeleteImageOptions
|
||||
|
||||
typedef struct _WSLCBuildImageOptions
|
||||
{
|
||||
LPCWSTR ContextPath;
|
||||
WSLCHandle DockerfileHandle;
|
||||
[unique, string] LPCWSTR ContextPath; // Windows build context directory. Must be null when ContextHandle is set.
|
||||
WSLCHandle DockerfileHandle; // Dockerfile contents relayed to docker's stdin. Must be unset when ContextHandle is set.
|
||||
WSLCStringArray Tags;
|
||||
WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker.
|
||||
LPCSTR Target; // Target build stage name passed as --target to docker.
|
||||
@@ -616,6 +616,8 @@ typedef struct _WSLCBuildImageOptions
|
||||
[unique, string] LPCWSTR OutputMountPath; // When set, the server mounts this Windows directory read-write into the VM and points the exporter's dest at it (plus OutputMountFile). Used for single-file exporters with a real destination; mutually exclusive with OutputHandle.
|
||||
[unique, string] LPCWSTR OutputMountFile; // Leaf filename within OutputMountPath that a single-file exporter writes to.
|
||||
[unique, string] LPCWSTR IidFilePath; // Absolute path of the client's --iidfile destination. The server mounts its parent directory read-write into the VM and passes --iidfile pointing at it, so buildx writes the image ID straight to the destination.
|
||||
WSLCHandle ContextHandle; // When Type != WSLCHandleTypeUnknown, the server relays this stream to docker's stdin as the build context ("-"). The stream is either a tar archive (optionally compressed) or a Dockerfile built with an empty context.
|
||||
[unique, string] LPCSTR DockerfilePath; // Dockerfile path inside the streamed context, passed as --file. Only valid with ContextHandle.
|
||||
} WSLCBuildImageOptions;
|
||||
|
||||
typedef struct _WSLCTagImageOptions
|
||||
|
||||
@@ -107,6 +107,112 @@ InputSource OpenImageInput(const std::wstring& input)
|
||||
return InputSource{std::move(handle), static_cast<ULONGLONG>(fileSize.QuadPart)};
|
||||
}
|
||||
|
||||
// Where the build context and Dockerfile come from. Either ContextPath names a Windows directory and
|
||||
// DockerfileHandle supplies the Dockerfile, or ContextHandle streams the whole context over stdin and
|
||||
// DockerfilePath optionally names the Dockerfile inside it.
|
||||
struct BuildContextInput
|
||||
{
|
||||
std::wstring ContextPath;
|
||||
HANDLE ContextHandle = nullptr;
|
||||
HANDLE DockerfileHandle = nullptr;
|
||||
wil::unique_hfile Dockerfile;
|
||||
std::optional<wsl::windows::common::filesystem::TempFile> SpooledStdin;
|
||||
std::string DockerfilePath;
|
||||
};
|
||||
|
||||
// Returns a relayable stdin handle. Character devices need a file because COM cannot relay their handles.
|
||||
HANDLE OpenStdin(BuildContextInput& input)
|
||||
{
|
||||
auto handle = GetStdHandle(STD_INPUT_HANDLE);
|
||||
|
||||
// Intentional divergence from docker, which accepts a Dockerfile typed into an interactive terminal.
|
||||
THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::WSLCCLI_BuildStdinIsTerminalError(), IsConsoleHandle(handle));
|
||||
|
||||
if (GetFileType(handle) != FILE_TYPE_CHAR)
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
input.SpooledStdin.emplace(
|
||||
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, wsl::windows::common::filesystem::TempFileFlags::DeleteOnClose);
|
||||
std::array<char, 4096> buffer{};
|
||||
for (;;)
|
||||
{
|
||||
DWORD read{};
|
||||
if (!ReadFile(handle, buffer.data(), static_cast<DWORD>(buffer.size()), &read, nullptr))
|
||||
{
|
||||
const auto error = GetLastError();
|
||||
// NUL reports ERROR_INVALID_FUNCTION instead of EOF when read as a character device.
|
||||
if (error == ERROR_INVALID_FUNCTION)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
THROW_WIN32(error);
|
||||
}
|
||||
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD written{};
|
||||
THROW_IF_WIN32_BOOL_FALSE(WriteFile(input.SpooledStdin->Handle.get(), buffer.data(), read, &written, nullptr));
|
||||
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT), written != read);
|
||||
}
|
||||
|
||||
THROW_LAST_ERROR_IF(SetFilePointer(input.SpooledStdin->Handle.get(), 0, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER);
|
||||
return input.SpooledStdin->Handle.get();
|
||||
}
|
||||
|
||||
BuildContextInput OpenBuildContext(const std::wstring& contextPath, const std::wstring& dockerfilePath)
|
||||
{
|
||||
BuildContextInput input;
|
||||
|
||||
if (contextPath == L"-")
|
||||
{
|
||||
THROW_HR_WITH_USER_ERROR_IF(
|
||||
E_INVALIDARG, Localization::MessageWslcBuildStdinContextAndDockerfile(), dockerfilePath == L"-");
|
||||
|
||||
// The Dockerfile path is resolved inside the streamed context by the Linux builder.
|
||||
auto pathInContext = dockerfilePath;
|
||||
std::ranges::replace(pathInContext, L'\\', L'/');
|
||||
|
||||
input.ContextHandle = OpenStdin(input);
|
||||
input.DockerfilePath = wsl::windows::common::string::WideToMultiByte(pathInContext);
|
||||
return input;
|
||||
}
|
||||
|
||||
auto absolutePath = std::filesystem::absolute(contextPath);
|
||||
THROW_HR_IF_MSG(
|
||||
HRESULT_FROM_WIN32(ERROR_DIRECTORY),
|
||||
!std::filesystem::is_directory(absolutePath),
|
||||
"Path must be a directory: %ls",
|
||||
absolutePath.c_str());
|
||||
|
||||
input.ContextPath = absolutePath.wstring();
|
||||
|
||||
if (dockerfilePath == L"-")
|
||||
{
|
||||
input.DockerfileHandle = OpenStdin(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
if (dockerfilePath.empty())
|
||||
{
|
||||
input.Dockerfile = ResolveBuildFile(absolutePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Dockerfile.reset(
|
||||
CreateFileW(dockerfilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
|
||||
THROW_LAST_ERROR_IF_MSG(!input.Dockerfile, "Failed to open Dockerfile: %ls", dockerfilePath.c_str());
|
||||
}
|
||||
|
||||
input.DockerfileHandle = input.Dockerfile.get();
|
||||
return input;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wsl::windows::wslc::services {
|
||||
@@ -131,30 +237,7 @@ void ImageService::Build(
|
||||
IProgressCallback* callback,
|
||||
HANDLE cancelEvent)
|
||||
{
|
||||
auto absolutePath = std::filesystem::absolute(contextPath);
|
||||
THROW_HR_IF_MSG(
|
||||
HRESULT_FROM_WIN32(ERROR_DIRECTORY),
|
||||
!std::filesystem::is_directory(absolutePath),
|
||||
"Path must be a directory: %ls",
|
||||
absolutePath.c_str());
|
||||
|
||||
HANDLE dockerfileHandle = nullptr;
|
||||
wil::unique_hfile dockerfile;
|
||||
if (dockerfilePath == L"-")
|
||||
{
|
||||
dockerfileHandle = GetStdHandle(STD_INPUT_HANDLE);
|
||||
}
|
||||
else if (!dockerfilePath.empty())
|
||||
{
|
||||
dockerfile.reset(CreateFileW(dockerfilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
|
||||
THROW_LAST_ERROR_IF_MSG(!dockerfile, "Failed to open Dockerfile: %ls", dockerfilePath.c_str());
|
||||
dockerfileHandle = dockerfile.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
dockerfile = ResolveBuildFile(absolutePath);
|
||||
dockerfileHandle = dockerfile.get();
|
||||
}
|
||||
auto buildContext = OpenBuildContext(contextPath, dockerfilePath);
|
||||
|
||||
auto toMultiByte = [](const std::vector<std::wstring>& input, std::vector<std::string>& strings, std::vector<LPCSTR>& pointers) {
|
||||
strings.reserve(input.size());
|
||||
@@ -262,8 +345,6 @@ void ImageService::Build(
|
||||
}
|
||||
}
|
||||
|
||||
auto contextPathStr = absolutePath.wstring();
|
||||
|
||||
// Resolve the --iidfile destination against the client's working directory; the server mounts its
|
||||
// parent directory read-write into the VM so buildx writes the image ID straight to it.
|
||||
std::wstring iidPathStr;
|
||||
@@ -273,8 +354,9 @@ void ImageService::Build(
|
||||
}
|
||||
|
||||
WSLCBuildImageOptions options{
|
||||
.ContextPath = contextPathStr.c_str(),
|
||||
.DockerfileHandle = ToCOMInputHandle(dockerfileHandle),
|
||||
.ContextPath = buildContext.ContextPath.empty() ? nullptr : buildContext.ContextPath.c_str(),
|
||||
.DockerfileHandle = buildContext.DockerfileHandle != nullptr ? ToCOMInputHandle(buildContext.DockerfileHandle)
|
||||
: WSLCHandle{.Type = WSLCHandleTypeUnknown},
|
||||
.Tags = {tagPointers.data(), static_cast<ULONG>(tagPointers.size())},
|
||||
.BuildArgs = {buildArgPointers.data(), static_cast<ULONG>(buildArgPointers.size())},
|
||||
.Target = targetStr.empty() ? nullptr : targetStr.c_str(),
|
||||
@@ -286,6 +368,9 @@ void ImageService::Build(
|
||||
.OutputMountPath = outputMountPath.empty() ? nullptr : outputMountPath.c_str(),
|
||||
.OutputMountFile = outputMountFile.empty() ? nullptr : outputMountFile.c_str(),
|
||||
.IidFilePath = iidPathStr.empty() ? nullptr : iidPathStr.c_str(),
|
||||
.ContextHandle = buildContext.ContextHandle != nullptr ? ToCOMInputHandle(buildContext.ContextHandle)
|
||||
: WSLCHandle{.Type = WSLCHandleTypeUnknown},
|
||||
.DockerfilePath = buildContext.DockerfilePath.empty() ? nullptr : buildContext.DockerfilePath.c_str(),
|
||||
};
|
||||
|
||||
THROW_IF_FAILED(session.Get()->BuildImage(&options, callback, cancelEvent));
|
||||
|
||||
@@ -1017,8 +1017,15 @@ try
|
||||
WSLCExecutionContext context(this);
|
||||
|
||||
RETURN_HR_IF_NULL(E_POINTER, Options);
|
||||
RETURN_HR_IF_NULL(E_POINTER, Options->ContextPath);
|
||||
RETURN_HR_IF(E_INVALIDARG, *Options->ContextPath == L'\0');
|
||||
|
||||
// The build context is either a Windows directory or a stream relayed to docker's stdin, never both. The stream is
|
||||
// a tar archive, or a bare Dockerfile that is built with an empty context. A streamed context occupies stdin, so it
|
||||
// cannot also carry a Dockerfile handle.
|
||||
const bool streamContext = Options->ContextHandle.Type != WSLCHandleTypeUnknown;
|
||||
const bool hasContextPath = Options->ContextPath != nullptr && *Options->ContextPath != L'\0';
|
||||
RETURN_HR_IF(E_INVALIDARG, streamContext == hasContextPath);
|
||||
RETURN_HR_IF(E_INVALIDARG, streamContext && Options->DockerfileHandle.Type != WSLCHandleTypeUnknown);
|
||||
RETURN_HR_IF(E_INVALIDARG, !streamContext && Options->DockerfilePath != nullptr);
|
||||
RETURN_HR_IF(E_INVALIDARG, Options->Tags.Count > 0 && Options->Tags.Values == nullptr);
|
||||
RETURN_HR_IF(E_INVALIDARG, Options->BuildArgs.Count > 0 && Options->BuildArgs.Values == nullptr);
|
||||
RETURN_HR_IF(E_INVALIDARG, Options->Labels.Count > 0 && Options->Labels.Values == nullptr);
|
||||
@@ -1029,7 +1036,7 @@ try
|
||||
"Invalid flags: 0x%x",
|
||||
Options->Flags);
|
||||
|
||||
auto buildFileHandle = OpenUserHandle(Options->DockerfileHandle);
|
||||
auto stdinHandle = OpenUserHandle(streamContext ? Options->ContextHandle : Options->DockerfileHandle);
|
||||
|
||||
std::optional<UserCOMCallback> comCall;
|
||||
if (ProgressCallback != nullptr)
|
||||
@@ -1078,7 +1085,11 @@ try
|
||||
buildEnv.emplace_back(std::string{"EXPERIMENTAL_BUILDKIT_SOURCE_POLICY="} + WSLCVirtualMachine::c_buildKitPolicyPath);
|
||||
}
|
||||
|
||||
auto mountPath = mountInVm(Options->ContextPath, TRUE);
|
||||
std::string mountPath;
|
||||
if (!streamContext)
|
||||
{
|
||||
mountPath = mountInVm(Options->ContextPath, TRUE);
|
||||
}
|
||||
|
||||
// Progress is requested as JSON so it can be parsed into the formatted progress messages sent to the
|
||||
// client. The raw JSON is a docker implementation detail and is never forwarded.
|
||||
@@ -1271,9 +1282,21 @@ try
|
||||
}
|
||||
}
|
||||
|
||||
buildArgs.push_back("-f");
|
||||
buildArgs.push_back("-");
|
||||
buildArgs.push_back(mountPath);
|
||||
if (streamContext)
|
||||
{
|
||||
if (Options->DockerfilePath != nullptr)
|
||||
{
|
||||
buildArgs.push_back(std::format("--file={}", Options->DockerfilePath));
|
||||
}
|
||||
|
||||
buildArgs.push_back("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
buildArgs.push_back("-f");
|
||||
buildArgs.push_back("-");
|
||||
buildArgs.push_back(mountPath);
|
||||
}
|
||||
|
||||
WSL_LOG("BuildImageStart", TraceLoggingValue(wsl::shared::string::Join(buildArgs, ' ').c_str(), "Command"));
|
||||
|
||||
@@ -1290,7 +1313,7 @@ try
|
||||
auto io = CreateIOContext();
|
||||
|
||||
io.AddHandle(
|
||||
std::make_unique<io::RelayHandle<io::ReadHandle>>(buildFileHandle.Get(), common::io::HandleWrapper{buildProcess.GetStdHandle(WSLCFDStdin)}),
|
||||
std::make_unique<io::RelayHandle<io::ReadHandle>>(stdinHandle.Get(), common::io::HandleWrapper{buildProcess.GetStdHandle(WSLCFDStdin)}),
|
||||
MultiHandleWait::NeedNotComplete,
|
||||
[&buildProcess]() {
|
||||
// If we receive an error relaying stdin, it could be because the process exited.
|
||||
|
||||
@@ -396,6 +396,8 @@ COMMAND_LINE_TEST_CASE(L"image build C:\\context --tag test:latest", L"build", t
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context -t test", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context --file Dockerfile.custom", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context -f -", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build - -t test", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build - -f sub/Dockerfile", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context -t test:latest -f Dockerfile.other", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context -t tag1 -t tag2", L"build", true)
|
||||
COMMAND_LINE_TEST_CASE(L"image build C:\\context --tag tag1 --tag tag2 --tag tag3", L"build", true)
|
||||
|
||||
@@ -1529,6 +1529,72 @@ class WSLCE2EImageBuildTests
|
||||
}
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinTarContext_Success)
|
||||
{
|
||||
BuildFromStdinArchive(BuiltImageStdinTar, L"-cf", L"Dockerfile", L"");
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinGzipTarContext_Success)
|
||||
{
|
||||
BuildFromStdinArchive(BuiltImageStdinGzip, L"-czf", L"Dockerfile", L"");
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinTarContext_FileInArchive_Success)
|
||||
{
|
||||
// The backslash is converted so the path resolves inside the Linux build context.
|
||||
BuildFromStdinArchive(BuiltImageStdinTarFile, L"-cf", L"sub\\custom.Dockerfile", L"sub\\custom.Dockerfile");
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinDockerfile_EmptyContext_Success)
|
||||
{
|
||||
auto imageCleanup = DeleteImageOnExit(BuiltImageStdinDockerfile);
|
||||
auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-stdin-dockerfile";
|
||||
auto cleanup = SetupTestDirectory(testRoot);
|
||||
|
||||
auto dockerfilePath = testRoot / L"Dockerfile";
|
||||
WriteTestFileContent(dockerfilePath, "FROM debian:latest\nCMD [\"echo\", \"stdin-dockerfile-ok\"]\n");
|
||||
|
||||
auto buildResult = RunWslcWithStdinFile(std::format(L"build - -t {}", BuiltImageStdinDockerfile.NameAndTag()), dockerfilePath);
|
||||
buildResult.Verify({.Stdout = L"", .ExitCode = 0});
|
||||
|
||||
auto inspectData = InspectImage(BuiltImageStdinDockerfile.NameAndTag());
|
||||
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
|
||||
VERIFY_ARE_EQUAL(BuiltImageStdinDockerfile.NameAndTag(), wsl::shared::string::MultiByteToWide(inspectData.RepoTags.value()[0]));
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinContext_Terminal_Fails)
|
||||
{
|
||||
// A pseudo console gives wslc a console stdin, which build must reject.
|
||||
auto session = RunWslcInteractive(L"build - --progress=quiet", ElevationType::Elevated, PseudoConsole{200, 50});
|
||||
WaitForPseudoConsoleOutput(session, string::WideToMultiByte(Localization::WSLCCLI_BuildStdinIsTerminalError()));
|
||||
VERIFY_ARE_EQUAL(1, session.Wait());
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinDockerfile_Terminal_Fails)
|
||||
{
|
||||
auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-stdin-dockerfile-terminal";
|
||||
auto cleanup = SetupTestDirectory(testRoot);
|
||||
|
||||
auto session = RunWslcInteractive(
|
||||
std::format(L"build \"{}\" -f - --progress=quiet", testRoot.wstring()), ElevationType::Elevated, PseudoConsole{200, 50});
|
||||
WaitForPseudoConsoleOutput(session, string::WideToMultiByte(Localization::WSLCCLI_BuildStdinIsTerminalError()));
|
||||
VERIFY_ARE_EQUAL(1, session.Wait());
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinNul_ReachesBuilder)
|
||||
{
|
||||
auto result = RunWslc(L"build - --progress=quiet");
|
||||
VERIFY_ARE_EQUAL(1u, result.ExitCode.value());
|
||||
VERIFY_IS_TRUE(result.StderrContainsSubstring(L"failed to read dockerfile"), result.Stderr.value().c_str());
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Build_StdinContextAndStdinDockerfile_Fails)
|
||||
{
|
||||
auto buildResult = RunWslc(L"build - -f -");
|
||||
buildResult.Verify(
|
||||
{.Stderr = FormatErrorMessage(L"Can't use stdin for both the build context and the Dockerfile.", L"E_INVALIDARG"), .ExitCode = 1});
|
||||
}
|
||||
|
||||
private:
|
||||
const TestImage BuiltImage{L"wslc-e2e-build-empty-context", L"latest", L""};
|
||||
const TestImage BuiltImageTag1{L"wslc-e2e-build-args-tags", L"v1", L""};
|
||||
@@ -1568,6 +1634,37 @@ private:
|
||||
const TestImage BuiltImageProgressPlain{L"wslc-e2e-build-progress-plain", L"latest", L""};
|
||||
const TestImage BuiltImageProgressQuiet{L"wslc-e2e-build-progress-quiet", L"latest", L""};
|
||||
const TestImage BuiltImageIidFileRelative{L"wslc-e2e-build-iidfile-relative", L"latest", L""};
|
||||
const TestImage BuiltImageStdinTar{L"wslc-e2e-build-stdin-tar", L"latest", L""};
|
||||
const TestImage BuiltImageStdinGzip{L"wslc-e2e-build-stdin-gzip", L"latest", L""};
|
||||
const TestImage BuiltImageStdinTarFile{L"wslc-e2e-build-stdin-tar-file", L"latest", L""};
|
||||
const TestImage BuiltImageStdinDockerfile{L"wslc-e2e-build-stdin-dockerfile", L"latest", L""};
|
||||
|
||||
// Archives a context holding a Dockerfile (at dockerfileInContext) and a marker file, streams it to
|
||||
// `wslc build -` over stdin, and verifies the build could COPY the marker out of the streamed context.
|
||||
void BuildFromStdinArchive(const TestImage& image, std::wstring_view tarFlags, std::wstring_view dockerfileInContext, std::wstring_view fileArg)
|
||||
{
|
||||
auto imageCleanup = DeleteImageOnExit(image);
|
||||
auto testRoot = std::filesystem::current_path() / image.Name;
|
||||
auto cleanup = SetupTestDirectory(testRoot);
|
||||
|
||||
auto contextDir = testRoot / L"context";
|
||||
std::filesystem::create_directories((contextDir / dockerfileInContext).parent_path());
|
||||
WriteTestFileContent(contextDir / dockerfileInContext, "FROM debian:latest\nCOPY marker.txt /marker.txt\n");
|
||||
WriteTestFileContent(contextDir / L"marker.txt", "stdin-context-marker\n");
|
||||
|
||||
auto tarPath = testRoot / L"context.tar";
|
||||
auto tarCmd = std::format(L"tar.exe {} \"{}\" -C \"{}\" .", tarFlags, tarPath.wstring(), contextDir.wstring());
|
||||
wsl::windows::common::SubProcess tar(nullptr, tarCmd.c_str());
|
||||
VERIFY_ARE_EQUAL(0u, tar.RunAndCaptureOutput().ExitCode, L"tar.exe failed to create the build context archive");
|
||||
|
||||
auto fileOption = fileArg.empty() ? std::wstring{} : std::format(L" -f {}", fileArg);
|
||||
auto buildResult = RunWslcWithStdinFile(std::format(L"build -{} -t {}", fileOption, image.NameAndTag()), tarPath);
|
||||
buildResult.Verify({.Stdout = L"", .ExitCode = 0});
|
||||
|
||||
auto inspectData = InspectImage(image.NameAndTag());
|
||||
VERIFY_IS_TRUE(inspectData.RepoTags.has_value());
|
||||
VERIFY_ARE_EQUAL(image.NameAndTag(), wsl::shared::string::MultiByteToWide(inspectData.RepoTags.value()[0]));
|
||||
}
|
||||
|
||||
// Runs `tar.exe -tf <path>` and returns the member listing so tests can assert an exporter produced a
|
||||
// valid, non-empty archive that contains an expected entry.
|
||||
|
||||
Reference in new issue
Block a user