mirror of
https://github.com/microsoft/WSL.git
synced 2026-05-04 06:09:54 -05:00
CLI: Add initial support for image tag command (#14416)
* Initial support for image tag command * Init test * Init e2e test * Adde E2E tests * Added more tests * Added more tests * Resolve copilot comment * Clang format * Clang format * Fix build * Update parser * Update loc * Fix test * Added more tests * Clang format * Loc * Addressed comments
This commit is contained in:
@@ -2126,6 +2126,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
|
||||
<value>No WSLC session found in '{}'</value>
|
||||
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name="MessageWslcTagImageInvalidFormat" xml:space="preserve">
|
||||
<value>Invalid image tag format: '{}'. Expected format is 'name:tag'</value>
|
||||
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name="WSLCCLI_RootCommandDesc" xml:space="preserve">
|
||||
<value>WSLC is the Windows Subsystem for Linux Container CLI tool.</value>
|
||||
<comment>{Locked="WSLC"}Product names should not be translated</comment>
|
||||
@@ -2259,6 +2263,12 @@ For privacy information about this product please visit https://aka.ms/privacy.<
|
||||
<data name="WSLCCLI_ImageSaveLongDesc" xml:space="preserve">
|
||||
<value>Saves images.</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_ImageTagDesc" xml:space="preserve">
|
||||
<value>Tag an image.</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_ImageTagLongDesc" xml:space="preserve">
|
||||
<value>Tags an image.</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_SessionCommandDesc" xml:space="preserve">
|
||||
<value>Manage sessions.</value>
|
||||
</data>
|
||||
@@ -2403,9 +2413,15 @@ On first run, creates the file with all settings commented out at their defaults
|
||||
<value>Signal to send (default: {})</value>
|
||||
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
|
||||
</data>
|
||||
<data name="WSLCCLI_SourceArgDescription" xml:space="preserve">
|
||||
<value>Current or existing image reference in the image-name[:tag] format</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_TagArgDescription" xml:space="preserve">
|
||||
<value>Tag for the built image</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_TargetArgDescription" xml:space="preserve">
|
||||
<value>New image reference in the image-name[:tag] format</value>
|
||||
</data>
|
||||
<data name="WSLCCLI_TimeArgDescription" xml:space="preserve">
|
||||
<value>Time in seconds to wait before executing (default 5)</value>
|
||||
</data>
|
||||
|
||||
@@ -1243,7 +1243,7 @@ std::tuple<uint32_t, uint32_t, uint32_t> wsl::windows::common::wslutil::ParseWsl
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::string, std::optional<std::string>> wsl::windows::common::wslutil::ParseImage(const std::string& Input)
|
||||
std::pair<std::string, std::optional<std::string>> wsl::windows::common::wslutil::ParseImage(const std::string& Input, EnumReferenceFormat* Format)
|
||||
{
|
||||
static const auto regex = BuildImageReferenceRegex();
|
||||
std::smatch match;
|
||||
@@ -1258,18 +1258,25 @@ std::pair<std::string, std::optional<std::string>> wsl::windows::common::wslutil
|
||||
|
||||
THROW_HR_IF_MSG(E_UNEXPECTED, !repo.matched, "Unexpected regex match. Input: %hs", Input.c_str());
|
||||
|
||||
EnumReferenceFormat referenceFormat = EnumReferenceFormat::None;
|
||||
std::optional<std::string> tagOrDigest;
|
||||
if (digest.matched) // <repo>:[tag]@<digest> (If both digest and tag are specified, digest takes precedence).
|
||||
{
|
||||
return {repo.str(), digest.str()};
|
||||
tagOrDigest = digest.str();
|
||||
referenceFormat = EnumReferenceFormat::Digest;
|
||||
}
|
||||
else if (tag.matched) // <repo>:<tag>
|
||||
{
|
||||
return {repo.str(), tag.str()}; // <repo>
|
||||
tagOrDigest = tag.str();
|
||||
referenceFormat = EnumReferenceFormat::Tag;
|
||||
}
|
||||
else
|
||||
|
||||
if (Format)
|
||||
{
|
||||
return {repo.str(), std::nullopt};
|
||||
*Format = referenceFormat;
|
||||
}
|
||||
|
||||
return {repo.str(), std::move(tagOrDigest)};
|
||||
}
|
||||
|
||||
void wsl::windows::common::wslutil::PrintSystemError(_In_ HRESULT result, _Inout_ FILE* const stream)
|
||||
|
||||
@@ -48,6 +48,13 @@ inline auto c_vhdFileExtension = L".vhd";
|
||||
inline auto c_vhdxFileExtension = L".vhdx";
|
||||
inline constexpr auto c_vmOwner = L"WSL"; // TODO-WSLC: Does this apply to WSLC ?
|
||||
|
||||
enum class EnumReferenceFormat
|
||||
{
|
||||
None,
|
||||
Tag,
|
||||
Digest
|
||||
};
|
||||
|
||||
struct GitHubReleaseAsset
|
||||
{
|
||||
std::wstring url;
|
||||
@@ -276,7 +283,7 @@ void ParseIpv6Address(const char* Address, in_addr6& Result);
|
||||
|
||||
std::tuple<uint32_t, uint32_t, uint32_t> ParseWslPackageVersion(_In_ const std::wstring& Version);
|
||||
|
||||
std::pair<std::string, std::optional<std::string>> ParseImage(const std::string& Input);
|
||||
std::pair<std::string, std::optional<std::string>> ParseImage(const std::string& Input, EnumReferenceFormat* Format = nullptr);
|
||||
|
||||
void PrintSystemError(_In_ HRESULT result, _Inout_ FILE* stream = stdout);
|
||||
|
||||
|
||||
@@ -74,7 +74,9 @@ _(Session, "session", NO_ALIAS, Kind::Value, L
|
||||
_(SessionId, "session-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SessionIdPositionalArgDescription()) \
|
||||
_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \
|
||||
_(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription(L"SIGKILL")) \
|
||||
_(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \
|
||||
_(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \
|
||||
_(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \
|
||||
_(Time, "time", L"t", Kind::Value, Localization::WSLCCLI_TimeArgDescription()) \
|
||||
_(TMPFS, "tmpfs", NO_ALIAS, Kind::Value, Localization::WSLCCLI_TMPFSArgDescription()) \
|
||||
_(TTY, "tty", L"t", Kind::Flag, Localization::WSLCCLI_TTYArgDescription()) \
|
||||
|
||||
@@ -29,6 +29,7 @@ std::vector<std::unique_ptr<Command>> ImageCommand::GetCommands() const
|
||||
commands.push_back(std::make_unique<ImageLoadCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ImagePullCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ImageSaveCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ImageTagCommand>(FullName()));
|
||||
return commands;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,21 @@ struct ImageSaveCommand final : public Command
|
||||
std::wstring ShortDescription() const override;
|
||||
std::wstring LongDescription() const override;
|
||||
|
||||
protected:
|
||||
void ExecuteInternal(CLIExecutionContext& context) const override;
|
||||
};
|
||||
|
||||
// Tag Command
|
||||
struct ImageTagCommand final : public Command
|
||||
{
|
||||
constexpr static std::wstring_view CommandName = L"tag";
|
||||
ImageTagCommand(const std::wstring& parent) : Command(CommandName, parent)
|
||||
{
|
||||
}
|
||||
std::vector<Argument> GetArguments() const override;
|
||||
std::wstring ShortDescription() const override;
|
||||
std::wstring LongDescription() const override;
|
||||
|
||||
protected:
|
||||
void ExecuteInternal(CLIExecutionContext& context) const override;
|
||||
};
|
||||
|
||||
52
src/windows/wslc/commands/ImageTagCommand.cpp
Normal file
52
src/windows/wslc/commands/ImageTagCommand.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*++
|
||||
|
||||
Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
Module Name:
|
||||
|
||||
ImageTagCommand.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Implementation of command execution logic.
|
||||
|
||||
--*/
|
||||
|
||||
#include "ImageCommand.h"
|
||||
#include "CLIExecutionContext.h"
|
||||
#include "ImageTasks.h"
|
||||
#include "SessionTasks.h"
|
||||
#include "Task.h"
|
||||
|
||||
using namespace wsl::shared;
|
||||
using namespace wsl::windows::wslc::execution;
|
||||
using namespace wsl::windows::wslc::task;
|
||||
|
||||
namespace wsl::windows::wslc {
|
||||
// Image Tag Command
|
||||
std::vector<Argument> ImageTagCommand::GetArguments() const
|
||||
{
|
||||
return {
|
||||
Argument::Create(ArgType::Source, true),
|
||||
Argument::Create(ArgType::Target, true),
|
||||
Argument::Create(ArgType::Session),
|
||||
};
|
||||
}
|
||||
|
||||
std::wstring ImageTagCommand::ShortDescription() const
|
||||
{
|
||||
return Localization::WSLCCLI_ImageTagDesc();
|
||||
}
|
||||
|
||||
std::wstring ImageTagCommand::LongDescription() const
|
||||
{
|
||||
return Localization::WSLCCLI_ImageTagLongDesc();
|
||||
}
|
||||
|
||||
void ImageTagCommand::ExecuteInternal(CLIExecutionContext& context) const
|
||||
{
|
||||
context //
|
||||
<< CreateSession //
|
||||
<< TagImage;
|
||||
}
|
||||
} // namespace wsl::windows::wslc
|
||||
@@ -48,6 +48,7 @@ std::vector<std::unique_ptr<Command>> RootCommand::GetCommands() const
|
||||
commands.push_back(std::make_unique<ImageSaveCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ContainerStartCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ContainerStopCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<ImageTagCommand>(FullName()));
|
||||
commands.push_back(std::make_unique<VersionCommand>(FullName()));
|
||||
return commands;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,23 @@ void ImageService::Pull(wsl::windows::wslc::models::Session& session, const std:
|
||||
THROW_IF_FAILED(session.Get()->PullImage(image.c_str(), nullptr, callback));
|
||||
}
|
||||
|
||||
void ImageService::Tag(wsl::windows::wslc::models::Session& session, const std::string& sourceImage, const std::string& targetImage)
|
||||
{
|
||||
EnumReferenceFormat format;
|
||||
auto [repo, tag] = ParseImage(targetImage, &format);
|
||||
if (format == EnumReferenceFormat::Digest)
|
||||
{
|
||||
THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageWslcTagImageInvalidFormat(targetImage.c_str()));
|
||||
}
|
||||
|
||||
WSLCTagImageOptions options{};
|
||||
options.Image = sourceImage.c_str();
|
||||
options.Repo = repo.c_str();
|
||||
options.Tag = tag ? tag->c_str() : "";
|
||||
|
||||
THROW_IF_FAILED(session.Get()->TagImage(&options));
|
||||
}
|
||||
|
||||
InspectImage ImageService::Inspect(wsl::windows::wslc::models::Session& session, const std::string& image)
|
||||
{
|
||||
wil::unique_cotaskmem_ansistring inspectData;
|
||||
@@ -221,10 +238,6 @@ void ImageService::Save(wsl::windows::wslc::models::Session& session, const std:
|
||||
THROW_IF_FAILED(session.Get()->SaveImage(ToCOMInputHandle(outputFile.get()), image.c_str(), nullptr, cancelEvent));
|
||||
}
|
||||
|
||||
void ImageService::Tag()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageService::Prune()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public:
|
||||
static wsl::windows::common::wslc_schema::InspectImage Inspect(wsl::windows::wslc::models::Session& session, const std::string& image);
|
||||
static void Pull(wsl::windows::wslc::models::Session& session, const std::string& image, IProgressCallback* callback);
|
||||
static void Save(wsl::windows::wslc::models::Session& session, const std::string& image, const std::wstring& output, HANDLE cancelEvent = nullptr);
|
||||
static void Tag(wsl::windows::wslc::models::Session& session, const std::string& sourceImage, const std::string& targetImage);
|
||||
void Push();
|
||||
void Tag();
|
||||
void Prune();
|
||||
};
|
||||
} // namespace wsl::windows::wslc::services
|
||||
|
||||
@@ -194,4 +194,13 @@ void SaveImage(CLIExecutionContext& context)
|
||||
auto& output = context.Args.Get<ArgType::Output>();
|
||||
services::ImageService::Save(session, WideToMultiByte(imageId), output, context.CreateCancelEvent());
|
||||
}
|
||||
|
||||
void TagImage(CLIExecutionContext& context)
|
||||
{
|
||||
WI_ASSERT(context.Data.Contains(Data::Session));
|
||||
auto& session = context.Data.Get<Data::Session>();
|
||||
auto& source = context.Args.Get<ArgType::Source>();
|
||||
auto& target = context.Args.Get<ArgType::Target>();
|
||||
services::ImageService::Tag(session, WideToMultiByte(source), WideToMultiByte(target));
|
||||
}
|
||||
} // namespace wsl::windows::wslc::task
|
||||
|
||||
@@ -24,5 +24,6 @@ void LoadImage(CLIExecutionContext& context);
|
||||
void PullImage(CLIExecutionContext& context);
|
||||
void DeleteImage(CLIExecutionContext& context);
|
||||
void InspectImages(CLIExecutionContext& context);
|
||||
void TagImage(CLIExecutionContext& context);
|
||||
void SaveImage(CLIExecutionContext& context);
|
||||
} // namespace wsl::windows::wslc::task
|
||||
|
||||
@@ -428,6 +428,7 @@ private:
|
||||
{L"save", Localization::WSLCCLI_ImageSaveDesc()},
|
||||
{L"start", Localization::WSLCCLI_ContainerStartDesc()},
|
||||
{L"stop", Localization::WSLCCLI_ContainerStopDesc()},
|
||||
{L"tag", Localization::WSLCCLI_ImageTagDesc()},
|
||||
{L"version", Localization::WSLCCLI_VersionDesc()},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Abstract:
|
||||
|
||||
#include "precomp.h"
|
||||
#include "SessionModel.h"
|
||||
#include "ImageModel.h"
|
||||
#include "windows/Common.h"
|
||||
#include "WSLCExecutor.h"
|
||||
#include "WSLCE2EHelpers.h"
|
||||
@@ -194,6 +195,23 @@ void VerifyImageIsNotUsed(const TestImage& image)
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyImageIsListed(const TestImage& image)
|
||||
{
|
||||
auto result = RunWslc(L"image list --format json");
|
||||
result.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto images = wsl::shared::FromJson<std::vector<wsl::windows::wslc::models::ImageInformation>>(result.Stdout.value().c_str());
|
||||
for (const auto& img : images)
|
||||
{
|
||||
if (img.Repository == wsl::shared::string::WideToMultiByte(image.Name) &&
|
||||
img.Tag == wsl::shared::string::WideToMultiByte(image.Tag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY_FAIL(std::format(L"Image '{}' not found in image list output", image.NameAndTag()).c_str());
|
||||
}
|
||||
|
||||
std::string GetHashId(const std::string& id, bool fullId)
|
||||
{
|
||||
return wsl::windows::common::string::TruncateId(id, !fullId);
|
||||
|
||||
@@ -119,6 +119,7 @@ private:
|
||||
void VerifyContainerIsListed(const std::wstring& containerName, const std::wstring& status, const std::wstring& sessionName = L"");
|
||||
void VerifyImageIsUsed(const TestImage& image);
|
||||
void VerifyImageIsNotUsed(const TestImage& image);
|
||||
void VerifyImageIsListed(const TestImage& image);
|
||||
|
||||
std::string GetHashId(const std::string& id, bool fullId = false);
|
||||
wsl::windows::common::wslc_schema::InspectContainer InspectContainer(const std::wstring& containerName);
|
||||
|
||||
212
test/windows/wslc/e2e/WSLCE2EImageTagTests.cpp
Normal file
212
test/windows/wslc/e2e/WSLCE2EImageTagTests.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/*++
|
||||
|
||||
Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
Module Name:
|
||||
|
||||
WSLCE2EImageTagTests.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
This file contains end-to-end tests for WSLC.
|
||||
--*/
|
||||
|
||||
#include "precomp.h"
|
||||
#include "windows/Common.h"
|
||||
#include "WSLCExecutor.h"
|
||||
#include "WSLCE2EHelpers.h"
|
||||
|
||||
namespace WSLCE2ETests {
|
||||
|
||||
using namespace wsl::shared::string;
|
||||
|
||||
class WSLCE2EImageTagTests
|
||||
{
|
||||
WSL_TEST_CLASS(WSLCE2EImageTagTests)
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
EnsureImageIsDeleted(DebianTaggedImage);
|
||||
EnsureImageIsLoaded(DebianImage);
|
||||
EnsureImageIsLoaded(AlpineImage);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
EnsureImageIsDeleted(DebianTaggedImage);
|
||||
EnsureImageIsDeleted(DebianImage);
|
||||
EnsureImageIsDeleted(AlpineImage);
|
||||
return true;
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_HelpCommand)
|
||||
{
|
||||
auto result = RunWslc(L"image tag --help");
|
||||
result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_MissingSourceAndTarget)
|
||||
{
|
||||
auto result = RunWslc(L"image tag");
|
||||
result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'source'\r\n", .ExitCode = 1});
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_MissingTarget)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {}", DebianImage.NameAndTag()));
|
||||
result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'target'\r\n", .ExitCode = 1});
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_SourceImageNotFound)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", InvalidImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
auto errorMessage = std::format(L"No such image: {}\r\nError code: WSLC_E_IMAGE_NOT_FOUND\r\n", InvalidImage.NameAndTag());
|
||||
result.Verify({.Stdout = L"", .Stderr = errorMessage, .ExitCode = 1});
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_TargetImageWithDigest_Fail)
|
||||
{
|
||||
auto imageWithDigest = L"debian-mock:tag@sha256:11111111111111111111111111111111";
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), imageWithDigest));
|
||||
auto errorMessage =
|
||||
std::format(L"Invalid image tag format: '{}'. Expected format is 'name:tag'\r\nError code: E_INVALIDARG\r\n", imageWithDigest);
|
||||
result.Verify({.Stdout = L"", .Stderr = errorMessage, .ExitCode = 1});
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_Success)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
VerifyImageIsListed(DebianImage);
|
||||
VerifyImageIsListed(DebianTaggedImage);
|
||||
|
||||
auto resultSourceInspect = RunWslc(std::format(L"image inspect {}", DebianImage.NameAndTag()));
|
||||
resultSourceInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto sourceInspect = resultSourceInspect.Stdout;
|
||||
|
||||
auto resultTargetInspect = RunWslc(std::format(L"image inspect {}", DebianTaggedImage.NameAndTag()));
|
||||
resultTargetInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto targetInspect = resultTargetInspect.Stdout;
|
||||
|
||||
VERIFY_IS_TRUE(sourceInspect.has_value());
|
||||
VERIFY_IS_TRUE(targetInspect.has_value());
|
||||
VERIFY_ARE_EQUAL(sourceInspect, targetInspect);
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_SourceAndTargetAreTheSame_Noop)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), DebianImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
VerifyImageIsListed(DebianImage);
|
||||
|
||||
auto imageInspect = InspectImage(DebianImage.NameAndTag());
|
||||
VERIFY_ARE_EQUAL(1u, imageInspect.RepoTags->size());
|
||||
VERIFY_ARE_EQUAL(imageInspect.RepoTags->at(0), WideToMultiByte(DebianImage.NameAndTag()));
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_TargetAlreadyExists_OverwritesTarget)
|
||||
{
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
auto resultSourceInspect = RunWslc(std::format(L"image inspect {}", DebianImage.NameAndTag()));
|
||||
resultSourceInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto sourceInspect = resultSourceInspect.Stdout;
|
||||
|
||||
auto resultTargetInspect = RunWslc(std::format(L"image inspect {}", DebianTaggedImage.NameAndTag()));
|
||||
resultTargetInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto targetInspect = resultTargetInspect.Stdout;
|
||||
|
||||
VERIFY_IS_TRUE(sourceInspect.has_value());
|
||||
VERIFY_IS_TRUE(targetInspect.has_value());
|
||||
VERIFY_ARE_EQUAL(sourceInspect, targetInspect);
|
||||
}
|
||||
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", AlpineImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
auto resultSourceInspect = RunWslc(std::format(L"image inspect {}", AlpineImage.NameAndTag()));
|
||||
resultSourceInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto sourceInspect = resultSourceInspect.Stdout;
|
||||
|
||||
auto resultTargetInspect = RunWslc(std::format(L"image inspect {}", DebianTaggedImage.NameAndTag()));
|
||||
resultTargetInspect.Verify({.Stderr = L"", .ExitCode = 0});
|
||||
auto targetInspect = resultTargetInspect.Stdout;
|
||||
|
||||
VERIFY_IS_TRUE(sourceInspect.has_value());
|
||||
VERIFY_IS_TRUE(targetInspect.has_value());
|
||||
VERIFY_ARE_EQUAL(sourceInspect, targetInspect);
|
||||
}
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_DeleteSourceImage_TargetRemains)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
EnsureImageIsDeleted(DebianImage);
|
||||
VerifyImageIsListed(DebianTaggedImage);
|
||||
}
|
||||
|
||||
WSLC_TEST_METHOD(WSLCE2E_Image_Tag_DeleteTargetImage_SourceRemains)
|
||||
{
|
||||
auto result = RunWslc(std::format(L"image tag {} {}", DebianImage.NameAndTag(), DebianTaggedImage.NameAndTag()));
|
||||
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
|
||||
|
||||
EnsureImageIsDeleted(DebianTaggedImage);
|
||||
VerifyImageIsListed(DebianImage);
|
||||
}
|
||||
|
||||
private:
|
||||
const TestImage& DebianImage = DebianTestImage();
|
||||
const TestImage& AlpineImage = AlpineTestImage();
|
||||
const TestImage& InvalidImage = InvalidTestImage();
|
||||
const TestImage DebianTaggedImage{L"debian", L"e2e-new-tag"};
|
||||
|
||||
std::wstring GetHelpMessage() const
|
||||
{
|
||||
std::wstringstream output;
|
||||
output << GetWslcHeader() //
|
||||
<< GetDescription() //
|
||||
<< GetUsage() //
|
||||
<< GetAvailableCommands() //
|
||||
<< GetAvailableOptions();
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::wstring GetDescription() const
|
||||
{
|
||||
return wsl::shared::Localization::WSLCCLI_ImageTagLongDesc() + L"\r\n\r\n";
|
||||
}
|
||||
|
||||
std::wstring GetUsage() const
|
||||
{
|
||||
return L"Usage: wslc image tag [<options>] <source> <target>\r\n\r\n";
|
||||
}
|
||||
|
||||
std::wstring GetAvailableCommands() const
|
||||
{
|
||||
std::wstringstream commands;
|
||||
commands << L"The following arguments are available:\r\n" //
|
||||
<< L" source Current or existing image reference in the image-name[:tag] format\r\n" //
|
||||
<< L" target New image reference in the image-name[:tag] format\r\n" //
|
||||
<< L"\r\n";
|
||||
return commands.str();
|
||||
}
|
||||
|
||||
std::wstring GetAvailableOptions() const
|
||||
{
|
||||
std::wstringstream options;
|
||||
options << L"The following options are available:\r\n" //
|
||||
<< L" --session Specify the session to use\r\n" //
|
||||
<< L" -h,--help Shows help about the selected command\r\n" //
|
||||
<< L"\r\n";
|
||||
return options.str();
|
||||
}
|
||||
};
|
||||
} // namespace WSLCE2ETests
|
||||
@@ -74,6 +74,7 @@ private:
|
||||
{L"load", Localization::WSLCCLI_ImageLoadDesc()},
|
||||
{L"pull", Localization::WSLCCLI_ImagePullDesc()},
|
||||
{L"save", Localization::WSLCCLI_ImageSaveDesc()},
|
||||
{L"tag", Localization::WSLCCLI_ImageTagDesc()},
|
||||
};
|
||||
|
||||
size_t maxLen = 0;
|
||||
|
||||
Reference in New Issue
Block a user