From 3d93fb1173bc87e139d12fc7550343b000634614 Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Wed, 3 Jun 2026 17:30:29 -0400 Subject: [PATCH] M6: Secrets mutation - New/Update/Remove cmdlets + client methods + DTO tests --- build.ps1 | 5 +- .../SecretMutationDtoTests.cs | 73 +++++++++++ .../Cmdlets/NewInfisicalSecretCmdlet.cs | 73 +++++++++++ .../Cmdlets/RemoveInfisicalSecretCmdlet.cs | 56 ++++++++ .../Cmdlets/UpdateInfisicalSecretCmdlet.cs | 73 +++++++++++ .../Secrets/InfisicalSecretDtos.cs | 33 +++++ .../Secrets/InfisicalSecretQuery.cs | 39 ++++++ .../Secrets/InfisicalSecretsClient.cs | 123 ++++++++++++++++++ 8 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 src/PSInfisicalAPI.Tests/SecretMutationDtoTests.cs create mode 100644 src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs create mode 100644 src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs create mode 100644 src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs diff --git a/build.ps1 b/build.ps1 index 1591563..c1457a1 100644 --- a/build.ps1 +++ b/build.ps1 @@ -102,6 +102,9 @@ function Write-Manifest { 'Disconnect-Infisical', 'Get-InfisicalSecrets', 'Get-InfisicalSecret', + 'New-InfisicalSecret', + 'Update-InfisicalSecret', + 'Remove-InfisicalSecret', 'ConvertTo-InfisicalSecretDictionary', 'Export-InfisicalSecrets', 'Get-InfisicalProjects', @@ -183,7 +186,7 @@ if (`$null -eq `$manifest) { Import-Module -Name '$($ModuleDirectory.FullName)' -Force -`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProjects','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironments','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolders','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTags','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag') +`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProjects','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironments','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolders','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTags','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag') foreach (`$c in `$cmds) { if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) { throw "Cmdlet not found: `$c" diff --git a/src/PSInfisicalAPI.Tests/SecretMutationDtoTests.cs b/src/PSInfisicalAPI.Tests/SecretMutationDtoTests.cs new file mode 100644 index 0000000..70d0700 --- /dev/null +++ b/src/PSInfisicalAPI.Tests/SecretMutationDtoTests.cs @@ -0,0 +1,73 @@ +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace PSInfisicalAPI.Tests +{ + public class SecretMutationDtoTests + { + private static readonly System.Reflection.Assembly ModuleAssembly = + typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly; + + private static object MakeDto(string typeName) + { + System.Type t = ModuleAssembly.GetType(typeName, true); + return System.Activator.CreateInstance(t); + } + + [Fact] + public void CreateRequestDto_Serializes_With_Expected_Field_Names() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretCreateRequestDto"); + dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db"); + dto.GetType().GetProperty("Type").SetValue(dto, "shared"); + dto.GetType().GetProperty("SecretValue").SetValue(dto, "p@ss"); + dto.GetType().GetProperty("SecretComment").SetValue(dto, "comment"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("wks-1", (string)json["workspaceId"]); + Assert.Equal("prod", (string)json["environment"]); + Assert.Equal("/db", (string)json["secretPath"]); + Assert.Equal("shared", (string)json["type"]); + Assert.Equal("p@ss", (string)json["secretValue"]); + Assert.Equal("comment", (string)json["secretComment"]); + Assert.False(json.ContainsKey("skipMultilineEncoding")); + Assert.False(json.ContainsKey("tagIds")); + } + + [Fact] + public void UpdateRequestDto_Omits_Null_Optional_Fields() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretUpdateRequestDto"); + dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + dto.GetType().GetProperty("NewSecretName").SetValue(dto, "renamed"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("renamed", (string)json["newSecretName"]); + Assert.False(json.ContainsKey("secretValue")); + Assert.False(json.ContainsKey("secretComment")); + Assert.False(json.ContainsKey("type")); + Assert.False(json.ContainsKey("secretPath")); + } + + [Fact] + public void DeleteRequestDto_Serializes_With_Expected_Field_Names() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDeleteRequestDto"); + dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db"); + dto.GetType().GetProperty("Type").SetValue(dto, "shared"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("wks-1", (string)json["workspaceId"]); + Assert.Equal("prod", (string)json["environment"]); + Assert.Equal("/db", (string)json["secretPath"]); + Assert.Equal("shared", (string)json["type"]); + } + } +} diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs new file mode 100644 index 0000000..4dd0e82 --- /dev/null +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs @@ -0,0 +1,73 @@ +using System; +using System.Management.Automation; +using System.Security; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Models; +using PSInfisicalAPI.Secrets; +using PSInfisicalAPI.Security; + +namespace PSInfisicalAPI.Cmdlets +{ + [Cmdlet(VerbsCommon.New, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")] + [OutputType(typeof(InfisicalSecret))] + public sealed class NewInfisicalSecretCmdlet : InfisicalCmdletBase + { + [Parameter(Mandatory = true, Position = 0)] public string SecretName { get; set; } + + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "PlainText")] + public string SecretValue { get; set; } + + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")] + public SecureString SecureSecretValue { get; set; } + + [Parameter] public string SecretComment { get; set; } + [Parameter] public string ProjectId { get; set; } + [Parameter] public string Environment { get; set; } + [Parameter] public string SecretPath { get; set; } + [Parameter] public string ApiVersion { get; set; } + [Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared; + [Parameter] public SwitchParameter SkipMultilineEncoding { get; set; } + [Parameter] public string[] TagIds { get; set; } + + protected override void ProcessRecord() + { + try + { + if (!ShouldProcess(SecretName, "Create Infisical secret")) + { + return; + } + + string plainValue = SecureSecretValue != null + ? SecureStringUtility.UsePlainText(SecureSecretValue, p => p) + : SecretValue; + + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + InfisicalCreateSecretRequest request = new InfisicalCreateSecretRequest + { + SecretName = SecretName, + SecretValue = plainValue, + SecretComment = SecretComment, + ProjectId = ProjectId, + Environment = Environment, + SecretPath = SecretPath, + Type = Type.ToString(), + ApiVersion = ApiVersion, + SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, + TagIds = TagIds + }; + + InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); + InfisicalSecret secret = client.Create(connection, request); + if (secret != null) + { + WriteObject(secret); + } + } + catch (Exception exception) + { + ThrowTerminatingForException("NewInfisicalSecretCmdlet", "CreateSecret", exception); + } + } + } +} diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs new file mode 100644 index 0000000..3087596 --- /dev/null +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs @@ -0,0 +1,56 @@ +using System; +using System.Management.Automation; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Models; +using PSInfisicalAPI.Secrets; + +namespace PSInfisicalAPI.Cmdlets +{ + [Cmdlet(VerbsCommon.Remove, "InfisicalSecret", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + public sealed class RemoveInfisicalSecretCmdlet : InfisicalCmdletBase + { + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + public string SecretName { get; set; } + + [Parameter] public string ProjectId { get; set; } + [Parameter] public string Environment { get; set; } + [Parameter] public string SecretPath { get; set; } + [Parameter] public string ApiVersion { get; set; } + [Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared; + [Parameter] public SwitchParameter PassThru { get; set; } + + protected override void ProcessRecord() + { + try + { + if (!ShouldProcess(SecretName, "Remove Infisical secret")) + { + return; + } + + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest + { + SecretName = SecretName, + ProjectId = ProjectId, + Environment = Environment, + SecretPath = SecretPath, + Type = Type.ToString(), + ApiVersion = ApiVersion + }; + + InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); + client.Delete(connection, request); + + if (PassThru.IsPresent) + { + WriteObject(SecretName); + } + } + catch (Exception exception) + { + ThrowTerminatingForException("RemoveInfisicalSecretCmdlet", "DeleteSecret", exception); + } + } + } +} diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs new file mode 100644 index 0000000..359025f --- /dev/null +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs @@ -0,0 +1,73 @@ +using System; +using System.Management.Automation; +using System.Security; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Models; +using PSInfisicalAPI.Secrets; +using PSInfisicalAPI.Security; + +namespace PSInfisicalAPI.Cmdlets +{ + [Cmdlet(VerbsData.Update, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")] + [OutputType(typeof(InfisicalSecret))] + public sealed class UpdateInfisicalSecretCmdlet : InfisicalCmdletBase + { + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + public string SecretName { get; set; } + + [Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; } + [Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; } + + [Parameter] public string NewSecretName { get; set; } + [Parameter] public string SecretComment { get; set; } + [Parameter] public string ProjectId { get; set; } + [Parameter] public string Environment { get; set; } + [Parameter] public string SecretPath { get; set; } + [Parameter] public string ApiVersion { get; set; } + [Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared; + [Parameter] public SwitchParameter SkipMultilineEncoding { get; set; } + [Parameter] public string[] TagIds { get; set; } + + protected override void ProcessRecord() + { + try + { + if (!ShouldProcess(SecretName, "Update Infisical secret")) + { + return; + } + + string plainValue = SecureSecretValue != null + ? SecureStringUtility.UsePlainText(SecureSecretValue, p => p) + : SecretValue; + + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + InfisicalUpdateSecretRequest request = new InfisicalUpdateSecretRequest + { + SecretName = SecretName, + NewSecretName = NewSecretName, + SecretValue = plainValue, + SecretComment = SecretComment, + ProjectId = ProjectId, + Environment = Environment, + SecretPath = SecretPath, + Type = Type.ToString(), + ApiVersion = ApiVersion, + SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, + TagIds = TagIds + }; + + InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); + InfisicalSecret secret = client.Update(connection, request); + if (secret != null) + { + WriteObject(secret); + } + } + catch (Exception exception) + { + ThrowTerminatingForException("UpdateInfisicalSecretCmdlet", "UpdateSecret", exception); + } + } + } +} diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs index 83f8eaf..8d4ea3a 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs @@ -55,4 +55,37 @@ namespace PSInfisicalAPI.Secrets { [JsonProperty("secret")] public InfisicalSecretResponseDto Secret { get; set; } } + + internal sealed class InfisicalSecretCreateRequestDto + { + [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("environment")] public string Environment { get; set; } + [JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; } + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } + [JsonProperty("secretValue")] public string SecretValue { get; set; } + [JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; } + [JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; } + [JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; } + } + + internal sealed class InfisicalSecretUpdateRequestDto + { + [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("environment")] public string Environment { get; set; } + [JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; } + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } + [JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public string SecretValue { get; set; } + [JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; } + [JsonProperty("newSecretName", NullValueHandling = NullValueHandling.Ignore)] public string NewSecretName { get; set; } + [JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; } + [JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; } + } + + internal sealed class InfisicalSecretDeleteRequestDto + { + [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("environment")] public string Environment { get; set; } + [JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; } + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } + } } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs index 8ede056..a5e20d1 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs @@ -30,4 +30,43 @@ namespace PSInfisicalAPI.Secrets public bool? ExpandSecretReferences { get; set; } public bool? IncludeImports { get; set; } } + + public sealed class InfisicalCreateSecretRequest + { + public string SecretName { get; set; } + public string SecretValue { get; set; } + public string SecretComment { get; set; } + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string Type { get; set; } + public string ApiVersion { get; set; } + public bool? SkipMultilineEncoding { get; set; } + public string[] TagIds { get; set; } + } + + public sealed class InfisicalUpdateSecretRequest + { + public string SecretName { get; set; } + public string NewSecretName { get; set; } + public string SecretValue { get; set; } + public string SecretComment { get; set; } + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string Type { get; set; } + public string ApiVersion { get; set; } + public bool? SkipMultilineEncoding { get; set; } + public string[] TagIds { get; set; } + } + + public sealed class InfisicalDeleteSecretRequest + { + public string SecretName { get; set; } + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string Type { get; set; } + public string ApiVersion { get; set; } + } } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs index aa9d7a7..2f62246 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs @@ -136,6 +136,129 @@ namespace PSInfisicalAPI.Secrets } } + public InfisicalSecret Create(InfisicalConnection connection, InfisicalCreateSecretRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); } + if (request.SecretValue == null) { throw new InfisicalConfigurationException("SecretValue is required."); } + + string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId); + string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); } + + Dictionary pathParameters = new Dictionary { { "secretName", request.SecretName } }; + InfisicalSecretCreateRequestDto dtoRequest = new InfisicalSecretCreateRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(), + SecretValue = request.SecretValue, + SecretComment = request.SecretComment, + SkipMultilineEncoding = request.SkipMultilineEncoding, + TagIds = request.TagIds + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to create Infisical secret '", request.SecretName, "'. Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.CreateSecret, request.ApiVersion, "CreateSecret", pathParameters, null, body); + InfisicalSecretSingleResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null); + _logger.Information(Component, "Infisical secret creation was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical secret creation failed."); + throw; + } + } + + public InfisicalSecret Update(InfisicalConnection connection, InfisicalUpdateSecretRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); } + + string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId); + string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); } + + Dictionary pathParameters = new Dictionary { { "secretName", request.SecretName } }; + InfisicalSecretUpdateRequestDto dtoRequest = new InfisicalSecretUpdateRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(), + SecretValue = request.SecretValue, + SecretComment = request.SecretComment, + NewSecretName = request.NewSecretName, + SkipMultilineEncoding = request.SkipMultilineEncoding, + TagIds = request.TagIds + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to update Infisical secret '", request.SecretName, "'. Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.UpdateSecret, request.ApiVersion, "UpdateSecret", pathParameters, null, body); + InfisicalSecretSingleResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null); + _logger.Information(Component, "Infisical secret update was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical secret update failed."); + throw; + } + } + + public void Delete(InfisicalConnection connection, InfisicalDeleteSecretRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); } + + string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId); + string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); } + + Dictionary pathParameters = new Dictionary { { "secretName", request.SecretName } }; + InfisicalSecretDeleteRequestDto dtoRequest = new InfisicalSecretDeleteRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant() + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to delete Infisical secret '", request.SecretName, "'. Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.DeleteSecret, request.ApiVersion, "DeleteSecret", pathParameters, null, body); + response.Clear(); + _logger.Information(Component, "Infisical secret deletion was successful."); + } + catch (Exception) + { + _logger.Error(Component, "Infisical secret deletion failed."); + throw; + } + } + private InfisicalHttpResponse SendWithVersionFallback( InfisicalConnection connection, string endpointName,