Skip to content
Open
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
37 changes: 35 additions & 2 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3038,8 +3038,41 @@ On first run, creates the file with all settings commented out at their defaults
<value>Filter output based on conditions provided</value>
</data>
<data name="WSLCCLI_InvalidFilterError" xml:space="preserve">
<value>Invalid value "{}" for the '-f, --filter' option: bad format of filter (expected name=value)</value>
<comment>{FixedPlaceholder="{}"}{Locked="--filter'"}Command line arguments, file names and string inserts should not be translated</comment> </data>
<value>Invalid value "{}" for the '--filter' option: bad format of filter (expected name=value)</value>
<comment>{FixedPlaceholder="{}"}{Locked="--filter'"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
Comment thread
ggarzia-MSFT marked this conversation as resolved.
<data name="WSLCCLI_PruneFilterArgDescription" xml:space="preserve">
<value>Provide filter values (e.g. "until=&lt;timestamp&gt;")</value>
<comment>{Locked="until"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="WSLCCLI_VolumePruneFilterArgDescription" xml:space="preserve">
<value>Provide filter values (e.g. "label=&lt;label&gt;")</value>
<comment>{Locked="label"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="WSLCCLI_PruneForceArgDescription" xml:space="preserve">
<value>Do not prompt for confirmation</value>
</data>
<data name="WSLCCLI_PruneConfirmPrompt" xml:space="preserve">
<value>Are you sure you want to continue?</value>
</data>
<data name="WSLCCLI_ContainerPruneConfirm" xml:space="preserve">
<value>WARNING! This will remove all stopped containers.</value>
</data>
<data name="WSLCCLI_ImagePruneConfirm" xml:space="preserve">
<value>WARNING! This will remove all dangling images.</value>
</data>
<data name="WSLCCLI_ImagePruneAllConfirm" xml:space="preserve">
<value>WARNING! This will remove all images without at least one container associated to them.</value>
</data>
<data name="WSLCCLI_VolumePruneConfirm" xml:space="preserve">
<value>WARNING! This will remove anonymous local volumes not used by at least one container.</value>
</data>
<data name="WSLCCLI_VolumePruneAllConfirm" xml:space="preserve">
<value>WARNING! This will remove all local volumes not used by at least one container.</value>
</data>
<data name="WSLCCLI_NetworkPruneConfirm" xml:space="preserve">
<value>WARNING! This will remove all custom networks not used by at least one container.</value>
</data>
<data name="WSLCCLI_InvalidMountError" xml:space="preserve">
<value>Invalid argument "{}" for '--mount' option: {}</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}{Locked="--mount'"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down
10 changes: 0 additions & 10 deletions src/windows/wslc/arguments/ArgumentValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,16 +352,6 @@ void ValidateWSLCSignalFromString(const std::vector<std::wstring>& values, const
}
}

// Validates that each --filter argument is in the form "key=value". Rejects entries without an '=';
// the runtime validates the key and value for specific objects.
void ValidateFilter(const std::vector<std::wstring>& values)
{
for (const auto& value : values)
{
std::ignore = ParseFilter(value);
}
}

void ValidateTimestamp(const std::vector<std::wstring>& values, const std::wstring& argName)
{
for (const auto& value : values)
Expand Down
2 changes: 0 additions & 2 deletions src/windows/wslc/arguments/ArgumentValidation.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,4 @@ void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const

void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName);

void ValidateFilter(const std::vector<std::wstring>& values);

} // namespace wsl::windows::wslc::validation
5 changes: 4 additions & 1 deletion src/windows/wslc/commands/ContainerPruneCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ namespace wsl::windows::wslc {
// Container Prune Command
std::vector<Argument> ContainerPruneCommand::GetArguments() const
{
return {};
return {
Argument::Create(ArgType::Filter, {.Alias = NO_ALIAS, .Limit = Limit::Unlimited, .Desc = Localization::WSLCCLI_PruneFilterArgDescription()}),
Argument::Create(ArgType::Force, {.Desc = Localization::WSLCCLI_PruneForceArgDescription()}),
};
}

std::wstring ContainerPruneCommand::ShortDescription() const
Expand Down
3 changes: 2 additions & 1 deletion src/windows/wslc/commands/ImagePruneCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ std::vector<Argument> ImagePruneCommand::GetArguments() const
{
return {
Argument::Create(ArgType::All, {.Desc = Localization::WSLCCLI_ImagePruneAllArgDescription()}),
Argument::Create(ArgType::Filter, {.Limit = Limit::Unlimited}),
Argument::Create(ArgType::Filter, {.Alias = NO_ALIAS, .Limit = Limit::Unlimited, .Desc = Localization::WSLCCLI_PruneFilterArgDescription()}),
Argument::Create(ArgType::Force, {.Desc = Localization::WSLCCLI_PruneForceArgDescription()}),
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/windows/wslc/commands/NetworkPruneCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ namespace wsl::windows::wslc {
std::vector<Argument> NetworkPruneCommand::GetArguments() const
{
return {
Argument::Create(ArgType::Filter, {.Limit = Limit::Unlimited}),
Argument::Create(ArgType::Filter, {.Alias = NO_ALIAS, .Limit = Limit::Unlimited, .Desc = Localization::WSLCCLI_PruneFilterArgDescription()}),
Argument::Create(ArgType::Force, {.Desc = Localization::WSLCCLI_PruneForceArgDescription()}),
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/windows/wslc/commands/VolumePruneCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ std::vector<Argument> VolumePruneCommand::GetArguments() const
{
return {
Argument::Create(ArgType::All, {.Desc = Localization::WSLCCLI_VolumePruneAllArgDescription()}),
Argument::Create(ArgType::Filter, {.Limit = Limit::Unlimited}),
Argument::Create(ArgType::Filter, {.Alias = NO_ALIAS, .Limit = Limit::Unlimited, .Desc = Localization::WSLCCLI_VolumePruneFilterArgDescription()}),
Argument::Create(ArgType::Force, {.Desc = Localization::WSLCCLI_PruneForceArgDescription()}),
};
}

Expand Down
9 changes: 9 additions & 0 deletions src/windows/wslc/core/Exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,13 @@ struct ExecutionException : CLIException
{
}
};

// Specific exception for a user declining a confirmation prompt. The requested action is abandoned
// and the program exits successfully.
struct TerminateException : CLIException
{
TerminateException() : CLIException(std::wstring_view{})
{
}
};
} // namespace wsl::windows::wslc
4 changes: 4 additions & 0 deletions src/windows/wslc/core/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ enum class Data : size_t
Volumes,
Networks,
NetworkEndpointOptions,
ConfirmWarning,
ConfirmMessage,

Max
};
Expand All @@ -59,6 +61,8 @@ namespace details {
DEFINE_DATA_MAPPING(Volumes, std::vector<wsl::windows::common::wslc_schema::VolumeListEntry>);
DEFINE_DATA_MAPPING(Networks, std::vector<wsl::windows::common::wslc_schema::NetworkListEntry>);
DEFINE_DATA_MAPPING(NetworkEndpointOptions, wsl::windows::wslc::models::NetworkEndpointOptions);
DEFINE_DATA_MAPPING(ConfirmWarning, std::wstring);
DEFINE_DATA_MAPPING(ConfirmMessage, std::wstring);
} // namespace details

struct DataMap : wsl::windows::wslc::EnumBasedVariantMap<Data, wsl::windows::wslc::execution::details::DataMapping>
Expand Down
5 changes: 5 additions & 0 deletions src/windows/wslc/core/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ try
context.Terminal.Error(L"{}\n", ee.Message());
return 1;
}
catch (const TerminateException&)
{
// The user declined a confirmation prompt, so the requested action is not performed.
return 0;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
Expand Down
7 changes: 7 additions & 0 deletions src/windows/wslc/core/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ std::wstring Terminal::PromptForLine(Level level, std::wstring_view label, bool
return line.value_or(std::wstring{});
}

bool Terminal::Confirm(std::wstring_view message)
Comment thread
ggarzia-MSFT marked this conversation as resolved.
{
const auto answer = PromptForLine(std::format(L"{} [y/N] ", message));

return wsl::shared::string::IsEqual(wsl::shared::string::TrimAscii(std::wstring_view{answer}), L"y", true);
}

} // namespace wsl::windows::wslc
6 changes: 6 additions & 0 deletions src/windows/wslc/core/Terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ struct Terminal
return PromptForLine(Level::Output, label, mask);
}

// Prompts for confirmation by writing message followed by " [y/N] ", then reading one line.
// Returns true only when the answer is "y" (case-insensitive, surrounding ASCII whitespace
// ignored); every other answer, including end of input, declines. Declining at end of input is
// what lets a non-interactive prune abort instead of blocking.
bool Confirm(std::wstring_view message);

bool IsVTEnabled(Level level) const noexcept;

bool IsColorEnabled(Level level) const noexcept;
Expand Down
12 changes: 10 additions & 2 deletions src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,10 +870,18 @@ wsl::windows::common::docker_schema::ContainerStats ContainerService::Stats(Sess
return wsl::shared::FromJson<wsl::windows::common::docker_schema::ContainerStats>(output.get());
}

PruneContainersResult ContainerService::Prune(Session& session)
PruneContainersResult ContainerService::Prune(Session& session, const std::vector<std::pair<std::string, std::string>>& filters)
{
std::vector<WSLCFilter> filterEntries;
filterEntries.reserve(filters.size());
for (const auto& [key, value] : filters)
{
filterEntries.push_back({.Key = key.c_str(), .Value = value.c_str()});
}

PruneResult result;
THROW_IF_FAILED(session.Get()->PruneContainers(nullptr, 0, &result.result));
THROW_IF_FAILED(session.Get()->PruneContainers(
filterEntries.empty() ? nullptr : filterEntries.data(), static_cast<ULONG>(filterEntries.size()), &result.result));

PruneContainersResult pruneResult;
pruneResult.SpaceReclaimed = result.result.SpaceReclaimed;
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslc/services/ContainerService.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ struct ContainerService
static wsl::windows::common::wslc_schema::InspectContainer Inspect(models::Session& session, const std::string& id);
static void Logs(models::Session& session, const std::string& id, bool follow, bool timestamps, LONGLONG since, LONGLONG until, ULONGLONG tail = 0);
static wsl::windows::common::docker_schema::ContainerStats Stats(models::Session& session, const std::string& id);
static models::PruneContainersResult Prune(models::Session& session);
static models::PruneContainersResult Prune(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters);
};
} // namespace wsl::windows::wslc::services
47 changes: 47 additions & 0 deletions src/windows/wslc/tasks/CommonTasks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

CommonTasks.cpp

Abstract:

Implementation of execution logic shared by multiple commands.

--*/
#include "Argument.h"
#include "CLIExecutionContext.h"
#include "CommonTasks.h"
#include "Exceptions.h"

using namespace wsl::shared;
using namespace wsl::windows::wslc::execution;

namespace wsl::windows::wslc::task {

// Prompts the user to confirm the action described by Data::ConfirmMessage, preceded by the
// Data::ConfirmWarning explaining what the action affects. The prompt is skipped when --force is
// passed, and declining terminates the invocation without running the remaining tasks.
void ConfirmAction(CLIExecutionContext& context)
{
WI_ASSERT(context.Data.Contains(Data::ConfirmMessage));

if (context.Args.GetValue<ArgType::Force>())
{
return;
}

if (context.Data.Contains(Data::ConfirmWarning))
{
context.Terminal.Warn(L"{}\n", context.Data.Get<Data::ConfirmWarning>());
}

if (!context.Terminal.Confirm(context.Data.Get<Data::ConfirmMessage>()))
{
throw TerminateException{};
}
}

} // namespace wsl::windows::wslc::task
21 changes: 21 additions & 0 deletions src/windows/wslc/tasks/CommonTasks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

CommonTasks.h

Abstract:

Declaration of execution tasks shared by multiple commands.

--*/
#pragma once
#include "CLIExecutionContext.h"

using wsl::windows::wslc::execution::CLIExecutionContext;

namespace wsl::windows::wslc::task {
void ConfirmAction(CLIExecutionContext& context);
} // namespace wsl::windows::wslc::task
Comment thread
ggarzia-MSFT marked this conversation as resolved.
10 changes: 9 additions & 1 deletion src/windows/wslc/tasks/ContainerTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Module Name:
#include "ArgumentConvertedTypes.h"
#include "AsyncExecution.h"
#include "CLIExecutionContext.h"
#include "CommonTasks.h"
#include "ContainerModel.h"
#include "ContainerService.h"
#include "ContainerTasks.h"
Expand Down Expand Up @@ -1103,10 +1104,17 @@ void ViewContainerLogs(CLIExecutionContext& context)

void PruneContainers(CLIExecutionContext& context)
{
context.Data.Add<Data::ConfirmWarning>(Localization::WSLCCLI_ContainerPruneConfirm());
context.Data.Add<Data::ConfirmMessage>(Localization::WSLCCLI_PruneConfirmPrompt());
ConfirmAction(context);

WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();

auto result = ContainerService::Prune(session);
// Filter values are parsed and cached during argument validation.
auto filters = context.Args.GetAllValues<ArgType::Filter>();

auto result = ContainerService::Prune(session, filters);

if (!result.PrunedContainers.empty())
{
Expand Down
6 changes: 6 additions & 0 deletions src/windows/wslc/tasks/ImageTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Module Name:
#include "ArgumentConvertedTypes.h"
#include "BuildImageCallback.h"
#include "CLIExecutionContext.h"
#include "CommonTasks.h"
#include "ContainerService.h"
#include "ImageModel.h"
#include "ImageService.h"
Expand Down Expand Up @@ -423,6 +424,11 @@ void TagImage(CLIExecutionContext& context)

void PruneImages(CLIExecutionContext& context)
{
context.Data.Add<Data::ConfirmWarning>(
context.Args.GetValue<ArgType::All>() ? Localization::WSLCCLI_ImagePruneAllConfirm() : Localization::WSLCCLI_ImagePruneConfirm());
context.Data.Add<Data::ConfirmMessage>(Localization::WSLCCLI_PruneConfirmPrompt());
ConfirmAction(context);

WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();

Expand Down
5 changes: 5 additions & 0 deletions src/windows/wslc/tasks/NetworkTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Module Name:
#include "Argument.h"
#include "ArgumentConvertedTypes.h"
#include "CLIExecutionContext.h"
#include "CommonTasks.h"
#include "NetworkModel.h"
#include "NetworkService.h"
#include "NetworkTasks.h"
Expand Down Expand Up @@ -262,6 +263,10 @@ void ListNetworks(CLIExecutionContext& context)

void PruneNetworks(CLIExecutionContext& context)
{
context.Data.Add<Data::ConfirmWarning>(Localization::WSLCCLI_NetworkPruneConfirm());
context.Data.Add<Data::ConfirmMessage>(Localization::WSLCCLI_PruneConfirmPrompt());
ConfirmAction(context);

WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();

Expand Down
6 changes: 6 additions & 0 deletions src/windows/wslc/tasks/VolumeTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Module Name:
#include "Argument.h"
#include "ArgumentConvertedTypes.h"
#include "CLIExecutionContext.h"
#include "CommonTasks.h"
#include "VolumeModel.h"
#include "VolumeService.h"
#include "VolumeTasks.h"
Expand Down Expand Up @@ -236,6 +237,11 @@ void ListVolumes(CLIExecutionContext& context)

void PruneVolumes(CLIExecutionContext& context)
{
context.Data.Add<Data::ConfirmWarning>(
context.Args.GetValue<ArgType::All>() ? Localization::WSLCCLI_VolumePruneAllConfirm() : Localization::WSLCCLI_VolumePruneConfirm());
context.Data.Add<Data::ConfirmMessage>(Localization::WSLCCLI_PruneConfirmPrompt());
ConfirmAction(context);

WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();

Expand Down
Loading