[auto/dotnet] - pulumi state delete & unprotect functionality (#8202)
Co-authored-by: Komal <komal@pulumi.com>
This commit is contained in:
parent
e710125885
commit
caf5fcd525
|
@ -9,5 +9,8 @@
|
|||
SDK 3.15 or higher.
|
||||
|
||||
[#7899](https://github.com/pulumi/pulumi/pull/7899)
|
||||
|
||||
- [auto/dotnet] - Add `pulumi state delete` and `pulumi state unprotect` functionality
|
||||
[#8202](https://github.com/pulumi/pulumi/pull/8202)
|
||||
|
||||
### Bug Fixes
|
||||
|
|
|
@ -1723,6 +1723,275 @@ namespace Pulumi.Automation.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StateDelete()
|
||||
{
|
||||
const string type = "test:res";
|
||||
var program = PulumiFn.Create(() =>
|
||||
{
|
||||
var config = new Config();
|
||||
new ComponentResource(
|
||||
type,
|
||||
"a");
|
||||
});
|
||||
|
||||
var stackName = RandomStackName();
|
||||
var projectName = "test_state_delete";
|
||||
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string?>()
|
||||
{
|
||||
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
||||
}
|
||||
});
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// pulumi up
|
||||
var upResult = await stack.UpAsync();
|
||||
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
||||
Assert.True(upResult.Summary.ResourceChanges!.TryGetValue(OperationType.Create, out var upCount));
|
||||
Assert.Equal(2, upCount);
|
||||
|
||||
// export state
|
||||
var exportResult = await stack.ExportStackAsync();
|
||||
var state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(2, state.Deployment.Resources.Count);
|
||||
var resource = state.Deployment.Resources.Single(r => r.Urn.Contains(type));
|
||||
|
||||
// pulumi state delete
|
||||
await stack.State.DeleteAsync(resource.Urn);
|
||||
|
||||
// test
|
||||
exportResult = await stack.ExportStackAsync();
|
||||
state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Single(state.Deployment.Resources);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var destroyResult = await stack.DestroyAsync();
|
||||
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
||||
await stack.Workspace.RemoveStackAsync(stackName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StateDeleteForce()
|
||||
{
|
||||
const string type = "test:res";
|
||||
var program = PulumiFn.Create(() =>
|
||||
{
|
||||
var config = new Config();
|
||||
new ComponentResource(
|
||||
type,
|
||||
"a",
|
||||
new ComponentResourceOptions
|
||||
{
|
||||
Protect = true,
|
||||
});
|
||||
});
|
||||
|
||||
var stackName = RandomStackName();
|
||||
var projectName = "test_state_delete_force";
|
||||
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string?>()
|
||||
{
|
||||
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
||||
}
|
||||
});
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// pulumi up
|
||||
var upResult = await stack.UpAsync();
|
||||
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
||||
Assert.True(upResult.Summary.ResourceChanges!.TryGetValue(OperationType.Create, out var upCount));
|
||||
Assert.Equal(2, upCount);
|
||||
|
||||
// export state
|
||||
var exportResult = await stack.ExportStackAsync();
|
||||
var state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(2, state.Deployment.Resources.Count);
|
||||
var resource = state.Deployment.Resources.Single(r => r.Urn.Contains(type));
|
||||
|
||||
// pulumi state delete
|
||||
await Assert.ThrowsAsync<CommandException>(() => stack.State.DeleteAsync(resource.Urn));
|
||||
|
||||
// pulumi state delete force
|
||||
await stack.State.DeleteAsync(resource.Urn, force: true);
|
||||
|
||||
// test
|
||||
exportResult = await stack.ExportStackAsync();
|
||||
state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Single(state.Deployment.Resources);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var destroyResult = await stack.DestroyAsync();
|
||||
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
||||
await stack.Workspace.RemoveStackAsync(stackName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StateUnprotect()
|
||||
{
|
||||
const string type = "test:res";
|
||||
var program = PulumiFn.Create(() =>
|
||||
{
|
||||
var config = new Config();
|
||||
new ComponentResource(
|
||||
type,
|
||||
"a",
|
||||
new ComponentResourceOptions
|
||||
{
|
||||
Protect = true,
|
||||
});
|
||||
});
|
||||
|
||||
var stackName = RandomStackName();
|
||||
var projectName = "test_state_unprotect";
|
||||
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string?>()
|
||||
{
|
||||
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
||||
}
|
||||
});
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// pulumi up
|
||||
var upResult = await stack.UpAsync();
|
||||
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
||||
Assert.True(upResult.Summary.ResourceChanges!.TryGetValue(OperationType.Create, out var upCount));
|
||||
Assert.Equal(2, upCount);
|
||||
|
||||
// export state
|
||||
var exportResult = await stack.ExportStackAsync();
|
||||
var state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(2, state.Deployment.Resources.Count);
|
||||
var resource = state.Deployment.Resources.Single(r => r.Urn.Contains(type));
|
||||
Assert.True(resource.Protect);
|
||||
|
||||
// pulumi state unprotect
|
||||
await stack.State.UnprotectAsync(resource.Urn);
|
||||
|
||||
// test
|
||||
exportResult = await stack.ExportStackAsync();
|
||||
state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(2, state.Deployment.Resources.Count);
|
||||
resource = state.Deployment.Resources.Single(r => r.Urn.Contains(type));
|
||||
Assert.False(resource.Protect);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await stack.State.UnprotectAllAsync();
|
||||
var destroyResult = await stack.DestroyAsync();
|
||||
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
||||
await stack.Workspace.RemoveStackAsync(stackName);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StateUnprotectAll()
|
||||
{
|
||||
const string type = "test:res";
|
||||
var program = PulumiFn.Create(() =>
|
||||
{
|
||||
var config = new Config();
|
||||
new ComponentResource(
|
||||
type,
|
||||
"a",
|
||||
new ComponentResourceOptions
|
||||
{
|
||||
Protect = true,
|
||||
});
|
||||
|
||||
new ComponentResource(
|
||||
type,
|
||||
"b",
|
||||
new ComponentResourceOptions
|
||||
{
|
||||
Protect = true,
|
||||
});
|
||||
});
|
||||
|
||||
var stackName = RandomStackName();
|
||||
var projectName = "test_state_unprotect_all";
|
||||
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string?>()
|
||||
{
|
||||
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
||||
}
|
||||
});
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// pulumi up
|
||||
var upResult = await stack.UpAsync();
|
||||
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
||||
Assert.True(upResult.Summary.ResourceChanges!.TryGetValue(OperationType.Create, out var upCount));
|
||||
Assert.Equal(3, upCount);
|
||||
|
||||
// export state
|
||||
var exportResult = await stack.ExportStackAsync();
|
||||
var state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(3, state.Deployment.Resources.Count);
|
||||
var resources = state.Deployment.Resources.Where(x => x.Urn.Contains(type)).ToList();
|
||||
Assert.Equal(2, resources.Count);
|
||||
Assert.True(resources.All(x => x.Protect));
|
||||
|
||||
// pulumi state unprotect
|
||||
await stack.State.UnprotectAllAsync();
|
||||
|
||||
// test
|
||||
exportResult = await stack.ExportStackAsync();
|
||||
state = JsonSerializer.Deserialize<StackState>(exportResult.Json.GetRawText(), jsonOptions);
|
||||
Assert.Equal(3, state.Deployment.Resources.Count);
|
||||
resources = state.Deployment.Resources.Where(x => x.Urn.Contains(type)).ToList();
|
||||
Assert.Equal(2, resources.Count);
|
||||
Assert.DoesNotContain(resources, x => x.Protect);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await stack.State.UnprotectAllAsync();
|
||||
var destroyResult = await stack.DestroyAsync();
|
||||
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
||||
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
||||
await stack.Workspace.RemoveStackAsync(stackName);
|
||||
}
|
||||
}
|
||||
|
||||
private string ResourcePath(string path, [CallerFilePath] string pathBase = "LocalWorkspaceTests.cs")
|
||||
{
|
||||
var dir = Path.GetDirectoryName(pathBase) ?? ".";
|
||||
|
@ -1749,5 +2018,22 @@ namespace Pulumi.Automation.Tests
|
|||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
=> _action();
|
||||
}
|
||||
|
||||
private class StackState
|
||||
{
|
||||
public StackStateDeployment Deployment { get; set; } = new StackStateDeployment();
|
||||
}
|
||||
|
||||
private class StackStateDeployment
|
||||
{
|
||||
public List<StackStateResource> Resources { get; set; } = new List<StackStateResource>();
|
||||
}
|
||||
|
||||
private class StackStateResource
|
||||
{
|
||||
public string Urn { get; set; } = null!;
|
||||
|
||||
public bool Protect { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -356,8 +356,13 @@ Pulumi.Automation.WorkspaceStack.RemoveAllConfigAsync(System.Collections.Generic
|
|||
Pulumi.Automation.WorkspaceStack.RemoveConfigAsync(string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
Pulumi.Automation.WorkspaceStack.SetAllConfigAsync(System.Collections.Generic.IDictionary<string, Pulumi.Automation.ConfigValue> configMap, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
Pulumi.Automation.WorkspaceStack.SetConfigAsync(string key, Pulumi.Automation.ConfigValue value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
Pulumi.Automation.WorkspaceStack.State.get -> Pulumi.Automation.WorkspaceStackState
|
||||
Pulumi.Automation.WorkspaceStack.UpAsync(Pulumi.Automation.UpOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Pulumi.Automation.UpResult>
|
||||
Pulumi.Automation.WorkspaceStack.Workspace.get -> Pulumi.Automation.Workspace
|
||||
Pulumi.Automation.WorkspaceStackState
|
||||
Pulumi.Automation.WorkspaceStackState.DeleteAsync(string urn, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
Pulumi.Automation.WorkspaceStackState.UnprotectAllAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
Pulumi.Automation.WorkspaceStackState.UnprotectAsync(string urn, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
|
||||
abstract Pulumi.Automation.Workspace.CreateStackAsync(string stackName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
|
||||
abstract Pulumi.Automation.Workspace.EnvironmentVariables.get -> System.Collections.Generic.IDictionary<string, string>
|
||||
abstract Pulumi.Automation.Workspace.EnvironmentVariables.set -> void
|
||||
|
|
|
@ -1198,6 +1198,11 @@
|
|||
The Workspace the Stack was created from.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Pulumi.Automation.WorkspaceStack.State">
|
||||
<summary>
|
||||
A module for editing the Stack's state.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStack.CreateAsync(System.String,Pulumi.Automation.Workspace,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Creates a new stack using the given workspace, and stack name.
|
||||
|
@ -1348,5 +1353,59 @@
|
|||
supported for local backends.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Pulumi.Automation.WorkspaceStackState">
|
||||
<summary>
|
||||
Module class for manipulating stack state for a given <see cref="T:Pulumi.Automation.WorkspaceStack"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.DeleteAsync(System.String,System.Boolean,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
This command deletes a resource from a stack’s state, as long as it is safe to do so.
|
||||
The resource is specified by its Pulumi URN.
|
||||
<para/>
|
||||
Resources can’t be deleted if there exist other resources that depend on it or are parented to it.
|
||||
Protected resources will not be deleted unless it is specifically requested using the <paramref name="force"/> flag.
|
||||
</summary>
|
||||
<param name="urn">The Pulumi URN of the resource to be deleted.</param>
|
||||
<param name="force">A boolean indicating whether the deletion should be forced.</param>
|
||||
<param name="cancellationToken">A cancellation token.</param>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.UnprotectAsync(System.String)">
|
||||
<summary>
|
||||
Unprotect a resource in a stack's state.
|
||||
This command clears the ‘protect’ bit on the provided resource <paramref name="urn"/>, allowing the resource to be deleted.
|
||||
</summary>
|
||||
<param name="urn">The Pulumi URN to be unprotected.</param>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.UnprotectAsync(System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Unprotect a resource in a stack's state.
|
||||
This command clears the ‘protect’ bit on the provided resource <paramref name="urn"/>, allowing the resource to be deleted.
|
||||
</summary>
|
||||
<param name="urn">The Pulumi URN to be unprotected.</param>
|
||||
<param name="cancellationToken">A cancellation token.</param>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.UnprotectAsync(System.Collections.Generic.IEnumerable{System.String})">
|
||||
<summary>
|
||||
Unprotect resources in a stack's state.
|
||||
This command clears the ‘protect’ bit on the provided resource <paramref name="urns"/>, allowing those resources to be deleted.
|
||||
</summary>
|
||||
<param name="urns">The Pulumi URNs to be unprotected.</param>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.UnprotectAsync(System.Collections.Generic.IEnumerable{System.String},System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Unprotect resources in a stack's state.
|
||||
This command clears the ‘protect’ bit on the provided resource <paramref name="urns"/>, allowing those resources to be deleted.
|
||||
</summary>
|
||||
<param name="urns">The Pulumi URNs to be unprotected.</param>
|
||||
<param name="cancellationToken">A cancellation token.</param>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.WorkspaceStackState.UnprotectAllAsync(System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Unprotect all resources in a stack's state.
|
||||
This command clears the ‘protect’ bit on all resources in the stack, allowing those resources to be deleted.
|
||||
</summary>
|
||||
<param name="cancellationToken">A cancellation token.</param>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
|
|
|
@ -52,6 +52,11 @@ namespace Pulumi.Automation
|
|||
/// </summary>
|
||||
public Workspace Workspace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A module for editing the Stack's state.
|
||||
/// </summary>
|
||||
public WorkspaceStackState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new stack using the given workspace, and stack name.
|
||||
/// It fails if a stack with that name already exists.
|
||||
|
@ -115,6 +120,7 @@ namespace Pulumi.Automation
|
|||
{
|
||||
this.Name = name;
|
||||
this.Workspace = workspace;
|
||||
this.State = new WorkspaceStackState(this);
|
||||
|
||||
this._readyTask = mode switch
|
||||
{
|
||||
|
@ -644,7 +650,7 @@ namespace Pulumi.Automation
|
|||
public async Task CancelAsync(CancellationToken cancellationToken = default)
|
||||
=> await this.Workspace.RunCommandAsync(new[] { "cancel", "--stack", this.Name, "--yes" }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
private async Task<CommandResult> RunCommandAsync(
|
||||
internal async Task<CommandResult> RunCommandAsync(
|
||||
IList<string> args,
|
||||
Action<string>? onStandardOutput,
|
||||
Action<string>? onStandardError,
|
||||
|
|
68
sdk/dotnet/Pulumi.Automation/WorkspaceStackState.cs
Normal file
68
sdk/dotnet/Pulumi.Automation/WorkspaceStackState.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Pulumi.Automation
|
||||
{
|
||||
/// <summary>
|
||||
/// Module class for manipulating stack state for a given <see cref="WorkspaceStack"/>.
|
||||
/// </summary>
|
||||
public sealed class WorkspaceStackState
|
||||
{
|
||||
private readonly WorkspaceStack _workspaceStack;
|
||||
|
||||
internal WorkspaceStackState(WorkspaceStack workspaceStack)
|
||||
{
|
||||
this._workspaceStack = workspaceStack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This command deletes a resource from a stack’s state, as long as it is safe to do so.
|
||||
/// The resource is specified by its Pulumi URN.
|
||||
/// <para/>
|
||||
/// Resources can’t be deleted if there exist other resources that depend on it or are parented to it.
|
||||
/// Protected resources will not be deleted unless it is specifically requested using the <paramref name="force"/> flag.
|
||||
/// </summary>
|
||||
/// <param name="urn">The Pulumi URN of the resource to be deleted.</param>
|
||||
/// <param name="force">A boolean indicating whether the deletion should be forced.</param>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
public Task DeleteAsync(string urn, bool force = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var args = new List<string>()
|
||||
{
|
||||
"state",
|
||||
"delete",
|
||||
urn,
|
||||
};
|
||||
|
||||
if (force)
|
||||
args.Add("--force");
|
||||
|
||||
return this._workspaceStack.RunCommandAsync(args, null, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprotect a resource in a stack's state.
|
||||
/// This command clears the ‘protect’ bit on the provided resource <paramref name="urn"/>, allowing the resource to be deleted.
|
||||
/// </summary>
|
||||
/// <param name="urn">The Pulumi URN to be unprotected.</param>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
public Task UnprotectAsync(string urn, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var args = new string[] { "state", "unprotect", urn }.ToList();
|
||||
return this._workspaceStack.RunCommandAsync(args, null, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprotect all resources in a stack's state.
|
||||
/// This command clears the ‘protect’ bit on all resources in the stack, allowing those resources to be deleted.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
public Task UnprotectAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var args = new string[] { "state", "unprotect", "--all", }.ToList();
|
||||
return this._workspaceStack.RunCommandAsync(args, null, null, null, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue